diff --git a/src/doc/en/reference/categories/index.rst b/src/doc/en/reference/categories/index.rst index 55ee54888b3..93a71a970e3 100644 --- a/src/doc/en/reference/categories/index.rst +++ b/src/doc/en/reference/categories/index.rst @@ -162,6 +162,7 @@ Individual Categories sage/categories/monoids sage/categories/number_fields sage/categories/objects + sage/categories/ore_modules sage/categories/partially_ordered_monoids sage/categories/permutation_groups sage/categories/pointed_sets diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index 646f7c76680..5c5a01ae21e 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -51,6 +51,7 @@ Linear Algebra * :doc:`Matrices and Spaces of Matrices ` * :doc:`Vectors and Modules ` * :doc:`Tensors on Free Modules of Finite Rank ` +* :doc:`Modules over Ore rings ` Calculus and Analysis --------------------- diff --git a/src/doc/en/reference/oremodules/conf.py b/src/doc/en/reference/oremodules/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/oremodules/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/oremodules/index.rst b/src/doc/en/reference/oremodules/index.rst new file mode 100644 index 00000000000..b6928c8efca --- /dev/null +++ b/src/doc/en/reference/oremodules/index.rst @@ -0,0 +1,56 @@ +Modules over Ore rings +====================== + +Let `R` be a commutative ring, `\theta : K \to K` by a ring +endomorphism and `\partial : K \to K` be a `\theta`-derivation, +that is an additive map satisfying the following axiom + +.. MATH:: + + \partial(x y) = \theta(x) \partial(y) + \partial(x) y + +The Ore polynomial ring associated to these data is +`\mathcal S = R[X; \theta, \partial]`; its elements are the +usual polynomials over `R` but the multiplication is twisted +according to the rule + +.. MATH:: + + \partial(x y) = \theta(x) \partial(y) + \partial(x) y + +We refer to :mod:`sage.rings.polynomial.ore_polynomial_ring.OrePolynomial` +for more details. + +A Ore module over `(R, \theta, \partial)` is by definition a +module over `\mathcal S`; it is the same than a `R`-module `M` +equipped with an additive `f : M \to M` such that + +.. MATH:: + + f(a x) = \theta(a) f(x) + \partial(a) x + +Such a map `f` is called a pseudomorphism +(see also :meth:`sage.modules.free_module.FreeModule_generic.pseudohom`). + +SageMath provides support for creating and manipulating Ore +modules that are finite free over the base ring `R`. +This includes, in particular, Frobenius modules and modules +with connexions. + +Modules, submodules and quotients +--------------------------------- + +.. toctree:: + :maxdepth: 1 + + sage/modules/ore_module + sage/modules/ore_module_element + +Morphisms +--------- + +.. toctree:: + :maxdepth: 1 + + sage/modules/ore_module_homspace + sage/modules/ore_module_morphism diff --git a/src/sage/categories/meson.build b/src/sage/categories/meson.build index b5c6a46f038..642cee8e9e7 100644 --- a/src/sage/categories/meson.build +++ b/src/sage/categories/meson.build @@ -148,6 +148,7 @@ py.install_sources( 'noetherian_rings.py', 'number_fields.py', 'objects.py', + 'ore_modules.py', 'partially_ordered_monoids.py', 'permutation_groups.py', 'pointed_sets.py', diff --git a/src/sage/categories/ore_modules.py b/src/sage/categories/ore_modules.py new file mode 100644 index 00000000000..b4720a56269 --- /dev/null +++ b/src/sage/categories/ore_modules.py @@ -0,0 +1,177 @@ +from sage.misc.lazy_attribute import lazy_attribute + +from sage.categories.modules import Modules +from sage.categories.category_types import Category_over_base_ring +from sage.categories.homsets import Homsets +from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing + + +class OreModules(Category_over_base_ring): + r""" + Category of Ore modules. + """ + @staticmethod + def __classcall_private__(cls, ring, twist): + r""" + Normalize the input and call the init function. + + INPUT: + + - ``ring`` -- a commutative ring, the base ring of + the Ore modules + + - ``twist`` -- a twisting morphism/derivation or a + Ore polynomial ring + + TESTS:: + + sage: from sage.categories.ore_modules import OreModules + sage: K. = GF(5^3) + sage: Frob = K.frobenius_endomorphism() + sage: cat = OreModules(K, Frob) + sage: cat + Category of Ore modules over Finite Field in a of size 5^3 twisted by a |--> a^5 + + sage: S = cat.ore_ring('y') + sage: cat is OreModules(K, S) + True + """ + if isinstance(twist, OrePolynomialRing): + ore = twist.change_var('x') + if ore.base_ring() is not ring: + raise ValueError("base rings do not match") + else: + ore = OrePolynomialRing(ring, twist, names='x', polcast=False) + return cls.__classcall__(cls, ore) + + def __init__(self, ore): + r""" + Initialize this category. + + TESTS:: + + sage: from sage.categories.ore_modules import OreModules + sage: K. = GF(5^3) + sage: Frob = K.frobenius_endomorphism() + sage: cat = OreModules(K, Frob) + + sage: TestSuite(cat).run() + """ + base = ore.base_ring() + Category_over_base_ring.__init__(self, base) + self._ore = ore + + def __reduce__(self): + r""" + Return the arguments which were used to create this instance. + + This method is needed for pickling. + + TESTS:: + + sage: from sage.categories.ore_modules import OreModules + sage: K. = GF(5^3) + sage: Frob = K.frobenius_endomorphism() + sage: cat = OreModules(K, Frob) + sage: cat2 = loads(dumps(cat)) # indirect doctest + sage: cat is cat2 + True + """ + return OreModules, (self.base_ring(), self._ore) + + def super_categories(self): + r""" + Return the immediate super categories of this category. + + EXAMPLES:: + + sage: from sage.categories.ore_modules import OreModules + sage: K. = GF(5^3) + sage: Frob = K.frobenius_endomorphism() + sage: cat = OreModules(K, Frob) + sage: cat.super_categories() + [Category of vector spaces over Finite Field in a of size 5^3] + """ + return [Modules(self.base())] + + def _repr_object_names(self): + r""" + Return a string representation naming the objects + in this category. + + EXAMPLES:: + + sage: from sage.categories.ore_modules import OreModules + sage: K. = GF(5^3) + sage: Frob = K.frobenius_endomorphism() + sage: cat = OreModules(K, Frob) + sage: cat._repr_object_names() + 'Ore modules over Finite Field in a of size 5^3 twisted by a |--> a^5' + """ + return "Ore modules over %s %s" % (self.base_ring(), self._ore._repr_twist()) + + def ore_ring(self, var='x'): + r""" + Return the underlying Ore polynomial ring. + + INPUT: + + - ``var`` (default; ``x``) -- the variable name + + EXAMPLES:: + + sage: from sage.categories.ore_modules import OreModules + sage: K. = GF(5^3) + sage: Frob = K.frobenius_endomorphism() + sage: cat = OreModules(K, Frob) + sage: cat.ore_ring() + Ore Polynomial Ring in x over Finite Field in a of size 5^3 twisted by a |--> a^5 + + sage: cat.ore_ring('y') + Ore Polynomial Ring in y over Finite Field in a of size 5^3 twisted by a |--> a^5 + """ + return self._ore.change_var(var) + + def twisting_morphism(self): + r""" + Return the underlying twisting morphism. + + EXAMPLES:: + + sage: from sage.categories.ore_modules import OreModules + sage: K. = GF(5^3) + sage: Frob = K.frobenius_endomorphism() + sage: cat = OreModules(K, Frob) + sage: cat.twisting_morphism() + Frobenius endomorphism a |--> a^5 on Finite Field in a of size 5^3 + + If the twising morphism is the identity, nothing is returned:: + + sage: R. = QQ[] + sage: d = R.derivation() + sage: cat = OreModules(R, d) + sage: cat.twisting_morphism() + """ + return self._ore.twisting_morphism() + + def twisting_derivation(self): + r""" + Return the underlying twisting derivation. + + EXAMPLES:: + + sage: from sage.categories.ore_modules import OreModules + sage: R. = QQ[] + sage: d = R.derivation() + sage: cat = OreModules(R, d) + sage: cat.twisting_derivation() + d/dt + + If the twising derivation is zero, nothing is returned:: + + sage: K. = GF(5^3) + sage: Frob = K.frobenius_endomorphism() + sage: cat = OreModules(K, Frob) + sage: cat.twisting_derivation() + """ + return self._ore.twisting_derivation() diff --git a/src/sage/modules/free_module_pseudomorphism.py b/src/sage/modules/free_module_pseudomorphism.py index 7ed8cf1ef49..c9d9e05ebf2 100644 --- a/src/sage/modules/free_module_pseudomorphism.py +++ b/src/sage/modules/free_module_pseudomorphism.py @@ -500,5 +500,70 @@ def __eq__(self, other): if isinstance(other, FreeModulePseudoMorphism): return self.parent() is other.parent() and self._matrix == other._matrix + def ore_module(self, names=None): + r""" + Return the Ore module over which the Ore variable acts + through this pseudomorphism. + + INPUT: + + - ``names`` -- a string, a list of strings or ``None``, + the names of the vector of the canonical basis of the + Ore module; if ``None``, elements are represented as + vectors in `K^d` (where `K` is the base ring) + + EXAMPLES:: + + sage: Fq. = GF(7^3) + sage: Frob = Fq.frobenius_endomorphism() + sage: V = Fq^2 + sage: mat = matrix(2, [1, z, z^2, z^3]) + sage: f = V.pseudohom(mat, Frob) + + sage: M = f.ore_module() + sage: M + Ore module of rank 2 over Finite Field in z of size 7^3 twisted by z |--> z^7 + + Here `M` is a module over the Ore ring `\mathbb F_q[X; \text{Frob}]` + and the variable `X` acts on `M` through `f`:: + + sage: S. = M.ore_ring() + sage: S + Ore Polynomial Ring in X over Finite Field in z of size 7^3 twisted by z |--> z^7 + sage: v = M((1,0)) + sage: X*v + (1, z) + + The argument ``names`` can be used to give chosen names + to the vectors in the canonical basis:: + + sage: M = f.ore_module(names=('v', 'w')) + sage: M.basis() + [v, w] + + or even:: + + sage: M = f.ore_module(names='e') + sage: M.basis() + [e0, e1] + + Note that the bracket construction also works:: + + sage: M. = f.ore_module() + sage: M.basis() + [v, w] + sage: v + w + v + w + + We refer to :mod:`sage.modules.ore_module` for a + tutorial on Ore modules in SageMath. + + .. SEEALSO:: + + :mod:`sage.modules.ore_module` + """ + from sage.modules.ore_module import OreModule + return OreModule(self._matrix, self.parent()._ore, names) + def _test_nonzero_equal(self, tester): pass diff --git a/src/sage/modules/meson.build b/src/sage/modules/meson.build index 2edb6b1f10a..f47f647e92d 100644 --- a/src/sage/modules/meson.build +++ b/src/sage/modules/meson.build @@ -20,6 +20,10 @@ py.install_sources( 'module_functors.py', 'multi_filtered_vector_space.py', 'numpy_util.pxd', + 'ore_module.py', + 'ore_module_element.py', + 'ore_module_homspace.py', + 'ore_module_morphism.py', 'quotient_module.py', 'real_double_vector.py', 'submodule.py', diff --git a/src/sage/modules/ore_module.py b/src/sage/modules/ore_module.py new file mode 100644 index 00000000000..88996d9ddc6 --- /dev/null +++ b/src/sage/modules/ore_module.py @@ -0,0 +1,2206 @@ +r""" +Ore modules + +Let `R` be a commutative ring, `\theta : K \to K` by a ring +endomorphism and `\partial : K \to K` be a `\theta`-derivation, +that is an additive map satisfying the following axiom + +.. MATH:: + + \partial(x y) = \theta(x) \partial(y) + \partial(x) y + +A Ore module over `(R, \theta, \partial)` is a `R`-module `M` +equipped with a additive `f : M \to M` such that + +.. MATH:: + + f(a x) = \theta(a) f(x) + \partial(a) x + +Such a map `f` is called a pseudomorphism. + +Equivalently, a Ore module is a module over the (noncommutative) +Ore polynomial ring `\mathcal S = R[X; \theta, \partial]`. + +.. RUBRIC:: Defining Ore modules + +SageMath provides support for creating and manipulating Ore +modules that are finite free over the base ring `R`. + +To start with, the method +:meth:`sage.rings.polynomial.ore_polynomial_ring.OrePolynomialRing.quotient_module` +creates the quotient `\mathcal S/ \mathcal S P`, endowed with its structure +of `\mathcal S`-module, that is its structure of Ore module:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: M + Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + +Classical methods are available and we can work with elements in +`M` as we do usually for vectors in finite free modules:: + + sage: M.basis() + [(1, 0), (0, 1)] + + sage: v = M((z, z^2)); v + (z, z^2) + sage: z*v + (z^2, 2*z + 2) + +The Ore action (or equivalently the structure of `\mathcal S`-module) +is also easily accessible:: + + sage: X*v + (3*z^2 + 2*z, 2*z^2 + 4*z + 4) + +The method :meth:`sage.modules.ore_module.OreModule.pseudohom` +returns the map `f` defining the action of `X`:: + + sage: M.pseudohom() + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 0 1] + [4*z 0] + Domain: Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + Codomain: Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + +A useful feature is the possibility to give chosen names to the vectors +of the canonical basis. This is easily done as follows:: + + sage: N. = S.quotient_module(X^3 + z*X + 1) + sage: N + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: N.basis() + [u, v, w] + +Alternatively, one can pass in the argument ``names``; this +could be useful in particular when we want to name the vectors +basis `e_0, e_1, \ldots`:: + + sage: A = S.quotient_module(X^11 + z, names='e') + sage: A + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: A.basis() + [e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10] + +Do not forget to use the method :meth:`inject_variables` to get the +`e_i` in your namespace:: + + sage: e0 + Traceback (most recent call last): + ... + NameError: name 'e0' is not defined + sage: A.inject_variables() + Defining e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10 + sage: e0 + e0 + +.. RUBRIC:: Submodules and quotients + +SageMath provides facilities for creating submodules and quotient +modules of Ore modules. +First of all, we define the Ore module `\mathcal S/\mathcal S P^2` +(for some Ore polynomials `P`), which is obviously not simple:: + + sage: P = X^2 + z*X + 1 + sage: U = S.quotient_module(P^2, names='u') + sage: U.inject_variables() + Defining u0, u1, u2, u3 + +We now build the submodule `\mathcal S P / \mathcal S P^2` using +the method :meth:`sage.modules.ore_module.OreModule.span`:: + + sage: V = U.span(P*u0) + sage: V + Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: V.basis() + [u0 + (z^2+2*z+2)*u2 + 4*z*u3, + u1 + (2*z^2+4*z+4)*u2 + u3] + +We underline that the span is really the `\mathcal S`-span and +not the `R`-span (as otherwise, it will not be a Ore module). + +As before, one can use the attributes ``names`` to give explicit +names to the basis vectors:: + + sage: V = U.span(P*u0, names='v') + sage: V + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: V.inject_variables() + Defining v0, v1 + sage: v0 + v0 + sage: U(v0) + u0 + (z^2+2*z+2)*u2 + 4*z*u3 + +A coercion map from `V` to `U` is automatically created. +Hence, we can safely combine vectors in `V` and vectors in `U` in a +single expression:: + + sage: v0 - u0 + (z^2+2*z+2)*u2 + 4*z*u3 + +We can create the quotient `U/V` using a similar syntax:: + + sage: W = U.quo(P*u0) + sage: W + Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: W.basis() + [u2, u3] + +We see that SageMath reuses by default the names of the representatives +to denote the vectors in the quotient `U/V`. This behaviour can be +overridden by providing explicit names using the attributes ``names``. + +Shortcuts for creating quotients are also available:: + + sage: U / (P*u0) + Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: U/V + Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + +.. RUBRIC:: Morphisms of Ore modules + +For a tutorial on morphisms of Ore modules, we refer to +:mod:`sage.modules.ore_module_morphism`. + +AUTHOR: + +- Xavier Caruso (2024-10) +""" + +# *************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# *************************************************************************** + +import operator +from sage.misc.latex import latex +from sage.misc.latex import latex_variable_name +from sage.structure.sequence import Sequence +from sage.structure.unique_representation import UniqueRepresentation + +from sage.categories.action import Action +from sage.categories.fields import Fields +from sage.categories.ore_modules import OreModules + +from sage.matrix.matrix0 import Matrix +from sage.matrix.constructor import matrix +from sage.matrix.special import identity_matrix + +from sage.rings.polynomial.ore_polynomial_element import OrePolynomial +from sage.modules.free_module import FreeModule_ambient +from sage.modules.free_module_element import FreeModuleElement_generic_dense +from sage.modules.ore_module_element import OreModuleElement + +# Action by left multiplication on Ore modules +############################################## + + +class ScalarAction(Action): + r""" + Action by scalar multiplication on Ore modules. + """ + def _act_(self, a, x): + r""" + Return the result of the action of `a` on `x`. + + INPUT: + + - ``a`` -- a scalar in the base ring + + - ``x`` -- a vector in a Ore module + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module(X^2 + z) # indirect doctest + sage: z*e0 # indirect doctest + z*e0 + """ + return x._rmul_(a) + + +class OreAction(Action): + r""" + Action by left multiplication of Ore polynomial rings + over Ore modules. + """ + def _act_(self, P, x): + r""" + Return the result of the action of `P` on `x`. + + INPUT: + + - ``P`` -- a Ore polynomial + + - ``x`` -- a vector in a Ore module + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module(X^2 + z) # indirect doctest + sage: X*e0 # indirect doctest + e1 + """ + ans = P[0]*x + y = x + for i in range(1, P.degree() + 1): + y = y.image() + ans += y._rmul_(P[i]) + return ans + +# Generic class for Ore modules +############################### + + +def normalize_names(names, rank): + r""" + Return a normalized form of ``names``. + + INPUT: + + - ``names`` -- a string, a list of strings or ``None`` + + - ``rank`` -- the number of names to normalize + + EXAMPLES:: + + sage: from sage.modules.ore_module import normalize_names + + When ``names`` is a string, indices are added:: + + sage: normalize_names('e', 3) + ('e0', 'e1', 'e2') + + When ``names`` is a list or a tuple, it remains untouched + except that it is always casted to a tuple (in order to be + hashable and serve as a key):: + + sage: normalize_names(['u', 'v', 'w'], 3) + ('u', 'v', 'w') + + Similarly, when ``names`` is ``None``, nothing is returned:: + + sage: normalize_names(None, 3) + + If the number of names is not equal to ``rank``, an error + is raised:: + + sage: normalize_names(['u', 'v', 'w'], 2) + Traceback (most recent call last): + ... + ValueError: the number of given names does not match the rank of the Ore module + """ + if names is None: + pass + elif isinstance(names, (list, tuple)): + if rank != len(names): + raise ValueError("the number of given names does not match the rank of the Ore module") + names = tuple([str(name) for name in names]) + elif isinstance(names, str): + names = tuple([names + str(i) for i in range(rank)]) + else: + raise ValueError("names must be a string or a list/tuple of strings") + return names + + +class OreModule(UniqueRepresentation, FreeModule_ambient): + r""" + Generic class for Ore modules. + """ + Element = OreModuleElement + + def __classcall_private__(cls, mat, twist, names=None, category=None): + r""" + Normalize the input before passing it to the init function + (useful to ensure the uniqueness assumption). + + INPUT: + + - ``mat`` -- the matrix defining the action of the Ore variable + + - ``twist`` -- the twisting morphism/derivation + + - ``names`` (default: ``None``) -- a string of a list of strings, + the names of the vector of the canonical basis; if ``None``, + elements are represented as vectors in `K^d` + + - ``category`` (default: ``None``) -- the category of this + Ore module + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^2 + z + + sage: M1 = S.quotient_module(P) + sage: M2 = S.quotient_module(P, names='e') + sage: M3. = S.quotient_module(P) + + sage: M1 is M2 + False + sage: M2 is M3 + True + """ + base = mat.base_ring() + if category is None: + category = OreModules(base, twist) + rank = mat.nrows() + if mat.ncols() != rank: + raise ValueError("matrix must be square") + names = normalize_names(names, rank) + return cls.__classcall__(cls, mat, category._ore, names, category) + + def __init__(self, mat, ore, names, category): + r""" + Initialize this Ore module. + + INPUT: + + - ``mat`` -- the matrix defining the action of the Ore variable + + - ``ore`` -- the underlying Ore polynomial ring + + - ``names`` -- a string of a list of strings, + the names of the vector of the canonical basis; if ``None``, + elements are represented as vectors in `K^d` + + - ``category`` -- the category of this Ore module + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) # indirect doctest + sage: type(M) + + + sage: TestSuite(M).run() + """ + base = mat.base_ring() + rank = mat.nrows() + FreeModule_ambient.__init__(self, base, rank, category=category) + self.register_action(ScalarAction(base, self, True, operator.mul)) + self._ore = ore + self._ore_category = category + self._names = names + if names is not None: + self._latex_names = [latex_variable_name(name) for name in names] + self._submodule_class = OreSubmodule + self._quotientModule_class = OreQuotientModule + self._pseudohom = FreeModule_ambient.pseudohom(self, mat, ore, codomain=self) + + def _repr_(self): + r""" + Return a string representation of this Ore module. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: M._repr_() + 'Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5' + sage: N = S.quotient_module(X^2 + z, names='e') + sage: N._repr_() + 'Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5' + + :: + + sage: K. = Frac(GF(17)['z']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M = S.quotient_module(X^2 + z) + sage: M._repr_() + 'Ore module of rank 2 over Fraction Field of Univariate Polynomial Ring in z over Finite Field of size 17 twisted by d/dz' + """ + s = "Ore module " + if self._names is None: + s += "of rank %s " % self.rank() + else: + s += "<" + ", ".join(self._names) + "> " + s += "over %s %s" % (self.base_ring(), self._ore._repr_twist()) + return s + + def _latex_(self): + r""" + Return a LaTeX representation of this Ore module. + + TESTS:: + + sage: K. = GF(5^3) + sage: Frob = K.frobenius_endomorphism() + sage: S. = OrePolynomialRing(K, Frob) + sage: M = S.quotient_module(X^2 + z) + sage: latex(M) + \texttt{Ore module of rank } 2\texttt{ over } \Bold{F}_{5^{3}} \texttt{ twisted by } z \mapsto z^{5} + sage: N = S.quotient_module(X^2 + z, names='e') + sage: latex(N) + \left_{\Bold{F}_{5^{3}} , z \mapsto z^{5} } + + :: + + sage: T. = OrePolynomialRing(K, Frob^3, polcast=False) + sage: M = T.quotient_module(Y^2 + z^2) + sage: latex(M) + \texttt{Ore module of rank } 2\texttt{ over } \Bold{F}_{5^{3}}\texttt{ untwisted} + """ + if self._names is None: + s = "\\texttt{Ore module of rank } %s" % self.rank() + s += "\\texttt{ over } %s" % latex(self.base_ring()) + twist = self._ore._latex_twist() + if twist == "": + s += "\\texttt{ untwisted}" + else: + s += "\\texttt{ twisted by }" + twist + else: + s = "\\left<" + ", ".join(self._latex_names) + "\\right>" + s += "_{%s" % latex(self.base_ring()) + twist = self._ore._latex_twist() + if twist != "": + s += "," + twist + s += "}" + return s + + def _repr_element(self, x): + r""" + Return a string representation of the element `x` in + this Ore module. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: M((z, z^2)) # indirect doctest + (z, z^2) + """ + return FreeModuleElement_generic_dense._repr_(x) + + def _latex_element(self, x): + r""" + Return a LaTeX representation of the element `x` in + this Ore module. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: v = M((z, z^2)) + sage: latex(v) # indirect doctest + \left(z,\,z^{2}\right) + """ + return FreeModuleElement_generic_dense._latex_(x) + + def _coerce_map_from_(self, S): + r""" + Return a coercion map from `M` to ``self``, or ``None``. + + This method always returns ``None``; all coercions between + Ore modules are currently handled using the method + :meth:`register_coercion`. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module((X + z)^2) + sage: N = M.span((X + z)*v) + + sage: M._coerce_map_from_(N) + sage: M.coerce_map_from(N) + Ore module morphism: + From: Ore module of rank 1 over Finite Field in z of size 5^3 twisted by z |--> z^5 + To: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + """ + pass + + def is_zero(self): + r""" + Return ``True`` if this Ore module is reduced to zero. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: M + Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: M.is_zero() + False + + sage: Q = M.quo(M) + sage: Q.is_zero() + True + """ + return self.rank() == 0 + + def rename_basis(self, names, coerce=False): + r""" + Return the same Ore module with the given naming + for the vectors in its distinguished basis. + + INPUT: + + - ``names`` -- a string or a list of strings, the + new names + + - ``coerce`` (default: ``False``) -- a boolean; if + ``True``, a coercion map from this Ore module to + renamed version is set + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: M + Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + + sage: Me = M.rename_basis('e') + sage: Me + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + + Now compare how elements are displayed:: + + sage: M.random_element() # random + (3*z^2 + 4*z + 2, 3*z^2 + z) + sage: Me.random_element() # random + (2*z+4)*e0 + (z^2+4*z+4)*e1 + + At this point, there is no coercion map between ``M`` + and ``Me``. Therefore, adding elements in both parents + results in an error:: + + sage: M.random_element() + Me.random_element() + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for +: + 'Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5' and + 'Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5' + + In order to set this coercion, one should define ``Me`` + by passing the extra argument ``coerce=True``:: + + sage: Me = M.rename_basis('e', coerce=True) + sage: M.random_element() + Me.random_element() # random + 2*z^2*e0 + (z^2+z+4)*e1 + + .. WARNING:: + + Use ``coerce=True`` with extreme caution. Indeed, + setting inappropriate coercion maps may result in a + circular path in the coercion graph which, in turn, + could eventually break the coercion system. + + Note that the bracket construction also works:: + + sage: M. = M.rename_basis() + sage: M + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + + In this case, `v` and `w` are automatically defined:: + + sage: v + w + v + w + """ + rank = self.rank() + names = normalize_names(names, rank) + cls = self.__class__ + M = cls.__classcall__(cls, self._pseudohom._matrix, + self._ore, names, self._ore_category) + if coerce: + mat = identity_matrix(self.base_ring(), rank) + id = self.hom(mat, codomain=M) + M._unset_coercions_used() + M.register_coercion(id) + return M + + def pseudohom(self): + r""" + Return the pseudomorphism giving the action of the Ore + variable on this Ore module. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 - z^2*X + (z+2) + sage: M = S.quotient_module(P) + sage: M.pseudohom() + Free module pseudomorphism (twisted by z |--> z^5) defined by the matrix + [ 0 1 0] + [ 0 0 1] + [4*z + 3 z^2 4*z] + Domain: Ore module of rank 3 over Finite Field in z of size 5^3 twisted by z |--> z^5 + Codomain: Ore module of rank 3 over Finite Field in z of size 5^3 twisted by z |--> z^5 + + .. SEEALSO:: + + :meth:`matrix` + """ + return self._pseudohom + + def ore_ring(self, names='x', action=True): + r""" + Return the underlying Ore polynomial ring. + + INPUT: + + - ``names`` (default: ``x``) -- a string, the name + of the variable + + - ``action`` (default: ``True``) -- a boolean; if + ``True``, an action of the Ore polynomial ring on + the Ore module is set + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module(X^2 - a) + sage: M.ore_ring() + Ore Polynomial Ring in x over Finite Field in a of size 5^3 twisted by a |--> a^5 + + We can use a different variable name:: + + sage: M.ore_ring('Y') + Ore Polynomial Ring in Y over Finite Field in a of size 5^3 twisted by a |--> a^5 + + Alternatively, one can use the following shortcut:: + + sage: T. = M.ore_ring() + sage: T + Ore Polynomial Ring in Z over Finite Field in a of size 5^3 twisted by a |--> a^5 + + In all the above cases, an action of the returned Ore polynomial + ring on `M` is registered:: + + sage: Z*e1 + e2 + sage: Z*e2 + a*e1 + + Specifying ``action=False`` prevents this to happen:: + + sage: T. = M.ore_ring(action=False) + sage: U*e1 + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Ore Polynomial Ring in U over Finite Field in a of size 5^3 twisted by a |--> a^5' and + 'Ore module over Finite Field in a of size 5^3 twisted by a |--> a^5' + """ + S = self._ore_category.ore_ring(names) + if action: + self._unset_coercions_used() + self.register_action(OreAction(S, self, True, operator.mul)) + return S + + def twisting_morphism(self): + r""" + Return the twisting morphism corresponding to this Ore module. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X + z) + sage: M.twisting_morphism() + Frobenius endomorphism z |--> z^5 on Finite Field in z of size 5^3 + + When the twisting morphism is trivial (that is, the identity), + nothing is returned:: + + sage: R. = QQ[] + sage: T. = OrePolynomialRing(R, R.derivation()) + sage: M = T.quotient_module(Y + t^2) + sage: M.twisting_morphism() + + .. SEEALSO:: + + :meth:`twisting_derivation` + """ + return self._ore.twisting_morphism() + + def twisting_derivation(self): + r""" + Return the twisting derivation corresponding to this Ore module. + + EXAMPLES:: + + sage: R. = QQ[] + sage: T. = OrePolynomialRing(R, R.derivation()) + sage: M = T.quotient_module(Y + t^2) + sage: M.twisting_derivation() + d/dt + + When the twisting derivation in zero, nothing is returned:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X + z) + sage: M.twisting_derivation() + + .. SEEALSO:: + + :meth:`twisting_morphism` + """ + return self._ore.twisting_derivation() + + def matrix(self): + r""" + Return the matrix giving the action of the Ore variable + on this Ore module. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 - z^2*X + (z+2) + sage: M = S.quotient_module(P) + sage: M.matrix() + [ 0 1 0] + [ 0 0 1] + [4*z + 3 z^2 4*z] + + We recognize the companion matrix attached to the Ore + polynomial `P`. This is of course not a coincidence given + that the pseudomorphism corresponds to the left multiplication + + .. SEEALSO:: + + :meth:`pseudohom` + """ + return self._pseudohom.matrix() + + def basis(self): + r""" + Return the canonical basis of this Ore module. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^3 - z) + sage: M.basis() + [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + """ + rank = self.rank() + zero = self.base_ring().zero() + one = self.base_ring().one() + coeffs = [zero] * rank + B = [ ] + for i in range(rank): + coeffs[i] = one + B.append(self(coeffs)) + coeffs[i] = zero + return B + + def gens(self): + r""" + Return the canonical basis of this Ore module. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^3 - z) + sage: M.gens() + [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + """ + return self.basis() + + def gen(self, i): + r""" + Return the `i`-th vector of the canonical basis + of this Ore module. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^3 - z) + sage: M.gen(0) + (1, 0, 0) + sage: M.gen(1) + (0, 1, 0) + sage: M.gen(2) + (0, 0, 1) + sage: M.gen(3) + Traceback (most recent call last): + ... + IndexError: generator is not defined + """ + rank = self.rank() + if i < 0 or i >= rank: + raise IndexError("generator is not defined") + zero = self.base_ring().zero() + one = self.base_ring().one() + coeffs = [zero] * rank + coeffs[i] = one + return self(coeffs) + + def an_element(self): + r""" + Return an element of this Ore module. + + EXAMPLES: + + When the Ore module is not zero, the returned element + is the first vector of the distinguished basis:: + + sage: K. = Frac(QQ['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module(X^2 - t) + sage: M.an_element() + u + + On the contrary, when the Ore module vanishes, the + returned element is of course zero:: + + sage: N = M / u + sage: N + Ore module of rank 0 over Fraction Field of Univariate Polynomial Ring in t over Rational Field twisted by d/dt + sage: N.an_element() + 0 + """ + if self.rank() > 0: + return self.gen(0) + else: + return self.zero() + + def random_element(self, *args, **kwds): + r""" + Return a random element in this Ore module. + + Extra arguments are passed to the random generator + of the base ring. + + EXAMPLES:: + + sage: A. = QQ['t'] + sage: S. = OrePolynomialRing(A, A.derivation()) + sage: M = S.quotient_module(X^3 - t, names='e') + sage: M.random_element() # random + (-1/2*t^2 - 3/4*t + 3/2)*e0 + (-3/2*t^2 - 3*t + 4)*e1 + (-6*t + 2)*e2 + + sage: M.random_element(degree=5) # random + (4*t^5 - 1/2*t^4 + 3/2*t^3 + 6*t^2 - t - 1/10)*e0 + (19/3*t^5 - t^3 - t^2 + 1)*e1 + (t^5 + 4*t^4 + 4*t^2 + 1/3*t - 33)*e2 + """ + K = self.base_ring() + r = self.rank() + vs = [K.random_element(*args, **kwds) for _ in range(r)] + return self(vs) + + def module(self): + r""" + Return the underlying free module of this Ore module. + + EXAMPLES:: + + sage: A. = QQ['t'] + sage: S. = OrePolynomialRing(A, A.derivation()) + sage: M = S.quotient_module(X^3 - t) + sage: M + Ore module of rank 3 over Univariate Polynomial Ring in t over Rational Field twisted by d/dt + + sage: M.module() + Ambient free module of rank 3 over the principal ideal domain Univariate Polynomial Ring in t over Rational Field + """ + return self.base_ring() ** self.rank() + + def _Hom_(self, codomain, category): + r""" + Return the space of Ore morphisms from this Ore module + to ``codomain``. + + INPUT: + + - ``codomain`` -- a Ore module + + - ``category`` -- the category in which the morphisms are + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 - z) + sage: N = S.quotient_module(X^3 - z) + + sage: Hom(M, N) # indirect doctest + Set of Morphisms + from Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + to Ore module of rank 3 over Finite Field in z of size 5^3 twisted by z |--> z^5 + in Category of enumerated finite dimensional Ore modules with basis over Finite Field in z of size 5^3 twisted by z |--> z^5 + + :: + + sage: End(M) # indirect doctest + Set of Morphisms + from Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + to Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + in Category of enumerated finite dimensional Ore modules with basis over Finite Field in z of size 5^3 twisted by z |--> z^5 + """ + from sage.modules.ore_module_homspace import OreModule_homspace + return OreModule_homspace(self, codomain) + + def hom(self, im_gens, codomain=None): + r""" + Return the morphism from this Ore module to ``codomain`` + defined by ``im_gens``. + + INPUT: + + - ``im_gens`` -- a datum defining the morphism to build; + it could either a list, a tuple, a dictionary or a morphism + of Ore modules + + - ``codomain`` (default: ``None``) -- a Ore module, the + codomain of the morphism; if ``None``, it is inferred from + ``im_gens`` + + EXAMPLES:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: P = X^3 + 2*t*X^2 + (t^2 + 2)*X + t + sage: Q = t*X^2 - X + 1 + + sage: U = S.quotient_module(P, names='u') + sage: U.inject_variables() + Defining u0, u1, u2 + sage: V = S.quotient_module(P*Q, names='v') + sage: V.inject_variables() + Defining v0, v1, v2, v3, v4 + + The first method for creating a morphism from `U` to `V` is + to explicitly write down its matrix in the canonical bases:: + + sage: mat = matrix(3, 5, [1, 4, t, 0, 0, + ....: 0, 1, 0, t, 0, + ....: 0, 0, 1, 1, t]) + sage: f = U.hom(mat, codomain=V) + sage: f + Ore module morphism: + From: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + To: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + + This method is however not really convenient because it + requires to compute beforehand all the entries of the + defining matrix. + Instead, we can pass the list of images of the generators:: + + sage: g = U.hom([Q*v0, X*Q*v0, X^2*Q*v0]) + sage: g + Ore module morphism: + From: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + To: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: g.matrix() + [1 4 t 0 0] + [0 1 0 t 0] + [0 0 1 1 t] + + One can even give the values of the morphism on a smaller + set as soon as the latter generates the domain as Ore module. + The syntax uses dictionaries as follows:: + + sage: h = U.hom({u0: Q*v0}) + sage: h + Ore module morphism: + From: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + To: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: g == h + True + + Finally ``im_gens`` can also be itself a Ore morphism, in which + case SageMath tries to cast it into a morphism with the requested + domains and codomains. + As an example below, we restrict `g` to a subspace:: + + sage: C. = U.span((X + t)*u0) + sage: gC = C.hom(g) + sage: gC + Ore module morphism: + From: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + To: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + + sage: g(c0) == gC(c0) + True + sage: g(c1) == gC(c1) + True + + TESTS:: + + sage: U.hom(0) + Traceback (most recent call last): + ... + ValueError: im_gens must be a list, a tuple, a dictionary, a matrix or a Ore module morphism + + sage: U.hom([Q*v0]) + Traceback (most recent call last): + ... + ValueError: wrong number of generators + + sage: U.hom({u0: Q*v0, u1: Q*v0}) + Traceback (most recent call last): + ... + ValueError: does not define a morphism of Ore modules + + sage: U.hom({(X+t)*u0: (X+t)*Q*v0}) + Traceback (most recent call last): + ... + ValueError: does not define a morphism of Ore modules + """ + from sage.modules.ore_module_morphism import OreModuleMorphism + if codomain is None: + if isinstance(im_gens, Matrix): + codomain = self + elif isinstance(im_gens, OreModuleMorphism): + codomain = im_gens.codomain() + elif isinstance(im_gens, (list, tuple)): + codomain = Sequence(im_gens).universe() + elif isinstance(im_gens, dict): + codomain = Sequence(im_gens.values()).universe() + else: + raise ValueError("im_gens must be a list, a tuple, a dictionary, a matrix or a Ore module morphism") + H = self.Hom(codomain) + return H(im_gens) + + def multiplication_map(self, P): + r""" + Return the multiplication by `P` acting on this Ore module. + + INPUT: + + - ``P`` -- a scalar in the base ring, or a Ore polynomial + + EXAMPLES:: + + sage: K. = GF(7^5) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + a*X^2 + X - a^2 + sage: M = S.quotient_module(P) + + We define the scalar multiplication by an element in the base ring:: + + sage: f = M.multiplication_map(3) + sage: f + Ore module endomorphism of Ore module of rank 3 over Finite Field in a of size 7^5 twisted by a |--> a^7 + sage: f.matrix() + [3 0 0] + [0 3 0] + [0 0 3] + + Be careful that an element in the base ring defines a Ore morphism + if and only if it is fixed by the twisting morphisms and killed by + the derivation (otherwise the multiplication by this element does + not commute with the Ore action). + In SageMath, attempting to create the multiplication by an element + which does not fulfill these requirements leads to an error:: + + sage: M.multiplication_map(a) + Traceback (most recent call last): + ... + ValueError: does not define a morphism of Ore modules + + As soon as it defines a Ore morphism, one can also build the left + multiplication by an Ore polynomial:: + + sage: g = M.multiplication_map(X^5) + sage: g + Ore module endomorphism of Ore module of rank 3 over Finite Field in a of size 7^5 twisted by a |--> a^7 + sage: g.matrix() + [ 3*a^4 + 3*a^3 + 6*a^2 + 5*a 4*a^4 + 5*a^3 + 2*a^2 + 6 6*a^4 + 6*a^3 + a^2 + 4] + [ a^2 + 3 5*a^4 + 5*a^3 + 6*a^2 + 4*a + 1 a^3 + 5*a^2 + 4] + [6*a^4 + 6*a^3 + 3*a^2 + 3*a + 1 4*a^4 + 2*a^3 + 3*a + 5 6*a^4 + 6*a^3 + 2*a^2 + 5*a + 2] + + We check that the characteristic polynomial of `g` is the reduced + norm of the Ore polynomial `P` we started with (this is a classical + property):: + + sage: g.charpoly() + x^3 + 4*x^2 + 2*x + 5 + sage: P.reduced_norm(var='x') + x^3 + 4*x^2 + 2*x + 5 + """ + if isinstance(P, OrePolynomial): + S = P.parent() + ore = self._ore + if S._morphism != ore._morphism or S._derivation != ore._derivation: + raise ValueError("twist does not match") + action = OreAction(S, self, True, operator.mul) + M = matrix([action._act_(P, x).list() for x in self.basis()]) + else: + P = self.base_ring()(P) + r = self.rank() + M = matrix(r, r, P) + H = self.Hom(self) + return H(M) + + def identity_morphism(self): + r""" + Return the identity morphism of this Ore module. + + EXAMPLES:: + + sage: K. = GF(7^5) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module(X^2 + a*X + a^2) + sage: id = M.identity_morphism() + sage: id + Ore module endomorphism of Ore module over Finite Field in a of size 7^5 twisted by a |--> a^7 + + sage: id(u) + u + sage: id(v) + v + """ + H = self.Hom(self) + one = self.base_ring().one() + return H(one) + + def _span(self, gens): + r""" + Return a matrix whose lines form a basis over the base field + of the submodule of this Ore module generated over the Ore + ring by ``gens``. + + INPUT: + + - ``gens`` -- a list of vectors or submodules of this Ore module + + TESTS:: + + sage: K. = GF(7^5) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^2 + a + sage: Q = X^3 + a^2*X + 1 + sage: M = S.quotient_module(P*Q, names='e') + sage: M.inject_variables() + Defining e0, e1, e2, e3, e4 + + sage: M._span([Q*e0]) + [ 1 0 a^4 + 5*a^3 + 6*a^2 + a 1 6*a^2] + [ 0 1 2*a^4 + 6*a^2 + 2*a + 3 0 1] + + sage: M._span([Q*e0, e1]) + [1 0 0 0 0] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + [0 0 0 0 1] + + sage: N = M.span(Q*e0) + sage: M._span([N, e2]) + [1 0 0 0 0] + [0 1 0 0 0] + [0 0 1 0 0] + [0 0 0 1 0] + [0 0 0 0 1] + """ + base = self.base_ring() + rank = self.rank() + f = self._pseudohom + if not isinstance(gens, (list, tuple)): + gens = [gens] + rows = [] + for gen in gens: + if isinstance(gen, OreModule): + incl = self.coerce_map_from(gen) + if incl is None: + raise ValueError("not canonically a submodule") + rows += incl._matrix.rows() + elif isinstance(gen, OreModuleElement): + rows.append(self(gen).list()) + if len(rows) < 2*rank: + zero = rank * [base.zero()] + rows += (2*rank - len(rows)) * [zero] + M = matrix(base, rows) + M.echelonize() + oldr = 0 + r = M.rank() + iter = 1 + while r > oldr: + for i in range(r): + v = M.row(i) + for _ in range(iter): + v = f(v) + v = v.list() + for j in range(rank): + M[i+r,j] = v[j] + M.echelonize() + oldr = r + r = M.rank() + iter *= 2 + return M.matrix_from_rows(range(r)) + + def span(self, gens, names=None): + r""" + Return the submodule of this Ore module generated (over the + underlying Ore ring) by ``gens``. + + INPUT: + + - ``gens`` -- a list of vectors or submodules of this Ore module + + - ``names`` (default: ``None``) -- the name of the vectors in a + basis of this submodule + + EXAMPLES:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: P = X^2 + t*X + 1 + sage: M = S.quotient_module(P^3, names='e') + sage: M.inject_variables() + Defining e0, e1, e2, e3, e4, e5 + + We create the submodule `M P`:: + + sage: MP = M.span([P*e0]) + sage: MP + Ore module of rank 4 over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: MP.basis() + [e0 + (t^4+t^2+3)*e4 + t^3*e5, + e1 + (4*t^3+2*t)*e4 + (4*t^2+3)*e5, + e2 + (2*t^2+2)*e4 + 2*t*e5, + e3 + 4*t*e4 + 4*e5] + + When there is only one generator, encapsulating it in a list is + not necessary; one can equally write:: + + sage: MP = M.span(P*e0) + + If one wants, one can give names to the basis of the submodule using + the attribute ``names``:: + + sage: MP2 = M.span(P^2*e0, names='u') + sage: MP2.inject_variables() + Defining u0, u1 + sage: MP2.basis() + [u0, u1] + + sage: M(u0) + e0 + (t^2+4)*e2 + 3*t^3*e3 + (t^2+1)*e4 + 3*t*e5 + + Note that a coercion map from the submodule to the ambient module + is automatically set:: + + sage: M.has_coerce_map_from(MP2) + True + + Therefore, combining elements of ``M`` and ``MP2`` in the same + expression perfectly works:: + + sage: t*u0 + e1 + t*e0 + e1 + (t^3+4*t)*e2 + 3*t^4*e3 + (t^3+t)*e4 + 3*t^2*e5 + + Here is an example with multiple generators:: + + sage: MM = M.span([MP2, P*e1]) + sage: MM.basis() + [e0, e1, e2, e3, e4, e5] + + In this case, we obtain the whole space. + + Creating submodules of submodules is also allowed:: + + sage: N = MP.span(P^2*e0) + sage: N + Ore module of rank 2 over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: N.basis() + [e0 + (t^2+4)*e2 + 3*t^3*e3 + (t^2+1)*e4 + 3*t*e5, + e1 + (4*t^2+4)*e3 + 3*t*e4 + 4*e5] + + .. SEEALSO:: + + :meth:`quotient` + """ + gens = self._span(gens) + return self._submodule_class(self, gens, names=names) + + def quotient(self, sub, names=None, check=True): + r""" + Return the quotient of this Ore module by the submodule + generated (over the underlying Ore ring) by ``gens``. + + INPUT: + + - ``gens`` -- a list of vectors or submodules of this Ore module + + - ``names`` (default: ``None``) -- the name of the vectors in a + basis of the quotient + + - ``check`` (default: ``True``) -- a boolean, ignored + + EXAMPLES:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: P = X^2 + t*X + 1 + sage: M = S.quotient_module(P^3, names='e') + sage: M.inject_variables() + Defining e0, e1, e2, e3, e4, e5 + + We create the quotient `M/MP`:: + + sage: modP = M.quotient(P*e0) + sage: modP + Ore module of rank 2 over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + + As a shortcut, we can write ``quo`` instead of ``quotient`` or even + use the ``/`` operator:: + + sage: modP = M / (P*e0) + sage: modP + Ore module of rank 2 over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + + By default, the vectors in the quotient have the same names as their + representatives in `M`:: + + sage: modP.basis() + [e4, e5] + + One can override this behavior by setting the attributes ``names``:: + + sage: modP = M.quo(P*e0, names='u') + sage: modP.inject_variables() + Defining u0, u1 + sage: modP.basis() + [u0, u1] + + Note that a coercion map from the initial Ore module to its quotient + is automatically set. As a consequence, combining elements of ``M`` + and ``modP`` in the same formula works:: + + sage: t*u0 + e1 + (t^3+4*t)*u0 + (t^2+2)*u1 + + One can combine the construction of quotients and submodules without + trouble. For instance, here we build the space `M P / M P^2`:: + + sage: modP2 = M / (P^2*e0) + sage: N = modP2.span(P*e0) + sage: N + Ore module of rank 2 over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: N.basis() + [e2 + (2*t^2+2)*e4 + 2*t*e5, + e3 + 4*t*e4 + 4*e5] + + .. SEEALSO:: + + :meth:`quo`, :meth:`span` + """ + gens = self._span(sub) + return self._quotientModule_class(self, gens, names=names) + + quo = quotient + + def __eq__(self, other): + r""" + Return ``True`` if this Ore module is the same than ``other``. + + TESTS: + + Different names lead to different parents:: + + sage: K. = GF(7^5) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module(X^2 + a*X + a^2) + sage: N. = S.quotient_module(X^2 + a*X + a^2) + sage: M == N + False + + However, different syntaxes resulting in the same names lead + to the same parent:: + + sage: N2 = S.quotient_module(X^2 + a*X + a^2, names='e') + sage: N == N2 + True + """ + return self is other + + def __hash__(self): + r""" + Return a hash of this Ore module. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^3 - z) + sage: hash(M) # random + 128873304640624 + """ + return id(self) + + +# Submodules +############ + +class OreSubmodule(OreModule): + r""" + Class for submodules of Ore modules. + """ + def __classcall_private__(cls, ambient, gens, names): + r""" + Normalize the input before passing it to the init function + (useful to ensure the uniqueness assupmtion). + + INPUT: + + - ``ambient`` -- a Ore module, the ambient module where + this submodule sits + + - ``gens`` -- a list of generators (formatted as coordinates + vectors) of this submodule + + - ``names`` -- the name of the vectors of the basis of + the submodule, or ``None`` + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module((X + z)^2) + sage: N1 = M.span((X + z)*v) + sage: N2 = M.span((X + z^5)*w) + sage: N1 is N2 + True + + :: + + sage: R. = QQ[] + sage: S. = OrePolynomialRing(R, R.derivation()) + sage: M. = S.quotient_module((X + t)^2) + sage: M.span((X + t)*v) + Traceback (most recent call last): + ... + NotImplementedError: Ore submodules are currently only implemented over fields + """ + base = ambient.base_ring() + if base not in Fields(): + raise NotImplementedError("Ore submodules are currently only implemented over fields") + if isinstance(gens, Matrix): + basis = gens + else: + basis = matrix(base, gens) + basis = basis.echelon_form() + basis.set_immutable() + rank = basis.rank() + if basis.nrows() != rank: + basis = basis.matrix_from_rows(range(rank)) + names = normalize_names(names, rank) + return cls.__classcall__(cls, ambient, basis, names) + + def __init__(self, ambient, basis, names): + r""" + Initialize this Ore submodule. + + INPUT: + + - ``ambient`` -- a Ore module, the ambient module where + this submodule sits + + - ``basis`` -- the echelon basis of this submodule + + - ``names`` -- the name of the vectors of the basis of + the submodule, or ``None`` + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module((X + z)^2) + sage: N = M.span((X + z)*v) # indirect doctest + sage: type(N) + + + sage: TestSuite(N).run() + """ + from sage.modules.ore_module_morphism import OreModuleRetraction + base = ambient.base_ring() + self._ambient = ambient + self._basis = basis + rows = [basis.solve_left(ambient(x).image()) for x in basis.rows()] + OreModule.__init__(self, matrix(base, rows), + ambient.ore_ring(action=False), + names, ambient._ore_category) + coerce = self.hom(basis, codomain=ambient) + ambient.register_coercion(coerce) + self._inject = coerce.__copy__() + self.register_conversion(OreModuleRetraction(ambient, self)) + + def _repr_element(self, x): + r""" + Return a string representation of ``x``. + + By default, elements in a Ore submodule are printed as their + images in the ambient module. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module((X + z)^2) + sage: N = M.span((X + z)*v) + sage: N.an_element() # indirect doctest + v + (3*z^2+4)*w + """ + return self._ambient(x)._repr_() + + def _latex_element(self, x): + r""" + Return a LaTeX representation of ``x``. + + By default, elements in a Ore submodule are rendered as their + images in the ambient module. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module((X + z)^2) + sage: N = M.span((X + z)*v) + sage: latex(N.an_element()) # indirect doctest + v + \left(3 z^{2} + 4\right) w + """ + return self._ambient(x)._latex_() + + def ambient(self): + r""" + Return the ambient Ore module in which this submodule lives. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module((X + z)^2) + sage: N = M.span((X + z)*v) + sage: N.ambient() + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: N.ambient() is M + True + """ + return self._ambient + + def rename_basis(self, names, coerce=False): + r""" + Return the same Ore module with the given naming + for the vectors in its distinguished basis. + + INPUT: + + - ``names`` -- a string or a list of strings, the + new names + + - ``coerce`` (default: ``False``) -- a boolean; if + ``True``, a coercion map from this Ore module to + renamed version is set + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z^2) + sage: M + Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + + sage: Me = M.rename_basis('e') + sage: Me + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + + Now compare how elements are displayed:: + + sage: M.random_element() # random + (3*z^2 + 4*z + 2, 3*z^2 + z) + sage: Me.random_element() # random + (2*z + 4)*e0 + (z^2 + 4*z + 4)*e1 + + At this point, there is no coercion map between ``M`` + and ``Me``. Therefore, adding elements in both parents + results in an error:: + + sage: M.random_element() + Me.random_element() + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for +: + 'Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5' and + 'Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5' + + In order to set this coercion, one should define ``Me`` + by passing the extra argument ``coerce=True``:: + + sage: Me = M.rename_basis('e', coerce=True) + sage: M.random_element() + Me.random_element() # random + 2*z^2*e0 + (z^2 + z + 4)*e1 + + .. WARNING:: + + Use ``coerce=True`` with extreme caution. Indeed, + setting inappropriate coercion maps may result in a + circular path in the coercion graph which, in turn, + could eventually break the coercion system. + + Note that the bracket construction also works:: + + sage: M. = M.rename_basis() + sage: M + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + + In this case, `v` and `w` are automatically defined:: + + sage: v + w + v + w + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X + z + sage: A. = S.quotient_module(P^2) + sage: M = A.span(P*v) + sage: Me = M.rename_basis('e', coerce=True) + sage: M.an_element() + Me.an_element() + 2*e0 + """ + rank = self.rank() + names = normalize_names(names, rank) + cls = self.__class__ + M = cls.__classcall__(cls, self._ambient, self._basis, names) + if coerce: + mat = identity_matrix(self.base_ring(), rank) + id = self.hom(mat, codomain=M) + M._unset_coercions_used() + M.register_coercion(id) + return M + + def injection_morphism(self): + r""" + Return the inclusion of this submodule in the ambient space. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module((X + z)^2) + sage: N = M.span((X + z)*v) + sage: N.injection_morphism() + Ore module morphism: + From: Ore module of rank 1 over Finite Field in z of size 5^3 twisted by z |--> z^5 + To: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + """ + return self._inject + + def morphism_restriction(self, f): + r""" + Return the restriction of `f` to this submodule. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module((X + z)^2) + sage: N = M.span((X + z)*v) + + sage: f = M.multiplication_map(X^3) + sage: f + Ore module endomorphism of Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + + sage: g = N.morphism_restriction(f) + sage: g + Ore module morphism: + From: Ore module of rank 1 over Finite Field in z of size 5^3 twisted by z |--> z^5 + To: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: g.matrix() + [ 3 4*z^2 + 2] + + TESTS:: + + sage: N.morphism_restriction(g) + Traceback (most recent call last): + ... + ValueError: the domain of the morphism must be the ambient space + """ + if f.domain() is not self._ambient: + raise ValueError("the domain of the morphism must be the ambient space") + return f * self._inject + + def morphism_corestriction(self, f): + r""" + If the image of `f` is contained in this submodule, + return the corresponding corestriction of `f`. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X + z + sage: M. = S.quotient_module(P^2) + sage: N = M.span(P*v) + + sage: f = M.hom({v: P*v}) + sage: f + Ore module endomorphism of Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + + sage: g = N.morphism_corestriction(f) + sage: g + Ore module morphism: + From: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + To: Ore module of rank 1 over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: g.matrix() + [ z] + [4*z^2] + + When the image of the morphism is not contained in this submodule, + an error is raised:: + + sage: h = M.multiplication_map(X^3) + sage: N.morphism_corestriction(h) + Traceback (most recent call last): + ... + ValueError: the image of the morphism is not contained in this submodule + + TESTS:: + + sage: N.morphism_corestriction(g) + Traceback (most recent call last): + ... + ValueError: the codomain of the morphism must be the ambient space + """ + if f.codomain() is not self._ambient: + raise ValueError("the codomain of the morphism must be the ambient space") + rows = [] + basis = self._basis + try: + rows = [basis.solve_left(y) for y in f._matrix.rows()] + except ValueError: + raise ValueError("the image of the morphism is not contained in this submodule") + return f.domain().hom(rows, codomain=self) + + _hom_change_domain = morphism_restriction + _hom_change_codomain = morphism_corestriction + + +# Quotients +########### + +class OreQuotientModule(OreModule): + r""" + Class for quotients of Ore modules. + """ + def __classcall_private__(cls, cover, gens, names): + r""" + Normalize the input before passing it to the init function + (useful to ensure the uniqueness assumption). + + INPUT: + + - ``cover`` -- a Ore module, the cover module of this + quotient + + - ``gens`` -- a list of generators (formatted as coordinates + vectors) of the submodule by which we quotient out + + - ``names`` -- the name of the vectors of the basis of + the quotient, or ``None`` + + TESTS:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module((X + t)^2) + sage: Q1 = M.quo((X + t)*v) + sage: Q2 = M.quo(v + (X + t)*w) + sage: Q1 is Q2 + True + + :: + + sage: R. = QQ[] + sage: S. = OrePolynomialRing(R, R.derivation()) + sage: M. = S.quotient_module((X + t)^2) + sage: M.quo((X + t)*v) + Traceback (most recent call last): + ... + NotImplementedError: quotient of Ore modules are currently only implemented over fields + """ + base = cover.base_ring() + if base not in Fields(): + raise NotImplementedError("quotient of Ore modules are currently only implemented over fields") + if isinstance(gens, Matrix): + basis = gens + else: + basis = matrix(base, gens) + basis = basis.echelon_form() + basis.set_immutable() + rank = basis.rank() + if basis.nrows() != rank: + basis = basis.matrix_from_rows(range(rank)) + names = normalize_names(names, cover.rank() - rank) + return cls.__classcall__(cls, cover, basis, names) + + def __init__(self, cover, basis, names): + r""" + Initialize this Ore quotient. + + INPUT: + + - ``cover`` -- a Ore module, the cover module of this + quotient + + - ``basis`` -- the echelon basis of the submodule + defining the quotient + + - ``names`` -- the name of the vectors of the basis of + the submodule, or ``None`` + + TESTS:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module((X + t)^2) + sage: Q = M.quo((X + t)*v) # indirect doctest + sage: type(Q) + + + sage: TestSuite(N).run() + """ + from sage.modules.ore_module_morphism import OreModuleSection + self._cover = cover + d = cover.rank() + base = cover.base_ring() + self._relations = basis + pivots = basis.pivots() + r = basis.rank() + coerce = matrix(base, d, d-r) + indices = [] + i = 0 + for j in range(d): + if i < r and pivots[i] == j: + i += 1 + else: + indices.append(j) + coerce[j,j-i] = base.one() + for i in range(r): + for j in range(d-r): + coerce[pivots[i],j] = -basis[i,indices[j]] + rows = [cover.gen(i).image() * coerce for i in indices] + OreModule.__init__(self, matrix(base, rows), + cover.ore_ring(action=False), + names, cover._ore_category) + self._indices = indices + self._project = coerce = cover.hom(coerce, codomain=self) + self.register_coercion(coerce) + cover.register_conversion(OreModuleSection(self, cover)) + + def _repr_element(self, x): + r""" + Return a string representation of `x`. + + By default, elements in a Ore quotient are printed as + their (canonical) representatives. + + TESTS:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module((X + t)^2) + sage: Q = M.quo((X + t)*v) + sage: Q.an_element() # indirect doctest + w + """ + M = self._cover + indices = self._indices + base = self.base_ring() + coords = M.rank() * [base.zero()] + for i in range(self.rank()): + coords[indices[i]] = x[i] + return M(coords)._repr_() + + def _latex_element(self, x): + r""" + Return a LaTeX representation of `x`. + + By default, elements in a Ore quotient are rendered as + their (canonical) representatives with a bar. + + TESTS:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module((X + t)^2) + sage: Q = M.quo((X + t)*v) + sage: latex(Q.an_element()) # indirect doctest + \overline{w} + """ + M = self._cover + indices = self._indices + base = self.base_ring() + coords = M.rank() * [base.zero()] + for i in range(self.rank()): + coords[indices[i]] = x[i] + return "\\overline{%s}" % M(coords)._latex_() + + def cover(self): + r""" + If this quotient in `M/N`, return `M`. + + EXAMPLES:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module((X + t)^2) + sage: N = M.quo((X + t)*v) + + sage: N.cover() + Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: N.cover() is M + True + + .. SEEALSO:: + + :meth:`relations` + """ + return self._cover + + def relations(self, names=None): + r""" + If this quotient in `M/N`, return `N`. + + INPUT: + + - ``names`` -- the names of the vectors of the basis + of `N`, or ``None`` + + EXAMPLES:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module((X + t)^2) + sage: Q = M.quo((X + t)*v) + + sage: N = Q.relations() + sage: N + Ore module of rank 1 over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: (X + t)*v in N + True + sage: Q == M/N + True + + It is also possible to define names for the basis elements + of `N`:: + + sage: N. = Q.relations() + sage: N + Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: M(u) + v + 1/t*w + + .. SEEALSO:: + + :meth:`relations` + """ + return self._submodule_class(self._cover, self._relations, names=names) + + def rename_basis(self, names, coerce=False): + r""" + Return the same Ore module with the given naming + for the vectors in its distinguished basis. + + INPUT: + + - ``names`` -- a string or a list of strings, the + new names + + - ``coerce`` (default: ``False``) -- a boolean; if + ``True``, a coercion map from this Ore module to + the renamed version is set + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z*X + 1) + sage: M + Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5 + + sage: Me = M.rename_basis('e') + sage: Me + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + + Now compare how elements are displayed:: + + sage: M.random_element() # random + (3*z^2 + 4*z + 2, 3*z^2 + z) + sage: Me.random_element() # random + (2*z + 4)*e0 + (z^2 + 4*z + 4)*e1 + + At this point, there is no coercion map between ``M`` + and ``Me``. Therefore, adding elements in both parents + results in an error:: + + sage: M.random_element() + Me.random_element() + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for +: + 'Ore module of rank 2 over Finite Field in z of size 5^3 twisted by z |--> z^5' and + 'Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5' + + In order to set this coercion, one should define ``Me`` + by passing the extra argument ``coerce=True``:: + + sage: Me = M.rename_basis('e', coerce=True) + sage: M.random_element() + Me.random_element() # random + 2*z^2*e0 + (z^2 + z + 4)*e1 + + .. WARNING:: + + Use ``coerce=True`` with extreme caution. Indeed, + setting inappropriate coercion maps may result in a + circular path in the coercion graph which, in turn, + could eventually break the coercion system. + + Note that the bracket construction also works:: + + sage: M. = M.rename_basis() + sage: M + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + + In this case, `v` and `w` are automatically defined:: + + sage: v + w + v + w + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X + z + sage: A. = S.quotient_module(P^2) + sage: M = A.quo(P*v) + sage: Me = M.rename_basis('e', coerce=True) + sage: M.an_element() + Me.an_element() + 2*e0 + + """ + rank = self.rank() + names = normalize_names(names, rank) + cls = self.__class__ + M = cls.__classcall__(cls, self._cover, self._relations, names) + if coerce: + mat = identity_matrix(self.base_ring(), rank) + id = self.hom(mat, codomain=M) + M._unset_coercions_used() + M.register_coercion(id) + return M + + def projection_morphism(self): + r""" + Return the projection from the cover module to this quotient. + + EXAMPLES:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module((X + t)^2) + sage: Q = M.quo((X + t)*v) + sage: Q.projection_morphism() + Ore module morphism: + From: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + To: Ore module of rank 1 over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + """ + return self._project + + def morphism_quotient(self, f): + r""" + If this quotient in `M/N` and `f : M \to X` is a morphism + vanishing on `N`, return the induced map `M/N \to X`. + + EXAMPLES:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: P = X + t + sage: M. = S.quotient_module(P^2) + sage: Q. = M.quo(P*v) + + sage: f = M.hom({v: P*v}) + sage: f + Ore module endomorphism of Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: g = Q.morphism_quotient(f) + sage: g + Ore module morphism: + From: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + To: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + + When the given morphism does not vanish on `N`, an error is raised:: + + sage: h = M.multiplication_map(X^5) + sage: h + Ore module endomorphism of Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: Q.morphism_quotient(h) + Traceback (most recent call last): + ... + ValueError: the morphism does not factor through this quotient + + TESTS:: + + sage: Q.morphism_quotient(g) + Traceback (most recent call last): + ... + ValueError: the domain of the morphism must be the cover ring + """ + if f.domain() is not self._cover: + raise ValueError("the domain of the morphism must be the cover ring") + Z = self._relations * f._matrix + if not Z.is_zero(): + raise ValueError("the morphism does not factor through this quotient") + mat = f._matrix.matrix_from_rows(self._indices) + return self.hom(mat, codomain=f.codomain()) + + def morphism_modulo(self, f): + r""" + If this quotient in `M/N` and `f : X \to M` is a morphism, + return the induced map `X \to M/N`. + + EXAMPLES:: + + sage: K. = Frac(GF(5)['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: P = X + t + sage: M. = S.quotient_module(P^2) + sage: Q. = M.quo(P*v) + + sage: f = M.multiplication_map(X^5) + sage: f + Ore module endomorphism of Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + sage: g = Q.morphism_modulo(f) + sage: g + Ore module morphism: + From: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + To: Ore module over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 5 twisted by d/dt + + TESTS:: + + sage: Q.morphism_modulo(g) + Traceback (most recent call last): + ... + ValueError: the codomain of the morphism must be the cover ring + """ + if f.codomain() is not self._cover: + raise ValueError("the codomain of the morphism must be the cover ring") + return self._project * f + + _hom_change_domain = morphism_quotient + _hom_change_codomain = morphism_modulo diff --git a/src/sage/modules/ore_module_element.py b/src/sage/modules/ore_module_element.py new file mode 100644 index 00000000000..d4c83abb233 --- /dev/null +++ b/src/sage/modules/ore_module_element.py @@ -0,0 +1,177 @@ +r""" +Elements in Ore modules + +AUTHOR: + +- Xavier Caruso (2024-10) +""" + +# *************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# *************************************************************************** + +from sage.misc.repr import repr_lincomb +from sage.modules.free_module_element import FreeModuleElement_generic_dense + + +class OreModuleElement(FreeModuleElement_generic_dense): + r""" + A generic element of a Ore module. + """ + def _repr_(self): + r""" + Return a string representation of this element. + + EXAMPLES:: + + sage: K. = Frac(QQ['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module((t+1)*X^2 + 1) + + sage: v - w # indirect doctest + v - w + sage: w - v # indirect doctest + -v + w + sage: X^5*v # indirect doctest + ((-4*t+2)/(t^4+4*t^3+6*t^2+4*t+1))*v + ((t-5)/(t^3+3*t^2+3*t+1))*w + """ + parent = self.parent() + names = parent._names + if parent._names is None: + return self.parent()._repr_element(self) + else: + return repr_lincomb([(names[i], self[i]) for i in range(len(names))]) + + def _latex_(self): + r""" + Return a LaTeX representation of this element. + + EXAMPLES:: + + sage: K. = Frac(QQ['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module((t+1)*X^2 + 1) + + sage: latex(v - w) + v - w + sage: latex(w - v) + -v + w + sage: latex(X^5*v) + \left(\frac{-4 t + 2}{t^{4} + 4 t^{3} + 6 t^{2} + 4 t + 1}\right) v + \left(\frac{t - 5}{t^{3} + 3 t^{2} + 3 t + 1}\right) w + """ + parent = self.parent() + if parent._names is None: + return self.parent()._latex_element(self) + else: + names = parent._latex_names + return repr_lincomb([(names[i], self[i]) for i in range(len(names))], is_latex=True) + + def is_mutable(self): + r""" + Always return ``False`` since elements in Ore modules + are all immutable. + + EXAMPLES:: + + sage: K. = Frac(QQ['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M = S.quotient_module(X^2 + t) + + sage: v, w = M.basis() + sage: v + (1, 0) + sage: v.is_mutable() + False + sage: v[1] = 1 + Traceback (most recent call last): + ... + ValueError: vectors in Ore modules are immutable + """ + return False + + def __setitem__(self, i, v): + r""" + Always raise an error since elements in Ore modules are + all immutable. + + TESTS:: + + sage: K. = Frac(QQ['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module(X^2 + t) + sage: w[1] = 0 + Traceback (most recent call last): + ... + ValueError: vectors in Ore modules are immutable + """ + raise ValueError("vectors in Ore modules are immutable") + + def __hash__(self): + r""" + Return a hash of this element. + + TESTS:: + + sage: K. = Frac(QQ['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module(X^2 + t) + sage: hash(v) # random + -5164621852614943976 + sage: hash(w) # random + -1950498447580522560 + """ + return hash(tuple(self)) + + def vector(self): + r""" + Return the coordinates vector of this element. + + EXAMPLES:: + + sage: K. = Frac(QQ['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module(X^2 + t) + sage: v.vector() + (1, 0) + + We underline that this vector is not an element of the + Ore module; it lives in `K^2`. Compare:: + + sage: v.parent() + Ore module over Fraction Field of Univariate Polynomial Ring in t over Rational Field twisted by d/dt + sage: v.vector().parent() + Vector space of dimension 2 over Fraction Field of Univariate Polynomial Ring in t over Rational Field + """ + V = self.parent().module() + return V(self.list()) + + def image(self): + r""" + Return the image of this element by the pseudomorphism + defining the action of the Ore variable on this Ore module. + + EXAMPLES:: + + sage: K. = Frac(QQ['t']) + sage: S. = OrePolynomialRing(K, K.derivation()) + sage: M. = S.quotient_module(X^2 + t) + sage: v.image() + w + sage: w.image() + -t*v + + TESTS: + + We check that this corresponds to the action of `X`:: + + sage: x = M.random_element() + sage: x.image() == X*x + True + """ + return self.parent()._pseudohom(self) diff --git a/src/sage/modules/ore_module_homspace.py b/src/sage/modules/ore_module_homspace.py new file mode 100644 index 00000000000..25addede79c --- /dev/null +++ b/src/sage/modules/ore_module_homspace.py @@ -0,0 +1,145 @@ +r""" +Space of morphisms between Ore modules + +AUTHOR: + +- Xavier Caruso (2024-10) +""" + +# *************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# *************************************************************************** + +from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.homset import HomsetWithBase +from sage.matrix.matrix_space import MatrixSpace + +from sage.modules.ore_module import OreModule +from sage.modules.ore_module_morphism import OreModuleMorphism + + +class OreModule_homspace(UniqueRepresentation, HomsetWithBase): + r""" + Class for hom spaces between Ore modules. + """ + Element = OreModuleMorphism + + def __init__(self, domain, codomain, category=None): + r""" + Initialize this homspace. + + INPUT: + + - ``domain`` -- a Ore module + + - ``codomain`` -- a Ore module + + - ``category`` (default: ``None``) -- the category in which + the morphisms are + + TESTS:: + + sage: K. = GF(7^2) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X + z) + sage: N = S.quotient_module(X^2 + z) + sage: Hom(M, N) # indirect doctest + Set of Morphisms + from Ore module of rank 1 over Finite Field in z of size 7^2 twisted by z |--> z^7 + to Ore module of rank 2 over Finite Field in z of size 7^2 twisted by z |--> z^7 + in Category of enumerated finite dimensional Ore modules with basis over Finite Field in z of size 7^2 twisted by z |--> z^7 + sage: End(M) # indirect doctest + Set of Morphisms + from Ore module of rank 1 over Finite Field in z of size 7^2 twisted by z |--> z^7 + to Ore module of rank 1 over Finite Field in z of size 7^2 twisted by z |--> z^7 + in Category of enumerated finite dimensional Ore modules with basis over Finite Field in z of size 7^2 twisted by z |--> z^7 + + :: + + sage: V = M.module() + sage: Hom(M, V) + Traceback (most recent call last): + ... + ValueError: codomain must be a Ore module + """ + if not isinstance(domain, OreModule): + raise ValueError("domain must be a Ore module") + if not isinstance(codomain, OreModule): + raise ValueError("codomain must be a Ore module") + if domain.ore_ring(action=False) is not codomain.ore_ring(action=False): + raise ValueError("domain and codomain must be defined over the same ring with same twisting maps") + super().__init__(domain, codomain, category) + base = domain.base_ring() + self._matrix_space = MatrixSpace(base, domain.dimension(), codomain.dimension()) + + def _element_constructor_(self, *args, **kwds): + r""" + Return a morphism in this homspace. + + TESTS:: + + sage: K. = GF(7^2) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X + z) + sage: H = End(M) + sage: H(2) + Ore module endomorphism of Ore module of rank 1 over Finite Field in z of size 7^2 twisted by z |--> z^7 + """ + return self.element_class(self, *args, **kwds) + + def matrix_space(self): + r""" + Return the matrix space used to represent the + morphisms in this homspace. + + EXAMPLES:: + + sage: K. = GF(7^2) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^3 + z*X + 1) + sage: End(M).matrix_space() + Full MatrixSpace of 3 by 3 dense matrices over Finite Field in z of size 7^2 + + :: + + sage: N = S.quotient_module(X^2 + z) + sage: Hom(M, N).matrix_space() + Full MatrixSpace of 3 by 2 dense matrices over Finite Field in z of size 7^2 + """ + return self._matrix_space + + def identity(self): + r""" + Return the identity morphism in this homspace. + + EXAMPLES:: + + sage: K. = GF(7^2) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^3 + z*X + 1) + sage: End(M).identity() + Ore module endomorphism of Ore module of rank 3 over Finite Field in z of size 7^2 twisted by z |--> z^7 + """ + one = self.base_ring().one() + return self(one, check=False) + + def zero(self): + r""" + Return the zero morphism in this homspace. + + EXAMPLES:: + + sage: K. = GF(7^2) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^3 + z*X + 1) + sage: End(M).zero() + Ore module endomorphism of Ore module of rank 3 over Finite Field in z of size 7^2 twisted by z |--> z^7 + """ + zero = self.base_ring().zero() + return self(zero, check=False) diff --git a/src/sage/modules/ore_module_morphism.py b/src/sage/modules/ore_module_morphism.py new file mode 100644 index 00000000000..1c4546daa8a --- /dev/null +++ b/src/sage/modules/ore_module_morphism.py @@ -0,0 +1,966 @@ +r""" +Morphisms between Ore modules + +Let `R` be a commutative ring, `\theta : R \to R` by a ring +endomorphism and `\partial : R \to R` be a `\theta`-derivation. +Let also `\mathcal S = R[X; \theta, \partial]` denote the +associated Ore polynomial ring. + +By definition, a Ore module is a module over `\mathcal S`. In +SageMath, there are rather represented as modules over `R` +equipped with the map giving the action of the Ore variable `X`. +We refer to :mod:`sage.modules.ore_module` for more details. + +A morphism of Ore modules is a `R`-linear morphism commuting +with the Ore action, or equivalenty a `\mathcal S`-linear map. + +.. RUBRIC:: Construction of morphisms + +There are several ways for creating Ore modules morphisms in SageMath. +First of all, one can use the method :meth:`sage.modules.ore_module.OreModule.hom`, +passing to it the matrix (in the canonical bases) of the morphism +we want to build:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module(X^2 + X + z) + + sage: mat = matrix(2, 2, [z, 3*z^2 + z + 2, + ....: z + 1, 4*z + 4]) + sage: f = M.hom(mat) + sage: f + Ore module endomorphism of Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + +Clearly, this method is not optimal: typing all the entries of the +defining matrix is long and a potential source of errors. + +Instead, one can use a dictionary encoding the values taken by the +morphism on a set of generators; the morphism is then automatically +prolonged by `\mathcal S`-linearity. +Actually here, `f` was just the multiplication by `X^3` on `M`. +We can then redefine it simply as follows:: + + sage: g = M.hom({e0: X^3*e0}) + sage: g + Ore module endomorphism of Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + +One can then recover the matrix by using the method +:meth:`sage.modules.ore_module_morphism.OreModuleMorphism.matrix`:: + + sage: g.matrix() + [ z 3*z^2 + z + 2] + [ z + 1 4*z + 4] + +Alternatively, one can use the method +:meth:`sage.modules.ore_module.OreModule.multiplication_map`:: + + sage: h = M.multiplication_map(X^3) + sage: h + Ore module endomorphism of Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: g == h + True + +Of course, this method also accepts values in the base ring:: + + sage: h = M.multiplication_map(2) + sage: h + Ore module endomorphism of Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: h.matrix() + [2 0] + [0 2] + +Be careful that scalar multiplications do not always properly +define a morphism of Ore modules:: + + sage: M.multiplication_map(z) + Traceback (most recent call last): + ... + ValueError: does not define a morphism of Ore modules + +.. RUBRIC: Kernels, images and related things + +SageMath provides methods to compute kernels, cokernels, +images and coimages. In order to illustrate this, we will +build the sequence + +.. MATH:: + + 0 \to \mathcal S/ \mathcal S P + \to \mathcal S/ \mathcal S PQ + \to \mathcal S/ \mathcal S Q \to 0 + +and check that it is exact. +We first build the Ore modules:: + + sage: P = X^2 + z*X + 1 + sage: Q = X^3 + z^2*X^2 + X + z + sage: U = S.quotient_module(P, names='u') + sage: U.inject_variables() + Defining u0, u1 + sage: V = S.quotient_module(P*Q, names='v') + sage: V.inject_variables() + Defining v0, v1, v2, v3, v4 + sage: W = S.quotient_module(Q, names='w') + sage: W.inject_variables() + Defining w0, w1, w2 + +Next, we build the morphisms:: + + sage: f = U.hom({u0: Q*v0}) + sage: g = V.hom({v0: w0}) + +We can now check that `f` is injective by computing its +kernel:: + + sage: f.kernel() + Ore module of rank 0 over Finite Field in z of size 5^3 twisted by z |--> z^5 + +We see on the output that it has dimension `0`; so it +vanishes. Instead of reading the output, one can check +programmatically the vanishing of a Ore module using the +method :meth:`sage.modules.ore_module.OreModule.is_zero`:: + + sage: f.kernel().is_zero() + True + +Actually, in our use case, one can, more simply, use the method +:meth:`sage.modules.ore_module_morphism.OreModuleMorphism.is_injective`:: + + sage: f.is_injective() + True + +Similarly, one checks that `g` is surjective:: + + sage: g.is_surjective() + True + +or equivalently:: + + sage: g.cokernel().is_zero() + True + +Now, we need to check that the kernel of `g` equals the +image of `f`. For this, we compute both and compare the +results:: + + sage: ker = g.kernel() + sage: im = f.image() + sage: ker == im + True + +As a sanity check, one can also verity that the composite +`g \circ f` vanishes:: + + sage: h = g * f + sage: h + Ore module morphism: + From: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + To: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: h.is_zero() + True + +Let us now consider another morphism `f` and build the +canonical isomorphism `\text{coim }f \to \text{im }f` +that it induces. +We start by defining `f`:: + + sage: A = X + z + sage: B = X + z + 1 + sage: P = X^2 + X + z + sage: U = S.quotient_module(B*P, names='u') + sage: U.inject_variables() + Defining u0, u1, u2 + sage: V = S.quotient_module(P*A, names='v') + sage: V.inject_variables() + Defining v0, v1, v2 + + sage: f = U.hom({u0: A*v0}) + sage: f + Ore module morphism: + From: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + To: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + +Now we compute the image and the coimage:: + + sage: I = f.image(names='im') + sage: I + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + + sage: C = f.coimage(names='co') + sage: C + Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + +We can already check that the image and the coimage have the +same rank. We now want to construct the isomorphism between +them. For this, we first need to corestrict `f` to its image. +This is achieved via the method +:meth:`sage.modules.ore_module.OreSubmodule.morphism_corestriction` +of the Ore module:: + + sage: g = I.morphism_corestriction(f) + sage: g + Ore module morphism: + From: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + To: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + +Next, we want to factor `g` by the coimage. We proceed as follows:: + + sage: h = C.morphism_quotient(g) + sage: h + Ore module morphism: + From: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + To: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + +We have found the morphism we were looking for: it is `h`. +We can now check that it is an isomorphism:: + + sage: h.is_isomorphism() + True + +As a shortcut, we can use explicit conversions as follows:: + + sage: H = Hom(C, I) # the hom space + sage: h2 = H(f) + sage: h2 + Ore module morphism: + From: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + To: Ore module over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: h == h2 + True + +.. RUBRIC: Determinants and characteristic polynomials + +For endomorphisms, one can compute classical invariants as +determinants and characteristic polynomials. +To illustrate this, we check on an example that the characteristic +polynomial of the multiplication by `X^3` on the quotient +`\mathcal S / \mathcal S P` is the reduced norm of `P`:: + + sage: P = X^5 + z*X^4 + z^2*X^2 + z + 1 + sage: M = S.quotient_module(P) + sage: f = M.multiplication_map(X^3) + sage: f.charpoly() + x^5 + x^4 + x^3 + x^2 + 1 + + sage: P.reduced_norm('x') + x^5 + x^4 + x^3 + x^2 + 1 + +AUTHOR: + +- Xavier Caruso (2024-10) +""" + +# *************************************************************************** +# Copyright (C) 2024 Xavier Caruso +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# *************************************************************************** + +from sage.misc.latex import latex +from sage.structure.element import Element +from sage.matrix.matrix0 import Matrix +from sage.matrix.constructor import matrix +from sage.categories.map import Map +from sage.categories.morphism import Morphism +from sage.modules.ore_module import OreSubmodule, OreQuotientModule + + +class OreModuleMorphism(Morphism): + r""" + Generic class for morphism between Ore modules. + """ + def __init__(self, parent, im_gens, check=True): + r""" + Initialize this Ore module. + + INPUT: + + - ``parent`` -- the hom space + + - ``im_gens`` -- the image of the generators (formatted as + a list, a tuple, a dictionary or a matrix) or a Ore modules + morphism + + - ``check`` (default: ``True``) -- a boolean, whether we + should check if the given data correctly defined a morphism + of Ore modules + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: type(f) + + + sage: TestSuite(f).run() + """ + Morphism.__init__(self, parent) + domain = parent.domain() + codomain = parent.codomain() + base = domain.base_ring() + MS = parent.matrix_space() + if (isinstance(im_gens, Element) + and base.has_coerce_map_from(im_gens.parent())): + self._matrix = MS(im_gens) + elif isinstance(im_gens, Matrix): + self._matrix = MS(im_gens) + elif isinstance(im_gens, OreModuleMorphism): + # Not optimal: too many intermediate morphisms constructed + f = im_gens + if f.domain() is not domain: + f = domain._hom_change_domain(f) + if f.codomain() is not codomain: + f = codomain._hom_change_codomain(f) + self._matrix = f._matrix + elif isinstance(im_gens, (list, tuple)): + if len(im_gens) != domain.rank(): + raise ValueError("wrong number of generators") + self._matrix = matrix([codomain(v).list() for v in im_gens]) + elif isinstance(im_gens, dict): + zero = parent.base_ring().zero() + dimd = domain.rank() + dimc = codomain.rank() + d = dimc + dimd + vs = [domain(x).list() + codomain(y).list() for x, y in im_gens.items()] + if len(vs) < 2*d: + vs += (2*d - len(vs)) * [d * [zero]] + M = matrix(vs) + M.echelonize() + oldr = 0 + r = M.rank() + iter = 1 + fd = domain._pseudohom + fc = codomain._pseudohom + while r > oldr: + for i in range(r): + row = M.row(i).list() + x = row[:dimd] + y = row[dimd:] + for _ in range(iter): + x = fd(x) + y = fc(y) + v = x.list() + y.list() + for j in range(d): + M[i+r,j] = v[j] + M.echelonize() + oldr = r + r = M.rank() + iter *= 2 + if list(M.pivots()) != list(range(dimd)): + raise ValueError("does not define a morphism of Ore modules") + self._matrix = M.submatrix(0, dimd, dimd, dimc) + else: + raise ValueError("cannot construct a morphism from the given data") + if check: + for x in parent.domain().basis(): + if self._call_(x.image()) != self._call_(x).image(): + raise ValueError("does not define a morphism of Ore modules") + + def _repr_type(self): + r""" + Return a string with the type of this morphism. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: f._repr_type() + 'Ore module' + """ + return "Ore module" + + def _latex_(self): + r""" + Return a LaTeX representation of this morphism. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: latex(f) + \begin{array}{l} + \text{\texttt{Ore module morphism:}} \\ + \text{\texttt{{ }{ }From:}}\hspace{1ex} \texttt{Ore module of rank } 2\texttt{ over } \Bold{F}_{5^{3}} \texttt{ twisted by } z \mapsto z^{5} \\ + \text{\texttt{{ }{ }To:}}\hspace{3ex} \texttt{Ore module of rank } 2\texttt{ over } \Bold{F}_{5^{3}} \texttt{ twisted by } z \mapsto z^{5} + \end{array} + + :: + + sage: Me = M.rename_basis('e') + sage: fe = Me.multiplication_map(X^3) + sage: latex(fe) + \begin{array}{l} + \text{\texttt{Ore module morphism:}} \\ + \text{\texttt{{ }{ }From:}}\hspace{1ex} \left_{\Bold{F}_{5^{3}} , z \mapsto z^{5} } \\ + \text{\texttt{{ }{ }To:}}\hspace{3ex} \left_{\Bold{F}_{5^{3}} , z \mapsto z^{5} } + \end{array} + """ + s = "\\begin{array}{l}\n" + s += "\\text{\\texttt{%s morphism:}} \\\\\n" % self._repr_type() + s += "\\text{\\texttt{{ }{ }From:}}\\hspace{1ex} %s \\\\\n" % latex(self.domain()) + s += "\\text{\\texttt{{ }{ }To:}}\\hspace{3ex} %s\n" % latex(self.codomain()) + s += "\\end{array}" + return s + + def matrix(self): + r""" + Return the matrix defining this morphism. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(2) + sage: f.matrix() + [2 0] + [0 2] + + sage: g = M.multiplication_map(X^3) + sage: g.matrix() + [ 0 3*z^2 + z + 1] + [ 2*z + 1 0] + """ + return self._matrix.__copy__() + + def _call_(self, x): + r""" + Return the image of `x` by this morphism. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module(X^2 + z*X + 1) + sage: f = M.multiplication_map(X^3) + sage: f(v) + (2*z^2+4*z+4)*v + (4*z^2+3*z+3)*w + + We check that it is the correct answer:: + + sage: X^3 * v + (2*z^2+4*z+4)*v + (4*z^2+3*z+3)*w + """ + return self.codomain()(x * self._matrix) + + def is_zero(self): + r""" + Return ``True`` if this morphism is zero. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 + z^2 + 1 + sage: M = S.quotient_module(P^2, names='e') + sage: M.inject_variables() + Defining e0, e1, e2, e3, e4, e5 + + sage: f = M.hom({e0: P*e0}) + sage: f.is_zero() + False + sage: (f*f).is_zero() + True + """ + return self._matrix.is_zero() + + def is_identity(self): + r""" + Return ``True`` if this morphism is the identity. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module(X^2 + z) + sage: f = M.hom({v: v}) + sage: f.is_identity() + True + + sage: f = M.hom({v: 2*v}) + sage: f.is_identity() + False + """ + return self.domain() is self.codomain() and self._matrix.is_one() + + def _add_(self, other): + r""" + Return the sum of this morphism and ``other``. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: g = M.multiplication_map(X^6) + sage: h = f + g + sage: h == M.multiplication_map(X^3 + X^6) + True + """ + if not isinstance(other, OreModuleMorphism): + raise ValueError("the morphism is not a morphism of Ore modules") + H = self.parent() + return H(self._matrix + other._matrix, check=False) + + def _neg_(self): + r""" + Return the oppositive of this morphism. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: g = -f + sage: g == M.multiplication_map(-X^3) + True + """ + H = self.parent() + return H(-self._matrix, check=False) + + def _sub_(self, other): + r""" + Return the different between this morphism and ``other``. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: g = M.multiplication_map(X^6) + sage: h = f - g + sage: h == M.multiplication_map(X^3 - X^6) + True + """ + if not isinstance(other, OreModuleMorphism): + raise ValueError("the morphism is not a morphism of Ore modules") + H = self.parent() + return H(self._matrix - other._matrix, check=False) + + def _rmul_(self, a): + r""" + Return the product of the scalar `a` by this morphism. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: g = 2*f + sage: g == M.multiplication_map(2*X^3) + True + """ + H = self.parent() + return H(a*self._matrix, check=False) + + def __eq__(self, other): + r""" + Return ``True`` if this morphism is equal to ``other``. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = 2*M.multiplication_map(X^3) + sage: g = M.multiplication_map(2*X^3) + sage: f == g + True + """ + if not isinstance(other, OreModuleMorphism): + try: + other = self.parent()(other) + except ValueError: + return False + return self._matrix == other._matrix + + def is_injective(self): + r""" + Return ``True`` if this morphism is injective. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 + z^2 + sage: M = S.quotient_module(P^2, names='m') + sage: M.inject_variables() + Defining m0, m1, m2, m3, m4, m5 + sage: N = S.quotient_module(P, names='n') + sage: N.inject_variables() + Defining n0, n1, n2 + + sage: f = N.hom({n0: P*m0}) + sage: f.is_injective() + True + + sage: g = M.hom({m0: n0}) + sage: g.is_injective() + False + """ + return self._matrix.rank() == self.domain().rank() + + def is_surjective(self): + r""" + Return ``True`` if this morphism is surjective. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 + z^2 + sage: M = S.quotient_module(P^2, names='m') + sage: M.inject_variables() + Defining m0, m1, m2, m3, m4, m5 + sage: N = S.quotient_module(P, names='n') + sage: N.inject_variables() + Defining n0, n1, n2 + + sage: f = N.hom({n0: P*m0}) + sage: f.is_surjective() + False + + sage: g = M.hom({m0: n0}) + sage: g.is_surjective() + True + """ + return self._matrix.rank() == self.codomain().rank() + + def is_bijective(self): + r""" + Return ``True`` if this morphism is bijective. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: f.is_bijective() + True + + sage: N = S.quotient_module(X^2) + sage: g = N.multiplication_map(X^3) + sage: g.is_bijective() + False + """ + return self.is_injective() and self.is_surjective() + + def is_isomorphism(self): + r""" + Return ``True`` if this morphism is an isomorphism. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: f.is_isomorphism() + True + + sage: N = S.quotient_module(X^2) + sage: g = N.multiplication_map(X^3) + sage: g.is_isomorphism() + False + """ + return self.is_bijective() + + def _composition_(self, other, homset): + r""" + Return the composite ``other`` `\circ` ``self``. + + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: g = f * f + sage: g == M.multiplication_map(X^6) + True + """ + if not isinstance(other, OreModuleMorphism): + raise ValueError("the morphism is not a morphism of Ore modules") + return homset(other._matrix * self._matrix, check=False) + + def inverse(self): + r""" + Return the inverse of this morphism. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^2 + z + sage: M. = S.quotient_module(P^2) + + sage: f = M.multiplication_map(X^3) + sage: g = f.inverse() + sage: (f*g).is_identity() + True + sage: (g*f).is_identity() + True + + If the morphism is not invertible, an error is raised:: + + sage: h = M.hom({e0: P*e0}) + sage: h.inverse() + Traceback (most recent call last): + ... + ValueError: this morphism is not invertible + """ + if not self.is_isomorphism(): + raise ValueError("this morphism is not invertible") + H = self.parent() + return H(self._matrix.inverse(), check=False) + + __invert__ = inverse + + def kernel(self, names=None): + r""" + Return ``True`` if this morphism is injective. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 + z^2 + sage: M = S.quotient_module(P^2, names='m') + sage: M.inject_variables() + Defining m0, m1, m2, m3, m4, m5 + sage: N = S.quotient_module(P, names='n') + sage: N.inject_variables() + Defining n0, n1, n2 + + sage: f = M.hom({m0: n0}) + sage: ker = f.kernel() + sage: ker + Ore module of rank 3 over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: ker.basis() + [m0 + (2*z^2+3*z+1)*m3 + (4*z^2+3*z+3)*m4 + (2*z^2+3*z)*m5, + m1 + (z+3)*m3 + (z^2+z+4)*m4, + m2 + (2*z^2+4*z+2)*m4 + (2*z^2+z+1)*m5] + """ + ker = self._matrix.left_kernel_matrix() + return OreSubmodule(self.domain(), ker, names) + + def image(self, names=None): + r""" + Return ``True`` if this morphism is injective. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 + z^2 + sage: M = S.quotient_module(P^2, names='m') + sage: M.inject_variables() + Defining m0, m1, m2, m3, m4, m5 + sage: N = S.quotient_module(P, names='n') + sage: N.inject_variables() + Defining n0, n1, n2 + + sage: f = N.hom({n0: P*m0}) + sage: im = f.image() + sage: im + Ore module of rank 3 over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: im.basis() + [m0 + (2*z^2+3*z+1)*m3 + (4*z^2+3*z+3)*m4 + (2*z^2+3*z)*m5, + m1 + (z+3)*m3 + (z^2+z+4)*m4, + m2 + (2*z^2+4*z+2)*m4 + (2*z^2+z+1)*m5] + """ + return OreSubmodule(self.codomain(), self._matrix, names) + + def cokernel(self, names=None): + r""" + Return ``True`` if this morphism is injective. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 + z^2 + sage: M = S.quotient_module(P^2, names='m') + sage: M.inject_variables() + Defining m0, m1, m2, m3, m4, m5 + sage: N = S.quotient_module(P, names='n') + sage: N.inject_variables() + Defining n0, n1, n2 + + sage: f = N.hom({n0: P*m0}) + sage: coker = f.cokernel() + sage: coker + Ore module of rank 3 over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: coker.basis() + [m3, m4, m5] + """ + return OreQuotientModule(self.codomain(), self._matrix, names) + + def coimage(self, names=None): + r""" + Return ``True`` if this morphism is injective. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 + z^2 + sage: M = S.quotient_module(P^2, names='m') + sage: M.inject_variables() + Defining m0, m1, m2, m3, m4, m5 + sage: N = S.quotient_module(P, names='n') + sage: N.inject_variables() + Defining n0, n1, n2 + + sage: f = M.hom({m0: n0}) + sage: coim = f.coimage() + sage: coim + Ore module of rank 3 over Finite Field in z of size 5^3 twisted by z |--> z^5 + sage: coim.basis() + [m3, m4, m5] + """ + ker = self._matrix.left_kernel_matrix() + return OreQuotientModule(self.domain(), ker, names) + + def determinant(self): + r""" + Return the determinant of this endomorphism. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: M. = S.quotient_module(X^2 + z) + sage: f = M.multiplication_map(X^3) + sage: f.determinant() + 2 + + If the domain differs from the codomain (even if they have + the same rank), an error is raised:: + + sage: N. = S.quotient_module(X^2 + z^25) + sage: g = M.hom({z*m0: n0}) + sage: g.determinant() + Traceback (most recent call last): + ... + ValueError: determinants are only defined for endomorphisms + """ + if self.domain() is not self.codomain(): + raise ValueError("determinants are only defined for endomorphisms") + return self._matrix.determinant() + + det = determinant + + def characteristic_polynomial(self, var='x'): + r""" + Return the determinant of this endomorphism. + + EXAMPLES:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^3 + z*X^2 + (z^2 + 3)*X + z^5 + sage: M = S.quotient_module(P) + sage: f = M.multiplication_map(X^3) + sage: f.characteristic_polynomial() + x^3 + x^2 + 2*x + 2 + + We check that the latter is equal to the reduced norm + of `P`:: + + sage: P.reduced_norm('x') + x^3 + x^2 + 2*x + 2 + + TESTS:: + + sage: M. = S.quotient_module(X^2 + z) + sage: N. = S.quotient_module(X^2 + z^25) + sage: g = M.hom({z*m0: n0}) + sage: g.characteristic_polynomial() + Traceback (most recent call last): + ... + ValueError: characteristic polynomials are only defined for endomorphisms + """ + if self.domain() is not self.codomain(): + raise ValueError("characteristic polynomials are only defined for endomorphisms") + return self._matrix.charpoly(var) + + charpoly = characteristic_polynomial + + +class OreModuleRetraction(Map): + r""" + Conversion (partially defined) map from an ambient module + to one of its submodule. + """ + def _call_(self, y): + r""" + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^2 + z*X + 1 + sage: M = S.quotient_module(P^2, names='e') + sage: M.inject_variables() + Defining e0, e1, e2, e3 + sage: N = M.span(P*e0, names='u') + sage: N(P*e0) # indirect doctest + u0 + z*u1 + + sage: N(e0) + Traceback (most recent call last): + ... + ValueError: not in the submodule + """ + X = self.codomain() + try: + xs = X._basis.solve_left(y) + except ValueError: + raise ValueError("not in the submodule") + return X(xs) + + +class OreModuleSection(Map): + r""" + Section map of the projection onto a quotient. + It is not necessarily compatible with the Ore action. + """ + def _call_(self, y): + r""" + TESTS:: + + sage: K. = GF(5^3) + sage: S. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: P = X^2 + z*X + 1 + sage: M = S.quotient_module(P^2, names='e') + sage: M.inject_variables() + Defining e0, e1, e2, e3 + sage: N = M.quo(P*e0, names='u') + sage: N.inject_variables() + Defining u0, u1 + sage: M(u0) # indirect doctest + e2 + """ + X = self.codomain() + Y = self.domain() + indices = Y._indices + zero = X.base_ring().zero() + xs = X.rank() * [zero] + for i in range(Y.rank()): + xs[indices[i]] = y[i] + return X(xs) diff --git a/src/sage/rings/polynomial/ore_polynomial_element.pyx b/src/sage/rings/polynomial/ore_polynomial_element.pyx index 768565840f6..bbc06d6da64 100644 --- a/src/sage/rings/polynomial/ore_polynomial_element.pyx +++ b/src/sage/rings/polynomial/ore_polynomial_element.pyx @@ -2951,9 +2951,9 @@ cdef class OrePolynomial_generic_dense(OrePolynomial): if var is None: var = parent.variable_name() if derivation is None: - S = OrePolynomialRing(k, morphism, var) + S = OrePolynomialRing(k, morphism, var, polcast=False) else: - S = OrePolynomialRing(k, derivation, var) + S = OrePolynomialRing(k, derivation, var, polcast=False) if not self: return S.zero() X = S.gen() + s diff --git a/src/sage/rings/polynomial/ore_polynomial_ring.py b/src/sage/rings/polynomial/ore_polynomial_ring.py index 101adc593f1..3ad7b69815e 100644 --- a/src/sage/rings/polynomial/ore_polynomial_ring.py +++ b/src/sage/rings/polynomial/ore_polynomial_ring.py @@ -45,6 +45,7 @@ # https://www.gnu.org/licenses/ # *************************************************************************** +import operator from sage.categories.algebras import Algebras from sage.categories.commutative_rings import CommutativeRings from sage.categories.morphism import Morphism @@ -361,6 +362,8 @@ def __classcall_private__(cls, base_ring, twist=None, names=None, sparse=False, derivation = twist else: derivation = None + elif twist is None: + morphism = derivation = None else: raise TypeError("the twisting map must be a ring morphism or a derivation") if names is None: @@ -653,6 +656,36 @@ def _repr_twist(self): else: return "twisted by " + s + def _latex_twist(self): + r""" + Return a LaTeX representation of the twisting morphisms. + + This is a helper method. + + TESTS:: + + sage: F. = GF(5^3) + sage: Frob = F.frobenius_endomorphism() + + sage: S. = OrePolynomialRing(F, Frob) + sage: S._latex_twist() + z \mapsto z^{5} + + sage: T. = OrePolynomialRing(F, Frob^3, polcast=False) + sage: T._latex_twist() + '' + + """ + from sage.misc.latex import latex + s = "" + if self._morphism is not None: + s += latex(self._morphism) + if self._derivation is not None: + if s != "": + s += "," + s += latex(self._derivation) + return s + def _repr_(self) -> str: r""" Return a string representation of ``self``. @@ -696,12 +729,9 @@ def _latex_(self) -> str: """ from sage.misc.latex import latex s = "%s\\left[%s" % (latex(self.base_ring()), self.latex_variable_names()[0]) - sep = ";" - if self._morphism is not None: - s += sep + latex(self._morphism) - sep = "," - if self._derivation is not None: - s += sep + latex(self._derivation) + twist = self._latex_twist() + if twist != "": + s += ";" + twist return s + "\\right]" def change_var(self, var): @@ -730,7 +760,7 @@ def change_var(self, var): else: twist = self._derivation return OrePolynomialRing(self.base_ring(), twist, names=var, - sparse=self.__is_sparse) + sparse=self.__is_sparse, polcast=False) def characteristic(self): r""" @@ -1222,6 +1252,67 @@ def fraction_field(self): self._fraction_field.register_coercion(self) return self._fraction_field + def quotient_module(self, P, names=None): + r""" + Return the quotient ring `A/AP` as a module over `A` + where `A` is this Ore polynomial ring. + + INPUT: + + - ``names`` (default: ``None``) -- a string or a list + of string, the names of the vector of the canonical + basis + + EXAMPLES:: + + sage: k. = GF(5^3) + sage: S. = k['a', k.frobenius_endomorphism()] + sage: P = x^3 + a*x^2 + a^2 + 1 + sage: M = S.quotient_module(P) + sage: M + Ore module of rank 3 over Finite Field in a of size 5^3 twisted by a |--> a^5 + + The argument ``names`` can be used to give chosen names + to the vectors in the canonical basis:: + + sage: M = S.quotient_module(P, names=('u', 'v', 'w')) + sage: M.basis() + [u, v, w] + + or even:: + + sage: M = S.quotient_module(P, names='e') + sage: M.basis() + [e0, e1, e2] + + Note that the bracket construction also works:: + + sage: M. = S.quotient_module(P) + sage: M.basis() + [u, v, w] + + With this construction, the vectors `u`, `v` and `w` + are directly available in the namespace:: + + sage: x*u + v + 2*v + + We refer to :mod:`sage.modules.ore_module` for a + tutorial on Ore modules in SageMath. + + .. SEEALSO:: + + :mod:`sage.modules.ore_module` + """ + from sage.matrix.special import companion_matrix + from sage.modules.ore_module import OreModule, OreAction + coeffs = self(P).right_monic().list() + f = companion_matrix(coeffs, format='bottom') + M = OreModule(f, self, names=names) + M._unset_coercions_used() + M.register_action(OreAction(self, M, True, operator.mul)) + return M + def _pushout_(self, other): r""" Return the pushout of this Ore polynomial ring and ``other``.