From df3598cd28addbeea131fdaacb00c2acd9fae95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 26 Apr 2022 18:46:55 +0200 Subject: [PATCH 001/392] Create a draft of FiniteDrinfeldModule --- src/sage/modules/all.py | 1 + src/sage/modules/finite_drinfeld_module.py | 175 +++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/sage/modules/finite_drinfeld_module.py diff --git a/src/sage/modules/all.py b/src/sage/modules/all.py index a90258d7ec8..a6ca79ddf69 100644 --- a/src/sage/modules/all.py +++ b/src/sage/modules/all.py @@ -29,3 +29,4 @@ lazy_import('sage.modules.multi_filtered_vector_space', 'MultiFilteredVectorSpace') lazy_import('sage.modules.free_quadratic_module_integer_symmetric', 'IntegralLattice') lazy_import('sage.modules.torsion_quadratic_module', 'TorsionQuadraticForm') +lazy_import('sage.modules.finite_drinfeld_module', 'FiniteDrinfeldModule') diff --git a/src/sage/modules/finite_drinfeld_module.py b/src/sage/modules/finite_drinfeld_module.py new file mode 100644 index 00000000000..e6eb1f7aba1 --- /dev/null +++ b/src/sage/modules/finite_drinfeld_module.py @@ -0,0 +1,175 @@ +r""" + + + + +EXAMPLES:: + + + +AUTHORS: + +- Antoine Leudière (2022-04-26): initial version + +""" + +#***************************************************************************** +# Copyright (C) 2022 Antoine Leudière +# +# 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. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.action import Action +from sage.categories.homset import Hom +from sage.rings.morphism import RingHomomorphism_im_gens +from sage.rings.polynomial.ore_polynomial_element import OrePolynomial +from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing +from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field +from sage.misc.latex import latex + + +class FiniteDrinfeldModule(RingHomomorphism_im_gens): + + def __init__(self, polring, gen): + # VERIFICATIONS + # Check `polring` is an Fq[X]: + # See docstrings of `PolynomialRing_dense_finite_field` and + # `is_PolynomialRing`. + isinstance(polring, PolynomialRing_dense_finite_field) + # Check `gen` is an Ore polynomial: + if not isinstance(gen, OrePolynomial): + raise TypeError('The generator must be an Ore polynomial') + # Now we can define those for convenience: + FqX = polring + Ltau = gen.parent() + Fq = FqX.base_ring() + L = Ltau.base_ring() + # Check the Ore polynomial ring is an L{tau} with L a finite + # field extension of Fq: + _check_base_fields(Fq, L) + if not Ltau.twisting_derivation() is None: + raise ValueError('The Ore polynomial ring should have no ' \ + 'derivation') + # Check the frobenius is x -> x^q: + if Ltau.twisting_morphism().power() != Fq.degree(): + raise ValueError('The twisting morphism of the Ore polynomial ' \ + 'ring must be the Frobenius endomorphism of the base ' \ + 'field of the polynomial ring') + # The generator is not constant: + if gen.is_constant(): + raise ValueError('The generator must not be constant') + # ACTUAL WORK + super().__init__(Hom(FqX, Ltau), gen) + + ########### + # Methods # + ########### + + def change_ring(self, R): + # VERIFICATIONS + if not R.is_field() and R.is_finite(): + raise TypeError('Argument must be a finite field') + if not self.ore_polring().base_ring().is_subring(R): + raise ValueError('The new field must be a finite field ' \ + 'extension of the base field of the Ore polynomial ring.') + _check_base_fields(self.polring().base_ring(), R) + # ACTUAL WORK + new_frobenius = R.frobenius_endomorphism(self.frobenius().power()) + new_ore_polring = OrePolynomialRing(R, new_frobenius, + names=self.ore_polring().variable_names()) + return FiniteDrinfeldModule(self.polring(), new_ore_polring(self.gen())) + + def rank(self): + return self.gen().degree() + + ########################## + # Special Sage functions # + ########################## + + def _get_action_(self, extension): + return FiniteDrinfeldModuleAction(self, extension) + + def _latex_(self): + return f'\\text{{Finite{{ }}{latex(self.polring())}-Drinfeld{{ }}' \ + f'module{{ }}defined{{ }}over{{ }}}}' \ + f'{latex(self.ore_polring().base_ring())}\\text{{{{ }}' \ + f'by{{ }}}}\n' \ + f'\\begin{{align}}\n' \ + f' {latex(self.polring())}\n' \ + f' &\\to {latex(self.ore_polring())} \\\\\n' \ + f' {latex(self.polring().gen())}\n' \ + f' &\\mapsto {latex(self.gen())}\n' \ + f'\\end{{align}}' + + def _repr_(self): + return f'Finite Drinfeld module from {self.polring()} over ' \ + f'{self.ore_polring().base_ring()} defined by {self.gen()}.' + + ########### + # Getters # + ########### + + def frobenius(self): + return self.ore_polring().twisting_morphism() + + def gen(self): + [gen] = self.im_gens() + return gen + + def ore_polring(self): + return self.codomain() + + def polring(self): + return self.domain() + +class FiniteDrinfeldModuleAction(Action): + + def __init__(self, finite_drinfeld_module, extension): + # VERIFICATIONS + if not isinstance(finite_drinfeld_module, FiniteDrinfeldModule): + raise TypeError('First argument must be a FiniteDrinfeldModule') + if not (extension.is_field() and extension.is_finite() and + finite_drinfeld_module.polring().base_ring().is_subring(extension)): + raise ValueError('The extension must be a finite field ' \ + 'extension of the base field of the Ore polynomial ring') + # WORK + self.__finite_drinfeld_module = finite_drinfeld_module + super().__init__(finite_drinfeld_module.polring(), extension) + + ########### + # Methods # + ########### + + def extension(self): + return self.codomain() + + def finite_drinfeld_module(self): + return self.__finite_drinfeld_module + + ########################## + # Special Sage functions # + ########################## + + def _latex_(self): + phi = self.finite_drinfeld_module() + return f'\\text{{Drinfeld{{ }}module{{ }}action{{ }}' \ + f'on{{ }}}}{latex(self.extension())}\\text{{{{ }}' \ + f'induced{{ }}by{{ }}}}{latex(phi)}' + + def _repr_(self): + return f'Action on {self.domain()} induced by the ' \ + f'{self.finite_drinfeld_module()}' + + def _act_(self, g, x): + return self.finite_drinfeld_module().change_ring(self.extension())(g)(x) + + +def _check_base_fields(Fq, L): + if not (L.is_field() and L.is_finite() and Fq.is_subring(L)): + raise ValueError(f'The base field of the Ore polynomial ring must ' \ + 'be a finite field extension of the base field of the ' \ + 'polynomial ring') From 84c7b5b652ab828a68e492af31831fc25ed4b52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 26 Apr 2022 20:58:53 +0200 Subject: [PATCH 002/392] Create draft for finite Drinfeld module doctest --- src/sage/modules/finite_drinfeld_module.py | 120 +++++++++++++++++++-- 1 file changed, 111 insertions(+), 9 deletions(-) diff --git a/src/sage/modules/finite_drinfeld_module.py b/src/sage/modules/finite_drinfeld_module.py index e6eb1f7aba1..8170da59e7b 100644 --- a/src/sage/modules/finite_drinfeld_module.py +++ b/src/sage/modules/finite_drinfeld_module.py @@ -1,16 +1,26 @@ r""" - +This module provides classes for finite Drinfeld modules +(`FiniteDrinfeldModule`) and their module action on the algebraic +closure of `\Fq` (`FiniteDrinfeldModuleAction`). - - -EXAMPLES:: +AUTHORS: - +- Antoine Leudière (2022-04): initial version -AUTHORS: +Let `\tau` be the `\Fq`-linear Frobenius endomorphism of `\Fqbar` +defined by `x \mapsto x^q`. Let `L\{\tau\}` be the ring of Ore +polynomials in `\tau` with coefficients in L. Fix an element `\omega` in +`L` (global parameter). A finite Drinfeld module is an `\Fq`-algebra +morphism `\phi: \Fq[X] \to L\{\tau\]` such that: + - the constant coefficient of `\phi(X)` is `\omega`, + - there exists at least one `a \in \Fq[X]` such that `\phi(a)` has a + non zero `\tau`-degree. -- Antoine Leudière (2022-04-26): initial version +As an `\Fq[X]`-algebra morphism, a finite Drinfeld module is only +determined by the image of `X`. +Crucially, the Drinfeld module `\phi` gives rise to the `\Fq[X]`-module +law on `\Fqbar` defined by `(a, x) = \phi(a)(x)`. """ #***************************************************************************** @@ -25,14 +35,106 @@ from sage.categories.action import Action from sage.categories.homset import Hom +from sage.misc.latex import latex from sage.rings.morphism import RingHomomorphism_im_gens from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field -from sage.misc.latex import latex class FiniteDrinfeldModule(RingHomomorphism_im_gens): + r""" + Class for finite Drinfeld modules. + + INPUT: + + - ``polring`` -- the base polynomial ring + - ``gen`` -- the image of `X` + + EXAMPLES: + + .. RUBRIC:: Basics + + First, create base objects:: + + sage: Fq = GF(7^2) + sage: FqX. = Fq[] + sage: L = Fq.extension(3) + sage: frobenius = L.frobenius_endomorphism(2) + sage: Ltau. = OrePolynomialRing(L, frobenius) + + Then we instanciate the Drinfeld module:: + + sage: phi_X = 1 + t^2 + sage: phi = FiniteDrinfeldModule(FqX, phi_X) + sage: phi + Finite Drinfeld module from Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 over Finite Field in z6 of size 7^6 generated by t^2 + 1. + + There are getters for the base objects:: + + sage: phi.polring() + Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 + sage: phi.ore_polring() + Ore Polynomial Ring in t over Finite Field in z6 of size 7^6 twisted by z6 |--> z6^(7^2) + sage: phi.gen() + t^2 + 1 + + And the class inherits `RingHomomorphism_im_gens`, so that one can + use:: + + sage: phi.domain() + Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 + sage: phi.codomain() + Ore Polynomial Ring in t over Finite Field in z6 of size 7^6 twisted by z6 |--> z6^(7^2) + sage: phi.im_gens() + [t^2 + 1] + + The rank of the Drinfeld module is retrieved with:: + + sage: phi.rank() + 2 + + .. RUBRIC:: The module law induced by a Drinfeld module + + The most important feature of Drinfeld modules is that they induce + an `\Fq[X]`-module law on the algebraic closure `\Fqbar` of `\Fq`. + We implement this action for any finite field extension `M` of `L`. + The `_get_action_` method returns an `Action` object:: + + sage: M = L.extension(2) + sage: action = phi._get_action_(M) + sage: action + Action on Finite Field in z12 of size 7^12 induced by the Finite Drinfeld module from Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 over Finite Field in z6 of size 7^6 + generated by t^2 + 1. + sage: x = M.gen() + sage: g = X^3 + X + 5 + sage: action(g, x) + ... + 6*z12^11 + 5*z12^9 + 5*z12^8 + 2*z12^7 + 6*z12^6 + z12^5 + 6*z12^4 + 2*z12^3 + 3*z12^2 + 5*z12 + 4 + + Furthermore, it can be useful to embed a Drinfeld module into a + larger Ore polynomial ring:: + + sage: M = L.extension(2) + sage: psi = phi.change_ring(M); psi + Finite Drinfeld module from Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 over Finite Field in z12 of size 7^12 generated by t^2 + 1. + + .. NOTE:: + + The general definition of a Drinfeld module is out of the scope + of this implementation. + + :: + + You can see all available methods of `RingHomomorphism_im_gens` + with `dir(sage.rings.morphism.RingHomomorphism_im_gens)`. Same + for `Action`. + + .. SEEALSO:: + :mod:`sage.categories.action.Action` + :mod:`sage.rings.polynomial.ore_polynomial_element` + :mod:`sage.rings.polynomial.ore_polynomial_ring` + """ def __init__(self, polring, gen): # VERIFICATIONS @@ -107,7 +209,7 @@ def _latex_(self): def _repr_(self): return f'Finite Drinfeld module from {self.polring()} over ' \ - f'{self.ore_polring().base_ring()} defined by {self.gen()}.' + f'{self.ore_polring().base_ring()} generated by {self.gen()}.' ########### # Getters # From 2723f99dbcb6005d7446e47ca3ea8d20f1f09b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 27 Apr 2022 14:46:56 +0200 Subject: [PATCH 003/392] Fix FiniteDrinfeldModule action in all.py --- src/sage/modules/all.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/modules/all.py b/src/sage/modules/all.py index a6ca79ddf69..ab41fbcf564 100644 --- a/src/sage/modules/all.py +++ b/src/sage/modules/all.py @@ -30,3 +30,4 @@ lazy_import('sage.modules.free_quadratic_module_integer_symmetric', 'IntegralLattice') lazy_import('sage.modules.torsion_quadratic_module', 'TorsionQuadraticForm') lazy_import('sage.modules.finite_drinfeld_module', 'FiniteDrinfeldModule') +lazy_import('sage.modules.finite_drinfeld_module', 'FiniteDrinfeldModuleAction') From 8bef6ba375e8b60448764395d46c72144c9a66a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 5 May 2022 12:58:48 +0200 Subject: [PATCH 004/392] Refactor _latex_ method in FiniteDrinfeldModule --- src/sage/modules/finite_drinfeld_module.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/modules/finite_drinfeld_module.py b/src/sage/modules/finite_drinfeld_module.py index 8170da59e7b..5212bb808ff 100644 --- a/src/sage/modules/finite_drinfeld_module.py +++ b/src/sage/modules/finite_drinfeld_module.py @@ -197,15 +197,15 @@ def _get_action_(self, extension): def _latex_(self): return f'\\text{{Finite{{ }}{latex(self.polring())}-Drinfeld{{ }}' \ - f'module{{ }}defined{{ }}over{{ }}}}' \ - f'{latex(self.ore_polring().base_ring())}\\text{{{{ }}' \ - f'by{{ }}}}\n' \ + f'module{{ }}defined{{ }}by{{ }}}}\n' \ f'\\begin{{align}}\n' \ f' {latex(self.polring())}\n' \ f' &\\to {latex(self.ore_polring())} \\\\\n' \ f' {latex(self.polring().gen())}\n' \ f' &\\mapsto {latex(self.gen())}\n' \ - f'\\end{{align}}' + f'\\end{{align}}\n' \ + f'\\text{{with{{ }}characteristic{{ }}}} ' \ + f'{latex(self.characteristic())}' def _repr_(self): return f'Finite Drinfeld module from {self.polring()} over ' \ From f0a9f630a6f7be1215f883cbb9d07f893ca27cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 6 May 2022 17:48:53 +0200 Subject: [PATCH 005/392] Refactor FiniteDrinfeldModuleAction --- src/sage/modules/finite_drinfeld_module.py | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/sage/modules/finite_drinfeld_module.py b/src/sage/modules/finite_drinfeld_module.py index 5212bb808ff..68ee9630579 100644 --- a/src/sage/modules/finite_drinfeld_module.py +++ b/src/sage/modules/finite_drinfeld_module.py @@ -192,8 +192,8 @@ def rank(self): # Special Sage functions # ########################## - def _get_action_(self, extension): - return FiniteDrinfeldModuleAction(self, extension) + def _get_action_(self): + return FiniteDrinfeldModuleAction(self) def _latex_(self): return f'\\text{{Finite{{ }}{latex(self.polring())}-Drinfeld{{ }}' \ @@ -230,25 +230,19 @@ def polring(self): class FiniteDrinfeldModuleAction(Action): - def __init__(self, finite_drinfeld_module, extension): - # VERIFICATIONS + def __init__(self, finite_drinfeld_module): + # Verifications if not isinstance(finite_drinfeld_module, FiniteDrinfeldModule): raise TypeError('First argument must be a FiniteDrinfeldModule') - if not (extension.is_field() and extension.is_finite() and - finite_drinfeld_module.polring().base_ring().is_subring(extension)): - raise ValueError('The extension must be a finite field ' \ - 'extension of the base field of the Ore polynomial ring') - # WORK + # Work self.__finite_drinfeld_module = finite_drinfeld_module - super().__init__(finite_drinfeld_module.polring(), extension) + super().__init__(finite_drinfeld_module.polring(), + finite_drinfeld_module.ore_polring().base_ring()) ########### # Methods # ########### - def extension(self): - return self.codomain() - def finite_drinfeld_module(self): return self.__finite_drinfeld_module @@ -257,17 +251,16 @@ def finite_drinfeld_module(self): ########################## def _latex_(self): - phi = self.finite_drinfeld_module() - return f'\\text{{Drinfeld{{ }}module{{ }}action{{ }}' \ - f'on{{ }}}}{latex(self.extension())}\\text{{{{ }}' \ - f'induced{{ }}by{{ }}}}{latex(phi)}' + return f'\\text{{Action{{ }}on{{ }}}}' \ + f'{latex(self.extension())}\\text{{{{ }}' \ + f'induced{{ }}by{{ }}}}{self.finite_drinfeld_module()}' def _repr_(self): - return f'Action on {self.domain()} induced by the ' \ + return f'Action on {self.domain()} induced by ' \ f'{self.finite_drinfeld_module()}' def _act_(self, g, x): - return self.finite_drinfeld_module().change_ring(self.extension())(g)(x) + return self.finite_drinfeld_module()(g)(x) def _check_base_fields(Fq, L): From 86348fff6bdd0aec5a414ed07925291cb4e13041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 5 May 2022 12:52:52 +0200 Subject: [PATCH 006/392] Add various getters for FiniteDrinfeldModule This commit is a bit dirty, apologies. There are new getters (see below), and the commit includes some refactoring of the doctests. The added getters are: - characteristic, - constant_term, - delta (rank two specific), - g (rank two specific), - height, - j (rank two specific). --- src/sage/modules/finite_drinfeld_module.py | 135 ++++++++++++++------- 1 file changed, 88 insertions(+), 47 deletions(-) diff --git a/src/sage/modules/finite_drinfeld_module.py b/src/sage/modules/finite_drinfeld_module.py index 68ee9630579..3f8f9b20a8c 100644 --- a/src/sage/modules/finite_drinfeld_module.py +++ b/src/sage/modules/finite_drinfeld_module.py @@ -36,6 +36,7 @@ from sage.categories.action import Action from sage.categories.homset import Hom from sage.misc.latex import latex +from sage.rings.integer import Integer from sage.rings.morphism import RingHomomorphism_im_gens from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing @@ -57,68 +58,75 @@ class FiniteDrinfeldModule(RingHomomorphism_im_gens): First, create base objects:: - sage: Fq = GF(7^2) + sage: Fq. = GF(3^2) sage: FqX. = Fq[] - sage: L = Fq.extension(3) - sage: frobenius = L.frobenius_endomorphism(2) - sage: Ltau. = OrePolynomialRing(L, frobenius) + sage: p = X^3 + (z2 + 2)*X^2 + (6*z2 + 1)*X + 3*z2 + 5 + sage: L = Fq.extension(6) + sage: Ltau. = OrePolynomialRing(L, L.frobenius_endomorphism(2)) + sage: omega = p.roots(L, multiplicities=False)[0] + sage: phi_X = omega + t + t^2 Then we instanciate the Drinfeld module:: - sage: phi_X = 1 + t^2 - sage: phi = FiniteDrinfeldModule(FqX, phi_X) + sage: phi = FiniteDrinfeldModule(FqX, phi_X, p) sage: phi - Finite Drinfeld module from Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 over Finite Field in z6 of size 7^6 generated by t^2 + 1. + Finite Drinfeld module: + Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) + Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 + Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 There are getters for the base objects:: sage: phi.polring() - Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 + Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 sage: phi.ore_polring() - Ore Polynomial Ring in t over Finite Field in z6 of size 7^6 twisted by z6 |--> z6^(7^2) + Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) sage: phi.gen() - t^2 + 1 + t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 And the class inherits `RingHomomorphism_im_gens`, so that one can use:: sage: phi.domain() - Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 + Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 sage: phi.codomain() - Ore Polynomial Ring in t over Finite Field in z6 of size 7^6 twisted by z6 |--> z6^(7^2) + Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) sage: phi.im_gens() - [t^2 + 1] + [t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12] - The rank of the Drinfeld module is retrieved with:: + We can retrieve basic quantities: sage: phi.rank() 2 + sage: phi.j() + 1 .. RUBRIC:: The module law induced by a Drinfeld module The most important feature of Drinfeld modules is that they induce - an `\Fq[X]`-module law on the algebraic closure `\Fqbar` of `\Fq`. - We implement this action for any finite field extension `M` of `L`. - The `_get_action_` method returns an `Action` object:: + an `\Fq[X]`-module law on any subextension of the algebraic closure + `\Fqbar` of `\Fq`. For this, we created the class + `FiniteDrinfeldModuleAction`, which inherits `Action`. For the sake + of simplicity, `phi` will only act on the base field (`L`) of its + Ore polynomial ring. If you want to act on a bigger field, you can + use the method `change_ring`. sage: M = L.extension(2) - sage: action = phi._get_action_(M) - sage: action + sage: phi_M = phi.change_ring(M) # todo: not tested + sage: action = phi._get_action_() # todo: not tested + sage: action # todo: not tested Action on Finite Field in z12 of size 7^12 induced by the Finite Drinfeld module from Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 over Finite Field in z6 of size 7^6 generated by t^2 + 1. - sage: x = M.gen() - sage: g = X^3 + X + 5 - sage: action(g, x) + + The calculation of the action is simple: + + sage: x = M.gen() # todo: not tested + sage: g = X^3 + X + 5 # todo: not tested + sage: action(g, x) # todo: not tested ... 6*z12^11 + 5*z12^9 + 5*z12^8 + 2*z12^7 + 6*z12^6 + z12^5 + 6*z12^4 + 2*z12^3 + 3*z12^2 + 5*z12 + 4 - Furthermore, it can be useful to embed a Drinfeld module into a - larger Ore polynomial ring:: - - sage: M = L.extension(2) - sage: psi = phi.change_ring(M); psi - Finite Drinfeld module from Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 over Finite Field in z12 of size 7^12 generated by t^2 + 1. - .. NOTE:: The general definition of a Drinfeld module is out of the scope @@ -136,35 +144,37 @@ class FiniteDrinfeldModule(RingHomomorphism_im_gens): :mod:`sage.rings.polynomial.ore_polynomial_ring` """ - def __init__(self, polring, gen): - # VERIFICATIONS - # Check `polring` is an Fq[X]: - # See docstrings of `PolynomialRing_dense_finite_field` and - # `is_PolynomialRing`. - isinstance(polring, PolynomialRing_dense_finite_field) - # Check `gen` is an Ore polynomial: + def __init__(self, polring, gen, characteristic): + # Verifications + if not isinstance(polring, PolynomialRing_dense_finite_field): + raise TypeError('First argument must be a polynomial ring') + if not characteristic in polring: + raise TypeError('The characteristic must be in the polynomial ' \ + 'ring') + if not characteristic.is_prime(): + raise ValueError('The characteristic must be irreducible') if not isinstance(gen, OrePolynomial): raise TypeError('The generator must be an Ore polynomial') - # Now we can define those for convenience: + # We define those for convenience before continuing FqX = polring Ltau = gen.parent() Fq = FqX.base_ring() L = Ltau.base_ring() - # Check the Ore polynomial ring is an L{tau} with L a finite - # field extension of Fq: _check_base_fields(Fq, L) if not Ltau.twisting_derivation() is None: raise ValueError('The Ore polynomial ring should have no ' \ 'derivation') - # Check the frobenius is x -> x^q: if Ltau.twisting_morphism().power() != Fq.degree(): raise ValueError('The twisting morphism of the Ore polynomial ' \ 'ring must be the Frobenius endomorphism of the base ' \ 'field of the polynomial ring') - # The generator is not constant: + if characteristic(gen[0]) != 0: + raise ValueError('The constant coefficient of the generator ' \ + 'must be a root of the characteristic') if gen.is_constant(): raise ValueError('The generator must not be constant') - # ACTUAL WORK + # Finally + self.__characteristic = characteristic super().__init__(Hom(FqX, Ltau), gen) ########### @@ -183,7 +193,30 @@ def change_ring(self, R): new_frobenius = R.frobenius_endomorphism(self.frobenius().power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, names=self.ore_polring().variable_names()) - return FiniteDrinfeldModule(self.polring(), new_ore_polring(self.gen())) + return FiniteDrinfeldModule(self.polring(), + new_ore_polring(self.gen()), self.characteristic()) + + def delta(self): + if self.rank() != 2: + raise ValueError('The rank must equal 2') + return self.gen()[2] + + def g(self): + if self.rank() != 2: + raise ValueError('The rank must equal 2') + return self.gen()[1] + + def height(self): + return Integer(1) + + def j(self): + if self.rank() != 2: + raise ValueError('The j-invariant is only defined for rank 2 ' \ + 'Drinfeld modules') + g = self.gen()[1] + delta = self.gen()[2] + q = self.polring().base_ring().order() + return (g**(q+1)) / delta def rank(self): return self.gen().degree() @@ -196,8 +229,7 @@ def _get_action_(self): return FiniteDrinfeldModuleAction(self) def _latex_(self): - return f'\\text{{Finite{{ }}{latex(self.polring())}-Drinfeld{{ }}' \ - f'module{{ }}defined{{ }}by{{ }}}}\n' \ + return f'\\text{{Finite{{ }}Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}}\n' \ f'\\begin{{align}}\n' \ f' {latex(self.polring())}\n' \ f' &\\to {latex(self.ore_polring())} \\\\\n' \ @@ -208,13 +240,22 @@ def _latex_(self): f'{latex(self.characteristic())}' def _repr_(self): - return f'Finite Drinfeld module from {self.polring()} over ' \ - f'{self.ore_polring().base_ring()} generated by {self.gen()}.' + return f'Finite Drinfeld module:\n' \ + f' Polring: {self.polring()}\n' \ + f' Ore polring: {self.ore_polring()}\n' \ + f' Generator: {self.gen()}\n' \ + f' Characteristic: {self.characteristic()}' ########### # Getters # ########### + def characteristic(self): + return self.__characteristic + + def constant_term(self): + return self.gen()[0] + def frobenius(self): return self.ore_polring().twisting_morphism() From 2c24562b74fdcb23c8844d727a15d8c217ff0ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 6 May 2022 17:43:31 +0200 Subject: [PATCH 007/392] Add methods to deal with complex multiplication in FiniteDrinfeldModule The added methods are: - frobenius_trace, - frobenius_norm, - invert (required by frobenius_norm), - is_ordinary, - is_supersingular, - characteristic_polynomial. --- src/sage/modules/finite_drinfeld_module.py | 69 +++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/sage/modules/finite_drinfeld_module.py b/src/sage/modules/finite_drinfeld_module.py index 3f8f9b20a8c..5bc6280d694 100644 --- a/src/sage/modules/finite_drinfeld_module.py +++ b/src/sage/modules/finite_drinfeld_module.py @@ -35,12 +35,15 @@ from sage.categories.action import Action from sage.categories.homset import Hom +from sage.matrix.constructor import Matrix from sage.misc.latex import latex +from sage.modules.free_module_element import vector from sage.rings.integer import Integer from sage.rings.morphism import RingHomomorphism_im_gens from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing class FiniteDrinfeldModule(RingHomomorphism_im_gens): @@ -51,6 +54,10 @@ class FiniteDrinfeldModule(RingHomomorphism_im_gens): - ``polring`` -- the base polynomial ring - ``gen`` -- the image of `X` + - ``characteristic`` -- the Fq[X]-characteristic of the Drinfeld + module, i.e. an irreducible polynomial in `polring` that defines a + subextension of L (the base field of the Ore polynomial ring) and + for which the constant coefficient of `gen` is a root. EXAMPLES: @@ -173,10 +180,20 @@ def __init__(self, polring, gen, characteristic): 'must be a root of the characteristic') if gen.is_constant(): raise ValueError('The generator must not be constant') - # Finally + # Work self.__characteristic = characteristic super().__init__(Hom(FqX, Ltau), gen) + ################# + # Private utils # + ################# + + def _Fq(self): + return self.polring().base_ring() + + def _L(self): + return self.ore_polring().base_ring() + ########### # Methods # ########### @@ -196,11 +213,30 @@ def change_ring(self, R): return FiniteDrinfeldModule(self.polring(), new_ore_polring(self.gen()), self.characteristic()) + def characteristic_polynomial(self): + FqXT = PolynomialRing(self.polring(), 'T') + return FqXT([self.frobenius_norm(), self.frobenius_trace(), 1]) + def delta(self): if self.rank() != 2: raise ValueError('The rank must equal 2') return self.gen()[2] + def frobenius_norm(self): + # Notations from Schost-Musleh: + n = self._L().over(self._Fq()).degree_over(self._Fq()) + d = self.characteristic().degree() + m = n // d + norm = self._L().over(self._Fq())(self.delta()).norm() + return ((-1)**n) * (self.characteristic()**m) / norm + + def frobenius_trace(self): + # Notations from Schost-Musleh: + n = self._L().over(self._Fq()).degree_over(self._Fq()) + B = self.frobenius_norm() + t = self.ore_polring().gen() + return self.invert(t**n + (self(B) // t**n)) + def g(self): if self.rank() != 2: raise ValueError('The rank must equal 2') @@ -209,6 +245,37 @@ def g(self): def height(self): return Integer(1) + def invert(self, image): + """ + Given an Ore polynomial `image` of the form `phi(c)`, find c. + """ + if not image in self.ore_polring(): + raise TypeError('The tested image should be in the Ore ' \ + 'polynomial ring') + if image in self._L(): # Only works if `image` is in the image of self + return self._Fq()(image) + r = self.rank() + X = self.polring().gen() + k = image.degree() // r + m_lines = [[0 for _ in range(k+1)] for _ in range(k+1)] + for i in range(k+1): + phi_X_i = self(X**i) + for j in range(i+1): + m_lines[j][i] = phi_X_i[r*j] + m = Matrix(m_lines) + v = vector([list(image)[r*j] for j in range(k+1)]) + pre_image = self.polring()(list((m**(-1)) * v)) + if self(pre_image) == image: + return pre_image + else: + return None + + def is_supersingular(self): + return self.characteristic().divides(self.frobenius_trace()) + + def is_ordinary(self): + return not self.is_supersingular() + def j(self): if self.rank() != 2: raise ValueError('The j-invariant is only defined for rank 2 ' \ From 0499221177f9c76783cd672cb65ae52242a57c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 10 May 2022 10:46:36 +0200 Subject: [PATCH 008/392] Add methods to deal with isogenies in FiniteDrinfeldModule The added methods are: - is_morphism, - is_endomorphism, - is_automorphism, - is_isogeny, - velu. --- src/sage/modules/finite_drinfeld_module.py | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/sage/modules/finite_drinfeld_module.py b/src/sage/modules/finite_drinfeld_module.py index 5bc6280d694..86d4d3253da 100644 --- a/src/sage/modules/finite_drinfeld_module.py +++ b/src/sage/modules/finite_drinfeld_module.py @@ -276,6 +276,30 @@ def is_supersingular(self): def is_ordinary(self): return not self.is_supersingular() + def is_morphism(self, candidate): + return candidate == 0 and self.is_isogeny(candidate) + + def is_isogeny(self, candidate): + if not candidate in self.ore_polring(): + raise TypeError('The candidate must be in the Ore polynomial ' \ + 'ring') + if candidate == 0: + return False + elif candidate in self.ore_polring().base_ring(): + return True + else: + return self.characteristic().degree().divides(candidate.valuation()) \ + and candidate.right_divides(candidate * self.gen()) + + def is_endomorphism(self, candidate): + return candidate == 0 or self == self.velu(candidate) + + def is_automorphism(self, candidate): + if not candidate in self.ore_polring(): + raise TypeError('The candidate must be in the Ore polynomial ' \ + 'ring') + return candidate != 0 and candidate in self._Fq() + def j(self): if self.rank() != 2: raise ValueError('The j-invariant is only defined for rank 2 ' \ @@ -288,6 +312,26 @@ def j(self): def rank(self): return self.gen().degree() + def velu(self, candidate): + if not candidate in self.ore_polring(): + raise TypeError('The candidate must be in the Ore polynomial ' \ + 'ring') + # There are two main ways to give the result. The first way is + # to return the Drinfeld module generated by the right-quotient + # of `candidate * self(X)` right-divided by `candidate`. The + # second way is to recursively find the coefficients (see + # arXiv:2203.06970, Eq. 1.1). For now, the former is + # implemented, as it is very easy to write. + if candidate == 0: + return None + if not self.characteristic().degree().divides(candidate.valuation()): + return None + q, r = (candidate * self.gen()).right_quo_rem(candidate) + if r != 0: + return None + else: + return FiniteDrinfeldModule(self.polring(), q, self.characteristic()) + ########################## # Special Sage functions # ########################## From 3609c915438fd89da5686cc310be848b587f672a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 10 May 2022 14:38:36 +0200 Subject: [PATCH 009/392] Move FiniteDrinfeldModule to sage.rings.function_field --- src/sage/modules/all.py | 2 -- src/sage/rings/function_field/all.py | 5 +++++ .../function_field}/finite_drinfeld_module.py | 0 3 files changed, 5 insertions(+), 2 deletions(-) rename src/sage/{modules => rings/function_field}/finite_drinfeld_module.py (100%) diff --git a/src/sage/modules/all.py b/src/sage/modules/all.py index ab41fbcf564..a90258d7ec8 100644 --- a/src/sage/modules/all.py +++ b/src/sage/modules/all.py @@ -29,5 +29,3 @@ lazy_import('sage.modules.multi_filtered_vector_space', 'MultiFilteredVectorSpace') lazy_import('sage.modules.free_quadratic_module_integer_symmetric', 'IntegralLattice') lazy_import('sage.modules.torsion_quadratic_module', 'TorsionQuadraticForm') -lazy_import('sage.modules.finite_drinfeld_module', 'FiniteDrinfeldModule') -lazy_import('sage.modules.finite_drinfeld_module', 'FiniteDrinfeldModuleAction') diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index b6bba3369c3..449d6f3f7a1 100644 --- a/src/sage/rings/function_field/all.py +++ b/src/sage/rings/function_field/all.py @@ -1 +1,6 @@ from .constructor import FunctionField + +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.rings.function_field.finite_drinfeld_module', 'FiniteDrinfeldModule') +lazy_import('sage.rings.function_field.finite_drinfeld_module', 'FiniteDrinfeldModuleAction') diff --git a/src/sage/modules/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py similarity index 100% rename from src/sage/modules/finite_drinfeld_module.py rename to src/sage/rings/function_field/finite_drinfeld_module.py From e3fc37f4ab6d0ea5034970751686d891429c6d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 12 May 2022 15:52:45 +0200 Subject: [PATCH 010/392] Fix method is_morphism in FiniteDrinfeldModule --- src/sage/rings/function_field/finite_drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index 86d4d3253da..b287725d460 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -277,7 +277,7 @@ def is_ordinary(self): return not self.is_supersingular() def is_morphism(self, candidate): - return candidate == 0 and self.is_isogeny(candidate) + return candidate == 0 or self.is_isogeny(candidate) def is_isogeny(self, candidate): if not candidate in self.ore_polring(): From 0515d9b19b550ec27326bf978c9a0e8e555f4e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 12 May 2022 16:32:01 +0200 Subject: [PATCH 011/392] Add blank line before FiniteDrinfeldModuleAction (PEP8) --- src/sage/rings/function_field/finite_drinfeld_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index b287725d460..4f9f21995b5 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -380,6 +380,7 @@ def ore_polring(self): def polring(self): return self.domain() + class FiniteDrinfeldModuleAction(Action): def __init__(self, finite_drinfeld_module): From 8c848d901a85e52e2a05fe0f13a16a25199db812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 11 May 2022 18:11:50 +0200 Subject: [PATCH 012/392] Refactor doctest in FiniteDrinfeldModule --- .../function_field/finite_drinfeld_module.py | 233 ++++++++++++++---- 1 file changed, 185 insertions(+), 48 deletions(-) diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index 4f9f21995b5..37e8e694803 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -53,27 +53,31 @@ class FiniteDrinfeldModule(RingHomomorphism_im_gens): INPUT: - ``polring`` -- the base polynomial ring - - ``gen`` -- the image of `X` + - ``gen`` -- the generator of the Drinfeld module, i.e. the image of `X` in + the Ore polynomial ring - ``characteristic`` -- the Fq[X]-characteristic of the Drinfeld - module, i.e. an irreducible polynomial in `polring` that defines a - subextension of L (the base field of the Ore polynomial ring) and - for which the constant coefficient of `gen` is a root. + module, i.e. the minimal polynomial in `polring` of the constant term of + the generator EXAMPLES: - - .. RUBRIC:: Basics - First, create base objects:: + .. RUBRIC:: Instantiation - sage: Fq. = GF(3^2) + We must first create the base objects:: + + sage: Fq = GF(3^2) + sage: z2 = Fq.gen() sage: FqX. = Fq[] sage: p = X^3 + (z2 + 2)*X^2 + (6*z2 + 1)*X + 3*z2 + 5 sage: L = Fq.extension(6) - sage: Ltau. = OrePolynomialRing(L, L.frobenius_endomorphism(2)) + sage: frob = L.frobenius_endomorphism(2) + sage: Ltau. = OrePolynomialRing(L, frob) sage: omega = p.roots(L, multiplicities=False)[0] sage: phi_X = omega + t + t^2 - Then we instanciate the Drinfeld module:: + Notice that we have freedom on choosing the polynomial `p`, but not + `omega`. It is generally more useful this way. Then we instantiate the + Drinfeld module:: sage: phi = FiniteDrinfeldModule(FqX, phi_X, p) sage: phi @@ -83,67 +87,200 @@ class FiniteDrinfeldModule(RingHomomorphism_im_gens): Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 - There are getters for the base objects:: + .. RUBRIC:: Getters + + With getters, we can retrieve many basic objects associated to a Drinfeld + module. + + First, we can retrieve the polynomial ring, the Ore polynomial ring, and + the generator. Note that the class inherits `RingHomomorphism_im_gens`, so + that `domain`, `codomain` and `im_gens` are available:: sage: phi.polring() Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + sage: phi.domain() is phi.polring() + True sage: phi.ore_polring() Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) + sage: phi.codomain() is phi.ore_polring() + True sage: phi.gen() t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 + sage: phi.im_gens()[0] is phi.gen() + True + + We can retrieve `omega`, the constant term of the generator, and ensure + that it is a root of `p`, the characteristic:: + + sage: phi.constant_term() + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 + sage: phi.constant_term() == omega + True + sage: phi.characteristic() + X^3 + (z2 + 2)*X^2 + X + 2 + sage: phi.characteristic() == p + True + sage: phi.characteristic()(phi.constant_term()) + 0 + + We can retrieve the rank and the height (note that the height is always one + here):: - And the class inherits `RingHomomorphism_im_gens`, so that one can - use:: + sage: phi.rank() + 2 + sage: phi.height() + 1 - sage: phi.domain() - Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - sage: phi.codomain() - Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) - sage: phi.im_gens() - [t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12] + And finally we can retrieve some rank-two specifics:: - We can retrieve basic quantities: + sage: phi.j() # j-invariant + 1 + sage: phi.g() # Standard notation + 1 + sage: phi.delta() # Standard notation + 1 + sage: phi(X) == phi.constant_term() + phi.g()*t + phi.delta()*t^2 + True - sage: phi.rank() - 2 - sage: phi.j() + .. RUBRIC:: Evaluation of the Drinfeld module + + By definition, a Drinfeld module is a ring morphism from an polynomial ring + to an Ore polynomial ring. We can compute the images under this morphism + using the standard `phi(...)` notation:: + + sage: phi(X) + t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 + sage: phi(X) == phi.gen() + True + sage: phi(1) 1 + sage: phi(z2) == z2 + True + sage: phi(X^2 + 1) + t^4 + 2*t^3 + (2*z12^11 + 2*z12^10 + z12^9 + z12^6 + z12^5 + 2*z12^4 + z12^3 + 2*z12^2 + z12 + 2)*t^2 + (2*z12^8 + z12^7 + 2*z12^6 + z12^5 + z12^4 + z12 + 1)*t + 2*z12^11 + 2*z12^10 + z12^8 + z12^7 + 2*z12^6 + 2*z12^5 + z12^4 + 2*z12 .. RUBRIC:: The module law induced by a Drinfeld module - The most important feature of Drinfeld modules is that they induce - an `\Fq[X]`-module law on any subextension of the algebraic closure - `\Fqbar` of `\Fq`. For this, we created the class - `FiniteDrinfeldModuleAction`, which inherits `Action`. For the sake - of simplicity, `phi` will only act on the base field (`L`) of its - Ore polynomial ring. If you want to act on a bigger field, you can - use the method `change_ring`. - - sage: M = L.extension(2) - sage: phi_M = phi.change_ring(M) # todo: not tested - sage: action = phi._get_action_() # todo: not tested - sage: action # todo: not tested - Action on Finite Field in z12 of size 7^12 induced by the Finite Drinfeld module from Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 over Finite Field in z6 of size 7^6 - generated by t^2 + 1. + The most important feature of Drinfeld modules is that they endow any + subextension of `Fqbar` with an `Fq[X]`-module law. This action is + represented by the class `FiniteDrinfeldModuleAction`, which inherits + `Action`. For the sake of simplicity, `phi` will only act on the base field + (`L`) of its Ore polynomial ring. If you want to act on a bigger field, you + can define a new Drinfeld module using the method `change_ring`. + + sage: action = phi._get_action_() + sage: action + Action on Finite Field in z12 of size 3^12 induced by Finite Drinfeld module: + Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) + Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 + Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 - The calculation of the action is simple: + The calculation of the action is simple. Careful, the evaluation of Ore + polynomial is, at the moment, experimental:: - sage: x = M.gen() # todo: not tested - sage: g = X^3 + X + 5 # todo: not tested - sage: action(g, x) # todo: not tested + sage: x = L.gen() + 1 + sage: g = X^3 + X + 5 + sage: action(g, x) ... - 6*z12^11 + 5*z12^9 + 5*z12^8 + 2*z12^7 + 6*z12^6 + z12^5 + 6*z12^4 + 2*z12^3 + 3*z12^2 + 5*z12 + 4 + z12^11 + z12^10 + 2*z12^9 + z12^7 + z12^6 + z12^4 + 2*z12^2 + z12 + 1 + + To change ring, use:: + + sage: M = L.extension(5) + sage: psi = phi.change_ring(M) + sage: psi + Finite Drinfeld module: + Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + Ore polring: Ore Polynomial Ring in t over Finite Field in z60 of size 3^60 twisted by z60 |--> z60^(3^2) + Generator: t^2 + t + 2*z60^59 + z60^57 + 2*z60^56 + 2*z60^54 + 2*z60^53 + 2*z60^52 + z60^51 + z60^50 + 2*z60^48 + z60^47 + z60^46 + 2*z60^45 + 2*z60^44 + 2*z60^42 + 2*z60^41 + z60^40 + z60^39 + z60^38 + z60^37 + 2*z60^34 + z60^33 + z60^31 + 2*z60^29 + z60^27 + z60^26 + z60^25 + 2*z60^24 + z60^22 + 2*z60^21 + z60^19 + 2*z60^17 + 2*z60^16 + 2*z60^15 + z60^14 + z60^12 + z60^11 + 2*z60^10 + z60^8 + z60^7 + 2*z60^6 + 2*z60^5 + 2*z60 + 1 + Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 + + .. RUBRIC:: Morphisms and isogenies + + Being given an Ore polynomial `m`, we can decide if `m` is a morphism or + isogeny of Drinfeld module whose domain is `phi`:: + + sage: m = phi(X) + sage: phi.is_morphism(m) + True + sage: phi.is_isogeny(m) + True + sage: phi.is_endomorphism(m) + True + sage: phi.is_automorphism(m) + False + sage: m = 0 + sage: phi.is_endomorphism(m) + True + sage: phi.is_automorphism(m) + False + sage: phi.is_isogeny(m) + False + sage: m = t^6 + sage: phi.is_endomorphism(m) + True + sage: phi.is_automorphism(m) + False + + We implemented the Vélu formula for Drinfeld modules, in the sense that + given `m`, we can compute (if it exists) the unique Drinfeld module `psi` + such that `m` is an isogeny from `phi` to `psi`:: + + sage: m = phi(X^2 + 1) + sage: phi.velu(m) + Finite Drinfeld module: + Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) + Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 + Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 + sage: phi.velu(m) == phi + True + sage: z12 = L.gen() + sage: m = (z12^7 + z12^6 + 2*z12^4)*t^3 + sage: phi.is_isogeny(m) + True + sage: phi.is_endomorphism(m) + False + sage: phi.velu(m) + Finite Drinfeld module: + Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) + Generator: (2*z12^10 + z12^9 + 2*z12^7 + 2*z12^6 + z12^3 + 2*z12^2 + 2)*t^2 + (2*z12^9 + z12^7 + 2*z12^5 + z12 + 1)*t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 + Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 + sage: phi.velu(m) == phi + False + + .. RUBRIC:: Complex multiplication of Drinfeld modules + + There are various methods to manipulate the complex multiplication theory + of Drinfeld modules. We can compute the Frobenius norm, Frobenius trace and + characteristic polynomial:: + + sage: phi.frobenius_norm() + X^6 + (2*z2 + 1)*X^5 + (2*z2 + 1)*X^4 + (2*z2 + 2)*X^3 + z2*X^2 + X + 1 + sage: phi.frobenius_trace() + 2*X^3 + (2*z2 + 1)*X^2 + 2*X + z2 + 2 + sage: phi.characteristic_polynomial() + T^2 + (2*X^3 + (2*z2 + 1)*X^2 + 2*X + z2 + 2)*T + X^6 + (2*z2 + 1)*X^5 + (2*z2 + 1)*X^4 + (2*z2 + 2)*X^3 + z2*X^2 + X + 1 + + With those methods, it is easy to decide if a Drinfeld module is ordinary + or supersingular:: + + sage: phi.is_ordinary() + True + sage: phi.is_supersingular() + False - .. NOTE:: + .. NOTE:: - The general definition of a Drinfeld module is out of the scope - of this implementation. + The general definition of a Drinfeld module is out of the scope of this + implementation. :: - You can see all available methods of `RingHomomorphism_im_gens` - with `dir(sage.rings.morphism.RingHomomorphism_im_gens)`. Same - for `Action`. + You can see all available methods of `RingHomomorphism_im_gens` with + `dir(sage.rings.morphism.RingHomomorphism_im_gens)`. Same for `Action`. .. SEEALSO:: :mod:`sage.categories.action.Action` From 7c96d1f73f6d0ee9054eff247ade1a597381a449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 12 May 2022 18:08:02 +0200 Subject: [PATCH 013/392] Alphabetically order methods in sage.rings.function_field.finite_drinfeld_module.py --- .../function_field/finite_drinfeld_module.py | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index 37e8e694803..0c75d78f3f1 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -407,14 +407,14 @@ def invert(self, image): else: return None - def is_supersingular(self): - return self.characteristic().divides(self.frobenius_trace()) - - def is_ordinary(self): - return not self.is_supersingular() + def is_automorphism(self, candidate): + if not candidate in self.ore_polring(): + raise TypeError('The candidate must be in the Ore polynomial ' \ + 'ring') + return candidate != 0 and candidate in self._Fq() - def is_morphism(self, candidate): - return candidate == 0 or self.is_isogeny(candidate) + def is_endomorphism(self, candidate): + return candidate == 0 or self == self.velu(candidate) def is_isogeny(self, candidate): if not candidate in self.ore_polring(): @@ -428,14 +428,14 @@ def is_isogeny(self, candidate): return self.characteristic().degree().divides(candidate.valuation()) \ and candidate.right_divides(candidate * self.gen()) - def is_endomorphism(self, candidate): - return candidate == 0 or self == self.velu(candidate) + def is_morphism(self, candidate): + return candidate == 0 or self.is_isogeny(candidate) - def is_automorphism(self, candidate): - if not candidate in self.ore_polring(): - raise TypeError('The candidate must be in the Ore polynomial ' \ - 'ring') - return candidate != 0 and candidate in self._Fq() + def is_ordinary(self): + return not self.is_supersingular() + + def is_supersingular(self): + return self.characteristic().divides(self.frobenius_trace()) def j(self): if self.rank() != 2: @@ -540,6 +540,9 @@ def finite_drinfeld_module(self): # Special Sage functions # ########################## + def _act_(self, g, x): + return self.finite_drinfeld_module()(g)(x) + def _latex_(self): return f'\\text{{Action{{ }}on{{ }}}}' \ f'{latex(self.extension())}\\text{{{{ }}' \ @@ -549,9 +552,6 @@ def _repr_(self): return f'Action on {self.domain()} induced by ' \ f'{self.finite_drinfeld_module()}' - def _act_(self, g, x): - return self.finite_drinfeld_module()(g)(x) - def _check_base_fields(Fq, L): if not (L.is_field() and L.is_finite() and Fq.is_subring(L)): From 1a7891f0bbbd8486a511407df223a436df3b08f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 13 May 2022 18:00:37 +0200 Subject: [PATCH 014/392] Create class FiniteDrinfeldModule_rank_two Rank two finite Drinfeld modules have specific enough properties to have their own class. Therefore, we created the class `FiniteDrinfeldModule_rank_two`. The following methods were moved from `FiniteDrinfeldModule` to `FiniteDrinfeldModule_rank_two`: - characteristic_polynomial, - delta, - frobenius_norm, - frobenius_trace, - g, - is_ordinary, - is_supersingular. Before this change, those methods were regular methods of just `FiniteDrinfeldModule`; an exception was raised when the rank of the Drinfeld module was not two. Furthermore, the methods `_latex_` and `_repr_` were slightly changed. --- src/sage/rings/function_field/all.py | 2 + .../function_field/finite_drinfeld_module.py | 108 +++++++++++------- 2 files changed, 66 insertions(+), 44 deletions(-) diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index 449d6f3f7a1..3b3f7bfc8bd 100644 --- a/src/sage/rings/function_field/all.py +++ b/src/sage/rings/function_field/all.py @@ -3,4 +3,6 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.rings.function_field.finite_drinfeld_module', 'FiniteDrinfeldModule') +lazy_import('sage.rings.function_field.finite_drinfeld_module', + 'FiniteDrinfeldModule_rank_two') lazy_import('sage.rings.function_field.finite_drinfeld_module', 'FiniteDrinfeldModuleAction') diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index 0c75d78f3f1..a9be93f93d4 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -350,35 +350,6 @@ def change_ring(self, R): return FiniteDrinfeldModule(self.polring(), new_ore_polring(self.gen()), self.characteristic()) - def characteristic_polynomial(self): - FqXT = PolynomialRing(self.polring(), 'T') - return FqXT([self.frobenius_norm(), self.frobenius_trace(), 1]) - - def delta(self): - if self.rank() != 2: - raise ValueError('The rank must equal 2') - return self.gen()[2] - - def frobenius_norm(self): - # Notations from Schost-Musleh: - n = self._L().over(self._Fq()).degree_over(self._Fq()) - d = self.characteristic().degree() - m = n // d - norm = self._L().over(self._Fq())(self.delta()).norm() - return ((-1)**n) * (self.characteristic()**m) / norm - - def frobenius_trace(self): - # Notations from Schost-Musleh: - n = self._L().over(self._Fq()).degree_over(self._Fq()) - B = self.frobenius_norm() - t = self.ore_polring().gen() - return self.invert(t**n + (self(B) // t**n)) - - def g(self): - if self.rank() != 2: - raise ValueError('The rank must equal 2') - return self.gen()[1] - def height(self): return Integer(1) @@ -431,21 +402,6 @@ def is_isogeny(self, candidate): def is_morphism(self, candidate): return candidate == 0 or self.is_isogeny(candidate) - def is_ordinary(self): - return not self.is_supersingular() - - def is_supersingular(self): - return self.characteristic().divides(self.frobenius_trace()) - - def j(self): - if self.rank() != 2: - raise ValueError('The j-invariant is only defined for rank 2 ' \ - 'Drinfeld modules') - g = self.gen()[1] - delta = self.gen()[2] - q = self.polring().base_ring().order() - return (g**(q+1)) / delta - def rank(self): return self.gen().degree() @@ -518,6 +474,70 @@ def polring(self): return self.domain() +class FiniteDrinfeldModule_rank_two(FiniteDrinfeldModule): + + def __init__(self, polring, gen, characteristic): + if not isinstance(gen, OrePolynomial): + raise TypeError('The generator must be an Ore polynomial') + if gen.degree() != 2: + raise ValueError('The degree of the generator must be 2') + super().__init__(polring, gen, characteristic) + + ########### + # Methods # + ########### + + def characteristic_polynomial(self): + FqXT = PolynomialRing(self.polring(), 'T') + return FqXT([self.frobenius_norm(), self.frobenius_trace(), 1]) + + def frobenius_norm(self): + # Notations from Schost-Musleh: + n = self._L().over(self._Fq()).degree_over(self._Fq()) + d = self.characteristic().degree() + m = n // d + norm = self._L().over(self._Fq())(self.delta()).norm() + return ((-1)**n) * (self.characteristic()**m) / norm + + def frobenius_trace(self): + # Notations from Schost-Musleh: + n = self._L().over(self._Fq()).degree_over(self._Fq()) + B = self.frobenius_norm() + t = self.ore_polring().gen() + return self.invert(t**n + (self(B) // t**n)) + + def is_ordinary(self): + return not self.is_supersingular() + + def is_supersingular(self): + return self.characteristic().divides(self.frobenius_trace()) + + ########################## + # Special Sage functions # + ########################## + + def _repr_(self): + super_repr = super()._repr_() + return f'Rank two f{super_repr[1:]}' + + def _latex_(self): + super_latex = super()._latex_() + return f'{super_latex[:6]}Rank{{ }}two{{ }}f{super_latex[7:]}' + + ########### + # Getters # + ########### + + def delta(self): + return self.gen()[2] + + def g(self): + return self.gen()[1] + + def j(self): + return (self.g()**(q+1)) / self.delta() + + class FiniteDrinfeldModuleAction(Action): def __init__(self, finite_drinfeld_module): From 9bcadf21f86e8e4e6fda8e151ecb33db7acd6552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 9 Jun 2022 13:27:28 +0200 Subject: [PATCH 015/392] Fix j-invariant method for FiniteDrinfeldModule_rank_two --- src/sage/rings/function_field/finite_drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index a9be93f93d4..cf5b2ded53c 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -535,7 +535,7 @@ def g(self): return self.gen()[1] def j(self): - return (self.g()**(q+1)) / self.delta() + return (self.g()**(self._Fq().order()+1)) / self.delta() class FiniteDrinfeldModuleAction(Action): From 343506477529fab41fd6188b1e23bbcf59ca684f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 16 Jun 2022 11:48:10 +0200 Subject: [PATCH 016/392] Delete class FiniteDrinfeldModule_rank_two --- .../function_field/finite_drinfeld_module.py | 95 ++++++++----------- 1 file changed, 41 insertions(+), 54 deletions(-) diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index cf5b2ded53c..481defdf284 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -331,6 +331,11 @@ def _Fq(self): def _L(self): return self.ore_polring().base_ring() + def _test_rank_two(self): + if self.rank() != 2: + raise NotImplementedError('this method is only available for ' \ + 'rank two Drinfeld modules') + ########### # Methods # ########### @@ -425,6 +430,38 @@ def velu(self, candidate): else: return FiniteDrinfeldModule(self.polring(), q, self.characteristic()) + # Rank two methods + + def characteristic_polynomial(self): + self._test_rank_two() + FqXT = PolynomialRing(self.polring(), 'T') + return FqXT([self.frobenius_norm(), self.frobenius_trace(), 1]) + + def frobenius_norm(self): + self._test_rank_two() + # Notations from Schost-Musleh: + n = self._L().over(self._Fq()).degree_over(self._Fq()) + d = self.characteristic().degree() + m = n // d + norm = self._L().over(self._Fq())(self.delta()).norm() + return ((-1)**n) * (self.characteristic()**m) / norm + + def frobenius_trace(self): + self._test_rank_two() + # Notations from Schost-Musleh: + n = self._L().over(self._Fq()).degree_over(self._Fq()) + B = self.frobenius_norm() + t = self.ore_polring().gen() + return self.invert(t**n + (self(B) // t**n)) + + def is_ordinary(self): + self._test_rank_two() + return not self.is_supersingular() + + def is_supersingular(self): + self._test_rank_two() + return self.characteristic().divides(self.frobenius_trace()) + ########################## # Special Sage functions # ########################## @@ -473,68 +510,18 @@ def ore_polring(self): def polring(self): return self.domain() - -class FiniteDrinfeldModule_rank_two(FiniteDrinfeldModule): - - def __init__(self, polring, gen, characteristic): - if not isinstance(gen, OrePolynomial): - raise TypeError('The generator must be an Ore polynomial') - if gen.degree() != 2: - raise ValueError('The degree of the generator must be 2') - super().__init__(polring, gen, characteristic) - - ########### - # Methods # - ########### - - def characteristic_polynomial(self): - FqXT = PolynomialRing(self.polring(), 'T') - return FqXT([self.frobenius_norm(), self.frobenius_trace(), 1]) - - def frobenius_norm(self): - # Notations from Schost-Musleh: - n = self._L().over(self._Fq()).degree_over(self._Fq()) - d = self.characteristic().degree() - m = n // d - norm = self._L().over(self._Fq())(self.delta()).norm() - return ((-1)**n) * (self.characteristic()**m) / norm - - def frobenius_trace(self): - # Notations from Schost-Musleh: - n = self._L().over(self._Fq()).degree_over(self._Fq()) - B = self.frobenius_norm() - t = self.ore_polring().gen() - return self.invert(t**n + (self(B) // t**n)) - - def is_ordinary(self): - return not self.is_supersingular() - - def is_supersingular(self): - return self.characteristic().divides(self.frobenius_trace()) - - ########################## - # Special Sage functions # - ########################## - - def _repr_(self): - super_repr = super()._repr_() - return f'Rank two f{super_repr[1:]}' - - def _latex_(self): - super_latex = super()._latex_() - return f'{super_latex[:6]}Rank{{ }}two{{ }}f{super_latex[7:]}' - - ########### - # Getters # - ########### + # Rank two methods def delta(self): + self._test_rank_two() return self.gen()[2] def g(self): + self._test_rank_two() return self.gen()[1] def j(self): + self._test_rank_two() return (self.g()**(self._Fq().order()+1)) / self.delta() From a32d290ec526a52c48ed34dd8464f0a070b96c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 16 Jun 2022 15:22:36 +0200 Subject: [PATCH 017/392] Make FiniteDrinfeldModule inherit CategoryObject --- .../function_field/finite_drinfeld_module.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index 481defdf284..dba6444eb97 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -44,9 +44,10 @@ from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.structure.category_object import CategoryObject -class FiniteDrinfeldModule(RingHomomorphism_im_gens): +class FiniteDrinfeldModule(CategoryObject): r""" Class for finite Drinfeld modules. @@ -318,8 +319,12 @@ def __init__(self, polring, gen, characteristic): if gen.is_constant(): raise ValueError('The generator must not be constant') # Work - self.__characteristic = characteristic - super().__init__(Hom(FqX, Ltau), gen) + self._characteristic = characteristic + self._morphism = Hom(FqX, Ltau)(gen) + self._polring = FqX + self._ore_polring = Ltau + self._gen = gen + super().__init__() ################# # Private utils # @@ -340,6 +345,9 @@ def _test_rank_two(self): # Methods # ########### + def __call__(self, a): + return self._morphism(a) + def change_ring(self, R): # VERIFICATIONS if not R.is_field() and R.is_finite(): @@ -492,7 +500,7 @@ def _repr_(self): ########### def characteristic(self): - return self.__characteristic + return self._characteristic def constant_term(self): return self.gen()[0] @@ -501,14 +509,16 @@ def frobenius(self): return self.ore_polring().twisting_morphism() def gen(self): - [gen] = self.im_gens() - return gen + return self._gen + + def morphism(self): + return self._morphism def ore_polring(self): - return self.codomain() + return self._ore_polring def polring(self): - return self.domain() + return self._polring # Rank two methods @@ -524,9 +534,7 @@ def j(self): self._test_rank_two() return (self.g()**(self._Fq().order()+1)) / self.delta() - class FiniteDrinfeldModuleAction(Action): - def __init__(self, finite_drinfeld_module): # Verifications if not isinstance(finite_drinfeld_module, FiniteDrinfeldModule): From 135506941bfa1ad703abe0bd899c9a75f7e65fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 16 Jun 2022 15:22:56 +0200 Subject: [PATCH 018/392] Fix FiniteDrinfeldModule characteristic polynomial --- src/sage/rings/function_field/finite_drinfeld_module.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index dba6444eb97..31d6e2e3558 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -35,6 +35,7 @@ from sage.categories.action import Action from sage.categories.homset import Hom +from sage.categories.drinfeld_modules import DrinfeldModules from sage.matrix.constructor import Matrix from sage.misc.latex import latex from sage.modules.free_module_element import vector @@ -319,12 +320,12 @@ def __init__(self, polring, gen, characteristic): if gen.is_constant(): raise ValueError('The generator must not be constant') # Work + super().__init__(category=DrinfeldModules(FqX, gen.parent())) self._characteristic = characteristic self._morphism = Hom(FqX, Ltau)(gen) self._polring = FqX self._ore_polring = Ltau self._gen = gen - super().__init__() ################# # Private utils # @@ -443,7 +444,7 @@ def velu(self, candidate): def characteristic_polynomial(self): self._test_rank_two() FqXT = PolynomialRing(self.polring(), 'T') - return FqXT([self.frobenius_norm(), self.frobenius_trace(), 1]) + return FqXT([self.frobenius_norm(), -self.frobenius_trace(), 1]) def frobenius_norm(self): self._test_rank_two() From 927c47c3a1583dd4dbe9bded6af4a869d2e30b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 16 Jun 2022 16:21:06 +0200 Subject: [PATCH 019/392] Create category DrinfeldModules --- src/sage/categories/drinfeld_modules.py | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/sage/categories/drinfeld_modules.py diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py new file mode 100644 index 00000000000..14ca0fe1b3a --- /dev/null +++ b/src/sage/categories/drinfeld_modules.py @@ -0,0 +1,62 @@ +r""" +Drinfeld modules +""" +#***************************************************************************** +# Copyright (C) 2022 Xavier Caruso +# Antoine Leudière +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.category import CategoryWithParameters + +# from sage.misc.cachefunc import cached_method +# from sage.categories.basic import Fields + +class DrinfeldModules(CategoryWithParameters): + r""" + The category of Drinfeld modules. + + EXAMPLES: + + We create the category of function fields:: + + sage: C = FunctionFields() + sage: C + Category of function fields + + TESTS:: + + sage: TestSuite(FunctionFields()).run() + """ + + def __init__(self, domain, codomain): + r""" + """ + self._domain = domain + self._codomain = codomain + + def _call_(self, phi_X): + r""" + Constructs an object in this category from the data in ``x``, + or throws a TypeError. + """ + return FiniteDrinfeldModule(self._domain, self._codomain(x), characteristic) + + def super_categories(self): + return [] + + def _repr_(self): + return f'Category of Drinfeld modules:\n'\ + f' Domain: {self._domain}\n' \ + f' Codomain: {self._codomain}' + + def _make_named_class_key(self, name): + return self._domain.category() + + class ParentMethods: + pass + + class ElementMethods: + pass From 2537cb3691639719f54ce3935bc03610028f542f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 16 Jun 2022 16:51:16 +0200 Subject: [PATCH 020/392] Define category DrinfeldModules with gamma --- src/sage/categories/drinfeld_modules.py | 28 ++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 14ca0fe1b3a..bc0f15ea313 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -10,6 +10,9 @@ #****************************************************************************** from sage.categories.category import CategoryWithParameters +from sage.misc.functional import log +from sage.rings.polynomial.polynomial_ring import PolynomialRing_general +from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing # from sage.misc.cachefunc import cached_method # from sage.categories.basic import Fields @@ -31,11 +34,30 @@ class DrinfeldModules(CategoryWithParameters): sage: TestSuite(FunctionFields()).run() """ - def __init__(self, domain, codomain): + def __init__(self, gamma, name='t'): r""" """ - self._domain = domain - self._codomain = codomain + self._gamma = gamma + self._domain = FqX = gamma.domain() + K = gamma.codomain() + if not isinstance(FqX, PolynomialRing_general): + raise NotImplementedError('domain must be a polynomial ring') + Fq = FqX.base_ring() + if not Fq.is_field() or not Fq.is_finite() : + raise TypeError('the base ring of the domain must be a finite field') + d = log(Fq.cardinality(), Fq.characteristic()) + tau = K.frobenius_endomorphism(d) + self._codomain = OrePolynomialRing(K, tau, names=name) + # Create characteristic + if K.is_finite(): + f = gamma * FqX.coerce_map_from(Fq) + E = K.over(f) + self._characteristic = E(gamma(FqX.gen())).minpoly() + + def characteristic(self): + if not K.is_finite(): + raise NotImplementedError + return self._characteristic def _call_(self, phi_X): r""" From 445efbe2739fe13640041c2397f506c1d4d13eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 16 Jun 2022 16:51:30 +0200 Subject: [PATCH 021/392] Change DrinfeldModules _repr_ --- src/sage/categories/drinfeld_modules.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index bc0f15ea313..7a5f057bbc6 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -70,9 +70,7 @@ def super_categories(self): return [] def _repr_(self): - return f'Category of Drinfeld modules:\n'\ - f' Domain: {self._domain}\n' \ - f' Codomain: {self._codomain}' + return f'Category of Drinfeld modules defined by {self._gamma}' def _make_named_class_key(self, name): return self._domain.category() From d032810e44b1a6bd29d97b9bd600481808765c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 16 Jun 2022 17:33:32 +0200 Subject: [PATCH 022/392] BACKUP COMMIT --- src/sage/categories/drinfeld_modules.py | 23 +++++- .../function_field/finite_drinfeld_module.py | 75 +++++++++---------- 2 files changed, 55 insertions(+), 43 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 7a5f057bbc6..e35b95a18a3 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -49,22 +49,27 @@ def __init__(self, gamma, name='t'): tau = K.frobenius_endomorphism(d) self._codomain = OrePolynomialRing(K, tau, names=name) # Create characteristic + self._characteristic = None if K.is_finite(): f = gamma * FqX.coerce_map_from(Fq) E = K.over(f) self._characteristic = E(gamma(FqX.gen())).minpoly() def characteristic(self): - if not K.is_finite(): + if self._characteristic is None: raise NotImplementedError return self._characteristic - def _call_(self, phi_X): + def _call_(self, gen): r""" Constructs an object in this category from the data in ``x``, or throws a TypeError. """ - return FiniteDrinfeldModule(self._domain, self._codomain(x), characteristic) + from sage.rings.function_field.finite_drinfeld_module import FiniteDrinfeldModule + gen = self._codomain(gen) + if self.characteristic()(gen[0]) != 0: + raise ValueError('incorrect characteristic') + return FiniteDrinfeldModule(self._domain, gen) def super_categories(self): return [] @@ -75,8 +80,18 @@ def _repr_(self): def _make_named_class_key(self, name): return self._domain.category() + def domain(self): + return self._domain + + def codomain(self): + return self._codomain + + def gamma(self): + return self._gamma + class ParentMethods: - pass + def characteristic(self): + return self.category().characteristic() class ElementMethods: pass diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index 31d6e2e3558..6eb18fde0c2 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -46,6 +46,7 @@ from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.structure.category_object import CategoryObject +from sage.structure.sequence import Sequence class FiniteDrinfeldModule(CategoryObject): @@ -290,42 +291,34 @@ class FiniteDrinfeldModule(CategoryObject): :mod:`sage.rings.polynomial.ore_polynomial_ring` """ - def __init__(self, polring, gen, characteristic): - # Verifications - if not isinstance(polring, PolynomialRing_dense_finite_field): - raise TypeError('First argument must be a polynomial ring') - if not characteristic in polring: - raise TypeError('The characteristic must be in the polynomial ' \ - 'ring') - if not characteristic.is_prime(): - raise ValueError('The characteristic must be irreducible') - if not isinstance(gen, OrePolynomial): - raise TypeError('The generator must be an Ore polynomial') - # We define those for convenience before continuing - FqX = polring - Ltau = gen.parent() - Fq = FqX.base_ring() - L = Ltau.base_ring() - _check_base_fields(Fq, L) - if not Ltau.twisting_derivation() is None: - raise ValueError('The Ore polynomial ring should have no ' \ - 'derivation') - if Ltau.twisting_morphism().power() != Fq.degree(): - raise ValueError('The twisting morphism of the Ore polynomial ' \ - 'ring must be the Frobenius endomorphism of the base ' \ - 'field of the polynomial ring') - if characteristic(gen[0]) != 0: - raise ValueError('The constant coefficient of the generator ' \ - 'must be a root of the characteristic') - if gen.is_constant(): - raise ValueError('The generator must not be constant') + def __init__(self, polring, gen, name='t'): + Fq = polring.base_ring() + if isinstance(gen, OrePolynomial): + Ltau = gen.parent() + L = Ltau.base_ring() + name = Ltau.variable_name() + elif isinstance(gen, (list, tuple)): + Ltau = None + L = Sequence(gen).universe() + else: + raise TypeError('generator must be an Ore polynomial or a list of coefficients') + if not L.has_coerce_map_from(Fq): + raise TypeError('base ring of polring must coerce to base ring ' \ + 'of Ore polynomial ring') + gamma = Hom(polring, L)(gen[0]) + category = DrinfeldModules(gamma, name=name) + if Ltau is not None and Ltau is not category.codomain(): + raise ValueError(f'generator must lie in {category.codomain()}') + Ltau = category.codomain() + self._gen = Ltau(gen) + if self._gen.degree() <= 0: + raise ValueError('generator must have positive degree') # Work - super().__init__(category=DrinfeldModules(FqX, gen.parent())) - self._characteristic = characteristic - self._morphism = Hom(FqX, Ltau)(gen) - self._polring = FqX + super().__init__(category=category) + self._morphism = Hom(polring, Ltau)(self._gen) + self._polring = polring self._ore_polring = Ltau - self._gen = gen + self._ore_variable = Ltau.gen() ################# # Private utils # @@ -346,6 +339,11 @@ def _test_rank_two(self): # Methods # ########### + def __eq__(self, other): + if not isinstance(other, FiniteDrinfeldModule): + return False + return self.category() is other.category() and self.gen() == other.gen() + def __call__(self, a): return self._morphism(a) @@ -493,16 +491,12 @@ def _repr_(self): return f'Finite Drinfeld module:\n' \ f' Polring: {self.polring()}\n' \ f' Ore polring: {self.ore_polring()}\n' \ - f' Generator: {self.gen()}\n' \ - f' Characteristic: {self.characteristic()}' + f' Generator: {self.gen()}' \ ########### # Getters # ########### - def characteristic(self): - return self._characteristic - def constant_term(self): return self.gen()[0] @@ -518,6 +512,9 @@ def morphism(self): def ore_polring(self): return self._ore_polring + def ore_variable(self): + return self._ore_variable + def polring(self): return self._polring From 94bad90cd43c800227f5cbdf62595ee67a02c61c Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 21 Jun 2022 12:33:46 +0200 Subject: [PATCH 023/392] characteristic polynomial of Frobenius in all rank --- .../rings/function_field/finite_drinfeld_module.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/finite_drinfeld_module.py index 6eb18fde0c2..55c48eb0772 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/finite_drinfeld_module.py @@ -290,7 +290,6 @@ class FiniteDrinfeldModule(CategoryObject): :mod:`sage.rings.polynomial.ore_polynomial_element` :mod:`sage.rings.polynomial.ore_polynomial_ring` """ - def __init__(self, polring, gen, name='t'): Fq = polring.base_ring() if isinstance(gen, OrePolynomial): @@ -435,7 +434,14 @@ def velu(self, candidate): if r != 0: return None else: - return FiniteDrinfeldModule(self.polring(), q, self.characteristic()) + return FiniteDrinfeldModule(self.polring(), q) + + def frobenius_charpoly(self, var='x'): + # Does not work when Fq is not a prime field... + chi = self._gen.reduced_charpoly() + A = self._polring + S = PolynomialRing(A, name=var) + return -chi(A.gen(), S.gen()) # Rank two methods From 3b47737b087305806a6d07d5f2da595f0183fef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 22 Jun 2022 13:29:11 +0200 Subject: [PATCH 024/392] Change FiniteDrinfeldModule to DrinfeldModule --- src/sage/rings/function_field/all.py | 6 ++-- ..._drinfeld_module.py => drinfeld_module.py} | 36 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) rename src/sage/rings/function_field/{finite_drinfeld_module.py => drinfeld_module.py} (95%) diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index 3b3f7bfc8bd..86d2372ff67 100644 --- a/src/sage/rings/function_field/all.py +++ b/src/sage/rings/function_field/all.py @@ -2,7 +2,5 @@ from sage.misc.lazy_import import lazy_import -lazy_import('sage.rings.function_field.finite_drinfeld_module', 'FiniteDrinfeldModule') -lazy_import('sage.rings.function_field.finite_drinfeld_module', - 'FiniteDrinfeldModule_rank_two') -lazy_import('sage.rings.function_field.finite_drinfeld_module', 'FiniteDrinfeldModuleAction') +lazy_import('sage.rings.function_field.drinfeld_module', 'DrinfeldModule') +lazy_import('sage.rings.function_field.drinfeld_module', 'DrinfeldModuleAction') diff --git a/src/sage/rings/function_field/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_module.py similarity index 95% rename from src/sage/rings/function_field/finite_drinfeld_module.py rename to src/sage/rings/function_field/drinfeld_module.py index 6eb18fde0c2..9e11f8c6ad6 100644 --- a/src/sage/rings/function_field/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_module.py @@ -1,7 +1,7 @@ r""" This module provides classes for finite Drinfeld modules -(`FiniteDrinfeldModule`) and their module action on the algebraic -closure of `\Fq` (`FiniteDrinfeldModuleAction`). +(`DrinfeldModule`) and their module action on the algebraic +closure of `\Fq` (`DrinfeldModuleAction`). AUTHORS: @@ -49,7 +49,7 @@ from sage.structure.sequence import Sequence -class FiniteDrinfeldModule(CategoryObject): +class DrinfeldModule(CategoryObject): r""" Class for finite Drinfeld modules. @@ -82,9 +82,9 @@ class FiniteDrinfeldModule(CategoryObject): `omega`. It is generally more useful this way. Then we instantiate the Drinfeld module:: - sage: phi = FiniteDrinfeldModule(FqX, phi_X, p) + sage: phi = DrinfeldModule(FqX, phi_X, p) sage: phi - Finite Drinfeld module: + Drinfeld module: Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 @@ -166,14 +166,14 @@ class FiniteDrinfeldModule(CategoryObject): The most important feature of Drinfeld modules is that they endow any subextension of `Fqbar` with an `Fq[X]`-module law. This action is - represented by the class `FiniteDrinfeldModuleAction`, which inherits + represented by the class `DrinfeldModuleAction`, which inherits `Action`. For the sake of simplicity, `phi` will only act on the base field (`L`) of its Ore polynomial ring. If you want to act on a bigger field, you can define a new Drinfeld module using the method `change_ring`. sage: action = phi._get_action_() sage: action - Action on Finite Field in z12 of size 3^12 induced by Finite Drinfeld module: + Action on Finite Field in z12 of size 3^12 induced by Drinfeld module: Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 @@ -193,7 +193,7 @@ class FiniteDrinfeldModule(CategoryObject): sage: M = L.extension(5) sage: psi = phi.change_ring(M) sage: psi - Finite Drinfeld module: + Drinfeld module: Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 Ore polring: Ore Polynomial Ring in t over Finite Field in z60 of size 3^60 twisted by z60 |--> z60^(3^2) Generator: t^2 + t + 2*z60^59 + z60^57 + 2*z60^56 + 2*z60^54 + 2*z60^53 + 2*z60^52 + z60^51 + z60^50 + 2*z60^48 + z60^47 + z60^46 + 2*z60^45 + 2*z60^44 + 2*z60^42 + 2*z60^41 + z60^40 + z60^39 + z60^38 + z60^37 + 2*z60^34 + z60^33 + z60^31 + 2*z60^29 + z60^27 + z60^26 + z60^25 + 2*z60^24 + z60^22 + 2*z60^21 + z60^19 + 2*z60^17 + 2*z60^16 + 2*z60^15 + z60^14 + z60^12 + z60^11 + 2*z60^10 + z60^8 + z60^7 + 2*z60^6 + 2*z60^5 + 2*z60 + 1 @@ -232,7 +232,7 @@ class FiniteDrinfeldModule(CategoryObject): sage: m = phi(X^2 + 1) sage: phi.velu(m) - Finite Drinfeld module: + Drinfeld module: Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 @@ -246,7 +246,7 @@ class FiniteDrinfeldModule(CategoryObject): sage: phi.is_endomorphism(m) False sage: phi.velu(m) - Finite Drinfeld module: + Drinfeld module: Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) Generator: (2*z12^10 + z12^9 + 2*z12^7 + 2*z12^6 + z12^3 + 2*z12^2 + 2)*t^2 + (2*z12^9 + z12^7 + 2*z12^5 + z12 + 1)*t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 @@ -340,7 +340,7 @@ def _test_rank_two(self): ########### def __eq__(self, other): - if not isinstance(other, FiniteDrinfeldModule): + if not isinstance(other, DrinfeldModule): return False return self.category() is other.category() and self.gen() == other.gen() @@ -359,7 +359,7 @@ def change_ring(self, R): new_frobenius = R.frobenius_endomorphism(self.frobenius().power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, names=self.ore_polring().variable_names()) - return FiniteDrinfeldModule(self.polring(), + return DrinfeldModule(self.polring(), new_ore_polring(self.gen()), self.characteristic()) def height(self): @@ -435,7 +435,7 @@ def velu(self, candidate): if r != 0: return None else: - return FiniteDrinfeldModule(self.polring(), q, self.characteristic()) + return DrinfeldModule(self.polring(), q, self.characteristic()) # Rank two methods @@ -474,7 +474,7 @@ def is_supersingular(self): ########################## def _get_action_(self): - return FiniteDrinfeldModuleAction(self) + return DrinfeldModuleAction(self) def _latex_(self): return f'\\text{{Finite{{ }}Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}}\n' \ @@ -488,7 +488,7 @@ def _latex_(self): f'{latex(self.characteristic())}' def _repr_(self): - return f'Finite Drinfeld module:\n' \ + return f'Drinfeld module:\n' \ f' Polring: {self.polring()}\n' \ f' Ore polring: {self.ore_polring()}\n' \ f' Generator: {self.gen()}' \ @@ -532,11 +532,11 @@ def j(self): self._test_rank_two() return (self.g()**(self._Fq().order()+1)) / self.delta() -class FiniteDrinfeldModuleAction(Action): +class DrinfeldModuleAction(Action): def __init__(self, finite_drinfeld_module): # Verifications - if not isinstance(finite_drinfeld_module, FiniteDrinfeldModule): - raise TypeError('First argument must be a FiniteDrinfeldModule') + if not isinstance(finite_drinfeld_module, DrinfeldModule): + raise TypeError('First argument must be a DrinfeldModule') # Work self.__finite_drinfeld_module = finite_drinfeld_module super().__init__(finite_drinfeld_module.polring(), From 45be70180e9cc59760943399dd9758b81ac47217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 22 Jun 2022 13:37:39 +0200 Subject: [PATCH 025/392] Clean DrinfeldModules category - Comment constructor - Check that codomain of input morphism is a field - Delete various useless docstrings (they will be written later, at a more final stage of development) - Change gamma to morphism - Various small enhancements --- src/sage/categories/drinfeld_modules.py | 51 +++++++++---------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index e35b95a18a3..a989f86fbc4 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -18,42 +18,31 @@ # from sage.categories.basic import Fields class DrinfeldModules(CategoryWithParameters): - r""" - The category of Drinfeld modules. - EXAMPLES: - - We create the category of function fields:: - - sage: C = FunctionFields() - sage: C - Category of function fields - - TESTS:: - - sage: TestSuite(FunctionFields()).run() - """ - - def __init__(self, gamma, name='t'): - r""" - """ - self._gamma = gamma - self._domain = FqX = gamma.domain() - K = gamma.codomain() + def __init__(self, morphism, name='t'): + self._morphism = morphism + self._domain = morphism.domain() + # Check domain is Fq[X] + FqX = self._domain if not isinstance(FqX, PolynomialRing_general): raise NotImplementedError('domain must be a polynomial ring') Fq = FqX.base_ring() if not Fq.is_field() or not Fq.is_finite() : raise TypeError('the base ring of the domain must be a finite field') + # Check domain is field + K = morphism.codomain() + if not K.is_field(): + raise TypeError('the codomain must be a field') + # Build K{t} d = log(Fq.cardinality(), Fq.characteristic()) tau = K.frobenius_endomorphism(d) self._codomain = OrePolynomialRing(K, tau, names=name) # Create characteristic self._characteristic = None if K.is_finite(): - f = gamma * FqX.coerce_map_from(Fq) + f = morphism * FqX.coerce_map_from(Fq) E = K.over(f) - self._characteristic = E(gamma(FqX.gen())).minpoly() + self._characteristic = E(morphism(FqX.gen())).minpoly() def characteristic(self): if self._characteristic is None: @@ -61,21 +50,19 @@ def characteristic(self): return self._characteristic def _call_(self, gen): - r""" - Constructs an object in this category from the data in ``x``, - or throws a TypeError. - """ - from sage.rings.function_field.finite_drinfeld_module import FiniteDrinfeldModule + # Avoid circular import + from sage.rings.function_field.drinfeld_module import DrinfeldModule + # If gen is not in the codomain, an exception is raised gen = self._codomain(gen) if self.characteristic()(gen[0]) != 0: raise ValueError('incorrect characteristic') - return FiniteDrinfeldModule(self._domain, gen) + return DrinfeldModule(self._domain, gen) def super_categories(self): return [] def _repr_(self): - return f'Category of Drinfeld modules defined by {self._gamma}' + return f'Category of Drinfeld modules defined by {self._morphism}' def _make_named_class_key(self, name): return self._domain.category() @@ -86,8 +73,8 @@ def domain(self): def codomain(self): return self._codomain - def gamma(self): - return self._gamma + def morphism(self): + return self._morphism class ParentMethods: def characteristic(self): From 0ccdf26500825e2b778258831466f16939ac74dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 22 Jun 2022 14:13:50 +0200 Subject: [PATCH 026/392] Clean DrinfeldModule constructor Change some variables' name and comment. This includes changing some variables' name in the constructor of the category DrinfeldModules, as to be consistent with the constructor of DrinfeldModule. --- src/sage/categories/drinfeld_modules.py | 10 ++-- .../rings/function_field/drinfeld_module.py | 59 +++++++++++++------ 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index a989f86fbc4..7a169c31a11 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -23,12 +23,14 @@ def __init__(self, morphism, name='t'): self._morphism = morphism self._domain = morphism.domain() # Check domain is Fq[X] - FqX = self._domain - if not isinstance(FqX, PolynomialRing_general): + functions_ring = self._domain # functions_ring + if not isinstance(functions_ring, PolynomialRing_general): raise NotImplementedError('domain must be a polynomial ring') - Fq = FqX.base_ring() - if not Fq.is_field() or not Fq.is_finite() : + functions_ring_base = functions_ring.base_ring() + if not functions_ring_base.is_field() or not functions_ring_base.is_finite() : raise TypeError('the base ring of the domain must be a finite field') + Fq = functions_ring_base + FqX = functions_ring # Check domain is field K = morphism.codomain() if not K.is_field(): diff --git a/src/sage/rings/function_field/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_module.py index 9e11f8c6ad6..adec664c9a5 100644 --- a/src/sage/rings/function_field/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_module.py @@ -291,34 +291,55 @@ class DrinfeldModule(CategoryObject): :mod:`sage.rings.polynomial.ore_polynomial_ring` """ - def __init__(self, polring, gen, name='t'): - Fq = polring.base_ring() + def __init__(self, functions_ring, gen, name='t'): + + # Check all possible input types + # `gen` is an Ore polynomial: if isinstance(gen, OrePolynomial): - Ltau = gen.parent() - L = Ltau.base_ring() - name = Ltau.variable_name() + ore_polring = gen.parent() + ore_polring_base = ore_polring.base_ring() + name = ore_polring.variable_name() + # `gen` is a list of coefficients (functions_ring = Fq[X]): elif isinstance(gen, (list, tuple)): - Ltau = None - L = Sequence(gen).universe() + ore_polring = None + ore_polring_base = Sequence(gen).universe() + # `gen` is a list of list of coefficients (multiple gens): + elif isinstance(gen, (list, tuple)) \ + and all(isinstance(x, (list, tuple)) for x in gen): + ore_polring = None + all_coeffs = [] + for coeffs in gen: + all_coeffs += coeffs + ore_polring_base = Sequence(all_coeffs).universe() else: - raise TypeError('generator must be an Ore polynomial or a list of coefficients') - if not L.has_coerce_map_from(Fq): - raise TypeError('base ring of polring must coerce to base ring ' \ - 'of Ore polynomial ring') - gamma = Hom(polring, L)(gen[0]) + raise TypeError('generator must be a list of coefficients, a list' \ + 'of list of coefficients, or an Ore polynomial') + + # Build the morphism that defines the category + functions_ring_base = functions_ring.base_ring() + if not ore_polring_base.has_coerce_map_from(functions_ring_base): + raise TypeError('base ring of functions_ring must coerce to base ' \ + 'ring of Ore polynomial ring') + gamma = Hom(functions_ring, ore_polring_base)(gen[0]) + + # Mathematical integrity of the data is delegated to the category category = DrinfeldModules(gamma, name=name) - if Ltau is not None and Ltau is not category.codomain(): + # Check gen as Ore polynomial + if ore_polring is not None and ore_polring is not category.codomain(): raise ValueError(f'generator must lie in {category.codomain()}') - Ltau = category.codomain() - self._gen = Ltau(gen) + # Sanity cast + ore_polring = category.codomain() + # Be sure to have a generator that is an Ore polynomial + self._gen = ore_polring(gen) if self._gen.degree() <= 0: raise ValueError('generator must have positive degree') + # Work super().__init__(category=category) - self._morphism = Hom(polring, Ltau)(self._gen) - self._polring = polring - self._ore_polring = Ltau - self._ore_variable = Ltau.gen() + self._morphism = Hom(functions_ring, ore_polring)(self._gen) + self.functions_ring = functions_ring + self._ore_polring = ore_polring + self._ore_variable = ore_polring.gen() ################# # Private utils # From 92b9e68c04a9f39180236428099bd4a9d22b84b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 22 Jun 2022 14:37:38 +0200 Subject: [PATCH 027/392] Change polring to functions_ring in DrinfeldModule --- .../rings/function_field/drinfeld_module.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_module.py index adec664c9a5..f525aa1bb52 100644 --- a/src/sage/rings/function_field/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_module.py @@ -337,7 +337,7 @@ def __init__(self, functions_ring, gen, name='t'): # Work super().__init__(category=category) self._morphism = Hom(functions_ring, ore_polring)(self._gen) - self.functions_ring = functions_ring + self._functions_ring = functions_ring self._ore_polring = ore_polring self._ore_variable = ore_polring.gen() @@ -346,7 +346,7 @@ def __init__(self, functions_ring, gen, name='t'): ################# def _Fq(self): - return self.polring().base_ring() + return self.functions_ring().base_ring() def _L(self): return self.ore_polring().base_ring() @@ -375,12 +375,12 @@ def change_ring(self, R): if not self.ore_polring().base_ring().is_subring(R): raise ValueError('The new field must be a finite field ' \ 'extension of the base field of the Ore polynomial ring.') - _check_base_fields(self.polring().base_ring(), R) + _check_base_fields(self.functions_ring().base_ring(), R) # ACTUAL WORK new_frobenius = R.frobenius_endomorphism(self.frobenius().power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, names=self.ore_polring().variable_names()) - return DrinfeldModule(self.polring(), + return DrinfeldModule(self.functions_ring(), new_ore_polring(self.gen()), self.characteristic()) def height(self): @@ -396,7 +396,7 @@ def invert(self, image): if image in self._L(): # Only works if `image` is in the image of self return self._Fq()(image) r = self.rank() - X = self.polring().gen() + X = self.functions_ring().gen() k = image.degree() // r m_lines = [[0 for _ in range(k+1)] for _ in range(k+1)] for i in range(k+1): @@ -405,7 +405,7 @@ def invert(self, image): m_lines[j][i] = phi_X_i[r*j] m = Matrix(m_lines) v = vector([list(image)[r*j] for j in range(k+1)]) - pre_image = self.polring()(list((m**(-1)) * v)) + pre_image = self.functions_ring()(list((m**(-1)) * v)) if self(pre_image) == image: return pre_image else: @@ -456,13 +456,13 @@ def velu(self, candidate): if r != 0: return None else: - return DrinfeldModule(self.polring(), q, self.characteristic()) + return DrinfeldModule(self.functions_ring(), q, self.characteristic()) # Rank two methods def characteristic_polynomial(self): self._test_rank_two() - FqXT = PolynomialRing(self.polring(), 'T') + FqXT = PolynomialRing(self.functions_ring(), 'T') return FqXT([self.frobenius_norm(), -self.frobenius_trace(), 1]) def frobenius_norm(self): @@ -500,9 +500,9 @@ def _get_action_(self): def _latex_(self): return f'\\text{{Finite{{ }}Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}}\n' \ f'\\begin{{align}}\n' \ - f' {latex(self.polring())}\n' \ + f' {latex(self.functions_ring())}\n' \ f' &\\to {latex(self.ore_polring())} \\\\\n' \ - f' {latex(self.polring().gen())}\n' \ + f' {latex(self.functions_ring().gen())}\n' \ f' &\\mapsto {latex(self.gen())}\n' \ f'\\end{{align}}\n' \ f'\\text{{with{{ }}characteristic{{ }}}} ' \ @@ -510,7 +510,7 @@ def _latex_(self): def _repr_(self): return f'Drinfeld module:\n' \ - f' Polring: {self.polring()}\n' \ + f' Polring: {self.functions_ring()}\n' \ f' Ore polring: {self.ore_polring()}\n' \ f' Generator: {self.gen()}' \ @@ -536,8 +536,8 @@ def ore_polring(self): def ore_variable(self): return self._ore_variable - def polring(self): - return self._polring + def functions_ring(self): + return self._functions_ring # Rank two methods From 2b01218c051d92d84f0b85b25e73c479b2e8ec7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 22 Jun 2022 19:20:19 +0200 Subject: [PATCH 028/392] Create morphisms for DrinfeldModule --- src/sage/categories/drinfeld_modules.py | 8 ++++++ .../rings/function_field/drinfeld_module.py | 25 +++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 7a169c31a11..8cf93536c72 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -14,6 +14,8 @@ from sage.rings.polynomial.polynomial_ring import PolynomialRing_general from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing +from sage.categories.homsets import Homsets + # from sage.misc.cachefunc import cached_method # from sage.categories.basic import Fields @@ -69,6 +71,12 @@ def _repr_(self): def _make_named_class_key(self, name): return self._domain.category() + def Homsets(self): + return Homsets() + + def Endsets(self): + return Homsets() + def domain(self): return self._domain diff --git a/src/sage/rings/function_field/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_module.py index f525aa1bb52..bb2cb5a8c42 100644 --- a/src/sage/rings/function_field/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_module.py @@ -34,8 +34,9 @@ #***************************************************************************** from sage.categories.action import Action -from sage.categories.homset import Hom from sage.categories.drinfeld_modules import DrinfeldModules +from sage.categories.homset import Hom +from sage.categories.homset import Homset from sage.matrix.constructor import Matrix from sage.misc.latex import latex from sage.modules.free_module_element import vector @@ -48,7 +49,6 @@ from sage.structure.category_object import CategoryObject from sage.structure.sequence import Sequence - class DrinfeldModule(CategoryObject): r""" Class for finite Drinfeld modules. @@ -458,6 +458,12 @@ def velu(self, candidate): else: return DrinfeldModule(self.functions_ring(), q, self.characteristic()) + def End(self): + return DrinfeldModuleHomset(self, self) + + def Hom(self, other): + return DrinfeldModuleHomset(self, other) + # Rank two methods def characteristic_polynomial(self): @@ -553,6 +559,21 @@ def j(self): self._test_rank_two() return (self.g()**(self._Fq().order()+1)) / self.delta() +class DrinfeldModuleHomset(Homset): + + def __init__(self, X, Y, base=None, check=True): + if X.category() != Y.category() \ + and not isinstance(X.category(), DrinfeldModules): + raise TypeError('Drinfeld modules must be in the same category') + super().__init__(X, Y, category=Homsets(), base=base, check=check) + + def __contains__(self, candidate): + phi = self.domain() + psi = self.codomain() + if candidate not in phi.ore_polring(): + raise TypeError('morphism must be in the Ore polynomial ring') + return candidate * phi.gen() == psi.gen() * candidate + class DrinfeldModuleAction(Action): def __init__(self, finite_drinfeld_module): # Verifications From b823a5721884dd04c0d50557bb2d31b30af47d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 22 Jun 2022 19:27:05 +0200 Subject: [PATCH 029/392] Remove is_morphism, etc, methods of DrinfeldModule --- .../rings/function_field/drinfeld_module.py | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_module.py index bb2cb5a8c42..40a920d6456 100644 --- a/src/sage/rings/function_field/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_module.py @@ -411,30 +411,6 @@ def invert(self, image): else: return None - def is_automorphism(self, candidate): - if not candidate in self.ore_polring(): - raise TypeError('The candidate must be in the Ore polynomial ' \ - 'ring') - return candidate != 0 and candidate in self._Fq() - - def is_endomorphism(self, candidate): - return candidate == 0 or self == self.velu(candidate) - - def is_isogeny(self, candidate): - if not candidate in self.ore_polring(): - raise TypeError('The candidate must be in the Ore polynomial ' \ - 'ring') - if candidate == 0: - return False - elif candidate in self.ore_polring().base_ring(): - return True - else: - return self.characteristic().degree().divides(candidate.valuation()) \ - and candidate.right_divides(candidate * self.gen()) - - def is_morphism(self, candidate): - return candidate == 0 or self.is_isogeny(candidate) - def rank(self): return self.gen().degree() From c01845237b3ea2b1f9a07edc3df2bad283a75b75 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 23 Jun 2022 11:59:57 +0200 Subject: [PATCH 030/392] refactor code + classcall_private --- src/sage/rings/function_field/all.py | 7 +- .../{ => drinfeld_modules}/drinfeld_module.py | 258 ++++++------------ 2 files changed, 86 insertions(+), 179 deletions(-) rename src/sage/rings/function_field/{ => drinfeld_modules}/drinfeld_module.py (79%) diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index 86d2372ff67..6904f3ef4fb 100644 --- a/src/sage/rings/function_field/all.py +++ b/src/sage/rings/function_field/all.py @@ -1,6 +1 @@ -from .constructor import FunctionField - -from sage.misc.lazy_import import lazy_import - -lazy_import('sage.rings.function_field.drinfeld_module', 'DrinfeldModule') -lazy_import('sage.rings.function_field.drinfeld_module', 'DrinfeldModuleAction') +from .drinfeld_modules.all import * diff --git a/src/sage/rings/function_field/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py similarity index 79% rename from src/sage/rings/function_field/drinfeld_module.py rename to src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 43a6a7763ca..ddfbec4961e 100644 --- a/src/sage/rings/function_field/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -33,23 +33,20 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.categories.action import Action -from sage.categories.drinfeld_modules import DrinfeldModules -from sage.categories.homset import Hom -from sage.categories.homset import Homset -from sage.matrix.constructor import Matrix -from sage.misc.latex import latex -from sage.modules.free_module_element import vector from sage.rings.integer import Integer -from sage.rings.morphism import RingHomomorphism_im_gens +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.category_object import CategoryObject + +from sage.categories.drinfeld_modules import DrinfeldModules from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing -from sage.rings.polynomial.polynomial_ring import PolynomialRing_dense_finite_field -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.structure.category_object import CategoryObject + +from sage.misc.latex import latex from sage.structure.sequence import Sequence +from sage.matrix.constructor import Matrix +from sage.modules.free_module_element import vector -class DrinfeldModule(CategoryObject): +class DrinfeldModule(UniqueRepresentation, CategoryObject): r""" Class for finite Drinfeld modules. @@ -290,7 +287,8 @@ class DrinfeldModule(CategoryObject): :mod:`sage.rings.polynomial.ore_polynomial_element` :mod:`sage.rings.polynomial.ore_polynomial_ring` """ - def __init__(self, functions_ring, gen, name='t'): + @staticmethod + def __classcall_private__(cls, functions_ring, gen, name='t'): # Check all possible input types # `gen` is an Ore polynomial: if isinstance(gen, OrePolynomial): @@ -318,9 +316,9 @@ def __init__(self, functions_ring, gen, name='t'): if not ore_polring_base.has_coerce_map_from(functions_ring_base): raise TypeError('base ring of functions_ring must coerce to base ' \ 'ring of Ore polynomial ring') - gamma = Hom(functions_ring, ore_polring_base)(gen[0]) + gamma = functions_ring.hom([ore_polring_base(gen[0])]) - # Mathematical integrity of the data is delegated to the category + # Mathematical integrity of the data is delegated to the category category = DrinfeldModules(gamma, name=name) # Check gen as Ore polynomial if ore_polring is not None and ore_polring is not category.codomain(): @@ -328,32 +326,84 @@ def __init__(self, functions_ring, gen, name='t'): # Sanity cast ore_polring = category.codomain() # Be sure to have a generator that is an Ore polynomial - self._gen = ore_polring(gen) - if self._gen.degree() <= 0: + gen = ore_polring(gen) + if gen.degree() <= 0: raise ValueError('generator must have positive degree') - # Work - super().__init__(category=category) - self._morphism = Hom(functions_ring, ore_polring)(self._gen) - self._functions_ring = functions_ring - self._ore_polring = ore_polring - self._ore_variable = ore_polring.gen() + # Instantiate the appropriate class + if ore_polring_base.is_finite(): + from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule + return FiniteDrinfeldModule(gen, category) + else: + return cls.__classcall__(cls, gen, category) + + def __init__(self, gen, category): + CategoryObject.__init__(self, category=category) + self._functions_ring = category.domain() + self._Fq = self._functions_ring.base_ring() + self._ore_polring = gen.parent() + self._L = self._ore_polring.base_ring() + self._gen = gen + self._morphism = self._functions_ring.hom([gen]) ################# # Private utils # ################# - def _Fq(self): - return self.functions_ring().base_ring() - - def _L(self): - return self.ore_polring().base_ring() - def _test_rank_two(self): if self.rank() != 2: raise NotImplementedError('this method is only available for ' \ 'rank two Drinfeld modules') + ########################## + # Special Sage functions # + ########################## + + def _get_action_(self): + from sage.rings.function_fields.drinfeld_modules.action import DrinfeldModuleAction + return DrinfeldModuleAction(self) + + def _latex_(self): + return f'\\text{{Finite{{ }}Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}}\n' \ + f'\\begin{{align}}\n' \ + f' {latex(self.functions_ring())}\n' \ + f' &\\to {latex(self.ore_polring())} \\\\\n' \ + f' {latex(self.functions_ring().gen())}\n' \ + f' &\\mapsto {latex(self.gen())}\n' \ + f'\\end{{align}}\n' \ + f'\\text{{with{{ }}characteristic{{ }}}} ' \ + f'{latex(self.characteristic())}' + + def _repr_(self): + return "Drinfeld module defined by %s |--> %s over %s" % (self._functions_ring.gen(), self._gen, self._L) + + + ########### + # Getters # + ########### + + def constant_term(self): + return self.gen()[0] + + def frobenius(self): + return self.ore_polring().twisting_morphism() + + def gen(self): + return self._gen + + def morphism(self): + return self._morphism + + def ore_polring(self): + return self._ore_polring + + def ore_variable(self): + return self._ore_polring.gen() + + def functions_ring(self): + return self._functions_ring + + ########### # Methods # ########### @@ -373,7 +423,7 @@ def change_ring(self, R): if not self.ore_polring().base_ring().is_subring(R): raise ValueError('The new field must be a finite field ' \ 'extension of the base field of the Ore polynomial ring.') - _check_base_fields(self.functions_ring().base_ring(), R) + _check_base_fields(self._Fq, R) # ACTUAL WORK new_frobenius = R.frobenius_endomorphism(self.frobenius().power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, @@ -391,8 +441,8 @@ def invert(self, image): if not image in self.ore_polring(): raise TypeError('The tested image should be in the Ore ' \ 'polynomial ring') - if image in self._L(): # Only works if `image` is in the image of self - return self._Fq()(image) + if image in self._L: # Only works if `image` is in the image of self + return self._Fq(image) r = self.rank() X = self.functions_ring().gen() k = image.degree() // r @@ -432,102 +482,12 @@ def velu(self, candidate): else: return FiniteDrinfeldModule(self.polring(), q) - def frobenius_charpoly(self, var='x'): - # Does not work when Fq is not a prime field... - chi = self._gen.reduced_charpoly() - A = self._polring - S = PolynomialRing(A, name=var) - return -chi(A.gen(), S.gen()) - - def End(self): - return DrinfeldModuleHomset(self, self) - - def Hom(self, other): + def _Hom_(self, other): + from sage.rings.function_fields.drinfeld_modules.morphism import DrinfeldModuleHomset return DrinfeldModuleHomset(self, other) # Rank two methods - def characteristic_polynomial(self): - self._test_rank_two() - FqXT = PolynomialRing(self.functions_ring(), 'T') - return FqXT([self.frobenius_norm(), -self.frobenius_trace(), 1]) - - def frobenius_norm(self): - self._test_rank_two() - # Notations from Schost-Musleh: - n = self._L().over(self._Fq()).degree_over(self._Fq()) - d = self.characteristic().degree() - m = n // d - norm = self._L().over(self._Fq())(self.delta()).norm() - return ((-1)**n) * (self.characteristic()**m) / norm - - def frobenius_trace(self): - self._test_rank_two() - # Notations from Schost-Musleh: - n = self._L().over(self._Fq()).degree_over(self._Fq()) - B = self.frobenius_norm() - t = self.ore_polring().gen() - return self.invert(t**n + (self(B) // t**n)) - - def is_ordinary(self): - self._test_rank_two() - return not self.is_supersingular() - - def is_supersingular(self): - self._test_rank_two() - return self.characteristic().divides(self.frobenius_trace()) - - ########################## - # Special Sage functions # - ########################## - - def _get_action_(self): - return DrinfeldModuleAction(self) - - def _latex_(self): - return f'\\text{{Finite{{ }}Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}}\n' \ - f'\\begin{{align}}\n' \ - f' {latex(self.functions_ring())}\n' \ - f' &\\to {latex(self.ore_polring())} \\\\\n' \ - f' {latex(self.functions_ring().gen())}\n' \ - f' &\\mapsto {latex(self.gen())}\n' \ - f'\\end{{align}}\n' \ - f'\\text{{with{{ }}characteristic{{ }}}} ' \ - f'{latex(self.characteristic())}' - - def _repr_(self): - return f'Drinfeld module:\n' \ - f' Polring: {self.functions_ring()}\n' \ - f' Ore polring: {self.ore_polring()}\n' \ - f' Generator: {self.gen()}' \ - - ########### - # Getters # - ########### - - def constant_term(self): - return self.gen()[0] - - def frobenius(self): - return self.ore_polring().twisting_morphism() - - def gen(self): - return self._gen - - def morphism(self): - return self._morphism - - def ore_polring(self): - return self._ore_polring - - def ore_variable(self): - return self._ore_variable - - def functions_ring(self): - return self._functions_ring - - # Rank two methods - def delta(self): self._test_rank_two() return self.gen()[2] @@ -538,55 +498,7 @@ def g(self): def j(self): self._test_rank_two() - return (self.g()**(self._Fq().order()+1)) / self.delta() - -class DrinfeldModuleHomset(Homset): - - def __init__(self, X, Y, base=None, check=True): - if X.category() != Y.category() \ - and not isinstance(X.category(), DrinfeldModules): - raise TypeError('Drinfeld modules must be in the same category') - super().__init__(X, Y, category=Homsets(), base=base, check=check) - - def __contains__(self, candidate): - phi = self.domain() - psi = self.codomain() - if candidate not in phi.ore_polring(): - raise TypeError('morphism must be in the Ore polynomial ring') - return candidate * phi.gen() == psi.gen() * candidate - -class DrinfeldModuleAction(Action): - def __init__(self, finite_drinfeld_module): - # Verifications - if not isinstance(finite_drinfeld_module, DrinfeldModule): - raise TypeError('First argument must be a DrinfeldModule') - # Work - self.__finite_drinfeld_module = finite_drinfeld_module - super().__init__(finite_drinfeld_module.polring(), - finite_drinfeld_module.ore_polring().base_ring()) - - ########### - # Methods # - ########### - - def finite_drinfeld_module(self): - return self.__finite_drinfeld_module - - ########################## - # Special Sage functions # - ########################## - - def _act_(self, g, x): - return self.finite_drinfeld_module()(g)(x) - - def _latex_(self): - return f'\\text{{Action{{ }}on{{ }}}}' \ - f'{latex(self.extension())}\\text{{{{ }}' \ - f'induced{{ }}by{{ }}}}{self.finite_drinfeld_module()}' - - def _repr_(self): - return f'Action on {self.domain()} induced by ' \ - f'{self.finite_drinfeld_module()}' + return (self.g()**(self._Fq.order()+1)) / self.delta() def _check_base_fields(Fq, L): From b26c8138ca4cf610880efb25e19ac0037825e9c8 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Thu, 23 Jun 2022 12:00:24 +0200 Subject: [PATCH 031/392] with all files --- .../drinfeld_modules/__init__.py | 0 .../function_field/drinfeld_modules/action.py | 37 +++++++++++++++++ .../function_field/drinfeld_modules/all.py | 3 ++ .../finite_drinfeld_module.py | 40 +++++++++++++++++++ .../drinfeld_modules/morphism.py | 15 +++++++ 5 files changed, 95 insertions(+) create mode 100644 src/sage/rings/function_field/drinfeld_modules/__init__.py create mode 100644 src/sage/rings/function_field/drinfeld_modules/action.py create mode 100644 src/sage/rings/function_field/drinfeld_modules/all.py create mode 100644 src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py create mode 100644 src/sage/rings/function_field/drinfeld_modules/morphism.py diff --git a/src/sage/rings/function_field/drinfeld_modules/__init__.py b/src/sage/rings/function_field/drinfeld_modules/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py new file mode 100644 index 00000000000..06ed070c7f5 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -0,0 +1,37 @@ +from sage.categories.action import Action + +from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule + + +class DrinfeldModuleAction(Action): + def __init__(self, finite_drinfeld_module): + # Verifications + if not isinstance(finite_drinfeld_module, DrinfeldModule): + raise TypeError('First argument must be a DrinfeldModule') + # Work + self.__finite_drinfeld_module = finite_drinfeld_module + super().__init__(finite_drinfeld_module.polring(), + finite_drinfeld_module.ore_polring().base_ring()) + + ########### + # Methods # + ########### + + def finite_drinfeld_module(self): + return self.__finite_drinfeld_module + + ########################## + # Special Sage functions # + ########################## + + def _act_(self, g, x): + return self.finite_drinfeld_module()(g)(x) + + def _latex_(self): + return f'\\text{{Action{{ }}on{{ }}}}' \ + f'{latex(self.extension())}\\text{{{{ }}' \ + f'induced{{ }}by{{ }}}}{self.finite_drinfeld_module()}' + + def _repr_(self): + return f'Action on {self.domain()} induced by ' \ + f'{self.finite_drinfeld_module()}' diff --git a/src/sage/rings/function_field/drinfeld_modules/all.py b/src/sage/rings/function_field/drinfeld_modules/all.py new file mode 100644 index 00000000000..71d56f75a35 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/all.py @@ -0,0 +1,3 @@ +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.rings.function_field.drinfeld_modules.drinfeld_module', 'DrinfeldModule') diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py new file mode 100644 index 00000000000..b4fb43079c0 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -0,0 +1,40 @@ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule + +class FiniteDrinfeldModule(DrinfeldModule): + def frobenius_charpoly(self, var='x'): + # Does not work when Fq is not a prime field... + chi = self._gen.reduced_charpoly() + A = self._polring + S = PolynomialRing(A, name=var) + return -chi(A.gen(), S.gen()) + + def characteristic_polynomial(self): + self._test_rank_two() + FqXT = PolynomialRing(self.functions_ring(), 'T') + return FqXT([self.frobenius_norm(), -self.frobenius_trace(), 1]) + + def frobenius_norm(self): + self._test_rank_two() + # Notations from Schost-Musleh: + n = self._L.over(self._Fq).degree_over(self._Fq) + d = self.characteristic().degree() + m = n // d + norm = self._L.over(self._Fq)(self.delta()).norm() + return ((-1)**n) * (self.characteristic()**m) / norm + + def frobenius_trace(self): + self._test_rank_two() + # Notations from Schost-Musleh: + n = self._L.over(self._Fq).degree_over(self._Fq) + B = self.frobenius_norm() + t = self.ore_polring().gen() + return self.invert(t**n + (self(B) // t**n)) + + def is_ordinary(self): + self._test_rank_two() + return not self.is_supersingular() + + def is_supersingular(self): + self._test_rank_two() + return self.characteristic().divides(self.frobenius_trace()) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py new file mode 100644 index 00000000000..a9d128646f0 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -0,0 +1,15 @@ +from sage.categories.homset import Homset, Hom + +class DrinfeldModuleHomset(Homset): + def __init__(self, X, Y, base=None, check=True): + if X.category() != Y.category() \ + and not isinstance(X.category(), DrinfeldModules): + raise NotImplementedError('Drinfeld modules must be in the same category') + Homset.__init__(self, X, Y, base=base, check=check) + + def __contains__(self, candidate): + phi = self.domain() + psi = self.codomain() + if candidate not in phi.ore_polring(): + raise TypeError('morphism must be in the Ore polynomial ring') + return candidate * phi.gen() == psi.gen() * candidate From 22e4f83972f3e1f37c5f934a2cb851307834ced0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 23 Jun 2022 18:09:05 +0200 Subject: [PATCH 032/392] Rename morphism.py to homset.py --- .../function_field/drinfeld_modules/{morphism.py => homset.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/sage/rings/function_field/drinfeld_modules/{morphism.py => homset.py} (100%) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/homset.py similarity index 100% rename from src/sage/rings/function_field/drinfeld_modules/morphism.py rename to src/sage/rings/function_field/drinfeld_modules/homset.py From a9848aec0fa50d13cfa47adb3729a22d7f543406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 23 Jun 2022 21:03:26 +0200 Subject: [PATCH 033/392] Fix DrinfeldModuleHomset import in DrinfeldModule --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index ddfbec4961e..498e14d118e 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -483,7 +483,7 @@ def velu(self, candidate): return FiniteDrinfeldModule(self.polring(), q) def _Hom_(self, other): - from sage.rings.function_fields.drinfeld_modules.morphism import DrinfeldModuleHomset + from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset return DrinfeldModuleHomset(self, other) # Rank two methods From 1bbad0c8d10a11bfcf006cd61bd64a29dee3aa58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 29 Jun 2022 19:51:29 +0200 Subject: [PATCH 034/392] Create DrinfeldModuleMorphism Comments: - `DrinfeldModuleMorphism` inherits `Element` and not `Morphism`, as the latter requires the domain to be a parent. - `DrinfeldModuleHomset` was modified to integrate `DrinfeldModuleMorphism`. The method `__contains__` only tests the parents. - The method `_Hom_` of `DrinfeldModule` is fixed and one can use `Hom(phi, psi)`. - The method `base` for the category `DrinfeldModules` was implemented. It returns the base ring of the Ore pol. ring; this may change. --- src/sage/categories/drinfeld_modules.py | 3 + .../drinfeld_modules/drinfeld_module.py | 6 +- .../finite_drinfeld_module.py | 1 + .../function_field/drinfeld_modules/homset.py | 43 ++++++++++---- .../drinfeld_modules/morphism.py | 58 +++++++++++++++++++ 5 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 src/sage/rings/function_field/drinfeld_modules/morphism.py diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 8cf93536c72..dcb0e985efc 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -48,6 +48,9 @@ def __init__(self, morphism, name='t'): E = K.over(f) self._characteristic = E(morphism(FqX.gen())).minpoly() + def base(self): + return self.codomain().base_ring() + def characteristic(self): if self._characteristic is None: raise NotImplementedError diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 498e14d118e..aaa997ebd94 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -377,7 +377,6 @@ def _latex_(self): def _repr_(self): return "Drinfeld module defined by %s |--> %s over %s" % (self._functions_ring.gen(), self._gen, self._L) - ########### # Getters # ########### @@ -403,7 +402,6 @@ def ore_variable(self): def functions_ring(self): return self._functions_ring - ########### # Methods # ########### @@ -482,9 +480,9 @@ def velu(self, candidate): else: return FiniteDrinfeldModule(self.polring(), q) - def _Hom_(self, other): + def _Hom_(self, other, category): from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset - return DrinfeldModuleHomset(self, other) + return DrinfeldModuleHomset(self, other, category) # Rank two methods diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index b4fb43079c0..5cd98685031 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -2,6 +2,7 @@ from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule class FiniteDrinfeldModule(DrinfeldModule): + def frobenius_charpoly(self, var='x'): # Does not work when Fq is not a prime field... chi = self._gen.reduced_charpoly() diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index a9d128646f0..a7176994572 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -1,15 +1,34 @@ +from sage.structure.parent import Parent +from sage.categories.drinfeld_modules import DrinfeldModules +from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism from sage.categories.homset import Homset, Hom class DrinfeldModuleHomset(Homset): - def __init__(self, X, Y, base=None, check=True): - if X.category() != Y.category() \ - and not isinstance(X.category(), DrinfeldModules): - raise NotImplementedError('Drinfeld modules must be in the same category') - Homset.__init__(self, X, Y, base=base, check=check) - - def __contains__(self, candidate): - phi = self.domain() - psi = self.codomain() - if candidate not in phi.ore_polring(): - raise TypeError('morphism must be in the Ore polynomial ring') - return candidate * phi.gen() == psi.gen() * candidate + + Element = DrinfeldModuleMorphism + __contains__ = Parent.__contains__ + + def __init__(self, X, Y, category=None, check=True): + if category is None: + category = X.category() + if check: + if X.category() != Y.category() \ + or not isinstance(X.category(), DrinfeldModules): + raise NotImplementedError('Drinfeld modules must be in the same category') + if category != X.category(): + raise NotImplementedError('category should be DrinfeldModules') + base = category.base() + Homset.__init__(self, X, Y, category=category, base=base, check=check) + + def __contains__(self, x): + try: + x = self(x) + return True + except (AttributeError, ValueError, TypeError): + return False + + def _element_constructor_(self, *args, **kwds): + return self.element_class(self, *args, **kwds) + + def _repr_(self): + return 'Our homset' diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py new file mode 100644 index 00000000000..a8da28b6d80 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -0,0 +1,58 @@ +#***************************************************************************** +# Copyright (C) 2022 Antoine Leudière +# +# 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. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +# from sage.categories.morphism import Morphism +from sage.structure.element import Element +from sage.rings.polynomial.ore_polynomial_element import OrePolynomial +from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing +from sage.categories.drinfeld_modules import DrinfeldModules + + +class DrinfeldModuleMorphism(Element): + + def __init__(self, parent, x): + super().__init__(parent) + domain = parent.domain() + codomain = parent.codomain() + if x.parent() is parent: + ore_polynomial = x.ore_polynomial() + else: + ore_polynomial = domain.ore_polring()(x) + # Test well-definition of the morphism + if domain.gen() * ore_polynomial != ore_polynomial * codomain.gen(): + raise ValueError('the Ore polynomial does not define a morphism') + # Instantiation + self._domain = domain + self._codomain = codomain + self._ore_polynomial = ore_polynomial + + def _repr_(self): + return f'Drinfeld Module morphism:\n' \ + f' From: {self._domain}\n' \ + f' To: {self._codomain}\n' \ + f' Defn: {self._ore_polynomial}' + + def codomain(self): + return self._codomain + + def domain(self): + return self._domain + + def ore_polynomial(self): + return self._ore_polynomial + + def is_zero(self): + return self._ore_polynomial.is_zero() + + def is_isogeny(self): + return not self.is_zero() + + def ore_polynomial(self): + return self._ore_polynomial From 578ccaac3e98db2284e67deb21e55db798b7e238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 11:29:32 +0200 Subject: [PATCH 035/392] Rename method functions_ring to function_ring in DrinfeldModule --- .../drinfeld_modules/drinfeld_module.py | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index aaa997ebd94..5a615cc125d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -288,14 +288,14 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): :mod:`sage.rings.polynomial.ore_polynomial_ring` """ @staticmethod - def __classcall_private__(cls, functions_ring, gen, name='t'): + def __classcall_private__(cls, function_ring, gen, name='t'): # Check all possible input types # `gen` is an Ore polynomial: if isinstance(gen, OrePolynomial): ore_polring = gen.parent() ore_polring_base = ore_polring.base_ring() name = ore_polring.variable_name() - # `gen` is a list of coefficients (functions_ring = Fq[X]): + # `gen` is a list of coefficients (function_ring = Fq[X]): elif isinstance(gen, (list, tuple)): ore_polring = None ore_polring_base = Sequence(gen).universe() @@ -312,11 +312,11 @@ def __classcall_private__(cls, functions_ring, gen, name='t'): 'of list of coefficients, or an Ore polynomial') # Build the morphism that defines the category - functions_ring_base = functions_ring.base_ring() - if not ore_polring_base.has_coerce_map_from(functions_ring_base): - raise TypeError('base ring of functions_ring must coerce to base ' \ + function_ring_base = function_ring.base_ring() + if not ore_polring_base.has_coerce_map_from(function_ring_base): + raise TypeError('base ring of function_ring must coerce to base ' \ 'ring of Ore polynomial ring') - gamma = functions_ring.hom([ore_polring_base(gen[0])]) + gamma = function_ring.hom([ore_polring_base(gen[0])]) # Mathematical integrity of the data is delegated to the category category = DrinfeldModules(gamma, name=name) @@ -339,12 +339,12 @@ def __classcall_private__(cls, functions_ring, gen, name='t'): def __init__(self, gen, category): CategoryObject.__init__(self, category=category) - self._functions_ring = category.domain() - self._Fq = self._functions_ring.base_ring() + self._function_ring = category.domain() + self._Fq = self._function_ring.base_ring() self._ore_polring = gen.parent() self._L = self._ore_polring.base_ring() self._gen = gen - self._morphism = self._functions_ring.hom([gen]) + self._morphism = self._function_ring.hom([gen]) ################# # Private utils # @@ -366,16 +366,16 @@ def _get_action_(self): def _latex_(self): return f'\\text{{Finite{{ }}Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}}\n' \ f'\\begin{{align}}\n' \ - f' {latex(self.functions_ring())}\n' \ + f' {latex(self.function_ring())}\n' \ f' &\\to {latex(self.ore_polring())} \\\\\n' \ - f' {latex(self.functions_ring().gen())}\n' \ + f' {latex(self.function_ring().gen())}\n' \ f' &\\mapsto {latex(self.gen())}\n' \ f'\\end{{align}}\n' \ f'\\text{{with{{ }}characteristic{{ }}}} ' \ f'{latex(self.characteristic())}' def _repr_(self): - return "Drinfeld module defined by %s |--> %s over %s" % (self._functions_ring.gen(), self._gen, self._L) + return "Drinfeld module defined by %s |--> %s over %s" % (self._function_ring.gen(), self._gen, self._L) ########### # Getters # @@ -399,8 +399,8 @@ def ore_polring(self): def ore_variable(self): return self._ore_polring.gen() - def functions_ring(self): - return self._functions_ring + def function_ring(self): + return self._function_ring ########### # Methods # @@ -426,7 +426,7 @@ def change_ring(self, R): new_frobenius = R.frobenius_endomorphism(self.frobenius().power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, names=self.ore_polring().variable_names()) - return DrinfeldModule(self.functions_ring(), + return DrinfeldModule(self.function_ring(), new_ore_polring(self.gen()), self.characteristic()) def height(self): @@ -442,7 +442,7 @@ def invert(self, image): if image in self._L: # Only works if `image` is in the image of self return self._Fq(image) r = self.rank() - X = self.functions_ring().gen() + X = self.function_ring().gen() k = image.degree() // r m_lines = [[0 for _ in range(k+1)] for _ in range(k+1)] for i in range(k+1): @@ -451,7 +451,7 @@ def invert(self, image): m_lines[j][i] = phi_X_i[r*j] m = Matrix(m_lines) v = vector([list(image)[r*j] for j in range(k+1)]) - pre_image = self.functions_ring()(list((m**(-1)) * v)) + pre_image = self.function_ring()(list((m**(-1)) * v)) if self(pre_image) == image: return pre_image else: From b2e597830b6e25adaddbe7de6fa247eb9e4d5ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 11:38:08 +0200 Subject: [PATCH 036/392] Fix frobenius_charpoly method in FiniteDrinfeldModule Refactor frobenius_charpoly --- .../drinfeld_modules/finite_drinfeld_module.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 5cd98685031..2d229625795 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -3,17 +3,13 @@ class FiniteDrinfeldModule(DrinfeldModule): - def frobenius_charpoly(self, var='x'): + def frobenius_charpoly(self, var='T'): + A = self._function_ring # Fq[X] + S = PolynomialRing(A, name=var) # Fq[X][T] # Does not work when Fq is not a prime field... - chi = self._gen.reduced_charpoly() - A = self._polring - S = PolynomialRing(A, name=var) - return -chi(A.gen(), S.gen()) - - def characteristic_polynomial(self): - self._test_rank_two() - FqXT = PolynomialRing(self.functions_ring(), 'T') - return FqXT([self.frobenius_norm(), -self.frobenius_trace(), 1]) + # chi = self._gen.reduced_charpoly() + # return -chi(A.gen(), S.gen()) + return S([self.frobenius_norm(), -self.frobenius_trace(), 1]) def frobenius_norm(self): self._test_rank_two() From 72f16428bbbc209cd0dac6aa9edb8946533d3dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 14:24:15 +0200 Subject: [PATCH 037/392] Remove __eq__ method in DrinfeldModule This was useless as the class inherits UniqueRepresentation. --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 5a615cc125d..8ab1b4c5227 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -406,11 +406,6 @@ def function_ring(self): # Methods # ########### - def __eq__(self, other): - if not isinstance(other, DrinfeldModule): - return False - return self.category() is other.category() and self.gen() == other.gen() - def __call__(self, a): return self._morphism(a) From 8f9c18ad0b2f96c78aaa18f2b812b8f4b8730598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 14:43:05 +0200 Subject: [PATCH 038/392] Refactor characteristic method in DrinfeldModules The characteristic is computed as a minpoly. It is cast to the polring. Also, some minor cosmetic changes that should be somewhere else. Refactor _latex_ for DrinfeldModule --- src/sage/categories/drinfeld_modules.py | 22 ++++++++++--------- .../drinfeld_modules/drinfeld_module.py | 13 ++++------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index dcb0e985efc..a64c59ea693 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -22,19 +22,21 @@ class DrinfeldModules(CategoryWithParameters): def __init__(self, morphism, name='t'): + gamma = morphism self._morphism = morphism - self._domain = morphism.domain() + self._domain = gamma.domain() # Check domain is Fq[X] - functions_ring = self._domain # functions_ring - if not isinstance(functions_ring, PolynomialRing_general): + function_ring = self._domain + if not isinstance(function_ring, PolynomialRing_general): raise NotImplementedError('domain must be a polynomial ring') - functions_ring_base = functions_ring.base_ring() - if not functions_ring_base.is_field() or not functions_ring_base.is_finite() : + function_ring_base = function_ring.base_ring() + if not function_ring_base.is_field() or not function_ring_base.is_finite() : raise TypeError('the base ring of the domain must be a finite field') - Fq = functions_ring_base - FqX = functions_ring + Fq = function_ring_base + FqX = function_ring + X = FqX.gen() # Check domain is field - K = morphism.codomain() + K = gamma.codomain() if not K.is_field(): raise TypeError('the codomain must be a field') # Build K{t} @@ -44,9 +46,9 @@ def __init__(self, morphism, name='t'): # Create characteristic self._characteristic = None if K.is_finite(): - f = morphism * FqX.coerce_map_from(Fq) + f = gamma * FqX.coerce_map_from(Fq) # Fq -> K E = K.over(f) - self._characteristic = E(morphism(FqX.gen())).minpoly() + self._characteristic = FqX(E(gamma(X)).minpoly()) def base(self): return self.codomain().base_ring() diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 8ab1b4c5227..e46c3657894 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -364,15 +364,10 @@ def _get_action_(self): return DrinfeldModuleAction(self) def _latex_(self): - return f'\\text{{Finite{{ }}Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}}\n' \ - f'\\begin{{align}}\n' \ - f' {latex(self.function_ring())}\n' \ - f' &\\to {latex(self.ore_polring())} \\\\\n' \ - f' {latex(self.function_ring().gen())}\n' \ - f' &\\mapsto {latex(self.gen())}\n' \ - f'\\end{{align}}\n' \ - f'\\text{{with{{ }}characteristic{{ }}}} ' \ - f'{latex(self.characteristic())}' + return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ + f'{latex(self._function_ring.gen())} '\ + f'\\mapsto {latex(self._gen)}' \ + f'\\text{{{{ }}over{{ }}}}{latex(self._L)}' def _repr_(self): return "Drinfeld module defined by %s |--> %s over %s" % (self._function_ring.gen(), self._gen, self._L) From 1c81b5ba55fd963edf3d8d54aaafd6c5a63018f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 15:05:55 +0200 Subject: [PATCH 039/392] Remove a constructor in DrinfeldModule Remove the constructor with a list of lists of coefficients, as it bloats the code but is not useful as long as the function ring can only be a polring. --- .../drinfeld_modules/drinfeld_module.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index e46c3657894..5654dd7bec2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -299,17 +299,9 @@ def __classcall_private__(cls, function_ring, gen, name='t'): elif isinstance(gen, (list, tuple)): ore_polring = None ore_polring_base = Sequence(gen).universe() - # `gen` is a list of list of coefficients (multiple gens): - elif isinstance(gen, (list, tuple)) \ - and all(isinstance(x, (list, tuple)) for x in gen): - ore_polring = None - all_coeffs = [] - for coeffs in gen: - all_coeffs += coeffs - ore_polring_base = Sequence(all_coeffs).universe() else: - raise TypeError('generator must be a list of coefficients, a list' \ - 'of list of coefficients, or an Ore polynomial') + raise TypeError('generator must be a list of coefficients '\ + 'or an Ore polynomial') # Build the morphism that defines the category function_ring_base = function_ring.base_ring() From 3fe93dabbee77523861d975ea03aa3de165d1835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 15:24:05 +0200 Subject: [PATCH 040/392] Change _L to _base_ring in DrinfeldModule --- .../drinfeld_modules/drinfeld_module.py | 16 ++++++++++------ .../drinfeld_modules/finite_drinfeld_module.py | 6 +++--- .../function_field/drinfeld_modules/homset.py | 3 --- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 5654dd7bec2..f193ec1c178 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -331,12 +331,12 @@ def __classcall_private__(cls, function_ring, gen, name='t'): def __init__(self, gen, category): CategoryObject.__init__(self, category=category) + self._base_ring = category.base() self._function_ring = category.domain() - self._Fq = self._function_ring.base_ring() - self._ore_polring = gen.parent() - self._L = self._ore_polring.base_ring() self._gen = gen self._morphism = self._function_ring.hom([gen]) + self._ore_polring = gen.parent() + self._Fq = self._function_ring.base_ring() ################# # Private utils # @@ -359,15 +359,19 @@ def _latex_(self): return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ f'{latex(self._function_ring.gen())} '\ f'\\mapsto {latex(self._gen)}' \ - f'\\text{{{{ }}over{{ }}}}{latex(self._L)}' + f'\\text{{{{ }}over{{ }}}}{latex(self._base_ring)}' def _repr_(self): - return "Drinfeld module defined by %s |--> %s over %s" % (self._function_ring.gen(), self._gen, self._L) + return f'Drinfeld module defined by {self._function_ring.gen()} ' \ + f'|--> {self._gen} over {self._base_ring}' ########### # Getters # ########### + def base_ring(self): + return self._base_ring + def constant_term(self): return self.gen()[0] @@ -421,7 +425,7 @@ def invert(self, image): if not image in self.ore_polring(): raise TypeError('The tested image should be in the Ore ' \ 'polynomial ring') - if image in self._L: # Only works if `image` is in the image of self + if image in self._base_ring: # Only works if `image` is in the image of self return self._Fq(image) r = self.rank() X = self.function_ring().gen() diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 2d229625795..ca5f83b20ee 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -14,16 +14,16 @@ def frobenius_charpoly(self, var='T'): def frobenius_norm(self): self._test_rank_two() # Notations from Schost-Musleh: - n = self._L.over(self._Fq).degree_over(self._Fq) + n = self._base_ring.over(self._Fq).degree_over(self._Fq) d = self.characteristic().degree() m = n // d - norm = self._L.over(self._Fq)(self.delta()).norm() + norm = self._base_ring.over(self._Fq)(self.delta()).norm() return ((-1)**n) * (self.characteristic()**m) / norm def frobenius_trace(self): self._test_rank_two() # Notations from Schost-Musleh: - n = self._L.over(self._Fq).degree_over(self._Fq) + n = self._base_ring.over(self._Fq).degree_over(self._Fq) B = self.frobenius_norm() t = self.ore_polring().gen() return self.invert(t**n + (self(B) // t**n)) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index a7176994572..d4ccf4880c8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -29,6 +29,3 @@ def __contains__(self, x): def _element_constructor_(self, *args, **kwds): return self.element_class(self, *args, **kwds) - - def _repr_(self): - return 'Our homset' From 01a42cce1c21207955db5a874235276713eaafe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 15:35:46 +0200 Subject: [PATCH 041/392] Fix velu method in DrinfeldModule --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index f193ec1c178..eb4636d9dff 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -464,7 +464,7 @@ def velu(self, candidate): if r != 0: return None else: - return FiniteDrinfeldModule(self.polring(), q) + return DrinfeldModule(self._function_ring, q) def _Hom_(self, other, category): from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset From c8049b0a4b538028f4db1421e9e4ec75e3bd2cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 15:40:26 +0200 Subject: [PATCH 042/392] Remove duplicate code in DrinfeldModuleMorphism --- src/sage/rings/function_field/drinfeld_modules/morphism.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index a8da28b6d80..0adb5fa18e3 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -53,6 +53,3 @@ def is_zero(self): def is_isogeny(self): return not self.is_zero() - - def ore_polynomial(self): - return self._ore_polynomial From aeeea81d5ca73a08be2ea0b37ffe079dee2ef97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 16:02:00 +0200 Subject: [PATCH 043/392] Create frobenius_endomorphism method in FiniteDrinfeldModule --- .../drinfeld_modules/finite_drinfeld_module.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index ca5f83b20ee..d6d55fa4706 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -3,6 +3,13 @@ class FiniteDrinfeldModule(DrinfeldModule): + def frobenius_endomorphism(self): + t = self.ore_variable() + L = self._base_ring + Fq = self._function_ring.base_ring() + deg = L.over(Fq).degree(Fq) + return self._Hom_(self, category=self.category())(t**deg) + def frobenius_charpoly(self, var='T'): A = self._function_ring # Fq[X] S = PolynomialRing(A, name=var) # Fq[X][T] From 055e46ca33d556027615139187fc8c2d39f3bddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 16:41:54 +0200 Subject: [PATCH 044/392] Comment out frobenius method in DrinfeldModule --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index eb4636d9dff..87f240472ac 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -375,8 +375,8 @@ def base_ring(self): def constant_term(self): return self.gen()[0] - def frobenius(self): - return self.ore_polring().twisting_morphism() + # def frobenius(self): + # return self.ore_polring().twisting_morphism() def gen(self): return self._gen From f8fdad5022fabf4328607e9252de0556aa2c2a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 18:01:50 +0200 Subject: [PATCH 045/392] Write first draft of DrinfeldModule docstring --- .../drinfeld_modules/drinfeld_module.py | 566 +++++++++--------- 1 file changed, 275 insertions(+), 291 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 87f240472ac..eebb64d5b8c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -1,26 +1,10 @@ r""" -This module provides classes for finite Drinfeld modules -(`DrinfeldModule`) and their module action on the algebraic -closure of `\Fq` (`DrinfeldModuleAction`). +Drinfeld modules AUTHORS: -- Antoine Leudière (2022-04): initial version - -Let `\tau` be the `\Fq`-linear Frobenius endomorphism of `\Fqbar` -defined by `x \mapsto x^q`. Let `L\{\tau\}` be the ring of Ore -polynomials in `\tau` with coefficients in L. Fix an element `\omega` in -`L` (global parameter). A finite Drinfeld module is an `\Fq`-algebra -morphism `\phi: \Fq[X] \to L\{\tau\]` such that: - - the constant coefficient of `\phi(X)` is `\omega`, - - there exists at least one `a \in \Fq[X]` such that `\phi(a)` has a - non zero `\tau`-degree. - -As an `\Fq[X]`-algebra morphism, a finite Drinfeld module is only -determined by the image of `X`. - -Crucially, the Drinfeld module `\phi` gives rise to the `\Fq[X]`-module -law on `\Fqbar` defined by `(a, x) = \phi(a)(x)`. +- Antoine Leudière (2022-04) +- Xavier Caruso (2022-06) """ #***************************************************************************** @@ -47,246 +31,6 @@ from sage.modules.free_module_element import vector class DrinfeldModule(UniqueRepresentation, CategoryObject): - r""" - Class for finite Drinfeld modules. - - INPUT: - - - ``polring`` -- the base polynomial ring - - ``gen`` -- the generator of the Drinfeld module, i.e. the image of `X` in - the Ore polynomial ring - - ``characteristic`` -- the Fq[X]-characteristic of the Drinfeld - module, i.e. the minimal polynomial in `polring` of the constant term of - the generator - - EXAMPLES: - - .. RUBRIC:: Instantiation - - We must first create the base objects:: - - sage: Fq = GF(3^2) - sage: z2 = Fq.gen() - sage: FqX. = Fq[] - sage: p = X^3 + (z2 + 2)*X^2 + (6*z2 + 1)*X + 3*z2 + 5 - sage: L = Fq.extension(6) - sage: frob = L.frobenius_endomorphism(2) - sage: Ltau. = OrePolynomialRing(L, frob) - sage: omega = p.roots(L, multiplicities=False)[0] - sage: phi_X = omega + t + t^2 - - Notice that we have freedom on choosing the polynomial `p`, but not - `omega`. It is generally more useful this way. Then we instantiate the - Drinfeld module:: - - sage: phi = DrinfeldModule(FqX, phi_X, p) - sage: phi - Drinfeld module: - Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) - Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 - Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 - - .. RUBRIC:: Getters - - With getters, we can retrieve many basic objects associated to a Drinfeld - module. - - First, we can retrieve the polynomial ring, the Ore polynomial ring, and - the generator. Note that the class inherits `RingHomomorphism_im_gens`, so - that `domain`, `codomain` and `im_gens` are available:: - - sage: phi.polring() - Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - sage: phi.domain() is phi.polring() - True - sage: phi.ore_polring() - Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) - sage: phi.codomain() is phi.ore_polring() - True - sage: phi.gen() - t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 - sage: phi.im_gens()[0] is phi.gen() - True - - We can retrieve `omega`, the constant term of the generator, and ensure - that it is a root of `p`, the characteristic:: - - sage: phi.constant_term() - z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 - sage: phi.constant_term() == omega - True - sage: phi.characteristic() - X^3 + (z2 + 2)*X^2 + X + 2 - sage: phi.characteristic() == p - True - sage: phi.characteristic()(phi.constant_term()) - 0 - - We can retrieve the rank and the height (note that the height is always one - here):: - - sage: phi.rank() - 2 - sage: phi.height() - 1 - - And finally we can retrieve some rank-two specifics:: - - sage: phi.j() # j-invariant - 1 - sage: phi.g() # Standard notation - 1 - sage: phi.delta() # Standard notation - 1 - sage: phi(X) == phi.constant_term() + phi.g()*t + phi.delta()*t^2 - True - - .. RUBRIC:: Evaluation of the Drinfeld module - - By definition, a Drinfeld module is a ring morphism from an polynomial ring - to an Ore polynomial ring. We can compute the images under this morphism - using the standard `phi(...)` notation:: - - sage: phi(X) - t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 - sage: phi(X) == phi.gen() - True - sage: phi(1) - 1 - sage: phi(z2) == z2 - True - sage: phi(X^2 + 1) - t^4 + 2*t^3 + (2*z12^11 + 2*z12^10 + z12^9 + z12^6 + z12^5 + 2*z12^4 + z12^3 + 2*z12^2 + z12 + 2)*t^2 + (2*z12^8 + z12^7 + 2*z12^6 + z12^5 + z12^4 + z12 + 1)*t + 2*z12^11 + 2*z12^10 + z12^8 + z12^7 + 2*z12^6 + 2*z12^5 + z12^4 + 2*z12 - - .. RUBRIC:: The module law induced by a Drinfeld module - - The most important feature of Drinfeld modules is that they endow any - subextension of `Fqbar` with an `Fq[X]`-module law. This action is - represented by the class `DrinfeldModuleAction`, which inherits - `Action`. For the sake of simplicity, `phi` will only act on the base field - (`L`) of its Ore polynomial ring. If you want to act on a bigger field, you - can define a new Drinfeld module using the method `change_ring`. - - sage: action = phi._get_action_() - sage: action - Action on Finite Field in z12 of size 3^12 induced by Drinfeld module: - Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) - Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 - Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 - - The calculation of the action is simple. Careful, the evaluation of Ore - polynomial is, at the moment, experimental:: - - sage: x = L.gen() + 1 - sage: g = X^3 + X + 5 - sage: action(g, x) - ... - z12^11 + z12^10 + 2*z12^9 + z12^7 + z12^6 + z12^4 + 2*z12^2 + z12 + 1 - - To change ring, use:: - - sage: M = L.extension(5) - sage: psi = phi.change_ring(M) - sage: psi - Drinfeld module: - Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - Ore polring: Ore Polynomial Ring in t over Finite Field in z60 of size 3^60 twisted by z60 |--> z60^(3^2) - Generator: t^2 + t + 2*z60^59 + z60^57 + 2*z60^56 + 2*z60^54 + 2*z60^53 + 2*z60^52 + z60^51 + z60^50 + 2*z60^48 + z60^47 + z60^46 + 2*z60^45 + 2*z60^44 + 2*z60^42 + 2*z60^41 + z60^40 + z60^39 + z60^38 + z60^37 + 2*z60^34 + z60^33 + z60^31 + 2*z60^29 + z60^27 + z60^26 + z60^25 + 2*z60^24 + z60^22 + 2*z60^21 + z60^19 + 2*z60^17 + 2*z60^16 + 2*z60^15 + z60^14 + z60^12 + z60^11 + 2*z60^10 + z60^8 + z60^7 + 2*z60^6 + 2*z60^5 + 2*z60 + 1 - Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 - - .. RUBRIC:: Morphisms and isogenies - - Being given an Ore polynomial `m`, we can decide if `m` is a morphism or - isogeny of Drinfeld module whose domain is `phi`:: - - sage: m = phi(X) - sage: phi.is_morphism(m) - True - sage: phi.is_isogeny(m) - True - sage: phi.is_endomorphism(m) - True - sage: phi.is_automorphism(m) - False - sage: m = 0 - sage: phi.is_endomorphism(m) - True - sage: phi.is_automorphism(m) - False - sage: phi.is_isogeny(m) - False - sage: m = t^6 - sage: phi.is_endomorphism(m) - True - sage: phi.is_automorphism(m) - False - - We implemented the Vélu formula for Drinfeld modules, in the sense that - given `m`, we can compute (if it exists) the unique Drinfeld module `psi` - such that `m` is an isogeny from `phi` to `psi`:: - - sage: m = phi(X^2 + 1) - sage: phi.velu(m) - Drinfeld module: - Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) - Generator: t^2 + t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 - Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 - sage: phi.velu(m) == phi - True - sage: z12 = L.gen() - sage: m = (z12^7 + z12^6 + 2*z12^4)*t^3 - sage: phi.is_isogeny(m) - True - sage: phi.is_endomorphism(m) - False - sage: phi.velu(m) - Drinfeld module: - Polring: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - Ore polring: Ore Polynomial Ring in t over Finite Field in z12 of size 3^12 twisted by z12 |--> z12^(3^2) - Generator: (2*z12^10 + z12^9 + 2*z12^7 + 2*z12^6 + z12^3 + 2*z12^2 + 2)*t^2 + (2*z12^9 + z12^7 + 2*z12^5 + z12 + 1)*t + z12^11 + z12^10 + z12^9 + 2*z12^5 + 2*z12^4 + z12^3 + 2*z12 - Characteristic: X^3 + (z2 + 2)*X^2 + X + 2 - sage: phi.velu(m) == phi - False - - .. RUBRIC:: Complex multiplication of Drinfeld modules - - There are various methods to manipulate the complex multiplication theory - of Drinfeld modules. We can compute the Frobenius norm, Frobenius trace and - characteristic polynomial:: - - sage: phi.frobenius_norm() - X^6 + (2*z2 + 1)*X^5 + (2*z2 + 1)*X^4 + (2*z2 + 2)*X^3 + z2*X^2 + X + 1 - sage: phi.frobenius_trace() - 2*X^3 + (2*z2 + 1)*X^2 + 2*X + z2 + 2 - sage: phi.characteristic_polynomial() - T^2 + (2*X^3 + (2*z2 + 1)*X^2 + 2*X + z2 + 2)*T + X^6 + (2*z2 + 1)*X^5 + (2*z2 + 1)*X^4 + (2*z2 + 2)*X^3 + z2*X^2 + X + 1 - - With those methods, it is easy to decide if a Drinfeld module is ordinary - or supersingular:: - - sage: phi.is_ordinary() - True - sage: phi.is_supersingular() - False - - .. NOTE:: - - The general definition of a Drinfeld module is out of the scope of this - implementation. - - :: - - You can see all available methods of `RingHomomorphism_im_gens` with - `dir(sage.rings.morphism.RingHomomorphism_im_gens)`. Same for `Action`. - - .. SEEALSO:: - :mod:`sage.categories.action.Action` - :mod:`sage.rings.polynomial.ore_polynomial_element` - :mod:`sage.rings.polynomial.ore_polynomial_ring` - """ @staticmethod def __classcall_private__(cls, function_ring, gen, name='t'): # Check all possible input types @@ -304,9 +48,8 @@ def __classcall_private__(cls, function_ring, gen, name='t'): 'or an Ore polynomial') # Build the morphism that defines the category - function_ring_base = function_ring.base_ring() - if not ore_polring_base.has_coerce_map_from(function_ring_base): - raise TypeError('base ring of function_ring must coerce to base ' \ + if not ore_polring_base.has_coerce_map_from(function_ring.base_ring()): + raise TypeError('base ring of function ring must coerce to base ' \ 'ring of Ore polynomial ring') gamma = function_ring.hom([ore_polring_base(gen[0])]) @@ -343,9 +86,11 @@ def __init__(self, gen, category): ################# def _test_rank_two(self): + r""" + Raise ``NotImplementedError`` if the rank is not two. + """ if self.rank() != 2: - raise NotImplementedError('this method is only available for ' \ - 'rank two Drinfeld modules') + raise NotImplementedError('the rank must be 2') ########################## # Special Sage functions # @@ -370,27 +115,105 @@ def _repr_(self): ########### def base_ring(self): + r""" + Return the base ring of the Drinfeld module. + + This is always a field. This is the base field of the codomain + (Ore polynomial ring) of the morphism that defines the Drinfeld + module. + + A Drinfeld module is said to be finite if the base ring is + finite. + + OUTPUT: + + - a field + """ return self._base_ring def constant_term(self): + r""" + Return the constant term of the generator (`\phi_X`). + + The `A`-characteristic of the base field (see + :meth:`sage.categories.drinfeld_modules.DrinfeldModules.characteristic`) + is the minimal polynomial of this constant term, over the base + ring of the function ring. Equivalently, the constant term is + the image, by the morphism (`\gamma`) that defines the category, + of the generator (`X`) of the polynomial ring. + + OUTPUT: + + - an element in the base ring + """ return self.gen()[0] # def frobenius(self): # return self.ore_polring().twisting_morphism() def gen(self): + r""" + Return the generator (`\phi_X`) of the Drinfeld module. + + This method makes sense because, in our case, the function ring is + a polynomial ring; it is generated by one element, whose image + characterizes the morphism that defines the Drinfeld module. + + OUTPUT: + + - an Ore polynomial + """ return self._gen def morphism(self): + r""" + Return the morphism object that defines the Drinfeld module. + + OUTPUT: + + - a ring morphism, from the function ring to the Ore polynomial + ring + """ return self._morphism def ore_polring(self): + r""" + Return the Ore polynomial ring of the Drinfeld module. + + If the Drinfeld module is defined by a morphism `A \to + K\{\tau\}`, we return the codomain `K\{\tau\}`. + + OUTPUT: + + - an Ore polynomial ring + """ return self._ore_polring def ore_variable(self): + r""" + Return the Ore variable. + + This is generator of the Ore polynomial ring. + + OUTPUT: + + - an Ore polynomial + """ return self._ore_polring.gen() def function_ring(self): + r""" + Return the function ring of the Drinfeld module. + + If the Drinfeld module is defined by a morphism `A \to + K\{\tau\}`, we return the domain `A`. + + In our case, this is a polynomial ring. + + OUTPUT: + + - a polynomial ring + """ return self._function_ring ########### @@ -398,17 +221,52 @@ def function_ring(self): ########### def __call__(self, a): + r""" + Return the image of ``a`` by the morphism that defines the + Drinfeld module, i.e. `\phi_a` if the Drinfeld module is denoted + `phi`. + + INPUT: + + - ``a`` -- the element in the function ring whose image is to + compute + + OUTPUT: + + - an element of the base ring + """ + return self._morphism(a) - def change_ring(self, R): - # VERIFICATIONS + def change_ring(self, new_field): + r""" + If ``new_field`` is a field extension of the base ring, return a + new Drinfeld module that extends ``self`` to the base ring + ``new_field``. + + Let `f` be the morphism that defines ``self``, let `i` be the + inclusion of ``self.ore_polring()`` into the Ore pol. ring whose + base is ``new_field``. The morphism that defines the new + Drinfeld module is the composition `i \circ f`. + + INPUT: + + - ``new_field`` -- the field extension of the base ring that + serves as base ring for the new Drinfeld module + + OUTPUT: + + - a Drinfeld module + """ + # TODO: Remove _check_base_field + R = new_field if not R.is_field() and R.is_finite(): raise TypeError('Argument must be a finite field') if not self.ore_polring().base_ring().is_subring(R): raise ValueError('The new field must be a finite field ' \ 'extension of the base field of the Ore polynomial ring.') _check_base_fields(self._Fq, R) - # ACTUAL WORK + new_frobenius = R.frobenius_endomorphism(self.frobenius().power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, names=self.ore_polring().variable_names()) @@ -416,71 +274,197 @@ def change_ring(self, R): new_ore_polring(self.gen()), self.characteristic()) def height(self): - return Integer(1) + r""" + Return the height of the Drinfeld module. - def invert(self, image): + When the function ring is a polynomial ring, the height is 1. + + OUTPUT: + + - an integer """ - Given an Ore polynomial `image` of the form `phi(c)`, find c. + return Integer(1) + + def invert(self, ore_pol): + r""" + Find the inverse of ``ore_pol`` by the morphism that defines the + Drinfeld module. If ``ore_pol`` is not in the image of the + morphism, return ``None``. + + Said otherwise, return `a` if ``ore_pol`` is `phi_a`, otherwise + return ``None``. + + INPUT: + + - ``ore_pol`` -- the Ore polynomial whose preimage we want to + compute + + OUTPUT: + + - a polynomial + + ALGORITHM: + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO """ - if not image in self.ore_polring(): - raise TypeError('The tested image should be in the Ore ' \ - 'polynomial ring') - if image in self._base_ring: # Only works if `image` is in the image of self - return self._Fq(image) + if not ore_pol in self.ore_polring(): + raise TypeError('ore_pol must be an Ore polynomial ring') + if ore_pol in self._base_ring: + return self._Fq(ore_pol) r = self.rank() X = self.function_ring().gen() - k = image.degree() // r + k = ore_pol.degree() // r m_lines = [[0 for _ in range(k+1)] for _ in range(k+1)] for i in range(k+1): phi_X_i = self(X**i) for j in range(i+1): m_lines[j][i] = phi_X_i[r*j] m = Matrix(m_lines) - v = vector([list(image)[r*j] for j in range(k+1)]) + v = vector([list(ore_pol)[r*j] for j in range(k+1)]) pre_image = self.function_ring()(list((m**(-1)) * v)) - if self(pre_image) == image: + if self(pre_image) == ore_pol: return pre_image else: return None def rank(self): + r""" + Return the rank of the Drinfeld module. + + When the function ring is a polynomial ring, the rank is the + degree of the generator. + + OUTPUT: + + - an integer + """ return self.gen().degree() def velu(self, candidate): + r""" + Return a new Drinfeld module such that ``candidate`` is an + isogeny to this module with domain ``self``. If no such isogeny + exists, return ``None``. + + If the candidate is zero, return ``None``, as an isogeny is + required to be non zero. + + INPUT: + + - ``candidate`` -- an Ore polynomial that defines the isogeny + with domain ``self`` and codomain the output of the method + + OUTPUT: + + - a Drinfeld module + + ALGORITHM: + + We write the Ore Euclidean division `\phi_X = + \mathrm{candidate}*q + r`, and return + The candidate is an isogeny if only if: + + 1. The degree of the characteristic devides the height + of the candidate. (The height of an Ore polynomial + `P(t)` is the maximum `n` such that `t^n` right-divides + `P(t)`.) + + 2. The candidate right-divides the generator, which can + be tested with Euclidean division. + + We test if the candidate is an isogeny, and, if it is, we + return the quotient of the Euclidean division. + + Height and Euclidean division of Ore polynomials are + implemented as methods of class + :class:`sage.rings.polynomial.ore_polynomial_element.OrePolynomial`. + + Another possible algorithm is to recursively solve a system, + see :arxiv:`2203.06970`, eq. 1.1. + """ if not candidate in self.ore_polring(): - raise TypeError('The candidate must be in the Ore polynomial ' \ - 'ring') - # There are two main ways to give the result. The first way is - # to return the Drinfeld module generated by the right-quotient - # of `candidate * self(X)` right-divided by `candidate`. The - # second way is to recursively find the coefficients (see - # arXiv:2203.06970, Eq. 1.1). For now, the former is - # implemented, as it is very easy to write. + raise TypeError('candidate must be an Ore polynomial') if candidate == 0: return None if not self.characteristic().degree().divides(candidate.valuation()): return None - q, r = (candidate * self.gen()).right_quo_rem(candidate) - if r != 0: - return None - else: - return DrinfeldModule(self._function_ring, q) + quo, rem = (candidate * self.gen()).right_quo_rem(candidate) + return None if rem != 0 else DrinfeldModule(self._function_ring, quo) def _Hom_(self, other, category): + r""" + Return ``DrinfeldModuleHomset(self, other, category)``. + + Validity of the input is checked at the instantiation of + ``DrinfeldModuleHomset``. ``self`` and ``other`` only need be in + the same category. + + INPUT: + + - ``other`` -- the codomain of the homset + - ``category`` -- the category in which we consider the + morphisms, usually `self.category()` + + OUTPUT: + + - an homset + """ from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset return DrinfeldModuleHomset(self, other, category) # Rank two methods def delta(self): + r""" + If the rank is two, return `\Delta` such that the generator is + `phi_X = \gamma(X) + g\tau + \Delta\tau^2`; if the rank is not + two, raise an exception. + + OUTPUT: + + - an element in the base ring if the rank is two; an + exception is raised otherwise + """ self._test_rank_two() return self.gen()[2] def g(self): + r""" + If the rank is two, return `g` such that the generator is `phi_X + = \gamma(X) + g\tau + \Delta\tau^2`; if the rank is not two, + raise an exception. + + OUTPUT: + + - an element in the base ring if the rank is two; an + exception is raised otherwise + """ self._test_rank_two() return self.gen()[1] def j(self): + r""" + If the rank is two, return the j-invariant of the Drinfeld + module; if the rank is not two, raise an exception. + + Write the generator `\phi_X = \gamma(X) + g\tau + \Delta\tau^2`. + The j-invariant is defined by `\frac{g^{q+1}}{\Delta}`, `q` + being the order of the base field of the polynomial ring. In our + case, this base field is always finite, as we force the function + ring to be of the form `\Fq[X]`. + + OUTPUT: + + - an element in the base ring if the rank is two; an + exception is raised otherwise + """ self._test_rank_two() return (self.g()**(self._Fq.order()+1)) / self.delta() From a1b47cecc7879d42fe15ca963c4f50947d499aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 4 Aug 2022 19:24:56 +0200 Subject: [PATCH 046/392] Add Drinfeld modules to the reference I just added the entry to the section *Function fields*. --- src/doc/en/reference/function_fields/index.rst | 1 + .../drinfeld_modules/drinfeld_module.py | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/doc/en/reference/function_fields/index.rst b/src/doc/en/reference/function_fields/index.rst index 50c04560a33..bc2350116ad 100644 --- a/src/doc/en/reference/function_fields/index.rst +++ b/src/doc/en/reference/function_fields/index.rst @@ -21,6 +21,7 @@ algebraic closure of `\QQ`. sage/rings/function_field/maps sage/rings/function_field/extensions sage/rings/function_field/constructor + sage/rings/function_field/drinfeld_modules/drinfeld_module A basic reference for the theory of algebraic function fields is [Stich2009]_. diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index eebb64d5b8c..7725343d6c5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -118,9 +118,8 @@ def base_ring(self): r""" Return the base ring of the Drinfeld module. - This is always a field. This is the base field of the codomain - (Ore polynomial ring) of the morphism that defines the Drinfeld - module. + This is the base field of Ore polynomial ring. In particular, the base + ring is always a field. A Drinfeld module is said to be finite if the base ring is finite. @@ -181,7 +180,7 @@ def ore_polring(self): Return the Ore polynomial ring of the Drinfeld module. If the Drinfeld module is defined by a morphism `A \to - K\{\tau\}`, we return the codomain `K\{\tau\}`. + K\{\tau\}`, this is the codomain `K\{\tau\}`. OUTPUT: @@ -206,9 +205,9 @@ def function_ring(self): Return the function ring of the Drinfeld module. If the Drinfeld module is defined by a morphism `A \to - K\{\tau\}`, we return the domain `A`. + K\{\tau\}`, this is the domain `A`. - In our case, this is a polynomial ring. + In our case, the function ring is a polynomial ring. OUTPUT: From cac6471f62d233c462b4b17f3e1cb2fb990fe7e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 5 Aug 2022 18:26:33 +0200 Subject: [PATCH 047/392] Fix constructor of DrinfeldModuleMorphism The constructor wrongfully raised TypeError when the input already was a morphism. See my comment inside the definition of the constructor. --- .../function_field/drinfeld_modules/morphism.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 0adb5fa18e3..7af3ad0230b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -21,17 +21,18 @@ def __init__(self, parent, x): super().__init__(parent) domain = parent.domain() codomain = parent.codomain() - if x.parent() is parent: - ore_polynomial = x.ore_polynomial() - else: - ore_polynomial = domain.ore_polring()(x) - # Test well-definition of the morphism - if domain.gen() * ore_polynomial != ore_polynomial * codomain.gen(): + # NOTE: it used to be x.parent() is parent, but this was False. + # DrinfeldModuleHomset inherits Homset, which does NOT inherit + # UniqueRepresentation + if x.parent() == parent: # x is a DrinfeldModuleMorphism + ore_pol = x.ore_polynomial() + else: # x is an Ore polynomial + ore_pol = domain.ore_polring()(x) + if ore_pol * domain.gen() != codomain.gen() * ore_pol: raise ValueError('the Ore polynomial does not define a morphism') - # Instantiation self._domain = domain self._codomain = codomain - self._ore_polynomial = ore_polynomial + self._ore_polynomial = ore_pol def _repr_(self): return f'Drinfeld Module morphism:\n' \ From d4eddc8346f69058d3b9f958e2ea24e8c9a659b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 5 Aug 2022 19:12:39 +0200 Subject: [PATCH 048/392] _Minor change --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 7725343d6c5..1485e0b4278 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -227,8 +227,7 @@ def __call__(self, a): INPUT: - - ``a`` -- the element in the function ring whose image is to - compute + - ``a`` -- an element in the function ring OUTPUT: From 4d9e5a096f73e7d78195e36e1b64f4a592a6e26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 5 Aug 2022 18:51:18 +0200 Subject: [PATCH 049/392] Create __eq__ method in DrinfeldModuleMorphism As the class does not inherit UniqueRepresentation or something similar, this is necessary. --- .../rings/function_field/drinfeld_modules/morphism.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 7af3ad0230b..4eeb5adf214 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -34,6 +34,15 @@ def __init__(self, parent, x): self._codomain = codomain self._ore_polynomial = ore_pol + # NOTE: Should I inherit UniqueRepresentation to avoid this? + def __eq__(self, other): + try: + if self.parent() == other.parent(): + return self.defining_ore_polynomial() == other.defining_ore_polynomial() + except AttributeError: + return False + return False + def _repr_(self): return f'Drinfeld Module morphism:\n' \ f' From: {self._domain}\n' \ @@ -46,7 +55,7 @@ def codomain(self): def domain(self): return self._domain - def ore_polynomial(self): + def defining_ore_polynomial(self): return self._ore_polynomial def is_zero(self): From fbddd9c5b1bb2ad5a2787693bcddd084169c9edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 5 Aug 2022 19:13:03 +0200 Subject: [PATCH 050/392] Create is_finite method in DrinfeldModule --- .../drinfeld_modules/drinfeld_module.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 1485e0b4278..97c50353847 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -332,6 +332,18 @@ def invert(self, ore_pol): else: return None + def is_finite(self): + r""" + Return ``True`` if the Drinfeld module is finite, return + ``False`` otherwise. + + OUTPUT: + + - ``True`` or ``False`` + """ + from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule + return isinstance(self, FiniteDrinfeldModule) + def rank(self): r""" Return the rank of the Drinfeld module. From 9ef98d313cc78827c8dfd88a4323e8098ef3e945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 8 Aug 2022 22:42:56 +0100 Subject: [PATCH 051/392] Create is_isomorphism method in DrinfeldModuleMorphism --- src/sage/rings/function_field/drinfeld_modules/morphism.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 4eeb5adf214..5bfe179adfb 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -63,3 +63,6 @@ def is_zero(self): def is_isogeny(self): return not self.is_zero() + + def is_isomorphism(self): + return self.is_isogeny() and self._ore_polynomial.degree() == 0 From f4bb02d7d13ce42a0849c82e0c07241f65cb9b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 5 Aug 2022 19:13:28 +0200 Subject: [PATCH 052/392] Refactor _repr_ method in DrinfeldModuleHomset Simply change the output. --- src/sage/rings/function_field/drinfeld_modules/homset.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index d4ccf4880c8..4dddb310049 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -20,6 +20,10 @@ def __init__(self, X, Y, category=None, check=True): base = category.base() Homset.__init__(self, X, Y, category=category, base=base, check=check) + def _repr_(self): + return f'Set of Drinfeld module morphisms from ' \ + f'{self.domain().gen()} to {self.codomain().gen()}' + def __contains__(self, x): try: x = self(x) From a21e5260e793600dbb61aabd5a58ed036c8a8d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 5 Aug 2022 19:32:32 +0200 Subject: [PATCH 053/392] Remove _check_base_fields function from drinfeld_module.py --- .../drinfeld_modules/drinfeld_module.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 97c50353847..337f507fbaa 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -147,9 +147,6 @@ def constant_term(self): """ return self.gen()[0] - # def frobenius(self): - # return self.ore_polring().twisting_morphism() - def gen(self): r""" Return the generator (`\phi_X`) of the Drinfeld module. @@ -256,16 +253,18 @@ def change_ring(self, new_field): - a Drinfeld module """ - # TODO: Remove _check_base_field R = new_field if not R.is_field() and R.is_finite(): raise TypeError('Argument must be a finite field') if not self.ore_polring().base_ring().is_subring(R): raise ValueError('The new field must be a finite field ' \ 'extension of the base field of the Ore polynomial ring.') - _check_base_fields(self._Fq, R) + if not (R.is_field() and R.is_finite() and self._Fq.is_subring(R)): + raise ValueError(f'the new base ring must be an extension of the ' \ + 'old base ring') - new_frobenius = R.frobenius_endomorphism(self.frobenius().power()) + frobenius = self.ore_polring().twisting_morphism() + new_frobenius = R.frobenius_endomorphism(frobenius.power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, names=self.ore_polring().variable_names()) return DrinfeldModule(self.function_ring(), @@ -477,10 +476,3 @@ def j(self): """ self._test_rank_two() return (self.g()**(self._Fq.order()+1)) / self.delta() - - -def _check_base_fields(Fq, L): - if not (L.is_field() and L.is_finite() and Fq.is_subring(L)): - raise ValueError(f'The base field of the Ore polynomial ring must ' \ - 'be a finite field extension of the base field of the ' \ - 'polynomial ring') From a7c48a41addcc77880d4eaa90d8817f1758cc6ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 5 Aug 2022 19:32:55 +0200 Subject: [PATCH 054/392] Fix __eq__ method in DrinfeldModuleMorphism I had previously introduced a bug by changing method `ore_polynomial` to `defining_ore_polynomial`. This is fixed. --- src/sage/rings/function_field/drinfeld_modules/morphism.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 5bfe179adfb..4a7e4f175f7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -38,7 +38,7 @@ def __init__(self, parent, x): def __eq__(self, other): try: if self.parent() == other.parent(): - return self.defining_ore_polynomial() == other.defining_ore_polynomial() + return self.ore_polynomial() == other.ore_polynomial() except AttributeError: return False return False @@ -55,7 +55,7 @@ def codomain(self): def domain(self): return self._domain - def defining_ore_polynomial(self): + def ore_polynomial(self): return self._ore_polynomial def is_zero(self): From a77f850a0da081228f32377b74f7d43cb9742cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Sun, 7 Aug 2022 19:00:26 +0200 Subject: [PATCH 055/392] Refactor exception messages in DrinfeldModule This mostly consisted in simplifying formulations and removing uppercases and stops. --- .../drinfeld_modules/drinfeld_module.py | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 337f507fbaa..52fceff9613 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -44,8 +44,9 @@ def __classcall_private__(cls, function_ring, gen, name='t'): ore_polring = None ore_polring_base = Sequence(gen).universe() else: - raise TypeError('generator must be a list of coefficients '\ - 'or an Ore polynomial') + + raise TypeError('generator must be list of coefficients or an ' \ + 'Ore polynomial') # Build the morphism that defines the category if not ore_polring_base.has_coerce_map_from(function_ring.base_ring()): @@ -254,15 +255,10 @@ def change_ring(self, new_field): - a Drinfeld module """ R = new_field - if not R.is_field() and R.is_finite(): - raise TypeError('Argument must be a finite field') - if not self.ore_polring().base_ring().is_subring(R): - raise ValueError('The new field must be a finite field ' \ - 'extension of the base field of the Ore polynomial ring.') - if not (R.is_field() and R.is_finite() and self._Fq.is_subring(R)): - raise ValueError(f'the new base ring must be an extension of the ' \ - 'old base ring') - + if not (R.is_field() and R.is_finite() and self._Fq.is_subring(R)) \ + and self.ore_polring().base_ring().is_subring(R): + raise ValueError('new base field must be a finite extension of ' \ + 'the base ring') frobenius = self.ore_polring().twisting_morphism() new_frobenius = R.frobenius_endomorphism(frobenius.power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, @@ -312,7 +308,7 @@ def invert(self, ore_pol): TODO """ if not ore_pol in self.ore_polring(): - raise TypeError('ore_pol must be an Ore polynomial ring') + raise TypeError('input must be an Ore polynomial') if ore_pol in self._base_ring: return self._Fq(ore_pol) r = self.rank() @@ -399,7 +395,7 @@ def velu(self, candidate): see :arxiv:`2203.06970`, eq. 1.1. """ if not candidate in self.ore_polring(): - raise TypeError('candidate must be an Ore polynomial') + raise TypeError('input must be an Ore polynomial') if candidate == 0: return None if not self.characteristic().degree().divides(candidate.valuation()): From a60bede03b545fd0d9c3e36c645511c0eb382b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 5 Aug 2022 19:35:30 +0200 Subject: [PATCH 056/392] Rewrite DrinfeldModule docstring --- .../drinfeld_modules/drinfeld_module.py | 293 +++++++++++++++++- 1 file changed, 289 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 52fceff9613..38ff6f64f6e 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -1,6 +1,12 @@ r""" Drinfeld modules +This module provides the class +:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. +For *finite* Drinfeld modules and their theory of complex multiplication, see +class +:class:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule`. + AUTHORS: - Antoine Leudière (2022-04) @@ -31,6 +37,287 @@ from sage.modules.free_module_element import vector class DrinfeldModule(UniqueRepresentation, CategoryObject): + r""" + Let `q` be the order of a finite field `\Fq`. Let `K` be a field + equiped a ring morphism `\gamma: \Fq[X] \to K` --- the field `K` is + said to be an *`\Fq[X]`-field*, and the monic polynomial that + generates `\Ker(\gamma)` is called the *`\Fq[X]`-characteristic of + the `\Fq[X]`-field `K`* (this `\Fq[X]`-characteristic plays the role in + `\Fq[X]` of the standard characteristic, in `\ZZ`, of a finite + field). Let `K\{\tau\}` be the ring of Ore polynomials with + coefficients in `K` and Frobenius variable `\tau: x \mapsto x^q`. A + *Drinfeld `\Fq[X]`-module over the `\Fq[X]`-field `K`* is a ring + morphism `\phi: \Fq[X] \to K\{\tau\}` such that: + + 1. The image of `\phi` has non-constant Ore polynomials. + 2. For every `a \in \Fq[X]`, the constant coefficient of the + Ore polynomial `\phi(a)` is `\gamma(a)`. + + For `a \in \Fq[X]`, `\phi(a)` is denoted `\phi_a`. + + We say that `K` is the *base ring of `\phi`*, `\Fq[X]` is the + *function ring of*, *K\{\tau\}* is the *Ore polynomial ring*, + `t` is the *Ore variable*. The *generator of `\phi`* is `\phi_X`, + its *constant coefficient* is the constant coefficient of `\phi_X`. + + The Drinfeld module `\phi` is uniquely determined by the image + `\phi_X` of `X`. This Ore polynomial is an input of the class + constructor. + + Classical references on Drinfeld modules include [Gos1998]_, + [Rosen2002]_, [VS06]_ and [Gek1998]_. + + .. NOTE:: + + Drinfeld modules are defined in a larger setting, in which + `\Fq[X]` is replaced by a more general ring: the ring of + functions in `k` that are regular outside `\infty`, where `k` is + a function field whose constant field is `\Fq` and with + transcendance degree `1`, and `\infty` is a fixed place of `k`. + This is out of the scope of this implementation. + + .. RUBRIC:: Construction + + A Drinfeld module object (class + :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`) + is constructed as follows:: + + sage: Fq. = GF(3^2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [K.gen(), 1, 1]) + sage: phi + Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + + In this example, we used a list of coefficients (``[K.gen(), 1, + 1]``) to represent the Ore polynomial `\phi_X = z + t + t^2`, `K + = \Fq(z)`. We can also use regular Ore polynomials:: + + sage: ore_polring = phi.ore_polring() + sage: t = phi.ore_variable() # Equals ore_polring.gen() + sage: psi_X = K.gen() + t^3 + sage: psi = DrinfeldModule(FqX, psi_X) + sage: psi + Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 3^12 + + Note that ``phi`` and ``psi`` are *finite* Drinfeld modules, in the + sense that `K` is finite. This is not mandatory:: + + sage: K_infinite = Frac(FqX) + sage: phi_infinite = DrinfeldModule(FqX, [K_infinite.gen(), 1, 1]) + sage: phi_infinite + Drinfeld module defined by X |--> t^2 + t + X over Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + sage: phi_infinite.is_finite() + False + + Drinfeld modules have their own category (see class + :class:`sage.categories.drinfeld_modules.DrinfeldModules`):: + + sage: phi.category() + Category of Drinfeld modules defined by Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z + sage: phi.category() is psi.category() + True + sage: phi.category() is phi_infinite.category() + False + + This category holds crucial information, like the + `\Fq[X]`-characteristic of `K`:: + + sage: char = phi.category().characteristic() + + .. NOTE:: + + As the output of + :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule.category` + suggests, the morphism `\gamma` uniquely determines the category of a Drinfeld + module. + + .. RUBRIC:: More general `K` + + The field `K` does not need be generated, over `\Fq`, by `\gamma(X)` + --- `K` can be an extension of `\Fq(\gamma(X))`. In the following + example, `K` is still a degree six extension of `\Fq`, but `\gamma` + is a projection over `\Fq[X]/p(X)`, with `p(X) = X^3 + (z_2 + 2)X^2 + + (6*z_2 + 1)X + 3z_2 + 5`:: + + sage: p = X^2 + z2 + 2 # Prime polynomial + sage: p_root = z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z # Root of p + sage: phi_inter = DrinfeldModule(FqX, [p_root, 1, 1]) + sage: phi_inter + Drinfeld module defined by X |--> t^2 + t + z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z over Finite Field in z of size 3^12 + + We can check that the morphisms `\gamma` are not the same for + ``phi`` and ``phi_inter``, and that the `\gamma` associated to + `\phi` is surjective, while the other one is not:: + + sage: phi_inter.category().morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z + sage: phi.category().morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z + + .. RUBRIC:: Basic methods + + For a polynomial `a \in \Fq[X]`, compute `\phi_a` by calling `phi`:: + + sage: phi(X) # phi_X + t^2 + t + z + sage: phi(X^3 + X + 1) # phi_X^3 +X + 1 + t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 + sage: phi(1) # phi_1 + 1 + + We can retrieve basic properties:: + + sage: phi.base_ring() # K + Finite Field in z of size 3^12 + sage: phi.ore_polring() # K{t} + Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) + sage: phi.ore_variable() # t + t + sage: phi.function_ring() # Fq[X] + Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + sage: phi.gen() # phi_X + t^2 + t + z + sage: phi.gen() == phi(X) + True + sage: phi.constant_coefficient() # Constant coefficient of phi_X + z + sage: phi.morphism() # The Drinfeld module as a morphism + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) + Defn: X |--> t^2 + t + z + + We can compute the rank and height:: + + sage: phi.rank() + 2 + sage: phi.height() + 1 + + And there are some rank-two specifics:: + + sage: phi.j() # j-invariant + 1 + sage: phi.g() # write phi_X = z + g*t + Delta*t^2 + 1 + sage: phi.delta() # Write phi_X = z + g*t + Delta*t^2 + 1 + + .. RUBRIC:: Morphisms, isogenies + + A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore + polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for + every $a \in \Fq[X]`. In our case, this is equivalent to verifying + `f \phi_X = \psi_X f`. An *isogeny* is a non-zero morphism. + + Use the ``in`` syntax to test if an Ore polynomial defines an + isogeny:: + + sage: phi(X) in Hom(phi, phi) + True + sage: t^6 in Hom(phi, phi) + True + sage: t^5 + 2*t^3 + 1 in Hom(phi, phi) + False + sage: 1 in Hom(phi, psi) + False + sage: 1 in Hom(phi, phi) + True + sage: 0 in Hom(phi, psi) + True + + To create a SageMath object representing the morphism, call the + homset ``hom``:: + + sage: hom = Hom(phi, phi) + sage: frob = hom(t^6) + sage: identity_morphism = hom(1) + sage: zero_morphism = hom(0) + sage: frob + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + To: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + Defn: t^6 + sage: identity_morphism + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + To: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + Defn: 1 + sage: zero_morphism + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + To: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + Defn: 0 + + We can retrieve the underlying Ore polynomial with the method + :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule.ore_polynomial`:: + + sage: frob.ore_polynomial() + t^6 + + And we can easily check if a morphism defines an isogeny or an + isomorphism (i.e. an isogeny whose underlying Ore polynomial has + degree `0`):: + + sage: frob.is_isogeny() + True + sage: identity_morphism.is_isogeny() + True + sage: zero_morphism.is_isogeny() + False + sage: frob.is_isomorphism() + False + sage: identity_morphism.is_isomorphism() + True + sage: zero_morphism.is_isomorphism() + False + + .. RUBRIC:: The Vélu formula + + Let ``ore_pol`` be a non-zero Ore polynomial ``ore_pol``. For + Drinfeld module, it is easy to decide --- and find as the case may + be --- if there exists a Drinfeld module ``new_drin_mod``, such that + ``ore_pol`` is an isogeny from ``self`` to ``new_drin_mod``. If this + Drinfeld module exists, it is unique. + + sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z + sage: new_drin_mod = phi.velu(ore_pol) + sage: new_drin_mod + Drinfeld module defined by X |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over Finite Field in z of size 3^12 + sage: ore_pol in Hom(phi, new_drin_mod) + True + sage: ore_pol * phi(X) == new_drin_mod(X) * ore_pol + True + + .. RUBRIC:: Other methods + + It is possible to change the base ring:: + + sage: L = K.extension(2) + sage: phi_rebased = phi.change_ring(L) + sage: Ltau = phi_rebased.ore_polring() + sage: Ltau(phi(X)) == phi_rebased(X) + True + + Given an Ore polynomial that equals `\phi(a)` for some `a \in + \Fq[X]`, we can retrieve `a` (as a morphism, a Drinfeld + module is injective, see [Gos1998]_, cor. 4.5.2.):: + + sage: a = FqX.random_element() + sage: phi.invert(phi(a)) == a + True + """ + @staticmethod def __classcall_private__(cls, function_ring, gen, name='t'): # Check all possible input types @@ -131,9 +418,9 @@ def base_ring(self): """ return self._base_ring - def constant_term(self): + def constant_coefficient(self): r""" - Return the constant term of the generator (`\phi_X`). + Return the constant coefficient of the generator (`\phi_X`). The `A`-characteristic of the base field (see :meth:`sage.categories.drinfeld_modules.DrinfeldModules.characteristic`) @@ -375,12 +662,10 @@ def velu(self, candidate): We write the Ore Euclidean division `\phi_X = \mathrm{candidate}*q + r`, and return The candidate is an isogeny if only if: - 1. The degree of the characteristic devides the height of the candidate. (The height of an Ore polynomial `P(t)` is the maximum `n` such that `t^n` right-divides `P(t)`.) - 2. The candidate right-divides the generator, which can be tested with Euclidean division. From a29334556c55b6ed88f433f61004d31f2fc09188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Sun, 7 Aug 2022 19:40:30 +0200 Subject: [PATCH 057/392] Add classic Drinfeld module references to bib. master file --- src/doc/en/reference/references/index.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 35725366e97..f83df6e7228 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2497,6 +2497,9 @@ REFERENCES: TR-737-05, (2005). ftp://ftp.cs.princeton.edu/reports/2005/737.pdf +.. [Gek1991] \E.-U. Gekeler. On finite Drinfeld modules. Journal of + algebra, 1(141):187–203, 1991. + .. [GG2012] Jim Geelen and Bert Gerards, Characterizing graphic matroids by a system of linear equations, submitted, 2012. Preprint: @@ -2656,6 +2659,9 @@ REFERENCES: .. [Gos1972] Bill Gosper, "Continued Fraction Arithmetic" https://perl.plover.com/classes/cftalk/INFO/gosper.txt +.. [Gos1998] \D. Goss. Basic structures of function field arithmetic. Springer, + 1998. + .. [Gor1980] Daniel Gorenstein, Finite Groups (New York: Chelsea Publishing, 1980) @@ -4452,6 +4458,11 @@ REFERENCES: Int. Math. Res. Not. (2015). :doi:`10.1093/imrn/rnv194`, :arxiv:`1408.0320`. +.. [MS2019] \Y. Musleh and É. Schost. Computing the characteristic polynomial + of a finite rank two Drinfeld module. In Proceedings of the 2019 + ACM on International Symposium on Symbolic and Algebraic + Computation, pages 307–314. ACM, 2019. + .. [MSSY2001] Mateescu, A., Salomaa, A., Salomaa, K. and Yu, S., *A sharpening of the Parikh mapping*. Theoret. Informatics Appl. 35 (2001) 551-564. @@ -5028,6 +5039,8 @@ REFERENCES: .. [Ros2002] Rosenfeld, Vladimir Raphael, 2002: Enumerating De Bruijn Sequences. *Communications in Math. and in Computer Chem.* +.. [Rosen2002] \M. Rosen. Number theory in function fields. Springer, 2022. + .. [Rot2001] Gunter Rote, *Division-Free Algorithms for the Determinant and the Pfaffian: Algebraic and Combinatorial Approaches*, H. Alt (Ed.): Computational Discrete @@ -5853,6 +5866,9 @@ REFERENCES: .. [Voi2012] \J. Voight. Identifying the matrix ring: algorithms for quaternion algebras and quadratic forms, to appear. +.. [VS06] \G.D. Villa Salvador. Topics in the Theory of Algebraic Function + Fields. Birkhäuser, 2006. + .. [VW1994] Leonard Van Wyk. *Graph groups are biautomatic*. J. Pure Appl. Alg. **94** (1994). no. 3, 341-352. From 50b0501c4ea31ff0287c16951223fa4f64f2d10f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 10 Aug 2022 10:30:19 +0100 Subject: [PATCH 058/392] Check input morphism in DrinfeldModules constructor Check that the input `morphism` in DrinfeldModules constructor is indeed a morphism. --- src/sage/categories/drinfeld_modules.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index a64c59ea693..c46e328ec75 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -11,6 +11,7 @@ from sage.categories.category import CategoryWithParameters from sage.misc.functional import log +from sage.rings.morphism import RingHomomorphism from sage.rings.polynomial.polynomial_ring import PolynomialRing_general from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing @@ -23,6 +24,9 @@ class DrinfeldModules(CategoryWithParameters): def __init__(self, morphism, name='t'): gamma = morphism + # Check input is a ring Morphism + if not isinstance(gamma, RingHomomorphism): + raise TypeError('input must be a Ring morphism') self._morphism = morphism self._domain = gamma.domain() # Check domain is Fq[X] From 73aa69be1623ddc8ad59689cde0abcbdfa388d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 10 Aug 2022 10:58:39 +0100 Subject: [PATCH 059/392] Check input in DrinfeldModule constructor Check that arg `function_ring` is an Fq[X]. This was not done, even though methods of `PolynomialRing` were used. Code is not great, see my comment in the code. --- .../drinfeld_modules/drinfeld_module.py | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 38ff6f64f6e..1bc6fb79551 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -30,6 +30,7 @@ from sage.categories.drinfeld_modules import DrinfeldModules from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing +from sage.rings.polynomial.polynomial_ring import PolynomialRing_general from sage.misc.latex import latex from sage.structure.sequence import Sequence @@ -320,7 +321,23 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): @staticmethod def __classcall_private__(cls, function_ring, gen, name='t'): - # Check all possible input types + + # FIXME: function_ring must be checked before calling base_ring + # on it. But then it is checked twice: firstly here, secondly in + # the category. Another problem is that those lines are + # duplicate. As a general comment, there are sanity checks both + # here and in the category constructor, which is not ideal. + # Check domain is Fq[X] + if not isinstance(function_ring, PolynomialRing_general): + raise NotImplementedError('domain must be a polynomial ring') + function_ring_base = function_ring.base_ring() + if not function_ring_base.is_field() or not function_ring_base.is_finite() : + raise TypeError('the base ring of the domain must be a finite field') + Fq = function_ring_base + FqX = function_ring + X = FqX.gen() + + # Check all possible input types for gen # `gen` is an Ore polynomial: if isinstance(gen, OrePolynomial): ore_polring = gen.parent() @@ -331,7 +348,6 @@ def __classcall_private__(cls, function_ring, gen, name='t'): ore_polring = None ore_polring_base = Sequence(gen).universe() else: - raise TypeError('generator must be list of coefficients or an ' \ 'Ore polynomial') @@ -341,15 +357,14 @@ def __classcall_private__(cls, function_ring, gen, name='t'): 'ring of Ore polynomial ring') gamma = function_ring.hom([ore_polring_base(gen[0])]) - # Mathematical integrity of the data is delegated to the category + # Other checks in the category definition category = DrinfeldModules(gamma, name=name) + # Check gen as Ore polynomial if ore_polring is not None and ore_polring is not category.codomain(): raise ValueError(f'generator must lie in {category.codomain()}') - # Sanity cast - ore_polring = category.codomain() - # Be sure to have a generator that is an Ore polynomial - gen = ore_polring(gen) + ore_polring = category.codomain() # Sanity cast + gen = ore_polring(gen) # Sanity cast if gen.degree() <= 0: raise ValueError('generator must have positive degree') From f8b7030df0953fb52ee0b06c37be37edca5f16ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 10 Aug 2022 11:01:50 +0100 Subject: [PATCH 060/392] Add a note in DrinfeldModule docstring Add a note in DrinfeldModule docstring on the situation in which the user gives, as an input of the constructor, a list of integer coefficients for the generator. --- .../function_field/drinfeld_modules/drinfeld_module.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 1bc6fb79551..8123f4781a6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -101,6 +101,12 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: psi Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 3^12 + .. NOTE:: + + If the Ore polynomial has coefficients in the integers, the + constructor does not try to guess if the user wants to see the + coefficients as elements of Fq, or an extension like `K`. + Note that ``phi`` and ``psi`` are *finite* Drinfeld modules, in the sense that `K` is finite. This is not mandatory:: From db42b9935d8e514aa1a1443d0c6729b8750d710a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 10 Aug 2022 11:27:46 +0100 Subject: [PATCH 061/392] Change _domain and _codomain names in DrinfeldModules Those names were misleading, as there was a conflict with names in DrinfeldModule class. I replaced `domain` by `function_ring`, `codomain` by `ore_polring`, and did the necessary replacements in this module and other ones. I did *not* tested every module in `sage.rings.function_field.drinfeld_modules`; surely there are bugs. --- src/sage/categories/drinfeld_modules.py | 51 ++++++++++--------- .../drinfeld_modules/drinfeld_module.py | 12 ++--- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index c46e328ec75..a81bb68e42e 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -28,9 +28,9 @@ def __init__(self, morphism, name='t'): if not isinstance(gamma, RingHomomorphism): raise TypeError('input must be a Ring morphism') self._morphism = morphism - self._domain = gamma.domain() + self._function_ring = gamma.domain() # Check domain is Fq[X] - function_ring = self._domain + function_ring = self._function_ring if not isinstance(function_ring, PolynomialRing_general): raise NotImplementedError('domain must be a polynomial ring') function_ring_base = function_ring.base_ring() @@ -39,14 +39,14 @@ def __init__(self, morphism, name='t'): Fq = function_ring_base FqX = function_ring X = FqX.gen() - # Check domain is field + # Check codomain of gamma is field K = gamma.codomain() if not K.is_field(): - raise TypeError('the codomain must be a field') + raise TypeError('the codomain of the morphism must be a field') # Build K{t} d = log(Fq.cardinality(), Fq.characteristic()) tau = K.frobenius_endomorphism(d) - self._codomain = OrePolynomialRing(K, tau, names=name) + self._ore_polring = OrePolynomialRing(K, tau, names=name) # Create characteristic self._characteristic = None if K.is_finite(): @@ -55,30 +55,43 @@ def __init__(self, morphism, name='t'): self._characteristic = FqX(E(gamma(X)).minpoly()) def base(self): - return self.codomain().base_ring() + return self._ore_polring.base_ring() def characteristic(self): if self._characteristic is None: raise NotImplementedError return self._characteristic + def function_ring(self): + return self._function_ring + + def morphism(self): + return self._morphism + + def ore_polring(self): + return self._ore_polring + + # Sage methods + def _call_(self, gen): # Avoid circular import - from sage.rings.function_field.drinfeld_module import DrinfeldModule + from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule # If gen is not in the codomain, an exception is raised - gen = self._codomain(gen) + gen = self._ore_polring(gen) if self.characteristic()(gen[0]) != 0: - raise ValueError('incorrect characteristic') - return DrinfeldModule(self._domain, gen) - - def super_categories(self): - return [] + raise ValueError('constant coefficient is not a root of the characteristic') + return DrinfeldModule(self._function_ring, gen) def _repr_(self): return f'Category of Drinfeld modules defined by {self._morphism}' + # Sage requires those: + def _make_named_class_key(self, name): - return self._domain.category() + return self._function_ring.category() + + def super_categories(self): + return [] def Homsets(self): return Homsets() @@ -86,16 +99,8 @@ def Homsets(self): def Endsets(self): return Homsets() - def domain(self): - return self._domain - - def codomain(self): - return self._codomain - - def morphism(self): - return self._morphism - class ParentMethods: + def characteristic(self): return self.category().characteristic() diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 8123f4781a6..de14a38df7d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -367,9 +367,9 @@ def __classcall_private__(cls, function_ring, gen, name='t'): category = DrinfeldModules(gamma, name=name) # Check gen as Ore polynomial - if ore_polring is not None and ore_polring is not category.codomain(): - raise ValueError(f'generator must lie in {category.codomain()}') - ore_polring = category.codomain() # Sanity cast + if ore_polring not in (None, category.ore_polring()): + raise ValueError(f'generator must lie in {category.ore_polring()}') + ore_polring = category.ore_polring() # Sanity cast gen = ore_polring(gen) # Sanity cast if gen.degree() <= 0: raise ValueError('generator must have positive degree') @@ -384,11 +384,11 @@ def __classcall_private__(cls, function_ring, gen, name='t'): def __init__(self, gen, category): CategoryObject.__init__(self, category=category) self._base_ring = category.base() - self._function_ring = category.domain() + self._function_ring = category.function_ring() self._gen = gen - self._morphism = self._function_ring.hom([gen]) + self._morphism = category._function_ring.hom([gen]) self._ore_polring = gen.parent() - self._Fq = self._function_ring.base_ring() + self._Fq = self._function_ring.base_ring() # Must be last ################# # Private utils # From 96776b71af5144fc6ee5ae869ce69c5e4cf22263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 10 Aug 2022 11:30:58 +0100 Subject: [PATCH 062/392] Fix change_ring method in DrinfeldModule class The import and the call of the DrinfeldModule constructor were wrong. --- .../function_field/drinfeld_modules/drinfeld_module.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index de14a38df7d..22e08e23e89 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -567,12 +567,11 @@ def change_ring(self, new_field): and self.ore_polring().base_ring().is_subring(R): raise ValueError('new base field must be a finite extension of ' \ 'the base ring') - frobenius = self.ore_polring().twisting_morphism() + frobenius = self._ore_polring.twisting_morphism() new_frobenius = R.frobenius_endomorphism(frobenius.power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, - names=self.ore_polring().variable_names()) - return DrinfeldModule(self.function_ring(), - new_ore_polring(self.gen()), self.characteristic()) + names=self._ore_polring.variable_names()) + return DrinfeldModule(self._function_ring, new_ore_polring(self._gen)) def height(self): r""" From 5a640835045f561d74dcedc449cd94cf9bf5daea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 12 Aug 2022 22:18:43 +0200 Subject: [PATCH 063/392] Add polcast flag to build Drinfeld modules This flag makes sure OrePolynomialRing does not build a regular polynomial ring when the twisting morphism is the identity. See ticket 34001 : https://trac.sagemath.org/ticket/34001. --- src/sage/categories/drinfeld_modules.py | 3 ++- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index a81bb68e42e..c7d3ced6d59 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -46,7 +46,8 @@ def __init__(self, morphism, name='t'): # Build K{t} d = log(Fq.cardinality(), Fq.characteristic()) tau = K.frobenius_endomorphism(d) - self._ore_polring = OrePolynomialRing(K, tau, names=name) + self._ore_polring = OrePolynomialRing(K, tau, names=name, + polcast=False) # Create characteristic self._characteristic = None if K.is_finite(): diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 22e08e23e89..37774759af7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -570,7 +570,7 @@ def change_ring(self, new_field): frobenius = self._ore_polring.twisting_morphism() new_frobenius = R.frobenius_endomorphism(frobenius.power()) new_ore_polring = OrePolynomialRing(R, new_frobenius, - names=self._ore_polring.variable_names()) + names=self._ore_polring.variable_names(), polcast=False) return DrinfeldModule(self._function_ring, new_ore_polring(self._gen)) def height(self): From 750d797641ec0cc00436737eabc1ba9a29f13977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Sat, 13 Aug 2022 11:35:44 +0200 Subject: [PATCH 064/392] (minor) Fix comment in DrinfeldModule constructor --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 37774759af7..b23656ab29c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -370,7 +370,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): if ore_polring not in (None, category.ore_polring()): raise ValueError(f'generator must lie in {category.ore_polring()}') ore_polring = category.ore_polring() # Sanity cast - gen = ore_polring(gen) # Sanity cast + gen = ore_polring(gen) if gen.degree() <= 0: raise ValueError('generator must have positive degree') From ab02ba1ff7e97555284c458897895bb5e06bea91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Sat, 13 Aug 2022 11:36:10 +0200 Subject: [PATCH 065/392] Add name flag to change_ring method in DrinfeldModule This is to allow choosing the Ore variable name of the new Drinfeld module. --- .../drinfeld_modules/drinfeld_module.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index b23656ab29c..c4486cd4f40 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -542,7 +542,7 @@ def __call__(self, a): return self._morphism(a) - def change_ring(self, new_field): + def change_ring(self, new_field, name=None): r""" If ``new_field`` is a field extension of the base ring, return a new Drinfeld module that extends ``self`` to the base ring @@ -562,16 +562,11 @@ def change_ring(self, new_field): - a Drinfeld module """ - R = new_field - if not (R.is_field() and R.is_finite() and self._Fq.is_subring(R)) \ - and self.ore_polring().base_ring().is_subring(R): - raise ValueError('new base field must be a finite extension of ' \ - 'the base ring') - frobenius = self._ore_polring.twisting_morphism() - new_frobenius = R.frobenius_endomorphism(frobenius.power()) - new_ore_polring = OrePolynomialRing(R, new_frobenius, - names=self._ore_polring.variable_names(), polcast=False) - return DrinfeldModule(self._function_ring, new_ore_polring(self._gen)) + coeffs = self._gen.coefficients() + new_coeffs = list(map(new_field, coeffs)) + if name == None: + name = self._ore_polring.variable_name() + return DrinfeldModule(self._function_ring, new_coeffs, name=name) def height(self): r""" From 4c9b81dfc492384b07d5fdf1eab4eeac08c2c7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 12 Aug 2022 23:23:51 +0200 Subject: [PATCH 066/392] Add examples in DrinfeldModule doc and rewrite docstrings EXAMPLES: fields were added to each method. Some now have TESTS:: fields, but not all of them. Unfortunately this commit is not very clean. It also includes: - some cosmectif refactoring of the velu method; - deletion of methods g and delta (thanks David, see https://trac.sagemath.org/ticket/33713#comment:80); - changing name of method _test_rank_two to _check_rank_two. --- .../drinfeld_modules/drinfeld_module.py | 571 ++++++++++++++---- .../finite_drinfeld_module.py | 8 +- 2 files changed, 473 insertions(+), 106 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index c4486cd4f40..266119365fa 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -77,6 +77,13 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): transcendance degree `1`, and `\infty` is a fixed place of `k`. This is out of the scope of this implementation. + INPUT: + + - ``function_ring`` -- the polynomial ring `\Fq[X]` + - ``gen`` -- the generator `\phi_X`, as a list of coefficients or an + Ore polynomial + - ``name`` (optional) the name of the Ore variable + .. RUBRIC:: Construction A Drinfeld module object (class @@ -116,6 +123,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): Drinfeld module defined by X |--> t^2 + t + X over Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 sage: phi_infinite.is_finite() False + sage: phi.is_finite() + True Drinfeld modules have their own category (see class :class:`sage.categories.drinfeld_modules.DrinfeldModules`):: @@ -144,11 +153,12 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: More general `K` - The field `K` does not need be generated, over `\Fq`, by `\gamma(X)` - --- `K` can be an extension of `\Fq(\gamma(X))`. In the following - example, `K` is still a degree six extension of `\Fq`, but `\gamma` - is a projection over `\Fq[X]/p(X)`, with `p(X) = X^3 + (z_2 + 2)X^2 + - (6*z_2 + 1)X + 3z_2 + 5`:: + The field `K` does not need be generated by the constant coefficient + (i.e. generated, as an extension of `\Fq`, by the image + `\gamma(X)`). In the following example, `K` is still a + degree six extension of `\Fq`, but `\gamma` is a projection over + `\Fq[X]/p(X)`, with `p(X) = X^3 + (z_2 + 2)X^2 + (6*z_2 + 1)X + 3z_2 + + 5`:: sage: p = X^2 + z2 + 2 # Prime polynomial sage: p_root = z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z # Root of p @@ -211,14 +221,10 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: phi.height() 1 - And there are some rank-two specifics:: + As well as the j-invariant if the rank is two:: sage: phi.j() # j-invariant 1 - sage: phi.g() # write phi_X = z + g*t + Delta*t^2 - 1 - sage: phi.delta() # Write phi_X = z + g*t + Delta*t^2 - 1 .. RUBRIC:: Morphisms, isogenies @@ -293,17 +299,17 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): Let ``ore_pol`` be a non-zero Ore polynomial ``ore_pol``. For Drinfeld module, it is easy to decide --- and find as the case may - be --- if there exists a Drinfeld module ``new_drin_mod``, such that - ``ore_pol`` is an isogeny from ``self`` to ``new_drin_mod``. If this + be --- if there exists a Drinfeld module ``psi``, such that + ``ore_pol`` is an isogeny from ``self`` to ``psi``. If this Drinfeld module exists, it is unique. sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z - sage: new_drin_mod = phi.velu(ore_pol) - sage: new_drin_mod + sage: psi = phi.velu(ore_pol) + sage: psi Drinfeld module defined by X |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over Finite Field in z of size 3^12 - sage: ore_pol in Hom(phi, new_drin_mod) + sage: ore_pol in Hom(phi, psi) True - sage: ore_pol * phi(X) == new_drin_mod(X) * ore_pol + sage: ore_pol * phi(X) == psi(X) * ore_pol True .. RUBRIC:: Other methods @@ -390,17 +396,6 @@ def __init__(self, gen, category): self._ore_polring = gen.parent() self._Fq = self._function_ring.base_ring() # Must be last - ################# - # Private utils # - ################# - - def _test_rank_two(self): - r""" - Raise ``NotImplementedError`` if the rank is not two. - """ - if self.rank() != 2: - raise NotImplementedError('the rank must be 2') - ########################## # Special Sage functions # ########################## @@ -436,6 +431,51 @@ def base_ring(self): OUTPUT: - a field + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.base_ring() + Finite Field in z12 of size 5^12 + + This is always true:: + + sage: phi.base_ring() is phi.ore_polring().base_ring() + True + sage: phi.base_ring() is K + True + + Note that in the above example, the base ring does is not + generated by the constant coefficient (i.e. generated, as an + extension of `\Fq`, by the image `\gamma(X)`). The base + ring may also be the same as ``Fq``:: + + sage: psi = DrinfeldModule(FqX, [Fq(1), Fq.gen()]) + sage: psi.base_ring() + Finite Field in z2 of size 5^2 + sage: psi.base_ring() is Fq + True + + In which case the Ore polynomial ring is isomorphic to a regular + polynomial ring:: + + sage: psi.ore_polring() + Ore Polynomial Ring in t over Finite Field in z2 of size 5^2 twisted by Identity + sage: psi.ore_polring().twisting_morphism() + Identity endomorphism of Finite Field in z2 of size 5^2 + + TESTS:: + + sage: psi.ore_polring().twisting_morphism().is_identity() + True + + sage: psi.base_ring() is psi.function_ring().base_ring() + True + """ return self._base_ring @@ -453,6 +493,47 @@ def constant_coefficient(self): OUTPUT: - an element in the base ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.constant_coefficient() == p_root + True + + The constant coefficient is the image of ``X`` by the + morphism that defines the category of ``phi``:: + + sage: cat = phi.category() + sage: gamma = cat.morphism() + sage: gamma(X) == phi.constant_coefficient() + True + + Two Drinfeld modules in the same category have the same constant + coefficient:: + + sage: t = phi.ore_variable() + sage: psi = cat(phi.constant_coefficient() + t^3) + sage: psi + Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + + Reciprocally, it is impossible to create two Drinfeld modules in + this category if they don't share the same constant + coefficient:: + + sage: rho = cat(phi.constant_coefficient() + 1 + t^3) + Traceback (most recent call last): + ... + ValueError: constant coefficient is not a root of the characteristic + + One cal also retrieve the constant coefficient using + ``phi(X)[0]`:: + + sage: phi.constant_coefficient() == phi(X)[0] + True """ return self.gen()[0] @@ -467,6 +548,16 @@ def gen(self): OUTPUT: - an Ore polynomial + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.gen() == phi(X) + True """ return self._gen @@ -478,7 +569,45 @@ def morphism(self): - a ring morphism, from the function ring to the Ore polynomial ring - """ + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + Defn: X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: from sage.rings.morphism import RingHomomorphism + sage: isinstance(phi.morphism(), RingHomomorphism) + True + + Actually, the ``DrinfeldModule`` method ``__call__`` simply + class the ``__call__`` method of this morphism:: + + sage: phi.morphism()(X) == phi(X) + True + sage: a = FqX.random_element() + sage: phi.morphism()(a) == phi(a) + True + + And many methods of the Drinfeld module have a counterpart in + the morphism object:: + + sage: m = phi.morphism() + sage: m.domain() is phi.function_ring() + True + sage: m.codomain() is phi.ore_polring() + True + sage: m.im_gens() + [z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12] + sage: phi(X) == m.im_gens()[0] + True + """ return self._morphism def ore_polring(self): @@ -491,6 +620,37 @@ def ore_polring(self): OUTPUT: - an Ore polynomial ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: ore_polring = phi.ore_polring() + sage: ore_polring + Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + + The Ore polynomial ring can also be retrieved from the category + of the Drinfeld module:: + + sage: ore_polring is phi.category().ore_polring() + True + + The generator of the Drinfeld module is in the Ore polynomial + ring:: + + sage: phi(X) in ore_polring + True + + The Ore variable is just the generator of the Ore polynomial + ring:: + + sage: ore_polring.gen() + t + sage: phi.ore_variable() is ore_polring.gen() + True """ return self._ore_polring @@ -503,6 +663,30 @@ def ore_variable(self): OUTPUT: - an Ore polynomial + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.ore_variable() + t + sage: phi.ore_variable() is phi.ore_polring().gen() + True + + We can use the Ore variable to instanciate new Drinfeld + modules...:: + + sage: t = phi.ore_variable() + sage: psi_X = phi.constant_coefficient() + 3*t + 2*t^4 + sage: psi = DrinfeldModule(FqX, psi_X) + + ...or morphisms and isogenies:: + + sage: t^6 in End(phi) # Frobenius endomorphism + True """ return self._ore_polring.gen() @@ -518,6 +702,16 @@ def function_ring(self): OUTPUT: - a polynomial ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.function_ring() is FqX + True """ return self._function_ring @@ -561,6 +755,52 @@ def change_ring(self, new_field, name=None): OUTPUT: - a Drinfeld module + + EXAMPLES: + + The new ring can be an extension of the base ring:: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, p_root^3, 2]) + sage: K2 = K.extension(2) + sage: phi_2 = phi.change_ring(K2) + sage: phi_2 + Drinfeld module defined by X |--> 2*t^2 + (3*z24^23 + 2*z24^22 + 2*z24^20 + z24^19 + 4*z24^18 + 3*z24^17 + 4*z24^15 + 2*z24^13 + 4*z24^12 + 4*z24^11 + 3*z24^10 + 3*z24^9 + 4*z24^8 + 4*z24^6 + 3*z24^5 + 4*z24^4 + 4*z24^3 + 2*z24)*t + 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 over Finite Field in z24 of size 5^24 + + And we can check various things:: + + sage: phi.change_ring(K2).change_ring(K) is phi + True + sage: phi_2.base_ring() is K2 + True + + Naturally, the category has changed:: + + sage: phi_2.category() + Category of Drinfeld modules defined by Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z24 of size 5^24 + Defn: X |--> 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 + + We can also change the base ring to a subfield, even though some things + do not work as expected:: + + sage: K0 = Fq.extension(2) + sage: phi_0 = phi.change_ring(K0) + sage: phi_0.base_ring() is K0 + True + sage: phi.change_ring(K0).change_ring(K) # known bug + Traceback (most recent call last) + ... + TypeError: no coercion defined + + Furthermore:: + + sage: phi.change_ring(K) is phi + True """ coeffs = self._gen.coefficients() new_coeffs = list(map(new_field, coeffs)) @@ -572,11 +812,21 @@ def height(self): r""" Return the height of the Drinfeld module. - When the function ring is a polynomial ring, the height is 1. + In our case, the height is always 1. OUTPUT: - an integer + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.height() == 1 + True """ return Integer(1) @@ -597,17 +847,54 @@ def invert(self, ore_pol): OUTPUT: - a polynomial - + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: a = FqX.random_element() + sage: phi.invert(phi(a)) == a + True + sage: phi.invert(phi(X)) == X + True + sage: phi.invert(phi(Fq.gen())) == Fq.gen() + True + + When the input is not in the image of the Drinfeld module, the + method returns None:: + + sage: t = phi.ore_variable() + sage: phi.invert(t + 1) is None + True + sage: phi.invert(t^3 + t^2 + 1) is None + True + ALGORITHM: - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO + + See [MS2019]_, 3.2.5. + + TESTS:: + + sage: a = FqX.random_element() + sage: cat = phi.category() + sage: phi_r1 = cat.random_element(1) + sage: phi_r1.invert(phi_r1(a)) == a + True + sage: phi_r2 = cat.random_element(2) + sage: phi_r2.invert(phi_r2(a)) == a + True + sage: phi_r3 = cat.random_element(3) + sage: phi_r3.invert(phi_r3(a)) == a + True + sage: phi_r4 = cat.random_element(4) + sage: phi_r4.invert(phi_r4(a)) == a + True + sage: phi_r5 = cat.random_element(5) + sage: phi_r5.invert(phi_r5(a)) == a + True """ if not ore_pol in self.ore_polring(): raise TypeError('input must be an Ore polynomial') @@ -637,10 +924,73 @@ def is_finite(self): OUTPUT: - ``True`` or ``False`` + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.is_finite() + True + sage: L = Frac(FqX) + sage: psi = DrinfeldModule(FqX, [L(2), L(1)]) + sage: psi.is_finite() + False """ from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule return isinstance(self, FiniteDrinfeldModule) + def j(self): + r""" + Return the j-invariant of the Drinfeld module. + + Only the rank two case has been implemented. An + NotImplementedError is raised if the rank is not two. + + Assume the rank is two. Write the generator `\phi_X = \gamma(X) + + g\tau + \Delta\tau^2`. The j-invariant is defined by + `\frac{g^{q+1}}{\Delta}`, `q` being the order of the base field + of the polynomial ring. In our case, this base field is always + finite, as we force the function ring to be of the form + `\Fq[X]`. + + OUTPUT: + + - an element in the base ring if the rank is two; an + exception is raised otherwise + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.j() + z12^10 + 4*z12^9 + 3*z12^8 + 2*z12^7 + 3*z12^6 + z12^5 + z12^3 + 4*z12^2 + z12 + 2 + sage: psi = DrinfeldModule(FqX, [p_root, 1, 1]) + sage: psi.j() + 1 + sage: rho = DrinfeldModule(FqX, [p_root, 0, 1]) + sage: rho.j() + 0 + + The rank must be two:: + + sage: theta = DrinfeldModule(FqX, [p_root, 1, 0]) + sage: theta.j() + Traceback (most recent call last): + ... + NotImplementedError: the rank must be 2 + """ + self._check_rank_two() + g = self._gen[1] + delta = self._gen[2] + q = self._Fq.order() + return (g**(q+1)) / delta + def rank(self): r""" Return the rank of the Drinfeld module. @@ -651,22 +1001,37 @@ def rank(self): OUTPUT: - an integer + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.rank() + 2 + sage: psi = DrinfeldModule(FqX, [p_root, 2]) + sage: psi.rank() + 1 + sage: rho = DrinfeldModule(FqX, [p_root, 0, 0, 0, 1]) + sage: rho.rank() + 4 """ return self.gen().degree() - def velu(self, candidate): + def velu(self, isog): r""" - Return a new Drinfeld module such that ``candidate`` is an + Return a new Drinfeld module such that ``isog`` is an isogeny to this module with domain ``self``. If no such isogeny exists, return ``None``. - If the candidate is zero, return ``None``, as an isogeny is + If the input is zero, return ``None``, as an isogeny is required to be non zero. INPUT: - - ``candidate`` -- an Ore polynomial that defines the isogeny - with domain ``self`` and codomain the output of the method + - ``isog`` -- the Ore polynomial that defines the isogeny OUTPUT: @@ -674,17 +1039,15 @@ def velu(self, candidate): ALGORITHM: - We write the Ore Euclidean division `\phi_X = - \mathrm{candidate}*q + r`, and return - The candidate is an isogeny if only if: + The input defines an isogeny if only if: 1. The degree of the characteristic devides the height - of the candidate. (The height of an Ore polynomial + of the input. (The height of an Ore polynomial `P(t)` is the maximum `n` such that `t^n` right-divides `P(t)`.) - 2. The candidate right-divides the generator, which can + 2. The input right-divides the generator, which can be tested with Euclidean division. - We test if the candidate is an isogeny, and, if it is, we + We test if the input is an isogeny, and, if it is, we return the quotient of the Euclidean division. Height and Euclidean division of Ore polynomials are @@ -693,14 +1056,43 @@ def velu(self, candidate): Another possible algorithm is to recursively solve a system, see :arxiv:`2203.06970`, eq. 1.1. + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: t = phi.ore_variable() + sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + sage: psi = phi.velu(isog) + sage: psi + Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + sage: isog in Hom(phi, psi) + True + + This method works for endomorphisms as well:: + + sage: phi.velu(phi(X)) is phi + True + sage: phi.velu(t^6) is phi + True + + The following inputs do not define isogenies, and the method + returns None:: + + sage: phi.velu(0) + sage: phi.velu(t) + sage: phi.velu(t^3 + t + 2) """ - if not candidate in self.ore_polring(): + if not isog in self.ore_polring(): raise TypeError('input must be an Ore polynomial') - if candidate == 0: + if isog == 0: return None - if not self.characteristic().degree().divides(candidate.valuation()): + if not self.characteristic().degree().divides(isog.valuation()): return None - quo, rem = (candidate * self.gen()).right_quo_rem(candidate) + quo, rem = (isog * self.gen()).right_quo_rem(isog) return None if rem != 0 else DrinfeldModule(self._function_ring, quo) def _Hom_(self, other, category): @@ -720,55 +1112,30 @@ def _Hom_(self, other, category): OUTPUT: - an homset + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: t = phi.ore_variable() + sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + sage: psi = phi.velu(isog) + sage: hom = phi._Hom_(psi, category=phi.category()) + sage: hom is Hom(phi, psi) # known bug + True + sage: from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset + sage: isinstance(hom, DrinfeldModuleHomset) + True """ from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset return DrinfeldModuleHomset(self, other, category) - # Rank two methods - - def delta(self): - r""" - If the rank is two, return `\Delta` such that the generator is - `phi_X = \gamma(X) + g\tau + \Delta\tau^2`; if the rank is not - two, raise an exception. - - OUTPUT: - - - an element in the base ring if the rank is two; an - exception is raised otherwise - """ - self._test_rank_two() - return self.gen()[2] - - def g(self): - r""" - If the rank is two, return `g` such that the generator is `phi_X - = \gamma(X) + g\tau + \Delta\tau^2`; if the rank is not two, - raise an exception. - - OUTPUT: - - - an element in the base ring if the rank is two; an - exception is raised otherwise - """ - self._test_rank_two() - return self.gen()[1] - - def j(self): - r""" - If the rank is two, return the j-invariant of the Drinfeld - module; if the rank is not two, raise an exception. - - Write the generator `\phi_X = \gamma(X) + g\tau + \Delta\tau^2`. - The j-invariant is defined by `\frac{g^{q+1}}{\Delta}`, `q` - being the order of the base field of the polynomial ring. In our - case, this base field is always finite, as we force the function - ring to be of the form `\Fq[X]`. - - OUTPUT: - - - an element in the base ring if the rank is two; an - exception is raised otherwise - """ - self._test_rank_two() - return (self.g()**(self._Fq.order()+1)) / self.delta() + def _check_rank_two(self): + r""" + Raise ``NotImplementedError`` if the rank is not two. + """ + if self.rank() != 2: + raise NotImplementedError('the rank must be 2') diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index d6d55fa4706..4fa9c15eea2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -19,7 +19,7 @@ def frobenius_charpoly(self, var='T'): return S([self.frobenius_norm(), -self.frobenius_trace(), 1]) def frobenius_norm(self): - self._test_rank_two() + self._check_rank_two() # Notations from Schost-Musleh: n = self._base_ring.over(self._Fq).degree_over(self._Fq) d = self.characteristic().degree() @@ -28,7 +28,7 @@ def frobenius_norm(self): return ((-1)**n) * (self.characteristic()**m) / norm def frobenius_trace(self): - self._test_rank_two() + self._check_rank_two() # Notations from Schost-Musleh: n = self._base_ring.over(self._Fq).degree_over(self._Fq) B = self.frobenius_norm() @@ -36,9 +36,9 @@ def frobenius_trace(self): return self.invert(t**n + (self(B) // t**n)) def is_ordinary(self): - self._test_rank_two() + self._check_rank_two() return not self.is_supersingular() def is_supersingular(self): - self._test_rank_two() + self._check_rank_two() return self.characteristic().divides(self.frobenius_trace()) From ec1d6bd1c598177f1bea4cc59a570143067a97fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Sat, 13 Aug 2022 15:38:27 +0200 Subject: [PATCH 067/392] Refactor invert method in DrinfeldModule Handle the case when the degree of the input is not a multiple of the rank, and change notations inside the method definition. --- .../drinfeld_modules/drinfeld_module.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 266119365fa..0aaaf206627 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -896,21 +896,26 @@ def invert(self, ore_pol): sage: phi_r5.invert(phi_r5(a)) == a True """ - if not ore_pol in self.ore_polring(): + deg = ore_pol.degree() + r = self.rank() + if not ore_pol in self._ore_polring: raise TypeError('input must be an Ore polynomial') if ore_pol in self._base_ring: return self._Fq(ore_pol) - r = self.rank() - X = self.function_ring().gen() - k = ore_pol.degree() // r - m_lines = [[0 for _ in range(k+1)] for _ in range(k+1)] + if deg % r != 0: + return None + + k = deg // r + X = self._function_ring.gen() + mat_lines = [[0 for _ in range(k+1)] for _ in range(k+1)] for i in range(k+1): phi_X_i = self(X**i) for j in range(i+1): - m_lines[j][i] = phi_X_i[r*j] - m = Matrix(m_lines) - v = vector([list(ore_pol)[r*j] for j in range(k+1)]) - pre_image = self.function_ring()(list((m**(-1)) * v)) + mat_lines[j][i] = phi_X_i[r*j] + mat = Matrix(mat_lines) + vec = vector([list(ore_pol)[r*j] for j in range(k+1)]) + pre_image = self._function_ring(list((mat**(-1)) * vec)) + if self(pre_image) == ore_pol: return pre_image else: From dca2f7964e11e9999196a90dcfcb7dce590c5aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 16 Aug 2022 17:19:20 +0200 Subject: [PATCH 068/392] Create method random_element in DrinfeldModules --- src/sage/categories/drinfeld_modules.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index c7d3ced6d59..12d32cb230b 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -11,6 +11,7 @@ from sage.categories.category import CategoryWithParameters from sage.misc.functional import log +from sage.rings.integer import Integer from sage.rings.morphism import RingHomomorphism from sage.rings.polynomial.polynomial_ring import PolynomialRing_general from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing @@ -72,6 +73,25 @@ def morphism(self): def ore_polring(self): return self._ore_polring + def random_element(self, rank): + + if not isinstance(rank, Integer): + raise TypeError('rank must be a positive integer') + if rank <= 0: + raise ValueError('rank must be a positive integer') + + K = self.base() + coeffs = [self._constant_coefficient] + for _ in range(rank-1): + coeffs.append(K.random_element()) + dom_coeff = 0 + while dom_coeff == 0: + dom_coeff = K.random_element() + coeffs.append(dom_coeff) + + return self(coeffs) + + # Sage methods def _call_(self, gen): From ad98d81dba0cfe02572d3369ad2e6a27a1df0d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 16 Aug 2022 17:19:56 +0200 Subject: [PATCH 069/392] Create method constant_coefficient in DrinfeldModules --- src/sage/categories/drinfeld_modules.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 12d32cb230b..229afc0a36c 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -49,6 +49,8 @@ def __init__(self, morphism, name='t'): tau = K.frobenius_endomorphism(d) self._ore_polring = OrePolynomialRing(K, tau, names=name, polcast=False) + # Create constant coefficient + self._constant_coefficient = gamma(X) # Create characteristic self._characteristic = None if K.is_finite(): @@ -64,6 +66,9 @@ def characteristic(self): raise NotImplementedError return self._characteristic + def constant_coefficient(self): + return self._constant_coefficient + def function_ring(self): return self._function_ring From 20e2eadfb1460fdf3cafff08ab7ae7bc4c7973d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 16 Aug 2022 17:20:08 +0200 Subject: [PATCH 070/392] Refactor _repr_ method in DrinfeldModuleHomset The previous version was really hard to parse. However, I don't know if the current version is standard. --- src/sage/rings/function_field/drinfeld_modules/homset.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 4dddb310049..aa295646e42 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -21,8 +21,9 @@ def __init__(self, X, Y, category=None, check=True): Homset.__init__(self, X, Y, category=category, base=base, check=check) def _repr_(self): - return f'Set of Drinfeld module morphisms from ' \ - f'{self.domain().gen()} to {self.codomain().gen()}' + return f'Set of Drinfeld module morphisms:\n' \ + f' From: {self.domain()}\n' \ + f' To: {self.codomain()}' def __contains__(self, x): try: From d61a0cf2e8330f8d8bacb6c7c3bf52bd2c5a6988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 16 Aug 2022 18:57:38 +0200 Subject: [PATCH 071/392] Apply David's suggestions (ticket 33713 comment 80) See https://trac.sagemath.org/ticket/33713#comment:80. --- .../drinfeld_modules/drinfeld_module.py | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 0aaaf206627..cf7ccec125c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -73,9 +73,9 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): Drinfeld modules are defined in a larger setting, in which `\Fq[X]` is replaced by a more general ring: the ring of functions in `k` that are regular outside `\infty`, where `k` is - a function field whose constant field is `\Fq` and with - transcendance degree `1`, and `\infty` is a fixed place of `k`. - This is out of the scope of this implementation. + a function field over `\Fq` with transcendance degree `1` and + `\infty` is a fixed place of `k`. This is out of the scope of + this implementation. INPUT: @@ -230,7 +230,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for - every $a \in \Fq[X]`. In our case, this is equivalent to verifying + every `a \in \Fq[X]`. In our case, this is equivalent to verifying `f \phi_X = \psi_X f`. An *isogeny* is a non-zero morphism. Use the ``in`` syntax to test if an Ore polynomial defines an @@ -322,7 +322,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: Ltau(phi(X)) == phi_rebased(X) True - Given an Ore polynomial that equals `\phi(a)` for some `a \in + Given an Ore polynomial that equals `\phi_a` for some `a \in \Fq[X]`, we can retrieve `a` (as a morphism, a Drinfeld module is injective, see [Gos1998]_, cor. 4.5.2.):: @@ -360,8 +360,8 @@ def __classcall_private__(cls, function_ring, gen, name='t'): ore_polring = None ore_polring_base = Sequence(gen).universe() else: - raise TypeError('generator must be list of coefficients or an ' \ - 'Ore polynomial') + raise TypeError('generator must be a list of coefficients '\ + 'or an Ore polynomial') # Build the morphism that defines the category if not ore_polring_base.has_coerce_map_from(function_ring.base_ring()): @@ -738,14 +738,12 @@ def __call__(self, a): def change_ring(self, new_field, name=None): r""" - If ``new_field`` is a field extension of the base ring, return a - new Drinfeld module that extends ``self`` to the base ring - ``new_field``. + Return a Drinfeld module defined like ``self``, but with base + ring ``new_field``. - Let `f` be the morphism that defines ``self``, let `i` be the - inclusion of ``self.ore_polring()`` into the Ore pol. ring whose - base is ``new_field``. The morphism that defines the new - Drinfeld module is the composition `i \circ f`. + The new base can either be a field extension of the base ring, + or field that has a coercion map from the field of definitions + of the coefficients of the generator. INPUT: @@ -832,8 +830,8 @@ def height(self): def invert(self, ore_pol): r""" - Find the inverse of ``ore_pol`` by the morphism that defines the - Drinfeld module. If ``ore_pol`` is not in the image of the + Return the inverse ``ore_pol`` by the morphism that defines the + Drinfeld module; if ``ore_pol`` is not in the image of the morphism, return ``None``. Said otherwise, return `a` if ``ore_pol`` is `phi_a`, otherwise @@ -923,7 +921,7 @@ def invert(self, ore_pol): def is_finite(self): r""" - Return ``True`` if the Drinfeld module is finite, return + Return ``True`` if the Drinfeld module is finite; return ``False`` otherwise. OUTPUT: @@ -949,10 +947,9 @@ def is_finite(self): def j(self): r""" - Return the j-invariant of the Drinfeld module. - - Only the rank two case has been implemented. An - NotImplementedError is raised if the rank is not two. + Return the j-invariant of the Drinfeld module; only the rank two + case has been implemented, a NotImplementedError is raised if + the rank is not two. Assume the rank is two. Write the generator `\phi_X = \gamma(X) + g\tau + \Delta\tau^2`. The j-invariant is defined by @@ -1028,7 +1025,7 @@ def rank(self): def velu(self, isog): r""" Return a new Drinfeld module such that ``isog`` is an - isogeny to this module with domain ``self``. If no such isogeny + isogeny to this module with domain ``self``; if no such isogeny exists, return ``None``. If the input is zero, return ``None``, as an isogeny is From f8e34f2d2ccea453f0cc98ca0fe6d154b352c10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 17 Aug 2022 13:15:59 +0200 Subject: [PATCH 072/392] Make DrinfeldModuleMorphism inherit UniqueRepresentation Thanks Travis for the help. Not too confident about the code here, but preliminary tests pass. --- .../function_field/drinfeld_modules/homset.py | 5 +++- .../drinfeld_modules/morphism.py | 30 +++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index aa295646e42..595a8836df2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -33,4 +33,7 @@ def __contains__(self, x): return False def _element_constructor_(self, *args, **kwds): - return self.element_class(self, *args, **kwds) + # NOTE: This used to be self.element_class(self, ...), but this + # would call __init__ instead of __classcall_private__. This + # seems to work, but I don't know what I'm doing. + return DrinfeldModuleMorphism(self, *args, **kwds) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 4a7e4f175f7..c1d6b29404e 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -8,17 +8,22 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -# from sage.categories.morphism import Morphism +from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass from sage.structure.element import Element +from sage.structure.unique_representation import UniqueRepresentation from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing from sage.categories.drinfeld_modules import DrinfeldModules -class DrinfeldModuleMorphism(Element): +class DrinfeldModuleMorphism(UniqueRepresentation, Element, + metaclass=InheritComparisonClasscallMetaclass): - def __init__(self, parent, x): - super().__init__(parent) + @staticmethod + def __classcall_private__(cls, parent, x): + from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset + if not isinstance(parent, DrinfeldModuleHomset): + raise TypeError('parent should be a DrinfeldModuleHomset') domain = parent.domain() codomain = parent.codomain() # NOTE: it used to be x.parent() is parent, but this was False. @@ -30,18 +35,13 @@ def __init__(self, parent, x): ore_pol = domain.ore_polring()(x) if ore_pol * domain.gen() != codomain.gen() * ore_pol: raise ValueError('the Ore polynomial does not define a morphism') - self._domain = domain - self._codomain = codomain - self._ore_polynomial = ore_pol + return cls.__classcall__(cls, parent, ore_pol) - # NOTE: Should I inherit UniqueRepresentation to avoid this? - def __eq__(self, other): - try: - if self.parent() == other.parent(): - return self.ore_polynomial() == other.ore_polynomial() - except AttributeError: - return False - return False + def __init__(self, parent, ore_pol): + Element.__init__(Element(parent), parent) + self._domain = parent.domain() + self._codomain = parent.codomain() + self._ore_polynomial = ore_pol def _repr_(self): return f'Drinfeld Module morphism:\n' \ From e9b8913dd4abfcb3adbe2aa47df27fa534a00eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 17 Aug 2022 13:47:35 +0200 Subject: [PATCH 073/392] Create _latex_ method in DrinfeldModuleMorphism --- .../rings/function_field/drinfeld_modules/morphism.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index c1d6b29404e..3e764661cb1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -9,6 +9,7 @@ #***************************************************************************** from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass +from sage.misc.latex import latex from sage.structure.element import Element from sage.structure.unique_representation import UniqueRepresentation from sage.rings.polynomial.ore_polynomial_element import OrePolynomial @@ -43,6 +44,14 @@ def __init__(self, parent, ore_pol): self._codomain = parent.codomain() self._ore_polynomial = ore_pol + def _latex_(self): + return f'\\begin{{array}}{{l}}\n' \ + f'\\text{{Drinfeld{{ }}module{{ }}morphism:}}\\\\\n' \ + f'\\text{{{{ }}{{ }}From:{{ }}}}{latex(self._domain)}}}\\\\\n' \ + f'\\text{{{{ }}{{ }}To:{{ }}}}{{ }}{{ }}{latex(self._codomain)}\\\\\n' \ + f'\\text{{{{ }}{{ }}Defn:{{ }}}}{latex(self._ore_polynomial)}\n' \ + f'\\end{{array}}' + def _repr_(self): return f'Drinfeld Module morphism:\n' \ f' From: {self._domain}\n' \ From 6e4595b0a4b0a1343a18a6dcecd38912ec08fc22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 17 Aug 2022 14:30:47 +0200 Subject: [PATCH 074/392] (fix) Remove delta() in FiniteDrinfeldModule --- .../function_field/drinfeld_modules/finite_drinfeld_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 4fa9c15eea2..92b93509661 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -24,7 +24,8 @@ def frobenius_norm(self): n = self._base_ring.over(self._Fq).degree_over(self._Fq) d = self.characteristic().degree() m = n // d - norm = self._base_ring.over(self._Fq)(self.delta()).norm() + delta = self._gen[2] + norm = self._base_ring.over(self._Fq)(delta).norm() return ((-1)**n) * (self.characteristic()**m) / norm def frobenius_trace(self): From 6e5cd2d42441cabcb2a65fb7b7afd3c041bbbaca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 17 Aug 2022 14:25:02 +0200 Subject: [PATCH 075/392] Write docstrings for DrinfeldModuleMorphism Docstrings for: - the module; - the class; - the methods. Also, I reordered the methods in the class definition, to comply with the SageMath source. --- .../drinfeld_modules/morphism.py | 247 +++++++++++++++++- 1 file changed, 244 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 3e764661cb1..02de4a781cb 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -1,3 +1,13 @@ +r""" +Drinfeld module morphisms + +This module provides the class +:class:`sage.rings.function_fields.drinfeld_module.morphism.DrinfeldModuleMorphism`. + +AUTHORS: +- Antoine Leudière (2022-04) +""" + #***************************************************************************** # Copyright (C) 2022 Antoine Leudière # @@ -19,6 +29,78 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, metaclass=InheritComparisonClasscallMetaclass): + r""" + Let `\phi,\psi` be two Drinfeld modules defined over the + `\Fq[X]`-field `K`. A *morphism of Drinfeld modules `\phi \to \psi`* + is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a + f` for every `a \in \Fq[X]`. In our case, this is equivalent to + verifying `f \phi_X = \psi_X f`. An *isogeny* is a non-zero + morphism. + + A Drinfeld module morphism is represented by instances of the class + `DrinfeldModuleMorphism`. + + To create a morphism object, do not explicitely use + `DrinfeldModuleMorphism`, but rather call the parent homset with the + defining Ore polynomial:: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: t = phi.ore_variable() + sage: ore_pol = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + sage: psi = phi.velu(ore_pol) + sage: morphism = Hom(phi, psi)(ore_pol) + sage: morphism + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + To: Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + + We can get basic data on the morphism:: + + sage: morphism.domain() + Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + sage: morphism.domain() is phi + True + + sage: morphism.codomain() + Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + sage: morphism.codomain() is psi + True + + sage: morphism.ore_polynomial() + t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + sage: morphism.ore_polynomial() is ore_pol + True + + We can check various properties:: + + sage: morphism.is_zero() + False + sage: morphism.is_isogeny() + True + sage: morphism.is_endomorphism() + False + sage: morphism.is_isomorphism() + False + + .. NOTE:: + + For the sake of completness, we explain how the user can + directly instanciate the class:: + + sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism + sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + To: Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) is morphism + True + """ @staticmethod def __classcall_private__(cls, parent, x): @@ -59,19 +141,178 @@ def _repr_(self): f' Defn: {self._ore_polynomial}' def codomain(self): + r""" + Return the codomain of the morphism. + + EXAMPLES: + + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_variable() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism.codomain() + Drinfeld module defined by X |--> t^2 + (z6^4 + z6^2 + 1)*t + z6 over Finite Field in z6 of size 2^6 + sage: morphism.codomain() is psi + True + """ return self._codomain def domain(self): - return self._domain + r""" + Return the codomain of the morphism. - def ore_polynomial(self): - return self._ore_polynomial + EXAMPLES: + + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_variable() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism.domain() + Drinfeld module defined by X |--> t^2 + t + z6 over Finite Field in z6 of size 2^6 + sage: morphism.domain() is phi + True + """ + return self._domain def is_zero(self): + r""" + Return the codomain of the morphism. + + EXAMPLES: + + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_variable() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism.is_zero() + False + + sage: zero_morphism = End(phi)(0) + sage: zero_morphism.is_zero() + True + """ return self._ore_polynomial.is_zero() + def is_endomorphism(self): + r""" + Return True if the morphism is an endomorphism; return False + otherwise. + + EXAMPLES: + + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_variable() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism.is_endomorphism() + False + + sage: zero_morphism = End(phi)(0) + sage: zero_morphism.is_endomorphism() + True + + sage: identity_morphism = End(phi)(1) + sage: identity_morphism.is_endomorphism() + True + + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism.is_endomorphism() + True + """ + return self._domain is self._codomain + def is_isogeny(self): + r""" + Return True if the morphism is an isogeny; return False + otherwise. + + EXAMPLES: + + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_variable() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism.is_isogeny() + True + + sage: zero_morphism = End(phi)(0) + sage: zero_morphism.is_isogeny() + False + + sage: identity_morphism = End(phi)(1) + sage: identity_morphism.is_isogeny() + True + + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism.is_isogeny() + True + """ return not self.is_zero() def is_isomorphism(self): + r""" + Return True if the morphism is an isomorphism; return False + otherwise. + + EXAMPLES: + + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_variable() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism.is_isomorphism() + False + + sage: zero_morphism = End(phi)(0) + sage: zero_morphism.is_isomorphism() + False + + sage: identity_morphism = End(phi)(1) + sage: identity_morphism.is_isomorphism() + True + + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism.is_isomorphism() + False + """ return self.is_isogeny() and self._ore_polynomial.degree() == 0 + + def ore_polynomial(self): + r""" + Return the Ore polynomial that defines the morphism. + + EXAMPLES: + + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_variable() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: ore_pol = morphism.ore_polynomial() + sage: ore_pol + t + z6^5 + z6^2 + 1 + + sage: ore_pol * phi(X) == psi(X) * ore_pol + True + """ + return self._ore_polynomial From 5decb91d4789727ac795d6c0fafc331fe8fdd8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 18 Aug 2022 11:47:05 +0200 Subject: [PATCH 076/392] Define __init__ and self._frobenius_trace/norm for FiniteDrinfeldModule I wanted to cache the Frobenius trace and norm, as to compute them only once. For this, I needed to add self._frobenius_norm = None self._frobenius_trace = None in __init__. However, __init__ did not exist (we simply did not need it), so I created it. If there is any better way to set self._frobenius_norm and self._frobenius_trace at init, please tell me. --- .../finite_drinfeld_module.py | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 92b93509661..050b48d1fa3 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -3,6 +3,16 @@ class FiniteDrinfeldModule(DrinfeldModule): + def __init__(self, gen, category): + + # NOTE: There used to be no __init__ here (which was fine). I + # added one to ensure that FiniteDrinfeldModule would always + # have _frobenius_norm and _frobenius_trace attributes. + + super().__init__(gen, category) + self._frobenius_norm = None + self._frobenius_trace = None + def frobenius_endomorphism(self): t = self.ore_variable() L = self._base_ring @@ -21,20 +31,24 @@ def frobenius_charpoly(self, var='T'): def frobenius_norm(self): self._check_rank_two() # Notations from Schost-Musleh: - n = self._base_ring.over(self._Fq).degree_over(self._Fq) - d = self.characteristic().degree() - m = n // d - delta = self._gen[2] - norm = self._base_ring.over(self._Fq)(delta).norm() - return ((-1)**n) * (self.characteristic()**m) / norm + if self._frobenius_norm is None: + n = self._base_ring.over(self._Fq).degree_over(self._Fq) + d = self.characteristic().degree() + m = n // d + delta = self._gen[2] + norm = self._base_ring.over(self._Fq)(delta).norm() + self._frobenius_norm = ((-1)**n) * (self.characteristic()**m) / norm + return self._frobenius_norm def frobenius_trace(self): self._check_rank_two() # Notations from Schost-Musleh: - n = self._base_ring.over(self._Fq).degree_over(self._Fq) - B = self.frobenius_norm() - t = self.ore_polring().gen() - return self.invert(t**n + (self(B) // t**n)) + if self._frobenius_trace is None: + n = self._base_ring.over(self._Fq).degree_over(self._Fq) + B = self.frobenius_norm() + t = self.ore_polring().gen() + self._frobenius_trace = self.invert(t**n + (self(B) // t**n)) + return self._frobenius_trace def is_ordinary(self): self._check_rank_two() From daf9b8a45624b8d7ff0ec9a54a20d8b96eaf0661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 18 Aug 2022 11:50:54 +0200 Subject: [PATCH 077/392] (minor) Add small enhancements to DrinfeldModule doc --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index cf7ccec125c..254a30f05a6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -39,6 +39,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): r""" + This class handles Drinfeld modules. + Let `q` be the order of a finite field `\Fq`. Let `K` be a field equiped a ring morphism `\gamma: \Fq[X] \to K` --- the field `K` is said to be an *`\Fq[X]`-field*, and the monic polynomial that @@ -872,7 +874,8 @@ def invert(self, ore_pol): ALGORITHM: - See [MS2019]_, 3.2.5. + The algorithm relies on the inversion of a linear algebra + system. See [MS2019]_, 3.2.5 for details. TESTS:: From f16c65afdf4ca6ee7e7a5664f66fc85cc60c2c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 18 Aug 2022 11:52:38 +0200 Subject: [PATCH 078/392] Write docstrings for FiniteDrinfeldModule --- .../finite_drinfeld_module.py | 343 ++++++++++++++++++ 1 file changed, 343 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 050b48d1fa3..68921a0f508 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -1,7 +1,103 @@ +r""" +Finite Drinfeld modules + +This module provides the class +:class:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.FiniteDrinfeldModule`, +which inherits +:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. + +AUTHORS: + +- Antoine Leudière (2022-04) +""" + +#***************************************************************************** +# Copyright (C) 2022 Antoine Leudière +# +# 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. +# http://www.gnu.org/licenses/ +#***************************************************************************** + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule class FiniteDrinfeldModule(DrinfeldModule): + r""" + This class handles finite Drinfeld modules. + + A *finite Drinfeld module* is a Drinfeld module whose base ring is + finite. For general definitions and help on Drinfeld modules, see + class + :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. + In this specific documentation, we only present the specifics of + ``FiniteDrinfeldModle``. + + The user does not ever need to directly call + ``FiniteDrinfeldModule``, as it is the (meta)class + ``DrinfeldModule`` that is reponsible for instanciating + ``DrinfeldModule`` or ``FiniteDrinfeldModule`` depending on its + input:: + + sage: Fq = GF(343) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z6, 0, 5]) + sage: isinstance(phi, DrinfeldModule) + True + sage: from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule + sage: isinstance(phi, FiniteDrinfeldModule) + True + + But, the user should never use ``FiniteDrinfeldModule`` to test if a + Drinfeld module is finite, but rather the ``is_finite`` method:: + + sage: phi.is_finite() + True + + .. RUBRIC:: Complex multiplication of rank two finite Drinfeld modules + + We can handle some aspects of the theory of complex multiplication + of finite Drinfeld modules. Apart from the method + ``frobenius_endomorphism``, we only handle rank two Drinfeld + modules. + + First of all, it is easy to create the Frobenius endomorphism:: + + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> 5*t^2 + z6 over Finite Field in z6 of size 7^6 + To: Drinfeld module defined by X |--> 5*t^2 + z6 over Finite Field in z6 of size 7^6 + Defn: t^2 + + Its characteristic polynomial can be computed:: + + sage: chi = phi.frobenius_charpoly() + sage: chi + T^2 + (X + 2*z3^2 + 2*z3 + 1)*T + 2*X^2 + (z3^2 + z3 + 4)*X + 2*z3 + sage: frob_pol = frobenius_endomorphism.ore_polynomial() + sage: chi(frob_pol, phi(X)) + 0 + + This makes it possible to compute the Frobenius trace and norm:: + + sage: phi.frobenius_trace() + 6*X + 5*z3^2 + 5*z3 + 6 + sage: phi.frobenius_trace() == -chi[1] + True + sage: phi.frobenius_norm() + 2*X^2 + (z3^2 + z3 + 4)*X + 2*z3 + + And to decide if a Drinfeld module is ordinary or supersingular:: + + sage: phi.is_ordinary() + True + sage: phi.is_supersingular() + False + """ def __init__(self, gen, category): @@ -14,6 +110,34 @@ def __init__(self, gen, category): self._frobenius_trace = None def frobenius_endomorphism(self): + r""" + Return the Frobenius endomorphism, as an instance of + ``DrinfeldModuleMorphism``, of the Drinfeld module, if the rank + is two; raise a NotImplementedError otherwise.. + + Let `q` be the order of the base field of the function ring. The + *Frobenius endomorphism* is defined as the endomorphism whose + defining Ore polynomial is `t^q`. + + OUTPUT: + + - a Drinfeld module morphism + + EXAMPLES: + + sage: Fq = GF(343) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi.frobenius_endomorphism() + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> z6*t^2 + 1 over Finite Field in z6 of size 7^6 + To: Drinfeld module defined by X |--> z6*t^2 + 1 over Finite Field in z6 of size 7^6 + Defn: t^2 + sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism + sage: isinstance(phi.frobenius_endomorphism(), DrinfeldModuleMorphism) + True + """ t = self.ore_variable() L = self._base_ring Fq = self._function_ring.base_ring() @@ -21,6 +145,74 @@ def frobenius_endomorphism(self): return self._Hom_(self, category=self.category())(t**deg) def frobenius_charpoly(self, var='T'): + r""" + Return the characteristic polynomial of the Frobenius + endomorphism, if the rank is two; raise a NotImplementedError + otherwise. + + Let `\Fq` be the base field of the function ring. The + *characteristic polynomial `\chi` of the Frobenius endomorphism* is + defined in [Gek1991]_. An important feature of this polynomial + is that it is a monic bivariate polynomial in `T` with + coefficients in `\Fq[X]`. Write `\chi = T^2 - A(X)T + B(X)`, let + `t^n` be the Ore polynomial that defines the Frobenius + endomorphism of `\phi`; by definition, `n` is the degree of the + base ring over `\Fq`. We have `\chi(t^n)(\phi(X)) = t^{2n} - + \phi_A t^n + \phi_B = 0`, with `\deg(A) \leq \frac{n}{2}` and + `\deg(B) = n`. + + Note that the *Frobenius trace* is defined as `A(X)` and the + *Frobenius norm` is defined as `B(X)`. + + INPUT: + + - ``var`` -- (optional) the name of the second variable + + OUTPUT: + + - a polynomial in `\Fq[X][T]` + + EXAMPLES: + + sage: Fq = GF(343) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: chi = phi.frobenius_charpoly() + sage: chi + T^2 + ((3*z3^2 + z3 + 4)*X + 4*z3^2 + 6*z3 + 3)*T + (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 + + sage: frob_pol = phi.frobenius_endomorphism().ore_polynomial() + sage: chi(frob_pol, phi(X)) + 0 + + sage: A = phi.frobenius_trace() + sage: A + (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 + sage: B = phi.frobenius_norm() + sage: B + (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 + + sage: n = 2 # Degree of the base ring over `\Fq` + sage: A.degree() <= n/2 + True + sage: B.degree() == n + True + + ALGORITHM: + + We compute the Frobenius norm, and with it the Frobenius + trace. This gives the Frobenius characteristic polynomial. + See [SM2019]_, Section 4. + + See docstrings of methods + :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.FiniteDrinfeldModule.frobenius_norm` + and + :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.FiniteDrinfeldModule.frobenius_trace` + for furthere details on the computation of the norm and of + the trace. + """ + self._check_rank_two() A = self._function_ring # Fq[X] S = PolynomialRing(A, name=var) # Fq[X][T] # Does not work when Fq is not a prime field... @@ -29,6 +221,43 @@ def frobenius_charpoly(self, var='T'): return S([self.frobenius_norm(), -self.frobenius_trace(), 1]) def frobenius_norm(self): + r""" + Return Frobenius norm of the Drinfeld module, if the rank is + two; raise a NotImplementedError otherwise. + + Write `\chi = T^2 - A(X)T + B(X) \in \Fq[X][T]` to be the + characteristic polynomial of the Frobenius endomorphism. The + *Frobenius norm* is defined as the polynomial `B(X) \in \Fq[X]`. + + Let `n` be the degree of the base ring over `\Fq`. Then the + Frobenius norm has degree `n`. + + OUTPUT: + + - a polynomial in `\Fq[X]` + + EXAMPLES: + + sage: Fq = GF(343) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: B = phi.frobenius_norm() + sage: B + (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 + + sage: n = 2 # Degree of the base ring over `\Fq` + sage: B.degree() == n + True + + sage: B == phi.frobenius_charpoly()[0] + True + + ALGORITHM: + + The Frobenius norm is computed using the formula, by + Gekeler, given in [SM2019]_, Section 4, Proposition 3. + """ self._check_rank_two() # Notations from Schost-Musleh: if self._frobenius_norm is None: @@ -41,6 +270,52 @@ def frobenius_norm(self): return self._frobenius_norm def frobenius_trace(self): + r""" + Return Frobenius norm of the Drinfeld module, if the rank is + two; raise a NotImplementedError otherwise. + + Write `\chi = T^2 - A(X)T + B(X) \in \Fq[X][T]` to be the + characteristic polynomial of the Frobenius endomorphism. The + *Frobenius norm* is defined as the polynomial `B(X) \in \Fq[X]`. + + Let `n` be the degree of the base ring over `\Fq`. Then the + Frobenius trace has degree `\leq \frac{n}{2}`. + + OUTPUT: + + - a polynomial in `\Fq[X]` + + ALGORITHM: + + Let `A(X)` denote the Frobenius trace and `B(X)` denote the + Frobenius norm. We begin by computing `B(X)`, see docstring + of method + :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.FiniteDrinfeldModule.frobenius_norm` + for details. The characteristic polynomial of the Frobenius + yields `t^{2n} - \phi_A t^n + \phi_B = 0`, where `t^n` is + the Frobenius endomorphism. As `\phi_B` is now known, we can + compute `\phi_A = (t^{2n} + \phi_B) / t^n`. We get `A(X)` by + inverting this quantity, using the method + :meth:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule.invert`, + see its docstring for details. + + EXAMPLES: + + sage: Fq = GF(343) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: A = phi.frobenius_trace() + sage: A + (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 + + sage: n = 2 # Degree of the base ring over `\Fq` + sage: A.degree() <= n/2 + True + + sage: A == -phi.frobenius_charpoly()[1] + True + """ self._check_rank_two() # Notations from Schost-Musleh: if self._frobenius_trace is None: @@ -51,9 +326,77 @@ def frobenius_trace(self): return self._frobenius_trace def is_ordinary(self): + r""" + Return True if the Drinfeld module is ordinary, return False + otherwise; raise a NotImplementedError if the rank is not two. + + A rank two finite Drinfeld module is *ordinary* if and only if + the `\Fq[X]-characteristic of the base ring does not devide the + Frobenius trace. A *supersingular* rank two finite Drinfeld + module is a Drinfeld module that is not ordinary. + + OUTPUT: + + - a boolean + + EXAMPLES: + + sage: Fq = GF(343) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi.is_ordinary() + False + sage: phi_p = phi(phi.characteristic()) + sage: phi_p # Purely inseparable + z6*t^2 + + ALGORITHM: + + Compute the Frobenius trace and test if the `\Fq[X]` + characteristic divides it. + + We could also test if the image of the + `\Fq[X]`-characteristic under the Drinfeld module is purely + inseparable; see [Gek1991]_, Proposition 4.1. + """ self._check_rank_two() return not self.is_supersingular() def is_supersingular(self): + r""" + Return True if the Drinfeld module is supersingular, return False + otherwise; raise a NotImplementedError if the rank is not two. + + A rank two finite Drinfeld module is *supersingular* if and only + if the `\Fq[X]-characteristic of the base ring devides the + Frobenius trace. An *ordinary* rank two finite Drinfeld module + is a Drinfeld module that is not supersingular. + + OUTPUT: + + - a boolean + + EXAMPLES: + + sage: Fq = GF(343) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi.is_supersingular() + True + sage: phi_p = phi(phi.characteristic()) + sage: phi_p # Purely inseparable + z6*t^2 + + ALGORITHM: + + Compute the Frobenius trace and test if the `\Fq[X]` + characteristic divides it. + + We could also test if the image of the + `\Fq[X]`-characteristic under the Drinfeld module is purely + inseparable; see [Gek1991]_, Proposition 4.1. + """ self._check_rank_two() return self.characteristic().divides(self.frobenius_trace()) From a1e569d665a9272bf116f5d632a5038015f2cd16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 18 Aug 2022 15:36:48 +0200 Subject: [PATCH 079/392] Refactor exception messages for Drinfeld modules Simplify and shorten messages. --- src/sage/categories/drinfeld_modules.py | 10 +++++----- .../function_field/drinfeld_modules/action.py | 2 +- .../drinfeld_modules/drinfeld_module.py | 17 +++++++++-------- .../function_field/drinfeld_modules/morphism.py | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 229afc0a36c..b4cd81bf3ef 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -27,23 +27,23 @@ def __init__(self, morphism, name='t'): gamma = morphism # Check input is a ring Morphism if not isinstance(gamma, RingHomomorphism): - raise TypeError('input must be a Ring morphism') + raise TypeError('category input must be a Ring morphism') self._morphism = morphism self._function_ring = gamma.domain() # Check domain is Fq[X] function_ring = self._function_ring if not isinstance(function_ring, PolynomialRing_general): - raise NotImplementedError('domain must be a polynomial ring') + raise NotImplementedError('function ring must be a polynomial ring') function_ring_base = function_ring.base_ring() if not function_ring_base.is_field() or not function_ring_base.is_finite() : - raise TypeError('the base ring of the domain must be a finite field') + raise TypeError('function ring base must be a finite field') Fq = function_ring_base FqX = function_ring X = FqX.gen() # Check codomain of gamma is field K = gamma.codomain() if not K.is_field(): - raise TypeError('the codomain of the morphism must be a field') + raise TypeError('base must be a field') # Build K{t} d = log(Fq.cardinality(), Fq.characteristic()) tau = K.frobenius_endomorphism(d) @@ -105,7 +105,7 @@ def _call_(self, gen): # If gen is not in the codomain, an exception is raised gen = self._ore_polring(gen) if self.characteristic()(gen[0]) != 0: - raise ValueError('constant coefficient is not a root of the characteristic') + raise ValueError('constant coefficient must be a root of the characteristic') return DrinfeldModule(self._function_ring, gen) def _repr_(self): diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 06ed070c7f5..82f1ee0d207 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -7,7 +7,7 @@ class DrinfeldModuleAction(Action): def __init__(self, finite_drinfeld_module): # Verifications if not isinstance(finite_drinfeld_module, DrinfeldModule): - raise TypeError('First argument must be a DrinfeldModule') + raise TypeError('input must be a DrinfeldModule') # Work self.__finite_drinfeld_module = finite_drinfeld_module super().__init__(finite_drinfeld_module.polring(), diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 254a30f05a6..9f63a8a321f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -343,10 +343,10 @@ def __classcall_private__(cls, function_ring, gen, name='t'): # here and in the category constructor, which is not ideal. # Check domain is Fq[X] if not isinstance(function_ring, PolynomialRing_general): - raise NotImplementedError('domain must be a polynomial ring') + raise NotImplementedError('function ring must be a polynomial ring') function_ring_base = function_ring.base_ring() if not function_ring_base.is_field() or not function_ring_base.is_finite() : - raise TypeError('the base ring of the domain must be a finite field') + raise TypeError('function ring base must be a finite field') Fq = function_ring_base FqX = function_ring X = FqX.gen() @@ -362,13 +362,14 @@ def __classcall_private__(cls, function_ring, gen, name='t'): ore_polring = None ore_polring_base = Sequence(gen).universe() else: - raise TypeError('generator must be a list of coefficients '\ - 'or an Ore polynomial') + raise TypeError('generator must be list of coefficients or Ore ' \ + 'polynomial') + # The coefficients are in a base ring that has coercion from Fq: + if not (hasattr(ore_polring_base, 'has_coerce_map_from') and \ + ore_polring_base.has_coerce_map_from(function_ring.base_ring())): + raise ValueError('function ring base must coerce to base ring') # Build the morphism that defines the category - if not ore_polring_base.has_coerce_map_from(function_ring.base_ring()): - raise TypeError('base ring of function ring must coerce to base ' \ - 'ring of Ore polynomial ring') gamma = function_ring.hom([ore_polring_base(gen[0])]) # Other checks in the category definition @@ -1143,4 +1144,4 @@ def _check_rank_two(self): Raise ``NotImplementedError`` if the rank is not two. """ if self.rank() != 2: - raise NotImplementedError('the rank must be 2') + raise NotImplementedError('rank must be 2') diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 02de4a781cb..b7792a5a907 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -117,7 +117,7 @@ def __classcall_private__(cls, parent, x): else: # x is an Ore polynomial ore_pol = domain.ore_polring()(x) if ore_pol * domain.gen() != codomain.gen() * ore_pol: - raise ValueError('the Ore polynomial does not define a morphism') + raise ValueError('Ore polynomial does not define a morphism') return cls.__classcall__(cls, parent, ore_pol) def __init__(self, parent, ore_pol): From 5e6f4c72d724b7364a2b168ef6a1a909fc598b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 18 Aug 2022 15:39:45 +0200 Subject: [PATCH 080/392] Refactor invert method in DrinfeldModule Raise an exception when the input is not in the image of the Drinfeld module, instead of returning None. --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 9f63a8a321f..d6d60540781 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -905,7 +905,7 @@ def invert(self, ore_pol): if ore_pol in self._base_ring: return self._Fq(ore_pol) if deg % r != 0: - return None + raise ValueError('input must be in the image of the Drinfeld module') k = deg // r X = self._function_ring.gen() From 880ab6714dea3070600e48d6b122347efad50b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 18 Aug 2022 15:40:38 +0200 Subject: [PATCH 081/392] Proofread docstrings in DrinfeldModule No fundamental change, only rephrasing and enhancements. --- .../drinfeld_modules/drinfeld_module.py | 363 +++++++++++------- 1 file changed, 228 insertions(+), 135 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index d6d60540781..f3b69307931 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -41,25 +41,26 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): r""" This class handles Drinfeld modules. - Let `q` be the order of a finite field `\Fq`. Let `K` be a field + Let `\Fq` be a finite field with order `q`. Let `K` be a field equiped a ring morphism `\gamma: \Fq[X] \to K` --- the field `K` is said to be an *`\Fq[X]`-field*, and the monic polynomial that generates `\Ker(\gamma)` is called the *`\Fq[X]`-characteristic of - the `\Fq[X]`-field `K`* (this `\Fq[X]`-characteristic plays the role in - `\Fq[X]` of the standard characteristic, in `\ZZ`, of a finite - field). Let `K\{\tau\}` be the ring of Ore polynomials with - coefficients in `K` and Frobenius variable `\tau: x \mapsto x^q`. A - *Drinfeld `\Fq[X]`-module over the `\Fq[X]`-field `K`* is a ring - morphism `\phi: \Fq[X] \to K\{\tau\}` such that: + the `\Fq[X]`-field `K`* (this characteristic plays the role of the + characteristic of a function field). Let `K\{\tau\}` be the ring of + Ore polynomials with coefficients in `K` and Frobenius + variable `\tau: x \mapsto x^q`. A *Drinfeld `\Fq[X]`-module over the + `\Fq[X]`-field `K`* is a ring morphism `\phi: \Fq[X] \to K\{\tau\}` + such that: - 1. The image of `\phi` has non-constant Ore polynomials. - 2. For every `a \in \Fq[X]`, the constant coefficient of the - Ore polynomial `\phi(a)` is `\gamma(a)`. + 1. The image of `\phi` contains non-constant Ore polynomials. + 2. For every `a \in \Fq[X]`, the constant coefficient `\phi(a)` + is `\gamma(a)`. For `a \in \Fq[X]`, `\phi(a)` is denoted `\phi_a`. - We say that `K` is the *base ring of `\phi`*, `\Fq[X]` is the - *function ring of*, *K\{\tau\}* is the *Ore polynomial ring*, + We say that `\Fq[X]` is the *function ring of `\phi`*; `K` is the + *base ring of `\phi`*, or simply its base or base field; `\Fq[X]` is + the *function ring of*; *K\{\tau\}* is the *Ore polynomial ring*; `t` is the *Ore variable*. The *generator of `\phi`* is `\phi_X`, its *constant coefficient* is the constant coefficient of `\phi_X`. @@ -86,7 +87,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): Ore polynomial - ``name`` (optional) the name of the Ore variable - .. RUBRIC:: Construction + .. RUBRIC:: Construction and input A Drinfeld module object (class :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`) @@ -95,39 +96,142 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: Fq. = GF(3^2) sage: FqX. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [K.gen(), 1, 1]) + sage: phi = DrinfeldModule(FqX, [z, 1, 1]) sage: phi Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 - In this example, we used a list of coefficients (``[K.gen(), 1, - 1]``) to represent the Ore polynomial `\phi_X = z + t + t^2`, `K - = \Fq(z)`. We can also use regular Ore polynomials:: + In this example, we used a list of coefficients (``[z, 1, 1]``) to + represent the generator `\phi_X = z + t + t^2`, `K = \Fq(z)`. One can + also use regular Ore polynomials:: sage: ore_polring = phi.ore_polring() - sage: t = phi.ore_variable() # Equals ore_polring.gen() - sage: psi_X = K.gen() + t^3 + sage: t = phi.ore_variable() # Is ore_polring.gen() + sage: psi_X = z + t^3 sage: psi = DrinfeldModule(FqX, psi_X) sage: psi Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 3^12 + sage: psi(X) == psi_X + True + + Naturally, the input is checked, and exceptions are raised when the + input is invalid. + + The generator must have positive degree:: + + sage: DrinfeldModule(FqX, [z]) + Traceback (most recent call last): + ... + ValueError: generator must have positive degree + + The coefficients of the generator must live in some field `K` that + is the codomain of a morphism `\gamma` with domain `\Fq[X]`:: + + sage: DrinfeldModule(FqX, [z, QQ(1)]) + Traceback (most recent call last): + ... + ValueError: function ring base must coerce to base ring + + sage: DrinfeldModule(FqX, [1, QQ(1)]) + Traceback (most recent call last): + ... + ValueError: function ring base must coerce to base ring + + If the coefficients are regular integers, an exception is raised. + One needs to manually cast them to the field of their choice:: + + sage: DrinfeldModule(FqX, [1, 1, 1]) + Traceback (most recent call last): + ... + ValueError: function ring base must coerce to base ring + + sage: DrinfeldModule(FqX, [K(1), 1, 1]) + Drinfeld module defined by X |--> t^2 + t + 1 over Finite Field in z of size 3^12 + + sage: DrinfeldModule(FqX, [Fq(1), 1, 1]) + Drinfeld module defined by X |--> t^2 + t + 1 over Finite Field in z2 of size 3^2 + + The function ring must be an `\Fq[X]`:: + + sage: DrinfeldModule(K, [z, 1, 1]) + Traceback (most recent call last): + ... + NotImplementedError: function ring must be a polynomial ring + + sage: FqXY. = FqX[] + sage: DrinfeldModule(FqXY, [z, 1, 1]) + Traceback (most recent call last): + ... + TypeError: function ring base must be a finite field + + If you already defined a category of Drinfeld modules, you must + ensure that the constant coefficient is a root of the + `\Fq[X]-characteristic of the category base:: + + sage: cat = phi.category() + sage: cat([1, 1, K(1)]) + Traceback (most recent call last): + ... + ValueError: constant coefficient must be a root of the characteristic .. NOTE:: - If the Ore polynomial has coefficients in the integers, the - constructor does not try to guess if the user wants to see the - coefficients as elements of Fq, or an extension like `K`. + The reader may think that it is odd to build a Drinfeld module + without specifying the `\Fq[X]`-characteristic of the base (or, + more generally, its parent category). Indeed, the + `\Fq[X]`-characteristic plays the role of the characteristic of + a function field, and thus preexists Drinfeld modules. The base + field `K` should rather be seen as an `\Fq[X]`-field, i.e. the + field `K` equiped with a morphism `\gamma: \Fq[X] \to K`, + instead of just a field. + + However, as the characteristic may be deduced from the constant + coefficient of the Drinfeld module, we chose to ommit the + characteristic in the input of the class in order to have + concise definitions. + + .. RUBRIC:: Possible base rings + + The morphism `\gamma` does not need be surjective like in the above + examples. This is equivalent to say that the constant coefficient of + the Drinfeld module may be different to the generator of `K` over + `\Fq`. In the following example, `K` is still a degree six extension + of `\Fq`, but `\gamma` is a projection over a degree two extension + with modulus `X^3 + (z_2 + 2)X^2 + (6*z_2 + 1)X + 3z_2 + 5`:: + + sage: p = X^2 + z2 + 2 + sage: p_root = z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z + sage: rho = DrinfeldModule(FqX, [p_root, 1, 1]) + sage: rho + Drinfeld module defined by X |--> t^2 + t + z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z over Finite Field in z of size 3^12 + + Then one can check that the morphisms `\gamma` are not the same for + ``phi`` and ``rho``, and that the `\gamma` associated to `\phi` is + surjective, while the other one is not:: + + sage: rho.category().morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z + sage: phi.category().morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z Note that ``phi`` and ``psi`` are *finite* Drinfeld modules, in the - sense that `K` is finite. This is not mandatory:: + sense that `K` is finite. But `K` can be infinite:: - sage: K_infinite = Frac(FqX) - sage: phi_infinite = DrinfeldModule(FqX, [K_infinite.gen(), 1, 1]) - sage: phi_infinite + sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1, 1]) + sage: sigma Drinfeld module defined by X |--> t^2 + t + X over Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - sage: phi_infinite.is_finite() + sage: sigma.is_finite() False sage: phi.is_finite() True + .. RUBRIC:: The category of Drinfeld modules + Drinfeld modules have their own category (see class :class:`sage.categories.drinfeld_modules.DrinfeldModules`):: @@ -138,7 +242,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): Defn: X |--> z sage: phi.category() is psi.category() True - sage: phi.category() is phi_infinite.category() + sage: phi.category() is sigma.category() False This category holds crucial information, like the @@ -146,42 +250,10 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: char = phi.category().characteristic() - .. NOTE:: - - As the output of - :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule.category` - suggests, the morphism `\gamma` uniquely determines the category of a Drinfeld - module. - - .. RUBRIC:: More general `K` - - The field `K` does not need be generated by the constant coefficient - (i.e. generated, as an extension of `\Fq`, by the image - `\gamma(X)`). In the following example, `K` is still a - degree six extension of `\Fq`, but `\gamma` is a projection over - `\Fq[X]/p(X)`, with `p(X) = X^3 + (z_2 + 2)X^2 + (6*z_2 + 1)X + 3z_2 - + 5`:: - - sage: p = X^2 + z2 + 2 # Prime polynomial - sage: p_root = z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z # Root of p - sage: phi_inter = DrinfeldModule(FqX, [p_root, 1, 1]) - sage: phi_inter - Drinfeld module defined by X |--> t^2 + t + z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z over Finite Field in z of size 3^12 - - We can check that the morphisms `\gamma` are not the same for - ``phi`` and ``phi_inter``, and that the `\gamma` associated to - `\phi` is surjective, while the other one is not:: - - sage: phi_inter.category().morphism() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z - sage: phi.category().morphism() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z + As the output of + :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule.category` + suggests, the morphism `\gamma` uniquely determines the category of a Drinfeld + module. .. RUBRIC:: Basic methods @@ -194,29 +266,35 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: phi(1) # phi_1 1 - We can retrieve basic properties:: + One can retrieve basic properties:: sage: phi.base_ring() # K Finite Field in z of size 3^12 + sage: phi.ore_polring() # K{t} Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) + sage: phi.ore_variable() # t t + sage: phi.function_ring() # Fq[X] Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + sage: phi.gen() # phi_X t^2 + t + z sage: phi.gen() == phi(X) True + sage: phi.constant_coefficient() # Constant coefficient of phi_X z + sage: phi.morphism() # The Drinfeld module as a morphism Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 To: Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) Defn: X |--> t^2 + t + z - We can compute the rank and height:: + One can compute the rank and height:: sage: phi.rank() 2 @@ -252,13 +330,13 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): True To create a SageMath object representing the morphism, call the - homset ``hom``:: + homset (``hom`` in the next example):: sage: hom = Hom(phi, phi) - sage: frob = hom(t^6) + sage: frobenius_endomorphism = hom(t^6) sage: identity_morphism = hom(1) sage: zero_morphism = hom(0) - sage: frob + sage: frobenius_endomorphism Drinfeld Module morphism: From: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 To: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 @@ -274,23 +352,23 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): To: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 Defn: 0 - We can retrieve the underlying Ore polynomial with the method + One can retrieve the underlying Ore polynomial with the method :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule.ore_polynomial`:: - sage: frob.ore_polynomial() + sage: frobenius_endomorphism.ore_polynomial() t^6 - And we can easily check if a morphism defines an isogeny or an + And one can easily check if a morphism defines an isogeny or an isomorphism (i.e. an isogeny whose underlying Ore polynomial has degree `0`):: - sage: frob.is_isogeny() + sage: frobenius_endomorphism.is_isogeny() True sage: identity_morphism.is_isogeny() True sage: zero_morphism.is_isogeny() False - sage: frob.is_isomorphism() + sage: frobenius_endomorphism.is_isomorphism() False sage: identity_morphism.is_isomorphism() True @@ -300,10 +378,9 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: The Vélu formula Let ``ore_pol`` be a non-zero Ore polynomial ``ore_pol``. For - Drinfeld module, it is easy to decide --- and find as the case may - be --- if there exists a Drinfeld module ``psi``, such that - ``ore_pol`` is an isogeny from ``self`` to ``psi``. If this - Drinfeld module exists, it is unique. + Drinfeld module, it is easy to decide if there exists a Drinfeld + module ``psi`` such that ``ore_pol`` is an isogeny from ``self`` to + ``psi``. We can also find ``psi``, which is unique. sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z sage: psi = phi.velu(ore_pol) @@ -314,6 +391,34 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: ore_pol * phi(X) == psi(X) * ore_pol True + .. RUBRIC:: The action of a Drinfeld module + + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + TODO + .. RUBRIC:: Other methods It is possible to change the base ring:: @@ -325,7 +430,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): True Given an Ore polynomial that equals `\phi_a` for some `a \in - \Fq[X]`, we can retrieve `a` (as a morphism, a Drinfeld + \Fq[X]`, one can retrieve `a` (as a morphism, a Drinfeld module is injective, see [Gos1998]_, cor. 4.5.2.):: sage: a = FqX.random_element() @@ -425,12 +530,9 @@ def base_ring(self): r""" Return the base ring of the Drinfeld module. - This is the base field of Ore polynomial ring. In particular, the base + This is the Ore polynomial ring base. In particular, the base ring is always a field. - A Drinfeld module is said to be finite if the base ring is - finite. - OUTPUT: - a field @@ -452,9 +554,8 @@ def base_ring(self): sage: phi.base_ring() is K True - Note that in the above example, the base ring does is not - generated by the constant coefficient (i.e. generated, as an - extension of `\Fq`, by the image `\gamma(X)`). The base + Note that in the above example, the constant coefficient + generates a strict sub-extension of `K/\Fq`. In fact, the base ring may also be the same as ``Fq``:: sage: psi = DrinfeldModule(FqX, [Fq(1), Fq.gen()]) @@ -463,13 +564,13 @@ def base_ring(self): sage: psi.base_ring() is Fq True - In which case the Ore polynomial ring is isomorphic to a regular + In this case the Ore polynomial ring is isomorphic to a regular polynomial ring:: - sage: psi.ore_polring() - Ore Polynomial Ring in t over Finite Field in z2 of size 5^2 twisted by Identity - sage: psi.ore_polring().twisting_morphism() - Identity endomorphism of Finite Field in z2 of size 5^2 + sage: psi.ore_polring() + Ore Polynomial Ring in t over Finite Field in z2 of size 5^2 twisted by Identity + sage: psi.ore_polring().twisting_morphism() + Identity endomorphism of Finite Field in z2 of size 5^2 TESTS:: @@ -486,12 +587,9 @@ def constant_coefficient(self): r""" Return the constant coefficient of the generator (`\phi_X`). - The `A`-characteristic of the base field (see - :meth:`sage.categories.drinfeld_modules.DrinfeldModules.characteristic`) - is the minimal polynomial of this constant term, over the base - ring of the function ring. Equivalently, the constant term is - the image, by the morphism (`\gamma`) that defines the category, - of the generator (`X`) of the polynomial ring. + From the definition of a Drinfeld module, the constant coefficient equals + `\gamma(X)`. Hence, it is a root of the `\Fq[X]`-characteristic + of the base ring. OUTPUT: @@ -507,16 +605,15 @@ def constant_coefficient(self): sage: phi.constant_coefficient() == p_root True - The constant coefficient is the image of ``X`` by the - morphism that defines the category of ``phi``:: + The constant coefficient equals `\gamma(X)`:: sage: cat = phi.category() sage: gamma = cat.morphism() sage: gamma(X) == phi.constant_coefficient() True - Two Drinfeld modules in the same category have the same constant - coefficient:: + Naturally, two Drinfeld modules in the same category have the + same constant coefficient:: sage: t = phi.ore_variable() sage: psi = cat(phi.constant_coefficient() + t^3) @@ -524,15 +621,15 @@ def constant_coefficient(self): Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 Reciprocally, it is impossible to create two Drinfeld modules in - this category if they don't share the same constant + this category if they do not share the same constant coefficient:: sage: rho = cat(phi.constant_coefficient() + 1 + t^3) Traceback (most recent call last): ... - ValueError: constant coefficient is not a root of the characteristic + ValueError: constant coefficient must be a root of the characteristic - One cal also retrieve the constant coefficient using + One can also retrieve the constant coefficient using ``phi(X)[0]`:: sage: phi.constant_coefficient() == phi(X)[0] @@ -542,11 +639,11 @@ def constant_coefficient(self): def gen(self): r""" - Return the generator (`\phi_X`) of the Drinfeld module. + Return the generator of the Drinfeld module, i.e. `\phi_X`. - This method makes sense because, in our case, the function ring is - a polynomial ring; it is generated by one element, whose image - characterizes the morphism that defines the Drinfeld module. + This method makes sense because, in our case, the function ring + `\Fq[X]`, which is `\Fq`-generated by a single element element, + whose image characterizes the Drinfeld module. OUTPUT: @@ -617,9 +714,6 @@ def ore_polring(self): r""" Return the Ore polynomial ring of the Drinfeld module. - If the Drinfeld module is defined by a morphism `A \to - K\{\tau\}`, this is the codomain `K\{\tau\}`. - OUTPUT: - an Ore polynomial ring @@ -661,7 +755,8 @@ def ore_variable(self): r""" Return the Ore variable. - This is generator of the Ore polynomial ring. + The Ore variable is defined as the generator of the Ore + polynomial ring. OUTPUT: @@ -679,7 +774,7 @@ def ore_variable(self): sage: phi.ore_variable() is phi.ore_polring().gen() True - We can use the Ore variable to instanciate new Drinfeld + One can use the Ore variable to instanciate new Drinfeld modules...:: sage: t = phi.ore_variable() @@ -697,10 +792,7 @@ def function_ring(self): r""" Return the function ring of the Drinfeld module. - If the Drinfeld module is defined by a morphism `A \to - K\{\tau\}`, this is the domain `A`. - - In our case, the function ring is a polynomial ring. + In our case, the function ring is an `\Fq[X]`. OUTPUT: @@ -741,8 +833,8 @@ def __call__(self, a): def change_ring(self, new_field, name=None): r""" - Return a Drinfeld module defined like ``self``, but with base - ring ``new_field``. + Return a Drinfeld module defined like the current one, but with + base ring ``new_field``. The new base can either be a field extension of the base ring, or field that has a coercion map from the field of definitions @@ -771,7 +863,7 @@ def change_ring(self, new_field, name=None): sage: phi_2 Drinfeld module defined by X |--> 2*t^2 + (3*z24^23 + 2*z24^22 + 2*z24^20 + z24^19 + 4*z24^18 + 3*z24^17 + 4*z24^15 + 2*z24^13 + 4*z24^12 + 4*z24^11 + 3*z24^10 + 3*z24^9 + 4*z24^8 + 4*z24^6 + 3*z24^5 + 4*z24^4 + 4*z24^3 + 2*z24)*t + 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 over Finite Field in z24 of size 5^24 - And we can check various things:: + And one can check various things:: sage: phi.change_ring(K2).change_ring(K) is phi True @@ -786,7 +878,7 @@ def change_ring(self, new_field, name=None): To: Finite Field in z24 of size 5^24 Defn: X |--> 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 - We can also change the base ring to a subfield, even though some things + One can also change the base ring to a subfield, even though some things do not work as expected:: sage: K0 = Fq.extension(2) @@ -833,12 +925,9 @@ def height(self): def invert(self, ore_pol): r""" - Return the inverse ``ore_pol`` by the morphism that defines the - Drinfeld module; if ``ore_pol`` is not in the image of the - morphism, return ``None``. - - Said otherwise, return `a` if ``ore_pol`` is `phi_a`, otherwise - return ``None``. + Return the pre-image of the input under the Drinfeld module; + raise an exception if the input is not in the image of the + Drinfeld module. INPUT: @@ -864,14 +953,18 @@ def invert(self, ore_pol): sage: phi.invert(phi(Fq.gen())) == Fq.gen() True - When the input is not in the image of the Drinfeld module, the - method returns None:: + When the input is not in the image of the Drinfeld module, an + exception is raised:: sage: t = phi.ore_variable() - sage: phi.invert(t + 1) is None - True - sage: phi.invert(t^3 + t^2 + 1) is None - True + sage: phi.invert(t + 1) + Traceback (most recent call last): + ... + ValueError: input must be in the image of the Drinfeld module + sage: phi.invert(t^3 + t^2 + 1) + Traceback (most recent call last): + ... + ValueError: input must be in the image of the Drinfeld module ALGORITHM: @@ -930,7 +1023,7 @@ def is_finite(self): OUTPUT: - - ``True`` or ``False`` + - a boolean EXAMPLES: @@ -989,7 +1082,7 @@ def j(self): sage: theta.j() Traceback (most recent call last): ... - NotImplementedError: the rank must be 2 + NotImplementedError: rank must be 2 """ self._check_rank_two() g = self._gen[1] From 999a0aefcd1fb0abfb14c799428e71061feb04c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 18 Aug 2022 15:57:09 +0200 Subject: [PATCH 082/392] Fix function_fields imports --- .../drinfeld_modules/drinfeld_module.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index f3b69307931..1f12fb31717 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -2,10 +2,10 @@ Drinfeld modules This module provides the class -:class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. +:class:`sage.rings.function_field.drinfeld_module.drinfeld_module.DrinfeldModule`. For *finite* Drinfeld modules and their theory of complex multiplication, see class -:class:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule`. +:class:`sage.rings.function_field.drinfeld_module.finite_drinfeld_module.DrinfeldModule`. AUTHORS: @@ -90,7 +90,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: Construction and input A Drinfeld module object (class - :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`) + :class:`sage.rings.function_field.drinfeld_module.drinfeld_module.DrinfeldModule`) is constructed as follows:: sage: Fq. = GF(3^2) @@ -251,7 +251,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: char = phi.category().characteristic() As the output of - :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule.category` + :meth:`sage.rings.function_field.drinfeld_module.finite_drinfeld_module.DrinfeldModule.category` suggests, the morphism `\gamma` uniquely determines the category of a Drinfeld module. @@ -353,7 +353,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): Defn: 0 One can retrieve the underlying Ore polynomial with the method - :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.DrinfeldModule.ore_polynomial`:: + :meth:`sage.rings.function_field.drinfeld_module.finite_drinfeld_module.DrinfeldModule.ore_polynomial`:: sage: frobenius_endomorphism.ore_polynomial() t^6 @@ -509,7 +509,7 @@ def __init__(self, gen, category): ########################## def _get_action_(self): - from sage.rings.function_fields.drinfeld_modules.action import DrinfeldModuleAction + from sage.rings.function_field.drinfeld_modules.action import DrinfeldModuleAction return DrinfeldModuleAction(self) def _latex_(self): From 942a9c8d95c0c59115ef55e533cd3f6ac999590c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 18 Aug 2022 17:17:21 +0200 Subject: [PATCH 083/392] Fix DrinfeldModuleAction This class was all broken since it had not been updated since the begining of the project, which itself had evolved a lot in the meantime. --- .../function_field/drinfeld_modules/action.py | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 82f1ee0d207..bc263efd3eb 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -4,34 +4,29 @@ class DrinfeldModuleAction(Action): - def __init__(self, finite_drinfeld_module): - # Verifications - if not isinstance(finite_drinfeld_module, DrinfeldModule): - raise TypeError('input must be a DrinfeldModule') - # Work - self.__finite_drinfeld_module = finite_drinfeld_module - super().__init__(finite_drinfeld_module.polring(), - finite_drinfeld_module.ore_polring().base_ring()) - - ########### - # Methods # - ########### - - def finite_drinfeld_module(self): - return self.__finite_drinfeld_module - ########################## - # Special Sage functions # - ########################## + def __init__(self, drinfeld_module): + if not isinstance(drinfeld_module, DrinfeldModule): + raise TypeError('input must be a DrinfeldModule') + self._drinfeld_module = drinfeld_module + super().__init__(drinfeld_module.function_ring(), + drinfeld_module.base_ring()) - def _act_(self, g, x): - return self.finite_drinfeld_module()(g)(x) + def _act_(self, pol, x): + if pol not in self._drinfeld_module.function_ring(): + raise TypeError('first input must be in the function ring') + if x not in self._drinfeld_module.base_ring(): + raise TypeError('second input must be in the base ring') + return self._drinfeld_module(pol)(x) def _latex_(self): return f'\\text{{Action{{ }}on{{ }}}}' \ - f'{latex(self.extension())}\\text{{{{ }}' \ - f'induced{{ }}by{{ }}}}{self.finite_drinfeld_module()}' + f'{latex(self._drinfeld_module.base_ring())}\\text{{{{ }}' \ + f'induced{{ }}by{{ }}}}{self._drinfeld_module}' def _repr_(self): - return f'Action on {self.domain()} induced by ' \ - f'{self.finite_drinfeld_module()}' + return f'Action on {self._drinfeld_module.base_ring()} induced by ' \ + f'{self._drinfeld_module}' + + def drinfeld_module(self): + return self._drinfeld_module From 721f3072209d05e89fd3587b9bd0f2b4aee9f5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 19 Aug 2022 15:16:21 +0200 Subject: [PATCH 084/392] Use super().__init__(...) for Drinfeld modules (esp. DrinfeldModuleMorphism) In commit f8e34f2d2ccea453f0cc98ca0fe6d154b352c10d, I made DrinfeldModuleMorphism inherit UniqueRepresentation, which made me refactor the __init__ method. It turns out that I was doing it wrong, and that, `my_morphism.parent()` would be `None`. I fixed that, and I wrote some tests to detect this in the future. I took the opportunity to use `super()` for other Drinfeld module stuff. --- .../drinfeld_modules/drinfeld_module.py | 2 +- .../rings/function_field/drinfeld_modules/homset.py | 2 +- .../rings/function_field/drinfeld_modules/morphism.py | 11 ++++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 1f12fb31717..ada0618edc4 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -496,7 +496,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): return cls.__classcall__(cls, gen, category) def __init__(self, gen, category): - CategoryObject.__init__(self, category=category) + super().__init__(category=category) self._base_ring = category.base() self._function_ring = category.function_ring() self._gen = gen diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 595a8836df2..72f68d4461b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -18,7 +18,7 @@ def __init__(self, X, Y, category=None, check=True): if category != X.category(): raise NotImplementedError('category should be DrinfeldModules') base = category.base() - Homset.__init__(self, X, Y, category=category, base=base, check=check) + super().__init__(X, Y, category=category, base=base, check=check) def _repr_(self): return f'Set of Drinfeld module morphisms:\n' \ diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index b7792a5a907..b6bae6da67c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -87,6 +87,15 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, sage: morphism.is_isomorphism() False + TESTS:: + + sage: morphism.parent() == Hom(phi, psi) + True + sage: phi.frobenius_endomorphism().parent() == End(phi) + True + sage: End(phi)(0).parent() == End(phi) + True + .. NOTE:: For the sake of completness, we explain how the user can @@ -121,7 +130,7 @@ def __classcall_private__(cls, parent, x): return cls.__classcall__(cls, parent, ore_pol) def __init__(self, parent, ore_pol): - Element.__init__(Element(parent), parent) + super().__init__(parent) self._domain = parent.domain() self._codomain = parent.codomain() self._ore_polynomial = ore_pol From 919cf5ed1360e5f65ade295ac4326d4066fb4a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 19 Aug 2022 16:37:51 +0200 Subject: [PATCH 085/392] Create _latex_ method in DrinfeldModuleHomset --- src/sage/rings/function_field/drinfeld_modules/homset.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 72f68d4461b..e4fd38e7177 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -20,6 +20,11 @@ def __init__(self, X, Y, category=None, check=True): base = category.base() super().__init__(X, Y, category=category, base=base, check=check) + def _latex_(self): + return f'\\text{{Set{{ }}of{{ }}Drinfeld{{ }}module{{ }}morphisms' \ + f'{{ }}from}}{latex(self.domain())}\\text{{{{ }}to{{ }}}}' \ + f'{self.codomain()}' + def _repr_(self): return f'Set of Drinfeld module morphisms:\n' \ f' From: {self.domain()}\n' \ From 70c215aec33c26379d45000b9d65a76645ab8af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 19 Aug 2022 16:39:39 +0200 Subject: [PATCH 086/392] Write docstrings for DrinfeldModuleHomset --- .../function_field/drinfeld_modules/homset.py | 114 +++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index e4fd38e7177..7229df993a1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -1,9 +1,119 @@ -from sage.structure.parent import Parent +r""" +Set of morphisms between two Drinfeld modules + +This module provides the class +:class:`sage.rings.function_field.drinfeld_module.homset.DrinfeldModuleHomset`. + +AUTHORS: + +- Antoine Leudière (2022-04) +""" + +#***************************************************************************** +# Copyright (C) 2022 Antoine Leudière +# +# 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. +# http://www.gnu.org/licenses/ +#***************************************************************************** + from sage.categories.drinfeld_modules import DrinfeldModules -from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism from sage.categories.homset import Homset, Hom +from sage.misc.latex import latex +from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism +from sage.structure.parent import Parent class DrinfeldModuleHomset(Homset): + r""" + This class represents the set of morphisms between two Drinfeld + modules. + + INPUT: + + - ``X`` -- the domain + - ``Y`` -- the codomain + + EXAMPLES: + + sage: Fq = GF(27) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) + sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: hom = Hom(phi, psi) + sage: hom + Set of Drinfeld module morphisms: + From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + To: Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 + + sage: from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset + sage: isinstance(hom, DrinfeldModuleHomset) + True + + There is a simpler syntax for endomorphisms sets:: + + sage: end = End(phi) + sage: end + Set of Drinfeld module morphisms: + From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + sage: end is Hom(phi, phi) + True + + + One can create morphism objects by calling the homset:: + + sage: t = phi.ore_variable() + sage: identity_morphism = end(1) + sage: identity_morphism + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + Defn: 1 + + sage: frobenius_endomorphism = end(t^6) + sage: frobenius_endomorphism + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + Defn: t^6 + + sage: isogeny = hom(t + 1) + sage: isogeny + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + To: Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 + Defn: t + 1 + + And one can test if an Ore polynomial defines a morphism using the + ``in`` syntax:: + + sage: 1 in hom + False + sage: t^6 in hom + False + sage: t + 1 in hom + True + sage: 1 in end + True + sage: t^6 in end + True + sage: t + 1 in end + False + + This also works if the candidate is a morphism object:: + + sage: isogeny in hom + True + sage: end(0) in end + True + sage: identity_morphism in hom + False + sage: frobenius_endomorphism in hom + False + """ Element = DrinfeldModuleMorphism __contains__ = Parent.__contains__ From c9bfc054383b828e118fb79a4ee5cae60535d7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 19 Aug 2022 16:37:06 +0200 Subject: [PATCH 087/392] (minor) Delete a blank line in DrinfeldModules --- src/sage/categories/drinfeld_modules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index b4cd81bf3ef..5898e71ba5f 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -96,7 +96,6 @@ def random_element(self, rank): return self(coeffs) - # Sage methods def _call_(self, gen): From 394d0f5edd4941727f6420b8c76081bdad5269cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 19 Aug 2022 16:40:17 +0200 Subject: [PATCH 088/392] (minor) Change a sentence in DrinfeldModuleMorphism --- src/sage/rings/function_field/drinfeld_modules/morphism.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index b6bae6da67c..0f836451f84 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -99,7 +99,8 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, .. NOTE:: For the sake of completness, we explain how the user can - directly instanciate the class:: + directly instanciate the class, even though this should never be + explicitely done:: sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) From 0f869f2345c2e071023dc5a7832b1776e1ed71be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 19 Aug 2022 17:03:27 +0200 Subject: [PATCH 089/392] (minor) Change first sentences of Drinfeld module classes --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- .../function_field/drinfeld_modules/finite_drinfeld_module.py | 2 +- src/sage/rings/function_field/drinfeld_modules/morphism.py | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index ada0618edc4..82d8e5f8837 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -39,7 +39,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): r""" - This class handles Drinfeld modules. + This class represents a Drinfeld module. Let `\Fq` be a finite field with order `q`. Let `K` be a field equiped a ring morphism `\gamma: \Fq[X] \to K` --- the field `K` is diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 68921a0f508..40563bd83d4 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -26,7 +26,7 @@ class FiniteDrinfeldModule(DrinfeldModule): r""" - This class handles finite Drinfeld modules. + This class represnets a finite Drinfeld module. A *finite Drinfeld module* is a Drinfeld module whose base ring is finite. For general definitions and help on Drinfeld modules, see diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 0f836451f84..80625539d7d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -30,6 +30,8 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, metaclass=InheritComparisonClasscallMetaclass): r""" + This class represents a Drinfeld module morphism. + Let `\phi,\psi` be two Drinfeld modules defined over the `\Fq[X]`-field `K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a From 14f98aa4effbbea9a1e1f2e72aee31bc12988085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 19 Aug 2022 17:04:54 +0200 Subject: [PATCH 090/392] Change _get_action_ to action in DrinfeldModule See https://trac.sagemath.org/ticket/33713 for a discussion on this topic. Representations are very unusual in this context; this implementation seems more appropriate. I added documentation for this method in DrinfeldModule main docstring. NOTE: The commit is not very clean as I did some cleaning in the docstring too. --- .../drinfeld_modules/drinfeld_module.py | 66 ++++++++----------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 82d8e5f8837..217718e585f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -393,41 +393,29 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: The action of a Drinfeld module - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - TODO - - .. RUBRIC:: Other methods - - It is possible to change the base ring:: - - sage: L = K.extension(2) - sage: phi_rebased = phi.change_ring(L) - sage: Ltau = phi_rebased.ore_polring() - sage: Ltau(phi(X)) == phi_rebased(X) - True + An `\Fq[X]`-Drinfeld module `\phi` notoriously makes any field + extension `L/K` a left `\Fq[X]`-module with the law defined by + `(P(X), a) \mapsto \phi_P(a)`, where `a \in L`. The method + :meth:`action` returns an ``Action`` object representing the + Drinfeld module action; in this implementation, `K = L`. + + sage: action = phi.action() + sage: action + Action on Finite Field in z of size 3^12 induced by Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + + The action is computed as follows:: + + sage: P = X + 1 + sage: a = z + sage: action(P, a) + ... + z^9 + 2*z^8 + 2*z^7 + 2*z^6 + 2*z^3 + z^2 + sage: action(0, K.random_element()) + 0 + sage: action(FqX.random_element(), 0) + 0 + + .. RUBRIC:: Inversion of the Drinfeld module Given an Ore polynomial that equals `\phi_a` for some `a \in \Fq[X]`, one can retrieve `a` (as a morphism, a Drinfeld @@ -508,10 +496,6 @@ def __init__(self, gen, category): # Special Sage functions # ########################## - def _get_action_(self): - from sage.rings.function_field.drinfeld_modules.action import DrinfeldModuleAction - return DrinfeldModuleAction(self) - def _latex_(self): return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ f'{latex(self._function_ring.gen())} '\ @@ -526,6 +510,10 @@ def _repr_(self): # Getters # ########### + def action(self): + from sage.rings.function_field.drinfeld_modules.action import DrinfeldModuleAction + return DrinfeldModuleAction(self) + def base_ring(self): r""" Return the base ring of the Drinfeld module. From b014a199f9c25b30234d672cc9ca8e2e9292ba25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 19 Aug 2022 17:19:56 +0200 Subject: [PATCH 091/392] Reorder methods in DrinfeldModule --- .../drinfeld_modules/drinfeld_module.py | 548 +++++++++--------- 1 file changed, 268 insertions(+), 280 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 217718e585f..c1c679d0257 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -492,9 +492,67 @@ def __init__(self, gen, category): self._ore_polring = gen.parent() self._Fq = self._function_ring.base_ring() # Must be last - ########################## - # Special Sage functions # - ########################## + def __call__(self, a): + r""" + Return the image of ``a`` by the morphism that defines the + Drinfeld module, i.e. `\phi_a` if the Drinfeld module is denoted + `phi`. + + INPUT: + + - ``a`` -- an element in the function ring + + OUTPUT: + + - an element of the base ring + """ + + return self._morphism(a) + + def _Hom_(self, other, category): + r""" + Return ``DrinfeldModuleHomset(self, other, category)``. + + Validity of the input is checked at the instantiation of + ``DrinfeldModuleHomset``. ``self`` and ``other`` only need be in + the same category. + + INPUT: + + - ``other`` -- the codomain of the homset + - ``category`` -- the category in which we consider the + morphisms, usually `self.category()` + + OUTPUT: + + - an homset + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: t = phi.ore_variable() + sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + sage: psi = phi.velu(isog) + sage: hom = phi._Hom_(psi, category=phi.category()) + sage: hom is Hom(phi, psi) # known bug + True + sage: from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset + sage: isinstance(hom, DrinfeldModuleHomset) + True + """ + from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset + return DrinfeldModuleHomset(self, other, category) + + def _check_rank_two(self): + r""" + Raise ``NotImplementedError`` if the rank is not two. + """ + if self.rank() != 2: + raise NotImplementedError('rank must be 2') def _latex_(self): return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ @@ -506,10 +564,6 @@ def _repr_(self): return f'Drinfeld module defined by {self._function_ring.gen()} ' \ f'|--> {self._gen} over {self._base_ring}' - ########### - # Getters # - ########### - def action(self): from sage.rings.function_field.drinfeld_modules.action import DrinfeldModuleAction return DrinfeldModuleAction(self) @@ -571,6 +625,76 @@ def base_ring(self): """ return self._base_ring + def change_ring(self, new_field, name=None): + r""" + Return a Drinfeld module defined like the current one, but with + base ring ``new_field``. + + The new base can either be a field extension of the base ring, + or field that has a coercion map from the field of definitions + of the coefficients of the generator. + + INPUT: + + - ``new_field`` -- the field extension of the base ring that + serves as base ring for the new Drinfeld module + + OUTPUT: + + - a Drinfeld module + + EXAMPLES: + + The new ring can be an extension of the base ring:: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, p_root^3, 2]) + sage: K2 = K.extension(2) + sage: phi_2 = phi.change_ring(K2) + sage: phi_2 + Drinfeld module defined by X |--> 2*t^2 + (3*z24^23 + 2*z24^22 + 2*z24^20 + z24^19 + 4*z24^18 + 3*z24^17 + 4*z24^15 + 2*z24^13 + 4*z24^12 + 4*z24^11 + 3*z24^10 + 3*z24^9 + 4*z24^8 + 4*z24^6 + 3*z24^5 + 4*z24^4 + 4*z24^3 + 2*z24)*t + 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 over Finite Field in z24 of size 5^24 + + And one can check various things:: + + sage: phi.change_ring(K2).change_ring(K) is phi + True + sage: phi_2.base_ring() is K2 + True + + Naturally, the category has changed:: + + sage: phi_2.category() + Category of Drinfeld modules defined by Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z24 of size 5^24 + Defn: X |--> 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 + + One can also change the base ring to a subfield, even though some things + do not work as expected:: + + sage: K0 = Fq.extension(2) + sage: phi_0 = phi.change_ring(K0) + sage: phi_0.base_ring() is K0 + True + sage: phi.change_ring(K0).change_ring(K) # known bug + Traceback (most recent call last) + ... + TypeError: no coercion defined + + Furthermore:: + + sage: phi.change_ring(K) is phi + True + """ + coeffs = self._gen.coefficients() + new_coeffs = list(map(new_field, coeffs)) + if name == None: + name = self._ore_polring.variable_name() + return DrinfeldModule(self._function_ring, new_coeffs, name=name) + def constant_coefficient(self): r""" Return the constant coefficient of the generator (`\phi_X`). @@ -625,157 +749,6 @@ def constant_coefficient(self): """ return self.gen()[0] - def gen(self): - r""" - Return the generator of the Drinfeld module, i.e. `\phi_X`. - - This method makes sense because, in our case, the function ring - `\Fq[X]`, which is `\Fq`-generated by a single element element, - whose image characterizes the Drinfeld module. - - OUTPUT: - - - an Ore polynomial - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.gen() == phi(X) - True - """ - return self._gen - - def morphism(self): - r""" - Return the morphism object that defines the Drinfeld module. - - OUTPUT: - - - a ring morphism, from the function ring to the Ore polynomial - ring - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.morphism() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) - Defn: X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: from sage.rings.morphism import RingHomomorphism - sage: isinstance(phi.morphism(), RingHomomorphism) - True - - Actually, the ``DrinfeldModule`` method ``__call__`` simply - class the ``__call__`` method of this morphism:: - - sage: phi.morphism()(X) == phi(X) - True - sage: a = FqX.random_element() - sage: phi.morphism()(a) == phi(a) - True - - And many methods of the Drinfeld module have a counterpart in - the morphism object:: - - sage: m = phi.morphism() - sage: m.domain() is phi.function_ring() - True - sage: m.codomain() is phi.ore_polring() - True - sage: m.im_gens() - [z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12] - sage: phi(X) == m.im_gens()[0] - True - """ - return self._morphism - - def ore_polring(self): - r""" - Return the Ore polynomial ring of the Drinfeld module. - - OUTPUT: - - - an Ore polynomial ring - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: ore_polring = phi.ore_polring() - sage: ore_polring - Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) - - The Ore polynomial ring can also be retrieved from the category - of the Drinfeld module:: - - sage: ore_polring is phi.category().ore_polring() - True - - The generator of the Drinfeld module is in the Ore polynomial - ring:: - - sage: phi(X) in ore_polring - True - - The Ore variable is just the generator of the Ore polynomial - ring:: - - sage: ore_polring.gen() - t - sage: phi.ore_variable() is ore_polring.gen() - True - """ - return self._ore_polring - - def ore_variable(self): - r""" - Return the Ore variable. - - The Ore variable is defined as the generator of the Ore - polynomial ring. - - OUTPUT: - - - an Ore polynomial - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.ore_variable() - t - sage: phi.ore_variable() is phi.ore_polring().gen() - True - - One can use the Ore variable to instanciate new Drinfeld - modules...:: - - sage: t = phi.ore_variable() - sage: psi_X = phi.constant_coefficient() + 3*t + 2*t^4 - sage: psi = DrinfeldModule(FqX, psi_X) - - ...or morphisms and isogenies:: - - sage: t^6 in End(phi) # Frobenius endomorphism - True - """ - return self._ore_polring.gen() - def function_ring(self): r""" Return the function ring of the Drinfeld module. @@ -798,96 +771,29 @@ def function_ring(self): """ return self._function_ring - ########### - # Methods # - ########### - - def __call__(self, a): - r""" - Return the image of ``a`` by the morphism that defines the - Drinfeld module, i.e. `\phi_a` if the Drinfeld module is denoted - `phi`. - - INPUT: - - - ``a`` -- an element in the function ring - - OUTPUT: - - - an element of the base ring - """ - - return self._morphism(a) - - def change_ring(self, new_field, name=None): + def gen(self): r""" - Return a Drinfeld module defined like the current one, but with - base ring ``new_field``. - - The new base can either be a field extension of the base ring, - or field that has a coercion map from the field of definitions - of the coefficients of the generator. - - INPUT: - - - ``new_field`` -- the field extension of the base ring that - serves as base ring for the new Drinfeld module + Return the generator of the Drinfeld module, i.e. `\phi_X`. + This method makes sense because, in our case, the function ring + `\Fq[X]`, which is `\Fq`-generated by a single element element, + whose image characterizes the Drinfeld module. + OUTPUT: - - a Drinfeld module + - an Ore polynomial EXAMPLES: - The new ring can be an extension of the base ring:: - sage: Fq = GF(25) sage: FqX. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, p_root^3, 2]) - sage: K2 = K.extension(2) - sage: phi_2 = phi.change_ring(K2) - sage: phi_2 - Drinfeld module defined by X |--> 2*t^2 + (3*z24^23 + 2*z24^22 + 2*z24^20 + z24^19 + 4*z24^18 + 3*z24^17 + 4*z24^15 + 2*z24^13 + 4*z24^12 + 4*z24^11 + 3*z24^10 + 3*z24^9 + 4*z24^8 + 4*z24^6 + 3*z24^5 + 4*z24^4 + 4*z24^3 + 2*z24)*t + 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 over Finite Field in z24 of size 5^24 - - And one can check various things:: - - sage: phi.change_ring(K2).change_ring(K) is phi - True - sage: phi_2.base_ring() is K2 - True - - Naturally, the category has changed:: - - sage: phi_2.category() - Category of Drinfeld modules defined by Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z24 of size 5^24 - Defn: X |--> 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 - - One can also change the base ring to a subfield, even though some things - do not work as expected:: - - sage: K0 = Fq.extension(2) - sage: phi_0 = phi.change_ring(K0) - sage: phi_0.base_ring() is K0 - True - sage: phi.change_ring(K0).change_ring(K) # known bug - Traceback (most recent call last) - ... - TypeError: no coercion defined - - Furthermore:: - - sage: phi.change_ring(K) is phi + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.gen() == phi(X) True """ - coeffs = self._gen.coefficients() - new_coeffs = list(map(new_field, coeffs)) - if name == None: - name = self._ore_polring.variable_name() - return DrinfeldModule(self._function_ring, new_coeffs, name=name) + return self._gen def height(self): r""" @@ -1078,6 +984,133 @@ def j(self): q = self._Fq.order() return (g**(q+1)) / delta + def morphism(self): + r""" + Return the morphism object that defines the Drinfeld module. + + OUTPUT: + + - a ring morphism, from the function ring to the Ore polynomial + ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + Defn: X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: from sage.rings.morphism import RingHomomorphism + sage: isinstance(phi.morphism(), RingHomomorphism) + True + + Actually, the ``DrinfeldModule`` method ``__call__`` simply + class the ``__call__`` method of this morphism:: + + sage: phi.morphism()(X) == phi(X) + True + sage: a = FqX.random_element() + sage: phi.morphism()(a) == phi(a) + True + + And many methods of the Drinfeld module have a counterpart in + the morphism object:: + + sage: m = phi.morphism() + sage: m.domain() is phi.function_ring() + True + sage: m.codomain() is phi.ore_polring() + True + sage: m.im_gens() + [z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12] + sage: phi(X) == m.im_gens()[0] + True + """ + return self._morphism + + def ore_polring(self): + r""" + Return the Ore polynomial ring of the Drinfeld module. + + OUTPUT: + + - an Ore polynomial ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: ore_polring = phi.ore_polring() + sage: ore_polring + Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + + The Ore polynomial ring can also be retrieved from the category + of the Drinfeld module:: + + sage: ore_polring is phi.category().ore_polring() + True + + The generator of the Drinfeld module is in the Ore polynomial + ring:: + + sage: phi(X) in ore_polring + True + + The Ore variable is just the generator of the Ore polynomial + ring:: + + sage: ore_polring.gen() + t + sage: phi.ore_variable() is ore_polring.gen() + True + """ + return self._ore_polring + + def ore_variable(self): + r""" + Return the Ore variable. + + The Ore variable is defined as the generator of the Ore + polynomial ring. + + OUTPUT: + + - an Ore polynomial + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.ore_variable() + t + sage: phi.ore_variable() is phi.ore_polring().gen() + True + + One can use the Ore variable to instanciate new Drinfeld + modules...:: + + sage: t = phi.ore_variable() + sage: psi_X = phi.constant_coefficient() + 3*t + 2*t^4 + sage: psi = DrinfeldModule(FqX, psi_X) + + ...or morphisms and isogenies:: + + sage: t^6 in End(phi) # Frobenius endomorphism + True + """ + return self._ore_polring.gen() + def rank(self): r""" Return the rank of the Drinfeld module. @@ -1181,48 +1214,3 @@ def velu(self, isog): return None quo, rem = (isog * self.gen()).right_quo_rem(isog) return None if rem != 0 else DrinfeldModule(self._function_ring, quo) - - def _Hom_(self, other, category): - r""" - Return ``DrinfeldModuleHomset(self, other, category)``. - - Validity of the input is checked at the instantiation of - ``DrinfeldModuleHomset``. ``self`` and ``other`` only need be in - the same category. - - INPUT: - - - ``other`` -- the codomain of the homset - - ``category`` -- the category in which we consider the - morphisms, usually `self.category()` - - OUTPUT: - - - an homset - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: t = phi.ore_variable() - sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 - sage: psi = phi.velu(isog) - sage: hom = phi._Hom_(psi, category=phi.category()) - sage: hom is Hom(phi, psi) # known bug - True - sage: from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset - sage: isinstance(hom, DrinfeldModuleHomset) - True - """ - from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset - return DrinfeldModuleHomset(self, other, category) - - def _check_rank_two(self): - r""" - Raise ``NotImplementedError`` if the rank is not two. - """ - if self.rank() != 2: - raise NotImplementedError('rank must be 2') From 138902d263590c46d931ed79db753fd99b6b8beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 11:27:03 +0200 Subject: [PATCH 092/392] Write docstrings for DrinfeldModuleAction --- .../function_field/drinfeld_modules/action.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index bc263efd3eb..1901a07d371 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -1,9 +1,78 @@ +r""" +The left-module action induced by a Drinfeld module + +This module provides the class +:class:`sage.rings.function_field.drinfeld_module.action.DrinfeldModuleAction`. + +AUTHORS: + +- Antoine Leudière (2022-04) +""" + +#***************************************************************************** +# Copyright (C) 2022 Antoine Leudière +# +# 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. +# http://www.gnu.org/licenses/ +#***************************************************************************** + from sage.categories.action import Action from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule class DrinfeldModuleAction(Action): + r""" + This class represents the left `\Fq[X]`-module action induced by a + Drinfeld `\Fq[X]`-module defined over an `\Fq[X]`-field `K`. + + Let `L/K` be a field extension, let `x \in L`, let `P \in \Fq[X]`; + the action is defined as `(P, a) \mapsto \phi_P(a)`, where + `\phi_P(a)`. In this implementation, `L` is `K`. + + The action is instanciated as follows. Note that the user should + never explicitely instanciate the class `DrinfeldModuleAction`:: + + INPUT: a Drinfeld module + + EXAMPLES: + + sage: Fq. = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: action = phi.action() + sage: action + Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 11^2 + + The action on elements is computed as follows:: + + sage: P = X + 1 + sage: a = z + sage: action(P, a) + ... + 4*z + 2 + sage: action(0, K.random_element()) + 0 + sage: action(FqX.random_element(), 0) + 0 + + To act on a field larger than `K`, one can change the ring of the + Drinfeld module, then create the action:: + + sage: extended_action = phi.change_ring(K.extension(2)).action() + sage: extended_action + Action on Finite Field in z4 of size 11^4 induced by Drinfeld module defined by X |--> t + 10*z4^3 + 4*z4^2 + 5*z4 + 5 over Finite Field in z4 of size 11^4 + + Finally, given a Drinfeld module action, it is easy to recover the + corresponding Drinfeld module:: + + sage: action.drinfeld_module() is phi + True + """ def __init__(self, drinfeld_module): if not isinstance(drinfeld_module, DrinfeldModule): @@ -29,4 +98,17 @@ def _repr_(self): f'{self._drinfeld_module}' def drinfeld_module(self): + r""" + Return the Drinfeld module associated to the action. + + OUTPUT: a Drinfeld module + + sage: Fq. = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: action = phi.action() + sage: action.drinfeld_module() is phi + True + """ return self._drinfeld_module From de5e0380446a4c265026e23699357f2fac55b23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 13:53:55 +0200 Subject: [PATCH 093/392] Rename random_element method to random_object in DrinfeldModules See https://trac.sagemath.org/ticket/33713#comment:90. --- src/sage/categories/drinfeld_modules.py | 3 +-- .../function_field/drinfeld_modules/drinfeld_module.py | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 5898e71ba5f..8ae76b33e8b 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -78,8 +78,7 @@ def morphism(self): def ore_polring(self): return self._ore_polring - def random_element(self, rank): - + def random_object(self, rank): if not isinstance(rank, Integer): raise TypeError('rank must be a positive integer') if rank <= 0: diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index c1c679d0257..c4b0248c4e1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -869,19 +869,19 @@ def invert(self, ore_pol): sage: a = FqX.random_element() sage: cat = phi.category() - sage: phi_r1 = cat.random_element(1) + sage: phi_r1 = cat.random_object(1) sage: phi_r1.invert(phi_r1(a)) == a True - sage: phi_r2 = cat.random_element(2) + sage: phi_r2 = cat.random_object(2) sage: phi_r2.invert(phi_r2(a)) == a True - sage: phi_r3 = cat.random_element(3) + sage: phi_r3 = cat.random_object(3) sage: phi_r3.invert(phi_r3(a)) == a True - sage: phi_r4 = cat.random_element(4) + sage: phi_r4 = cat.random_object(4) sage: phi_r4.invert(phi_r4(a)) == a True - sage: phi_r5 = cat.random_element(5) + sage: phi_r5 = cat.random_object(5) sage: phi_r5.invert(phi_r5(a)) == a True """ From e50a5caab8c6dcfacc1c1a96338f28782c677457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 14:32:14 +0200 Subject: [PATCH 094/392] Handle some zero Fq[X]-characteristic special cases in DrinfeldModules The code is a simple two-liner `if` in `DrinfeldModules.__init__`. --- src/sage/categories/drinfeld_modules.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 8ae76b33e8b..1104c288375 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -57,6 +57,9 @@ def __init__(self, morphism, name='t'): f = gamma * FqX.coerce_map_from(Fq) # Fq -> K E = K.over(f) self._characteristic = FqX(E(gamma(X)).minpoly()) + elif FqX.is_subring(K): + self._characteristic = Integer(0) + def base(self): return self._ore_polring.base_ring() From f5aa48906702e0a42c01c63374072e5a9874861d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 15:22:32 +0200 Subject: [PATCH 095/392] Create _base attribute in DrinfeldModules This is more in line with the rest of the code. --- src/sage/categories/drinfeld_modules.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 1104c288375..b53db6023b1 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -42,6 +42,7 @@ def __init__(self, morphism, name='t'): X = FqX.gen() # Check codomain of gamma is field K = gamma.codomain() + self._base = K if not K.is_field(): raise TypeError('base must be a field') # Build K{t} @@ -62,7 +63,7 @@ def __init__(self, morphism, name='t'): def base(self): - return self._ore_polring.base_ring() + return self._base def characteristic(self): if self._characteristic is None: @@ -87,7 +88,7 @@ def random_object(self, rank): if rank <= 0: raise ValueError('rank must be a positive integer') - K = self.base() + K = self._base coeffs = [self._constant_coefficient] for _ in range(rank-1): coeffs.append(K.random_element()) From 386efec427689989407aae7f788ad4f0d05b6cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 15:23:49 +0200 Subject: [PATCH 096/392] Write docstrings in DrinfeldModules I also did a bit of cleaning and refactoring there and there, but it is only cosmetic changes (order of methods, some comments, etc); nothing important or meaningful. --- src/sage/categories/drinfeld_modules.py | 371 ++++++++++++++++++++++-- 1 file changed, 340 insertions(+), 31 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index b53db6023b1..98ccb24152a 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -1,6 +1,15 @@ r""" -Drinfeld modules +Category of Drinfeld modules + +This module provides the class +:class:`sage.category.drinfeld_modules.DrinfeldModules`. + +AUTHORS: + +- Antoine Leudière (2022-04) +- Xavier Caruso (2022-06) """ + #***************************************************************************** # Copyright (C) 2022 Xavier Caruso # Antoine Leudière @@ -10,18 +19,134 @@ #****************************************************************************** from sage.categories.category import CategoryWithParameters +from sage.categories.homsets import Homsets from sage.misc.functional import log from sage.rings.integer import Integer from sage.rings.morphism import RingHomomorphism -from sage.rings.polynomial.polynomial_ring import PolynomialRing_general from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing +from sage.rings.polynomial.polynomial_ring import PolynomialRing_general -from sage.categories.homsets import Homsets - -# from sage.misc.cachefunc import cached_method -# from sage.categories.basic import Fields class DrinfeldModules(CategoryWithParameters): + r""" + This class represents the category of Drinfeld modules on a given + `\Fq[X]`-field `K`. + + The `\Fq[X]`-field structure on `K` is given by a ring morphism + `\gamma: \Fq[X] \to K`. + + We say that `\Fq[X]` is the *function ring of the category*; `K` is the + *base of the category*, or simply its base ring or base field; `\Fq[X]` is + the *function ring of the category*; *K\{\tau\}* is the *Ore + polynomial ring of the category*; + `t` is the *Ore variable of the category*. The *constant coefficient + of the category* is `\gamma(X)`. + + INPUT: a ring morphism `\Fq[X] \to K` + + EXAMPLES: + + Generally, Drinfeld modules objects are created before their + category, and the category is retrieved as an attribute of the + Drinfeld module:: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat + Category of Drinfeld modules defined by Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^4 + Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + + The output tells the user that the category is only defined by the + ring morphism `\gamma`. + + .. RUBRIC:: Properties of the category + + The defining morphism is retrieved using the method + :meth:`morphism`:: + + sage: cat.morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^4 + Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + + The so-called *constant coefficient* --- which is the same for all + Drinfeld modules in the category --- is simply the image of `X` by + this morphism: + + sage: cat.constant_coefficient() + z^3 + 7*z^2 + 6*z + 10 + sage: cat.morphism()(X) == cat.constant_coefficient() + True + + Similarly, the *`\Fq[X]`-characteristic* of the category is either + `0` or the unique monic polynomial in `\Fq[X]` that generates + `\mathrm{Ker}(\gamma)`:: + + sage: cat.characteristic() + X^2 + 7*X + 2 + sage: cat.morphism()(cat.characteristic()) + 0 + + Like for its Drinfeld modules, the *base* of the category is the + field `K`:: + + sage: cat.base() + Finite Field in z of size 11^4 + sage: cat.base() is K + True + + And the *function ring* is the polynomial ring `\Fq[X]`:: + + sage: cat.function_ring() + Univariate Polynomial Ring in X over Finite Field of size 11 + sage: cat.function_ring() is FqX + True + + And as expected, the *Ore polynomial ring* is that of + the Drinfeld modules in the category: + + sage: cat.ore_polring() + Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11 + sage: cat.ore_polring() is phi.ore_polring() + True + + .. RUBRIC:: Creating Drinfeld module objects from the category + + Calling the category with an Ore polynomial creates a Drinfeld + module object in the category whose generator is the input:: + + sage: psi = cat([p_root, 1]) + sage: psi + Drinfeld module defined by X |--> t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + sage: psi.category() is cat + True + + Of course, the constant coefficient of the input must be the same as + the category':: + + sage: cat([z, 1]) + Traceback (most recent call last): + ... + ValueError: constant coefficient must be a root of the characteristic + + It is also possible to create a random object in the category, with + a given rank:: + + sage: rho = cat.random_object(2) + sage: rho # random + Drinfeld module defined by X |--> (7*z^3 + 7*z^2 + 10*z + 2)*t^2 + (9*z^3 + 5*z^2 + 2*z + 7)*t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + sage: rho.rank() == 2 + True + sage: rho.category() is cat + True + """ def __init__(self, morphism, name='t'): gamma = morphism @@ -61,28 +186,234 @@ def __init__(self, morphism, name='t'): elif FqX.is_subring(K): self._characteristic = Integer(0) + def _call_(self, gen): + r""" + Return a Drinfeld module object, in the category, whose + generator is the input. + + INPUT: the generator of the Drinfeld module, given as an Ore + polynomial or a list of coefficients + + OUTPUT: a Drinfeld module in the category + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: psi = cat([p_root, 0, 1]) + sage: psi + Drinfeld module defined by X |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + sage: t = phi.ore_variable() + sage: cat(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi + True + """ + from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule + # If gen is not in the Ore polring, an exception is raised + gen = self._ore_polring(gen) + if self.characteristic()(gen[0]) != 0: + raise ValueError('constant coefficient must be a root of the characteristic') + return DrinfeldModule(self._function_ring, gen) + + # Somehow required for the class definition + def _make_named_class_key(self, name): + return self._function_ring.category() + + def _repr_(self): + return f'Category of Drinfeld modules defined by {self._morphism}' + + # Somehow required for the class definition + def Homsets(self): + return Homsets() + + # Somehow required for the class definition + def Endsets(self): + return Homsets() def base(self): + r""" + Return the base of the category. + + OUTPUT: a ring + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat.base() + Finite Field in z of size 11^4 + sage: cat.base() is K + True + """ return self._base def characteristic(self): + r""" + Return the `\Fq[X]`-characteristic of the category. + + OUTPUT: `0` or a monic prime polynomial in `\Fq[X]` + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat.characteristic() + X^2 + 7*X + 2 + + sage: L = Frac(FqX) + sage: psi = DrinfeldModule(FqX, [L.gen(), 1]) + sage: psi + Drinfeld module defined by X |--> t + X over Fraction Field of Univariate Polynomial Ring in X over Finite Field of size 11 + sage: fox = psi.category() + sage: fox.characteristic() + 0 + """ if self._characteristic is None: raise NotImplementedError return self._characteristic def constant_coefficient(self): + r""" + Return the constant coefficient of the category. + + OUTPUT: an element in the base + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat.constant_coefficient() + z^3 + 7*z^2 + 6*z + 10 + sage: cat.constant_coefficient() == cat.morphism()(X) + True + """ return self._constant_coefficient def function_ring(self): + r""" + Return the function ring of the category. + + OUTPUT: the ring `\Fq[X]` + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat.function_ring() + Univariate Polynomial Ring in X over Finite Field of size 11 + sage: cat.function_ring() is FqX + True + """ return self._function_ring def morphism(self): + r""" + Return the morphism that defines the category. + + OUTPUT: a ring morphism + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: gamma = cat.morphism() + sage: gamma + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^4 + Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + sage: gamma(X) == cat.constant_coefficient() + True + """ return self._morphism def ore_polring(self): + r""" + Return the Ore polynomial ring of the category. + + OUTPUT: the Ore polynomial ring `K\{\tau\}` + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat.ore_polring() + Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11 + sage: cat.ore_polring() is phi.ore_polring() + True + """ return self._ore_polring + def ore_variable(self): + r""" + Return the Ore variable of the category. + + OUTPUT: the generator of the Ore polynomial ring + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat.ore_variable() + t + sage: cat.ore_variable() is phi.ore_variable() + True + """ + return self._ore_polring.gen() + def random_object(self, rank): + r""" + Return a random Drinfeld module in the category, whose rank is + the input. + + INPUT: an integer, the rank of the Drinfeld module + + OUTPUT: a Drinfeld module in the category + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: psi = cat.random_object(3) # random + Drinfeld module defined by X |--> (6*z^3 + 4*z^2 + 10*z + 9)*t^3 + (4*z^3 + 8*z^2 + 8*z)*t^2 + (10*z^3 + 3*z^2 + 6*z)*t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + sage: psi.rank() == 3 + True + """ if not isinstance(rank, Integer): raise TypeError('rank must be a positive integer') if rank <= 0: @@ -99,38 +430,16 @@ def random_object(self, rank): return self(coeffs) - # Sage methods - - def _call_(self, gen): - # Avoid circular import - from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule - # If gen is not in the codomain, an exception is raised - gen = self._ore_polring(gen) - if self.characteristic()(gen[0]) != 0: - raise ValueError('constant coefficient must be a root of the characteristic') - return DrinfeldModule(self._function_ring, gen) - - def _repr_(self): - return f'Category of Drinfeld modules defined by {self._morphism}' - - # Sage requires those: - - def _make_named_class_key(self, name): - return self._function_ring.category() - + # Somehow required for the class definition def super_categories(self): return [] - def Homsets(self): - return Homsets() - - def Endsets(self): - return Homsets() - + # Somehow required for the class definition class ParentMethods: def characteristic(self): return self.category().characteristic() + # Somehow required for the class definition class ElementMethods: pass From e80ed4b20d381b60171dc22548a355e7869fc0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 16:08:14 +0200 Subject: [PATCH 097/392] Make OUTPUT: fields oneliners in Drinfeld modules classes See https://trac.sagemath.org/ticket/33713#comment:87. --- .../drinfeld_modules/drinfeld_module.py | 68 +++++-------------- .../finite_drinfeld_module.py | 24 ++----- 2 files changed, 24 insertions(+), 68 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index c4b0248c4e1..f1480b9fd5b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -502,9 +502,7 @@ def __call__(self, a): - ``a`` -- an element in the function ring - OUTPUT: - - - an element of the base ring + OUTPUT: an element of the base ring """ return self._morphism(a) @@ -523,9 +521,7 @@ def _Hom_(self, other, category): - ``category`` -- the category in which we consider the morphisms, usually `self.category()` - OUTPUT: - - - an homset + OUTPUT: an homset EXAMPLES: @@ -575,9 +571,7 @@ def base_ring(self): This is the Ore polynomial ring base. In particular, the base ring is always a field. - OUTPUT: - - - a field + OUTPUT: a field EXAMPLES: @@ -639,9 +633,7 @@ def change_ring(self, new_field, name=None): - ``new_field`` -- the field extension of the base ring that serves as base ring for the new Drinfeld module - OUTPUT: - - - a Drinfeld module + OUTPUT: a Drinfeld module EXAMPLES: @@ -703,9 +695,7 @@ def constant_coefficient(self): `\gamma(X)`. Hence, it is a root of the `\Fq[X]`-characteristic of the base ring. - OUTPUT: - - - an element in the base ring + OUTPUT: an element in the base ring EXAMPLES: @@ -755,9 +745,7 @@ def function_ring(self): In our case, the function ring is an `\Fq[X]`. - OUTPUT: - - - a polynomial ring + OUTPUT: a polynomial ring EXAMPLES: @@ -779,9 +767,7 @@ def gen(self): `\Fq[X]`, which is `\Fq`-generated by a single element element, whose image characterizes the Drinfeld module. - OUTPUT: - - - an Ore polynomial + OUTPUT: an Ore polynomial EXAMPLES: @@ -801,9 +787,7 @@ def height(self): In our case, the height is always 1. - OUTPUT: - - - an integer + OUTPUT: an integer EXAMPLES: @@ -828,9 +812,7 @@ def invert(self, ore_pol): - ``ore_pol`` -- the Ore polynomial whose preimage we want to compute - OUTPUT: - - - a polynomial + OUTPUT: a polynomial EXAMPLES: @@ -915,9 +897,7 @@ def is_finite(self): Return ``True`` if the Drinfeld module is finite; return ``False`` otherwise. - OUTPUT: - - - a boolean + OUTPUT: a boolean EXAMPLES: @@ -949,10 +929,8 @@ def j(self): finite, as we force the function ring to be of the form `\Fq[X]`. - OUTPUT: - - - an element in the base ring if the rank is two; an - exception is raised otherwise + OUTPUT: an element in the base ring if the rank is two; an + exception is raised otherwise EXAMPLES: @@ -988,10 +966,8 @@ def morphism(self): r""" Return the morphism object that defines the Drinfeld module. - OUTPUT: - - - a ring morphism, from the function ring to the Ore polynomial - ring + OUTPUT: a ring morphism, from the function ring to the Ore + polynomial ring EXAMPLES: @@ -1037,9 +1013,7 @@ def ore_polring(self): r""" Return the Ore polynomial ring of the Drinfeld module. - OUTPUT: - - - an Ore polynomial ring + OUTPUT: an Ore polynomial ring EXAMPLES: @@ -1081,9 +1055,7 @@ def ore_variable(self): The Ore variable is defined as the generator of the Ore polynomial ring. - OUTPUT: - - - an Ore polynomial + OUTPUT: an Ore polynomial EXAMPLES: @@ -1118,9 +1090,7 @@ def rank(self): When the function ring is a polynomial ring, the rank is the degree of the generator. - OUTPUT: - - - an integer + OUTPUT: an integer EXAMPLES: @@ -1153,9 +1123,7 @@ def velu(self, isog): - ``isog`` -- the Ore polynomial that defines the isogeny - OUTPUT: - - - a Drinfeld module + OUTPUT: a Drinfeld module ALGORITHM: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 40563bd83d4..04080461170 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -119,9 +119,7 @@ def frobenius_endomorphism(self): *Frobenius endomorphism* is defined as the endomorphism whose defining Ore polynomial is `t^q`. - OUTPUT: - - - a Drinfeld module morphism + OUTPUT: a Drinfeld module morphism EXAMPLES: @@ -168,9 +166,7 @@ def frobenius_charpoly(self, var='T'): - ``var`` -- (optional) the name of the second variable - OUTPUT: - - - a polynomial in `\Fq[X][T]` + OUTPUT: a polynomial in `\Fq[X][T]` EXAMPLES: @@ -232,9 +228,7 @@ def frobenius_norm(self): Let `n` be the degree of the base ring over `\Fq`. Then the Frobenius norm has degree `n`. - OUTPUT: - - - a polynomial in `\Fq[X]` + OUTPUT: a polynomial in `\Fq[X]` EXAMPLES: @@ -281,9 +275,7 @@ def frobenius_trace(self): Let `n` be the degree of the base ring over `\Fq`. Then the Frobenius trace has degree `\leq \frac{n}{2}`. - OUTPUT: - - - a polynomial in `\Fq[X]` + OUTPUT: a polynomial in `\Fq[X]` ALGORITHM: @@ -335,9 +327,7 @@ def is_ordinary(self): Frobenius trace. A *supersingular* rank two finite Drinfeld module is a Drinfeld module that is not ordinary. - OUTPUT: - - - a boolean + OUTPUT: a boolean EXAMPLES: @@ -373,9 +363,7 @@ def is_supersingular(self): Frobenius trace. An *ordinary* rank two finite Drinfeld module is a Drinfeld module that is not supersingular. - OUTPUT: - - - a boolean + OUTPUT: a boolean EXAMPLES: From 3962e10587ebd857759b8ee18e0d5a81bcccfc30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 16:09:45 +0200 Subject: [PATCH 098/392] Raise an exception if invalid input for velu method in DrinfeldModule When the input Ore polynomial does not define an isogeny, raise ValueError. See https://trac.sagemath.org/ticket/33713#comment:88. --- .../drinfeld_modules/drinfeld_module.py | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index f1480b9fd5b..11f30b7f02c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -377,10 +377,10 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: The Vélu formula - Let ``ore_pol`` be a non-zero Ore polynomial ``ore_pol``. For - Drinfeld module, it is easy to decide if there exists a Drinfeld - module ``psi`` such that ``ore_pol`` is an isogeny from ``self`` to - ``psi``. We can also find ``psi``, which is unique. + Let ``ore_pol`` be a non-zero Ore polynomial. For Drinfeld module, + it is easy to decide if there exists a Drinfeld module ``psi`` such + that ``ore_pol`` is an isogeny from ``self`` to ``psi``. If so, we + find ``psi``:: sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z sage: psi = phi.velu(ore_pol) @@ -391,6 +391,17 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: ore_pol * phi(X) == psi(X) * ore_pol True + If the input does not define an isogeny, an exception is raised: + + sage: phi.velu(0) + Traceback (most recent call last): + ... + ValueError: the input does not define an isogeny + sage: phi.velu(t) + Traceback (most recent call last): + ... + ValueError: the input does not define an isogeny + .. RUBRIC:: The action of a Drinfeld module An `\Fq[X]`-Drinfeld module `\phi` notoriously makes any field @@ -1112,12 +1123,9 @@ def rank(self): def velu(self, isog): r""" - Return a new Drinfeld module such that ``isog`` is an + Return a new Drinfeld module such that input is an isogeny to this module with domain ``self``; if no such isogeny - exists, return ``None``. - - If the input is zero, return ``None``, as an isogeny is - required to be non zero. + exists, raise an exception. INPUT: @@ -1171,14 +1179,27 @@ def velu(self, isog): returns None:: sage: phi.velu(0) + Traceback (most recent call last): + ... + ValueError: the input does not define an isogeny sage: phi.velu(t) + Traceback (most recent call last): + ... + ValueError: the input does not define an isogeny sage: phi.velu(t^3 + t + 2) + Traceback (most recent call last): + ... + ValueError: the input does not define an isogeny """ if not isog in self.ore_polring(): raise TypeError('input must be an Ore polynomial') + e = ValueError('the input does not define an isogeny') if isog == 0: - return None - if not self.characteristic().degree().divides(isog.valuation()): - return None + raise e quo, rem = (isog * self.gen()).right_quo_rem(isog) - return None if rem != 0 else DrinfeldModule(self._function_ring, quo) + char_deg = self.characteristic().degree() + if not char_deg.divides(isog.valuation()) \ + or rem != 0: + raise e + else: + return self.category()(quo) From 5f8c0050392027b4a49b90a3aad75306d621e1e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 17:15:01 +0200 Subject: [PATCH 099/392] Add _latex_ and _repr_ docstrings in Drinfeld modules classes See https://trac.sagemath.org/ticket/33713#comment:88. --- src/sage/categories/drinfeld_modules.py | 46 +++++++++++++++++++ .../function_field/drinfeld_modules/action.py | 32 ++++++++++++- .../drinfeld_modules/drinfeld_module.py | 30 ++++++++++++ .../function_field/drinfeld_modules/homset.py | 34 ++++++++++++++ .../drinfeld_modules/morphism.py | 40 ++++++++++++++++ 5 files changed, 181 insertions(+), 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 98ccb24152a..e2f7951d669 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -21,6 +21,7 @@ from sage.categories.category import CategoryWithParameters from sage.categories.homsets import Homsets from sage.misc.functional import log +from sage.misc.latex import latex from sage.rings.integer import Integer from sage.rings.morphism import RingHomomorphism from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing @@ -222,7 +223,52 @@ def _call_(self, gen): def _make_named_class_key(self, name): return self._function_ring.category() + def _latex_(self): + r""" + Return a latex representation of the category + + OUTPUT: a string + + EXAMPLE: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: latex(cat) + sage: latex(cat) + \text{Category{ }of{ }Drinfeld{ }modules{ }defined{ }by\begin{array}{l} + \text{\texttt{Ring{ }morphism:}}\\ + \text{\texttt{{ }{ }From:{ }Univariate{ }Polynomial{ }Ring{ }in{ }X{ }over{ }Finite{ }Field{ }of{ }size{ }11}}\\ + \text{\texttt{{ }{ }To:{ }{ }{ }Finite{ }Field{ }in{ }z{ }of{ }size{ }11{\char`\^}4}}\\ + \text{\texttt{{ }{ }Defn:{ }X{ }|{-}{-}>{ }z{\char`\^}3{ }+{ }7*z{\char`\^}2{ }+{ }6*z{ }+{ }10}} + \end{array} + """ + return f'\\text{{Category{{ }}of{{ }}Drinfeld{{ }}modules{{ }}' \ + f'defined{{ }}by{latex(self._morphism)}' + def _repr_(self): + r""" + Return a string representation of the category + + OUTPUT: a string + + EXAMPLE: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat + Category of Drinfeld modules defined by Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^4 + Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + """ return f'Category of Drinfeld modules defined by {self._morphism}' # Somehow required for the class definition diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 1901a07d371..62a81dc3364 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -20,7 +20,7 @@ #***************************************************************************** from sage.categories.action import Action - +from sage.misc.latex import latex from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule @@ -89,11 +89,41 @@ def _act_(self, pol, x): return self._drinfeld_module(pol)(x) def _latex_(self): + r""" + Return a LaTeX representation of the action. + + OUTPUT: a string + + EXAMPLES: + + sage: Fq. = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: action = phi.action() + sage: latex(action) + \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 11^2 + """ return f'\\text{{Action{{ }}on{{ }}}}' \ f'{latex(self._drinfeld_module.base_ring())}\\text{{{{ }}' \ f'induced{{ }}by{{ }}}}{self._drinfeld_module}' def _repr_(self): + r""" + Return a string representation of the action. + + OUTPUT: a string + + EXAMPLES: + + sage: Fq. = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: action = phi.action() + sage: action + Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 11^2 + """ return f'Action on {self._drinfeld_module.base_ring()} induced by ' \ f'{self._drinfeld_module}' diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 11f30b7f02c..438d223f9e1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -562,12 +562,42 @@ def _check_rank_two(self): raise NotImplementedError('rank must be 2') def _latex_(self): + r""" + Return a LaTeX representation of the Drinfeld module. + + OUTPUT: a string + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: latex(phi) + \text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }}\Bold{F}_{5^{12}} + """ return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ f'{latex(self._function_ring.gen())} '\ f'\\mapsto {latex(self._gen)}' \ f'\\text{{{{ }}over{{ }}}}{latex(self._base_ring)}' def _repr_(self): + r""" + Return a string representation of the Drinfeld module. + + OUTPUT: a string + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi + Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + """ return f'Drinfeld module defined by {self._function_ring.gen()} ' \ f'|--> {self._gen} over {self._base_ring}' diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 7229df993a1..a5c2fd67df0 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -131,11 +131,45 @@ def __init__(self, X, Y, category=None, check=True): super().__init__(X, Y, category=category, base=base, check=check) def _latex_(self): + r""" + Return a LaTeX representation of the homset. + + OUTPUT: a string + + EXAMPLES: + + sage: Fq = GF(27) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) + sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: hom = Hom(phi, psi) + sage: latex(hom) + \text{Set{ }of{ }Drinfeld{ }module{ }morphisms{ }from}\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto 2 t^{2} + z_{6} t + z_{6}\text{{ }over{ }}\Bold{F}_{3^{6}}\text{{ }to{ }}Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 + """ return f'\\text{{Set{{ }}of{{ }}Drinfeld{{ }}module{{ }}morphisms' \ f'{{ }}from}}{latex(self.domain())}\\text{{{{ }}to{{ }}}}' \ f'{self.codomain()}' def _repr_(self): + r""" + Return a string representation of the homset. + + OUTPUT: a string + + EXAMPLES: + + sage: Fq = GF(27) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) + sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: hom = Hom(phi, psi) + sage: hom + Set of Drinfeld module morphisms: + From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + To: Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 + """ return f'Set of Drinfeld module morphisms:\n' \ f' From: {self.domain()}\n' \ f' To: {self.codomain()}' diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 80625539d7d..61ad14914d1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -139,6 +139,27 @@ def __init__(self, parent, ore_pol): self._ore_polynomial = ore_pol def _latex_(self): + r""" + Return a LaTeX representation of the morphism. + + OUTPUT: a string + + EXAMPLES: + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_variable() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: latex(morphism) + \begin{array}{l} + \text{Drinfeld{ }module{ }morphism:}\\ + \text{{ }{ }From:{ }}\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto t^{2} + t + z_{6}\text{{ }over{ }}\Bold{F}_{2^{6}}}\\ + \text{{ }{ }To:{ }}{ }{ }\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto t^{2} + \left(z_{6}^{4} + z_{6}^{2} + 1\right) t + z_{6}\text{{ }over{ }}\Bold{F}_{2^{6}}\\ + \text{{ }{ }Defn:{ }}t + z_{6}^{5} + z_{6}^{2} + 1 + \end{array} + """ return f'\\begin{{array}}{{l}}\n' \ f'\\text{{Drinfeld{{ }}module{{ }}morphism:}}\\\\\n' \ f'\\text{{{{ }}{{ }}From:{{ }}}}{latex(self._domain)}}}\\\\\n' \ @@ -147,6 +168,25 @@ def _latex_(self): f'\\end{{array}}' def _repr_(self): + r""" + Return a string representation of the morphism. + + OUTPUT: a string + + EXAMPLES: + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_variable() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> t^2 + t + z6 over Finite Field in z6 of size 2^6 + To: Drinfeld module defined by X |--> t^2 + (z6^4 + z6^2 + 1)*t + z6 over Finite Field in z6 of size 2^6 + Defn: t + z6^5 + z6^2 + 1 + """ return f'Drinfeld Module morphism:\n' \ f' From: {self._domain}\n' \ f' To: {self._codomain}\n' \ From 83c728ebf5fb752f5e36cc2693ac1f1ff37636f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 17:16:52 +0200 Subject: [PATCH 100/392] Rename j method to j_invariant in DrinfeldModule See https://trac.sagemath.org/ticket/33713#comment:90. --- .../drinfeld_modules/drinfeld_module.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 438d223f9e1..c3fac7893f6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -303,7 +303,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): As well as the j-invariant if the rank is two:: - sage: phi.j() # j-invariant + sage: phi.j_invariant() # j-invariant 1 .. RUBRIC:: Morphisms, isogenies @@ -957,7 +957,7 @@ def is_finite(self): from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule return isinstance(self, FiniteDrinfeldModule) - def j(self): + def j_invariant(self): r""" Return the j-invariant of the Drinfeld module; only the rank two case has been implemented, a NotImplementedError is raised if @@ -980,19 +980,19 @@ def j(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.j() + sage: phi.j_invariant() z12^10 + 4*z12^9 + 3*z12^8 + 2*z12^7 + 3*z12^6 + z12^5 + z12^3 + 4*z12^2 + z12 + 2 sage: psi = DrinfeldModule(FqX, [p_root, 1, 1]) - sage: psi.j() + sage: psi.j_invariant() 1 sage: rho = DrinfeldModule(FqX, [p_root, 0, 1]) - sage: rho.j() + sage: rho.j_invariant() 0 The rank must be two:: sage: theta = DrinfeldModule(FqX, [p_root, 1, 0]) - sage: theta.j() + sage: theta.j_invariant() Traceback (most recent call last): ... NotImplementedError: rank must be 2 From 53088a9d7f981d6bd5dba9fa60405c975de583a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 17:18:14 +0200 Subject: [PATCH 101/392] Add various docstrings in Drinfeld module classes Some methods were not properly documented; this is solved. --- .../function_field/drinfeld_modules/action.py | 27 ++++++ .../drinfeld_modules/drinfeld_module.py | 28 ++++++ .../function_field/drinfeld_modules/homset.py | 90 +++++++++++++++++++ 3 files changed, 145 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 62a81dc3364..be0b3ecd51c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -82,6 +82,33 @@ def __init__(self, drinfeld_module): drinfeld_module.base_ring()) def _act_(self, pol, x): + r""" + Return ``pol * x``, where ``*`` is the action. + + INPUT: + + - ``pol`` -- a polynomial in the function ring of the Drinfeld + module + - ``x`` -- an element in the base ring of the Drinfeld module + + OUTPUT: an element in the base ring of the Drinfeld module. + + EXAMPLES: + + sage: Fq. = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: action = phi.action() + sage: P = X + 1 + sage: a = z + sage: action(P, a) + 4*z + 2 + sage: action(0, K.random_element()) + 0 + sage: action(FqX.random_element(), 0) + 0 + """ if pol not in self._drinfeld_module.function_ring(): raise TypeError('first input must be in the function ring') if x not in self._drinfeld_module.base_ring(): diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index c3fac7893f6..2b82e8b8edd 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -602,6 +602,34 @@ def _repr_(self): f'|--> {self._gen} over {self._base_ring}' def action(self): + r""" + Return the action object that represents the action on the base that is + induced by the Drinfeld module. + + OUTPUT: a Drinfeld module action object + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: action = phi.action() + sage: action + Action on Finite Field in z12 of size 5^12 induced by Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + + The action on elements is computed as follows:: + + sage: P = X^2 + X + 1 + sage: a = z12 + 1 + sage: action(P, a) + 3*z12^11 + 2*z12^10 + 3*z12^9 + 3*z12^7 + 4*z12^5 + z12^4 + z12^3 + 2*z12 + 1 + sage: action(0, a) + 0 + sage: action(P, 0) + 0 + """ from sage.rings.function_field.drinfeld_modules.action import DrinfeldModuleAction return DrinfeldModuleAction(self) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index a5c2fd67df0..680defe2002 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -175,6 +175,57 @@ def _repr_(self): f' To: {self.codomain()}' def __contains__(self, x): + r""" + Implement the ``in`` operator for the homset; return ``True`` if + the input defines a morphism in the homset, return ``False`` + otherwise. + + INPUT: + + - ``x`` -- an Ore polynomial or a Drinfeld module morphism + + OUTPUT: a boolean + + EXAMPLES: + + In the next examples, the input is an Ore polynomial:: + + sage: Fq = GF(27) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) + sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: hom = Hom(phi, psi) + sage: end = End(phi) + sage: t = phi.ore_variable() + + sage: 1 in hom + False + sage: t^6 in hom + False + sage: t + 1 in hom + True + sage: 1 in end + True + sage: t^6 in end + True + sage: t + 1 in end + False + + Whereas the input is now a Drinfeld module morphism:: + + sage: isogeny = hom(t + 1) + sage: isogeny in hom + True + sage: end(0) in end + True + sage: identity_morphism = end(1) + sage: identity_morphism in hom + False + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism in hom + False + """ try: x = self(x) return True @@ -182,6 +233,45 @@ def __contains__(self, x): return False def _element_constructor_(self, *args, **kwds): + r""" + Return a Drinfeld module morphism defined by the input, which is + an Ore polynomial. + + INPUT: an Ore polynomial + + OUTPUT: a Drinfeld module morphism + + EXAMPLES: + + sage: Fq = GF(27) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) + sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: hom = Hom(phi, psi) + sage: end = End(phi) + sage: t = phi.ore_variable() + sage: identity_morphism = end(1) + sage: identity_morphism + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + Defn: 1 + + sage: frobenius_endomorphism = end(t^6) + sage: frobenius_endomorphism + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + Defn: t^6 + + sage: isogeny = hom(t + 1) + sage: isogeny + Drinfeld Module morphism: + From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + To: Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 + Defn: t + 1 + """ # NOTE: This used to be self.element_class(self, ...), but this # would call __init__ instead of __classcall_private__. This # seems to work, but I don't know what I'm doing. From 261ab62cdd20badc99fbcb1ddf59bfcf56073ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 17:19:09 +0200 Subject: [PATCH 102/392] Include final comments (87-90) from Ticket I added (almost all) suggestions from comments 87-91 from ticket https://trac.sagemath.org/ticket/33713. Some of the suggestions were already introduced in previous commits. --- .../function_field/drinfeld_modules/action.py | 2 + .../drinfeld_modules/drinfeld_module.py | 63 +++++++++++-------- .../function_field/drinfeld_modules/homset.py | 1 - 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index be0b3ecd51c..05999b7ea96 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -160,6 +160,8 @@ def drinfeld_module(self): OUTPUT: a Drinfeld module + EXAMPLES: + sage: Fq. = GF(11) sage: FqX. = Fq[] sage: K. = Fq.extension(2) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 2b82e8b8edd..5e2232398e1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -60,9 +60,10 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): We say that `\Fq[X]` is the *function ring of `\phi`*; `K` is the *base ring of `\phi`*, or simply its base or base field; `\Fq[X]` is - the *function ring of*; *K\{\tau\}* is the *Ore polynomial ring*; - `t` is the *Ore variable*. The *generator of `\phi`* is `\phi_X`, - its *constant coefficient* is the constant coefficient of `\phi_X`. + the *function ring of `\phi`*; *K\{\tau\}* is the *Ore polynomial + ring of `\phi`*; `t` is the *Ore variable of `\phi`*. The *generator of `\phi`* is + `\phi_X`, its *constant coefficient* is the constant coefficient of + `\phi_X`. The Drinfeld module `\phi` is uniquely determined by the image `\phi_X` of `X`. This Ore polynomial is an input of the class @@ -89,9 +90,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: Construction and input - A Drinfeld module object (class - :class:`sage.rings.function_field.drinfeld_module.drinfeld_module.DrinfeldModule`) - is constructed as follows:: + A Drinfeld module object is constructed as follows:: sage: Fq. = GF(3^2) sage: FqX. = Fq[] @@ -105,7 +104,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): also use regular Ore polynomials:: sage: ore_polring = phi.ore_polring() - sage: t = phi.ore_variable() # Is ore_polring.gen() + sage: t = phi.ore_variable() # same as ore_polring.gen() sage: psi_X = z + t^3 sage: psi = DrinfeldModule(FqX, psi_X) sage: psi @@ -113,9 +112,6 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: psi(X) == psi_X True - Naturally, the input is checked, and exceptions are raised when the - input is invalid. - The generator must have positive degree:: sage: DrinfeldModule(FqX, [z]) @@ -230,6 +226,14 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: phi.is_finite() True + It is possible to change the base ring:: + + sage: L = K.extension(2) + sage: phi_rebased = phi.change_ring(L) + sage: Ltau = phi_rebased.ore_polring() + sage: Ltau(phi(X)) == phi_rebased(X) + True + .. RUBRIC:: The category of Drinfeld modules Drinfeld modules have their own category (see class @@ -313,8 +317,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): every `a \in \Fq[X]`. In our case, this is equivalent to verifying `f \phi_X = \psi_X f`. An *isogeny* is a non-zero morphism. - Use the ``in`` syntax to test if an Ore polynomial defines an - isogeny:: + Use the ``in`` syntax to test if an Ore polynomial defines a + morphism:: sage: phi(X) in Hom(phi, phi) True @@ -405,16 +409,17 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: The action of a Drinfeld module An `\Fq[X]`-Drinfeld module `\phi` notoriously makes any field - extension `L/K` a left `\Fq[X]`-module with the law defined by - `(P(X), a) \mapsto \phi_P(a)`, where `a \in L`. The method - :meth:`action` returns an ``Action`` object representing the - Drinfeld module action; in this implementation, `K = L`. + extension `L/K` a left `\Fq[X]`-module. Let `x \in L`, let `P \in + \Fq[X]`; the action is defined as `(P, a) \mapsto \phi_P(a)`, where + `\phi_P(a)`. The method :meth:`action` returns an ``Action`` object + representing the Drinfeld module action; in this implementation, `K + = L`. sage: action = phi.action() sage: action Action on Finite Field in z of size 3^12 induced by Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 - The action is computed as follows:: + The action on elements is computed as follows:: sage: P = X + 1 sage: a = z @@ -426,6 +431,13 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: action(FqX.random_element(), 0) 0 + To act on a field larger than `K`, one can change the ring of the + Drinfeld module, then create the action:: + + sage: extended_action = phi.change_ring(K.extension(5)).action() + sage: extended_action + Action on Finite Field in z60 of size 3^60 induced by Drinfeld module defined by X |--> t^2 + t + 2*z60^59 + z60^56 + 2*z60^55 + 2*z60^54 + 2*z60^53 + z60^49 + z60^48 + z60^47 + 2*z60^45 + z60^44 + 2*z60^41 + 2*z60^40 + 2*z60^39 + 2*z60^37 + 2*z60^36 + z60^34 + z60^33 + z60^32 + 2*z60^31 + 2*z60^30 + 2*z60^27 + 2*z60^25 + z60^23 + z60^22 + z60^21 + 2*z60^20 + z60^19 + z60^18 + z60^17 + z60^16 + z60^15 + 2*z60^14 + z60^12 + 2*z60^11 + 2*z60^10 + z60^8 + z60^6 + 2*z60^5 + z60^4 + z60^3 + z60 + 1 over Finite Field in z60 of size 3^60 + .. RUBRIC:: Inversion of the Drinfeld module Given an Ore polynomial that equals `\phi_a` for some `a \in @@ -480,7 +492,8 @@ def __classcall_private__(cls, function_ring, gen, name='t'): category = DrinfeldModules(gamma, name=name) # Check gen as Ore polynomial - if ore_polring not in (None, category.ore_polring()): + if ore_polring is not None and \ + ore_polring != category.ore_polring(): raise ValueError(f'generator must lie in {category.ore_polring()}') ore_polring = category.ore_polring() # Sanity cast gen = ore_polring(gen) @@ -491,8 +504,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): if ore_polring_base.is_finite(): from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule return FiniteDrinfeldModule(gen, category) - else: - return cls.__classcall__(cls, gen, category) + return cls.__classcall__(cls, gen, category) def __init__(self, gen, category): super().__init__(category=category) @@ -833,8 +845,8 @@ def gen(self): Return the generator of the Drinfeld module, i.e. `\phi_X`. This method makes sense because, in our case, the function ring - `\Fq[X]`, which is `\Fq`-generated by a single element element, - whose image characterizes the Drinfeld module. + `\Fq[X]`, which is `\Fq`-generated by a single element, whose + image characterizes the Drinfeld module. OUTPUT: an Ore polynomial @@ -872,7 +884,7 @@ def height(self): def invert(self, ore_pol): r""" - Return the pre-image of the input under the Drinfeld module; + Return the preimage of the input under the Drinfeld module; raise an exception if the input is not in the image of the Drinfeld module. @@ -963,8 +975,7 @@ def invert(self, ore_pol): def is_finite(self): r""" - Return ``True`` if the Drinfeld module is finite; return - ``False`` otherwise. + Return ``True`` whether the Drinfeld module is finite. OUTPUT: a boolean @@ -1194,7 +1205,7 @@ def velu(self, isog): ALGORITHM: The input defines an isogeny if only if: - 1. The degree of the characteristic devides the height + 1. The degree of the characteristic divides the height of the input. (The height of an Ore polynomial `P(t)` is the maximum `n` such that `t^n` right-divides `P(t)`.) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 680defe2002..8ec98e7562f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -62,7 +62,6 @@ class DrinfeldModuleHomset(Homset): sage: end is Hom(phi, phi) True - One can create morphism objects by calling the homset:: sage: t = phi.ore_variable() From c6aaebdedd3a65cf0ba352bba0c713711ca3458d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 22 Aug 2022 19:38:39 +0200 Subject: [PATCH 103/392] Add coefficients methods in DrinfeldModule I created the DrinfeldModule methods: - coefficients, - coefficient, - __getitem__. The code is tested and I added documentation in the main docstring. See https://trac.sagemath.org/ticket/33713#comment:89. --- .../drinfeld_modules/drinfeld_module.py | 136 +++++++++++++++--- 1 file changed, 120 insertions(+), 16 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 5e2232398e1..0822d03fe44 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -259,16 +259,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): suggests, the morphism `\gamma` uniquely determines the category of a Drinfeld module. - .. RUBRIC:: Basic methods - - For a polynomial `a \in \Fq[X]`, compute `\phi_a` by calling `phi`:: - - sage: phi(X) # phi_X - t^2 + t + z - sage: phi(X^3 + X + 1) # phi_X^3 +X + 1 - t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 - sage: phi(1) # phi_1 - 1 + .. RUBRIC:: Getters and basic properties One can retrieve basic properties:: @@ -298,6 +289,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): To: Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) Defn: X |--> t^2 + t + z + .. RUBRIC:: Height, rank and j-invariant + One can compute the rank and height:: sage: phi.rank() @@ -310,6 +303,36 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: phi.j_invariant() # j-invariant 1 + .. RUBRIC:: The generator of a Drinfeld module + + For a polynomial `a \in \Fq[X]`, compute `\phi_a` by calling `phi`:: + + sage: phi(X) # phi_X + t^2 + t + z + sage: phi(X^3 + X + 1) # phi_X^3 +X + 1 + t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 + sage: phi(1) # phi_1 + 1 + + This is especially useful to quickly retrive `\phi_X`, the + generator. Furthermore, a Drinfeld `\Fq[X]`-module can be seen as an + Ore polynomial with positive degree and constant coefficient + `\gamma(X)`. This analogy is the motivation for the following + methods:: + + sage: phi.coefficients() + [z, 1, 1] + + sage: phi.coefficient(1) + 1 + sage: phi.coefficient(1) + 1 + + The ``[...]`` was introduced as a shortcut to ``phi(X)[...]``:: + + sage: phi[1] + 1 + .. RUBRIC:: Morphisms, isogenies A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore @@ -530,6 +553,12 @@ def __call__(self, a): return self._morphism(a) + def __getitem__(self, n): + r""" + See method :meth:`coefficient`. + """ + return self.coefficient(n) + def _Hom_(self, other, category): r""" Return ``DrinfeldModuleHomset(self, other, category)``. @@ -812,13 +841,88 @@ def constant_coefficient(self): ... ValueError: constant coefficient must be a root of the characteristic - One can also retrieve the constant coefficient using - ``phi(X)[0]`:: + One can also retrieve the constant coefficient using ``phi[0]`:: + + sage: phi.constant_coefficient() == phi[0] + True + """ + return self[0] + + def coefficient(self, n): + r""" + Return the n-th coefficient of the generator. + + INPUT: + + - ``n`` -- a non-negative integer + + OUTPUT: an element in the base ring + + EXAMPLES: - sage: phi.constant_coefficient() == phi(X)[0] + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.coefficient(0) + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi.coefficient(0) == p_root True + sage: phi.coefficient(1) + z12^3 + sage: phi.coefficient(2) + z12^5 + sage: phi.coefficient(5) + Traceback (most recent call last): + ... + ValueError: input must be >= 0 and <= rank + """ + if not isinstance(n, Integer) and not isinstance(n, int): + raise TypeError('input must be an integer') + if not 0 <= n <= self.rank(): + raise ValueError('input must be >= 0 and <= rank') + return self.coefficients(sparse=False)[n] + + def coefficients(self, sparse=True): + r""" + Return the coefficients of the generator, as a list. + + If the the flag ``sparse`` is ``True`` (default), only return the + non-zero coefficients; otherwise, return all of them. + + INPUT: + - ``sparse`` -- a boolean + + OUTPUT: a list of elements in the base ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.coefficients() + [2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12, + z12^3, + z12^5] + + Careful, the method only returns the non-zero coefficients, + unless otherwise specified:: + + sage: rho = DrinfeldModule(FqX, [p_root, 0, 0, 0, 1]) + sage: rho.coefficients() + [2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12, + 1] + sage: rho.coefficients(sparse=False) + [2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12, + 0, + 0, + 0, + 1] """ - return self.gen()[0] + return self._gen.coefficients(sparse=sparse) def function_ring(self): r""" @@ -1037,8 +1141,8 @@ def j_invariant(self): NotImplementedError: rank must be 2 """ self._check_rank_two() - g = self._gen[1] - delta = self._gen[2] + g = self[1] + delta = self[2] q = self._Fq.order() return (g**(q+1)) / delta From 529f5ec6b7757a7451d0dfeeaf51a910a9650dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 23 Aug 2022 14:25:28 +0200 Subject: [PATCH 104/392] Use Fq[X] notation as little as possible in Drinfeld modules classes (comment 96) See https://trac.sagemath.org/ticket/33713#comment:96. The suggestion is to use as little notations like `\Fq[X]` as possible, as different people use different variables. What I did is that I kept the same notations in the main docstring of each class, as I consider notations to be global notations in these giant docstrings. However, I removed them in the method docstrings. I may have forgot some modifications. Also feel free to suggest enhancements on those changes, as they were quickly made. --- src/sage/categories/drinfeld_modules.py | 23 ++-- .../function_field/drinfeld_modules/action.py | 16 ++- .../drinfeld_modules/drinfeld_module.py | 122 ++++++++++-------- .../finite_drinfeld_module.py | 79 +++++++----- .../drinfeld_modules/morphism.py | 16 +-- 5 files changed, 142 insertions(+), 114 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index e2f7951d669..4a7c4e6b155 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -31,10 +31,11 @@ class DrinfeldModules(CategoryWithParameters): r""" This class represents the category of Drinfeld modules on a given - `\Fq[X]`-field `K`. + base. - The `\Fq[X]`-field structure on `K` is given by a ring morphism - `\gamma: \Fq[X] \to K`. + Let `\phi` be a Drinfeld module in the present category. We denote + its function ring `\Fq[X]` and its base ring `K`. The `\Fq[X]`-field + structure on `K` is given by a morphism `\gamma` We say that `\Fq[X]` is the *function ring of the category*; `K` is the *base of the category*, or simply its base ring or base field; `\Fq[X]` is @@ -43,7 +44,11 @@ class DrinfeldModules(CategoryWithParameters): `t` is the *Ore variable of the category*. The *constant coefficient of the category* is `\gamma(X)`. - INPUT: a ring morphism `\Fq[X] \to K` + .. NOTE:: + + These notations will be used throughout this docstring. + + INPUT: a ring morphism from the function ring to the base ring EXAMPLES: @@ -86,7 +91,7 @@ class DrinfeldModules(CategoryWithParameters): sage: cat.morphism()(X) == cat.constant_coefficient() True - Similarly, the *`\Fq[X]`-characteristic* of the category is either + Similarly, the *function ring-characteristic* of the category is either `0` or the unique monic polynomial in `\Fq[X]` that generates `\mathrm{Ker}(\gamma)`:: @@ -302,9 +307,9 @@ def base(self): def characteristic(self): r""" - Return the `\Fq[X]`-characteristic of the category. + Return the function ring-characteristic of the category. - OUTPUT: `0` or a monic prime polynomial in `\Fq[X]` + OUTPUT: `0` or a monic prime polynomial in the function ring EXAMPLES: @@ -400,7 +405,7 @@ def ore_polring(self): r""" Return the Ore polynomial ring of the category. - OUTPUT: the Ore polynomial ring `K\{\tau\}` + OUTPUT: an Ore polynomial ring EXAMPLES: @@ -421,7 +426,7 @@ def ore_variable(self): r""" Return the Ore variable of the category. - OUTPUT: the generator of the Ore polynomial ring + OUTPUT: an Ore polynomial EXAMPLES: diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 05999b7ea96..535474c3f57 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -26,12 +26,15 @@ class DrinfeldModuleAction(Action): r""" - This class represents the left `\Fq[X]`-module action induced by a - Drinfeld `\Fq[X]`-module defined over an `\Fq[X]`-field `K`. - Let `L/K` be a field extension, let `x \in L`, let `P \in \Fq[X]`; - the action is defined as `(P, a) \mapsto \phi_P(a)`, where - `\phi_P(a)`. In this implementation, `L` is `K`. + This class represents the module action induced by a Drinfeld + module. + + Let `\phi` be a Drinfeld module with function ring `\Fq[X]` and base + ring `K`, whose `\Fq[X]`-field structure is given by a morphism + `\gamma`. Let `L/K` be a field extension, let `x \in L`, let `a` be + a function ring element; the action is defined as `(a, x) \mapsto + \phi_a(x)`. In this implementation, `L` is `K`. The action is instanciated as follows. Note that the user should never explicitely instanciate the class `DrinfeldModuleAction`:: @@ -87,8 +90,7 @@ def _act_(self, pol, x): INPUT: - - ``pol`` -- a polynomial in the function ring of the Drinfeld - module + - ``pol`` -- a function ring element - ``x`` -- an element in the base ring of the Drinfeld module OUTPUT: an element in the base ring of the Drinfeld module. diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 0822d03fe44..08b1309de86 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -45,25 +45,31 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): equiped a ring morphism `\gamma: \Fq[X] \to K` --- the field `K` is said to be an *`\Fq[X]`-field*, and the monic polynomial that generates `\Ker(\gamma)` is called the *`\Fq[X]`-characteristic of - the `\Fq[X]`-field `K`* (this characteristic plays the role of the - characteristic of a function field). Let `K\{\tau\}` be the ring of - Ore polynomials with coefficients in `K` and Frobenius - variable `\tau: x \mapsto x^q`. A *Drinfeld `\Fq[X]`-module over the - `\Fq[X]`-field `K`* is a ring morphism `\phi: \Fq[X] \to K\{\tau\}` - such that: + the `\Fq[X]`-field `K`*. This characteristic plays the role of the + characteristic of a function field. + + Let `K\{\tau\}` be the ring of Ore polynomials with coefficients in + `K` and Frobenius variable `\tau: x \mapsto x^q`. A *Drinfeld + `\Fq[X]`-module over the `\Fq[X]`-field `K`* is a ring morphism + `\phi: \Fq[X] \to K\{\tau\}` such that: 1. The image of `\phi` contains non-constant Ore polynomials. - 2. For every `a \in \Fq[X]`, the constant coefficient `\phi(a)` - is `\gamma(a)`. + 2. For every element `a` in the function ring, the constant + coefficient `\phi(a)` is `\gamma(a)`. + + For `a` in the function ring, `\phi(a)` is denoted `\phi_a`. + + .. NOTE:: - For `a \in \Fq[X]`, `\phi(a)` is denoted `\phi_a`. + These notations will be used throughout this docstring. We say that `\Fq[X]` is the *function ring of `\phi`*; `K` is the - *base ring of `\phi`*, or simply its base or base field; `\Fq[X]` is - the *function ring of `\phi`*; *K\{\tau\}* is the *Ore polynomial - ring of `\phi`*; `t` is the *Ore variable of `\phi`*. The *generator of `\phi`* is - `\phi_X`, its *constant coefficient* is the constant coefficient of - `\phi_X`. + *base ring of `\phi`*, or simply its base or base field; *K\{\tau\}* + is the *Ore polynomial ring of `\phi`*; `t` is the *Ore variable of + `\phi`*. The *generator of `\phi`* is `\phi_X`, its *constant + coefficient* is the constant coefficient of `\phi_X`. The + `\Fq[X]`-characteristic of the base ring `K` can also be referred to + as its *function ring-characteristic*. The Drinfeld module `\phi` is uniquely determined by the image `\phi_X` of `X`. This Ore polynomial is an input of the class @@ -74,16 +80,17 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. NOTE:: - Drinfeld modules are defined in a larger setting, in which - `\Fq[X]` is replaced by a more general ring: the ring of - functions in `k` that are regular outside `\infty`, where `k` is - a function field over `\Fq` with transcendance degree `1` and - `\infty` is a fixed place of `k`. This is out of the scope of - this implementation. + Drinfeld modules are defined in a larger setting, in which the + polynomial ring `\Fq[X]` is replaced by a more general function + ring: the ring of functions in `k` that are regular outside + `\infty`, where `k` is a function field over `\Fq` with + transcendance degree `1` and `\infty` is a fixed place of `k`. + This is out of the scope of this implementation. INPUT: - - ``function_ring`` -- the polynomial ring `\Fq[X]` + - ``function_ring`` -- the polynomial ring with coefficients in the finite + field `\Fq` - ``gen`` -- the generator `\phi_X`, as a list of coefficients or an Ore polynomial - ``name`` (optional) the name of the Ore variable @@ -119,8 +126,9 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): ... ValueError: generator must have positive degree - The coefficients of the generator must live in some field `K` that - is the codomain of a morphism `\gamma` with domain `\Fq[X]`:: + The coefficients of the generator must live in some base field `K` + that is the codomain of a morphism `\gamma: \Fq[X] \to K`, where + `\Fq[X]` is the function ring:: sage: DrinfeldModule(FqX, [z, QQ(1)]) Traceback (most recent call last): @@ -146,7 +154,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: DrinfeldModule(FqX, [Fq(1), 1, 1]) Drinfeld module defined by X |--> t^2 + t + 1 over Finite Field in z2 of size 3^2 - The function ring must be an `\Fq[X]`:: + The function ring must be an univariate polynomial ring whose + coefficients lie in a finite field:: sage: DrinfeldModule(K, [z, 1, 1]) Traceback (most recent call last): @@ -161,7 +170,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): If you already defined a category of Drinfeld modules, you must ensure that the constant coefficient is a root of the - `\Fq[X]-characteristic of the category base:: + function ring-characteristic of the category base:: sage: cat = phi.category() sage: cat([1, 1, K(1)]) @@ -250,7 +259,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): False This category holds crucial information, like the - `\Fq[X]`-characteristic of `K`:: + function ring-characteristic of the base:: sage: char = phi.category().characteristic() @@ -305,7 +314,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: The generator of a Drinfeld module - For a polynomial `a \in \Fq[X]`, compute `\phi_a` by calling `phi`:: + For a `a` in the function ring, `\phi_a` is computed by calling + `phi`:: sage: phi(X) # phi_X t^2 + t + z @@ -314,11 +324,11 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: phi(1) # phi_1 1 - This is especially useful to quickly retrive `\phi_X`, the - generator. Furthermore, a Drinfeld `\Fq[X]`-module can be seen as an - Ore polynomial with positive degree and constant coefficient - `\gamma(X)`. This analogy is the motivation for the following - methods:: + This is especially useful to quickly retrive `\phi_X`, the generator + of the Drinfeld module. Furthermore, a Drinfeld `\Fq[X]`-module can + be seen as an Ore polynomial with positive degree and constant + coefficient `\gamma(X)`. This analogy is the motivation for the + following methods:: sage: phi.coefficients() [z, 1, 1] @@ -337,8 +347,9 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for - every `a \in \Fq[X]`. In our case, this is equivalent to verifying - `f \phi_X = \psi_X f`. An *isogeny* is a non-zero morphism. + every `a` in the function ring. In our specific case, this is + equivalent to verifying `f \phi_X = \psi_X f`. An *isogeny* is a + non-zero morphism. Use the ``in`` syntax to test if an Ore polynomial defines a morphism:: @@ -432,9 +443,9 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: The action of a Drinfeld module An `\Fq[X]`-Drinfeld module `\phi` notoriously makes any field - extension `L/K` a left `\Fq[X]`-module. Let `x \in L`, let `P \in - \Fq[X]`; the action is defined as `(P, a) \mapsto \phi_P(a)`, where - `\phi_P(a)`. The method :meth:`action` returns an ``Action`` object + extension `L/K` a left `\Fq[X]`-module. Let `x \in L`, let `a` be in + the function ring; the action is defined as `(a, x) \mapsto + \phi_a(x)`. The method :meth:`action` returns an ``Action`` object representing the Drinfeld module action; in this implementation, `K = L`. @@ -463,8 +474,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: Inversion of the Drinfeld module - Given an Ore polynomial that equals `\phi_a` for some `a \in - \Fq[X]`, one can retrieve `a` (as a morphism, a Drinfeld + Given an Ore polynomial that equals `\phi_a` for some function ring + elelement `a`, one can retrieve `a` (as a morphism, a Drinfeld module is injective, see [Gos1998]_, cor. 4.5.2.):: sage: a = FqX.random_element() @@ -799,12 +810,8 @@ def change_ring(self, new_field, name=None): def constant_coefficient(self): r""" - Return the constant coefficient of the generator (`\phi_X`). + Return the constant coefficient of the generator. - From the definition of a Drinfeld module, the constant coefficient equals - `\gamma(X)`. Hence, it is a root of the `\Fq[X]`-characteristic - of the base ring. - OUTPUT: an element in the base ring EXAMPLES: @@ -817,7 +824,9 @@ def constant_coefficient(self): sage: phi.constant_coefficient() == p_root True - The constant coefficient equals `\gamma(X)`:: + Let `\Fq[X]` be the function ring, and let `\gamma` the morphism + defining the `\Fq[X]`-field structure of the base ring. The + constant coefficient equals `\gamma(X)`:: sage: cat = phi.category() sage: gamma = cat.morphism() @@ -928,7 +937,8 @@ def function_ring(self): r""" Return the function ring of the Drinfeld module. - In our case, the function ring is an `\Fq[X]`. + In our case, the function ring is an univariate polynomial ring + whose coefficients lie in a finite field `\Fq`. OUTPUT: a polynomial ring @@ -946,11 +956,13 @@ def function_ring(self): def gen(self): r""" - Return the generator of the Drinfeld module, i.e. `\phi_X`. + Return the generator of the Drinfeld module. + + Let `\phi` denote the Drinfeld module, and `\Fq[X]` be the + function ring. This method returns `\phi_X`. - This method makes sense because, in our case, the function ring - `\Fq[X]`, which is `\Fq`-generated by a single element, whose - image characterizes the Drinfeld module. + This method makes sense the function ring is `\Fq`-generated by + a single element, whose image characterizes the Drinfeld module. OUTPUT: an Ore polynomial @@ -997,7 +1009,7 @@ def invert(self, ore_pol): - ``ore_pol`` -- the Ore polynomial whose preimage we want to compute - OUTPUT: a polynomial + OUTPUT: an element in the function ring EXAMPLES: @@ -1109,9 +1121,8 @@ def j_invariant(self): Assume the rank is two. Write the generator `\phi_X = \gamma(X) + g\tau + \Delta\tau^2`. The j-invariant is defined by `\frac{g^{q+1}}{\Delta}`, `q` being the order of the base field - of the polynomial ring. In our case, this base field is always - finite, as we force the function ring to be of the form - `\Fq[X]`. + of the function ring. In our case, this base field is always + finite. OUTPUT: an element in the base ring if the rank is two; an exception is raised otherwise @@ -1271,8 +1282,7 @@ def rank(self): r""" Return the rank of the Drinfeld module. - When the function ring is a polynomial ring, the rank is the - degree of the generator. + In our case, the rank is the degree of the generator. OUTPUT: an integer diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 04080461170..c7cd5c6d2f5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -35,6 +35,8 @@ class FiniteDrinfeldModule(DrinfeldModule): In this specific documentation, we only present the specifics of ``FiniteDrinfeldModle``. + .. RUBRIC:: Construction: + The user does not ever need to directly call ``FiniteDrinfeldModule``, as it is the (meta)class ``DrinfeldModule`` that is reponsible for instanciating @@ -51,7 +53,7 @@ class FiniteDrinfeldModule(DrinfeldModule): sage: isinstance(phi, FiniteDrinfeldModule) True - But, the user should never use ``FiniteDrinfeldModule`` to test if a + The user should never use ``FiniteDrinfeldModule`` to test if a Drinfeld module is finite, but rather the ``is_finite`` method:: sage: phi.is_finite() @@ -149,15 +151,20 @@ def frobenius_charpoly(self, var='T'): otherwise. Let `\Fq` be the base field of the function ring. The - *characteristic polynomial `\chi` of the Frobenius endomorphism* is - defined in [Gek1991]_. An important feature of this polynomial - is that it is a monic bivariate polynomial in `T` with - coefficients in `\Fq[X]`. Write `\chi = T^2 - A(X)T + B(X)`, let - `t^n` be the Ore polynomial that defines the Frobenius - endomorphism of `\phi`; by definition, `n` is the degree of the - base ring over `\Fq`. We have `\chi(t^n)(\phi(X)) = t^{2n} - - \phi_A t^n + \phi_B = 0`, with `\deg(A) \leq \frac{n}{2}` and - `\deg(B) = n`. + *characteristic polynomial `\chi` of the Frobenius endomorphism* + is defined in [Gek1991]_. An important feature of this + polynomial is that it is a monic univariate polynomial with + coefficients in the function ring. As in our case the function + ring is a univariate polynomial ring, it is customary to see the + characteristic polynomial of the Frobenius endomorphism as a + bivariate polynomial. + + Let `\chi = T^2 - A(X)T + B(X)` be the characteristic polynomial + of the Frobenius endomorphism, let `t^n` be the Ore polynomial + that defines the Frobenius endomorphism of `\phi`; by + definition, `n` is the degree of the base ring over `\Fq`. We + have `\chi(t^n)(\phi(X)) = t^{2n} - \phi_A t^n + \phi_B = 0`, + with `\deg(A) \leq \frac{n}{2}` and `\deg(B) = n`. Note that the *Frobenius trace* is defined as `A(X)` and the *Frobenius norm` is defined as `B(X)`. @@ -166,7 +173,8 @@ def frobenius_charpoly(self, var='T'): - ``var`` -- (optional) the name of the second variable - OUTPUT: a polynomial in `\Fq[X][T]` + OUTPUT: an univariate polynomial with coefficients in the + function ring EXAMPLES: @@ -189,7 +197,7 @@ def frobenius_charpoly(self, var='T'): sage: B (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 - sage: n = 2 # Degree of the base ring over `\Fq` + sage: n = 2 # Degree of the base ring over Fq sage: A.degree() <= n/2 True sage: B.degree() == n @@ -221,14 +229,15 @@ def frobenius_norm(self): Return Frobenius norm of the Drinfeld module, if the rank is two; raise a NotImplementedError otherwise. - Write `\chi = T^2 - A(X)T + B(X) \in \Fq[X][T]` to be the - characteristic polynomial of the Frobenius endomorphism. The - *Frobenius norm* is defined as the polynomial `B(X) \in \Fq[X]`. + Let `\Fq[X]` be the function ring, write `\chi = T^2 - A(X)T + + B(X) \in \Fq[X][T]` for the characteristic polynomial of the + Frobenius endomorphism. The *Frobenius norm* is defined as the + polynomial `B(X) \in \Fq[X]`. Let `n` be the degree of the base ring over `\Fq`. Then the Frobenius norm has degree `n`. - OUTPUT: a polynomial in `\Fq[X]` + OUTPUT: an element in the function ring EXAMPLES: @@ -240,7 +249,7 @@ def frobenius_norm(self): sage: B (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 - sage: n = 2 # Degree of the base ring over `\Fq` + sage: n = 2 # Degree of the base ring over Fq sage: B.degree() == n True @@ -268,14 +277,15 @@ def frobenius_trace(self): Return Frobenius norm of the Drinfeld module, if the rank is two; raise a NotImplementedError otherwise. - Write `\chi = T^2 - A(X)T + B(X) \in \Fq[X][T]` to be the - characteristic polynomial of the Frobenius endomorphism. The - *Frobenius norm* is defined as the polynomial `B(X) \in \Fq[X]`. + Let `\Fq[X]` be the function ring, write `\chi = T^2 - A(X)T + + B(X) \in \Fq[X][T]` for the characteristic polynomial of the + Frobenius endomorphism. The *Frobenius norm* is defined as the + polynomial `B(X) \in \Fq[X]`. Let `n` be the degree of the base ring over `\Fq`. Then the Frobenius trace has degree `\leq \frac{n}{2}`. - OUTPUT: a polynomial in `\Fq[X]` + OUTPUT: an element in the function ring ALGORITHM: @@ -301,7 +311,7 @@ def frobenius_trace(self): sage: A (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 - sage: n = 2 # Degree of the base ring over `\Fq` + sage: n = 2 # Degree of the base ring over Fq sage: A.degree() <= n/2 True @@ -319,14 +329,17 @@ def frobenius_trace(self): def is_ordinary(self): r""" - Return True if the Drinfeld module is ordinary, return False - otherwise; raise a NotImplementedError if the rank is not two. + Return ``True`` whether the Drinfeld module is ordinary; raise a + NotImplementedError if the rank is not two. A rank two finite Drinfeld module is *ordinary* if and only if the `\Fq[X]-characteristic of the base ring does not devide the Frobenius trace. A *supersingular* rank two finite Drinfeld module is a Drinfeld module that is not ordinary. + A rnak two Drinfeld module is *ordinary* if and only if it is + note supersingular; see :meth:`is_supersingular`. + OUTPUT: a boolean EXAMPLES: @@ -355,13 +368,13 @@ def is_ordinary(self): def is_supersingular(self): r""" - Return True if the Drinfeld module is supersingular, return False - otherwise; raise a NotImplementedError if the rank is not two. + Return ``True`` whether the Drinfeld module is supersingular; raise a + NotImplementedError if the rank is not two. A rank two finite Drinfeld module is *supersingular* if and only - if the `\Fq[X]-characteristic of the base ring devides the - Frobenius trace. An *ordinary* rank two finite Drinfeld module - is a Drinfeld module that is not supersingular. + if the function field-characteristic of the base ring devides + the Frobenius trace. An *ordinary* rank two finite Drinfeld + module is a Drinfeld module that is not supersingular. OUTPUT: a boolean @@ -379,11 +392,11 @@ def is_supersingular(self): ALGORITHM: - Compute the Frobenius trace and test if the `\Fq[X]` - characteristic divides it. + Compute the Frobenius trace and test if the function + ring-characteristic divides it. - We could also test if the image of the - `\Fq[X]`-characteristic under the Drinfeld module is purely + We could also test if the image of the function + ring-characteristic under the Drinfeld module is purely inseparable; see [Gek1991]_, Proposition 4.1. """ self._check_rank_two() diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 61ad14914d1..67f7d5a1aba 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -32,8 +32,9 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, r""" This class represents a Drinfeld module morphism. - Let `\phi,\psi` be two Drinfeld modules defined over the - `\Fq[X]`-field `K`. A *morphism of Drinfeld modules `\phi \to \psi`* + Let `\phi, \psi` be two Drinfeld modules with function ring `\Fq[X]` + and base ring `K`, whose `\Fq[X]`-field structure is given by a + morphism `\gamma`. A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a \in \Fq[X]`. In our case, this is equivalent to verifying `f \phi_X = \psi_X f`. An *isogeny* is a non-zero @@ -234,7 +235,7 @@ def domain(self): def is_zero(self): r""" - Return the codomain of the morphism. + Return ``True`` whethere the morphism is the zero morphism. EXAMPLES: @@ -256,8 +257,7 @@ def is_zero(self): def is_endomorphism(self): r""" - Return True if the morphism is an endomorphism; return False - otherwise. + Return ``True`` whether the morphism is an endomorphism. EXAMPLES: @@ -287,8 +287,7 @@ def is_endomorphism(self): def is_isogeny(self): r""" - Return True if the morphism is an isogeny; return False - otherwise. + Return ``True`` whether the morphism is an isogeny. EXAMPLES: @@ -318,8 +317,7 @@ def is_isogeny(self): def is_isomorphism(self): r""" - Return True if the morphism is an isomorphism; return False - otherwise. + Return ``True`` whether the morphism is an isomorphism. EXAMPLES: From 8b890f4639cdf841a9e54ecea43be942b381c6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 23 Aug 2022 15:50:12 +0200 Subject: [PATCH 105/392] Change an exception text in DrinfeldModules --- .../function_field/drinfeld_modules/drinfeld_module.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 08b1309de86..e2641335987 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -133,12 +133,12 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: DrinfeldModule(FqX, [z, QQ(1)]) Traceback (most recent call last): ... - ValueError: function ring base must coerce to base ring + ValueError: function ring base must coerce into base ring sage: DrinfeldModule(FqX, [1, QQ(1)]) Traceback (most recent call last): ... - ValueError: function ring base must coerce to base ring + ValueError: function ring base must coerce into base ring If the coefficients are regular integers, an exception is raised. One needs to manually cast them to the field of their choice:: @@ -146,7 +146,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: DrinfeldModule(FqX, [1, 1, 1]) Traceback (most recent call last): ... - ValueError: function ring base must coerce to base ring + ValueError: function ring base must coerce into base ring sage: DrinfeldModule(FqX, [K(1), 1, 1]) Drinfeld module defined by X |--> t^2 + t + 1 over Finite Field in z of size 3^12 @@ -517,7 +517,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): # The coefficients are in a base ring that has coercion from Fq: if not (hasattr(ore_polring_base, 'has_coerce_map_from') and \ ore_polring_base.has_coerce_map_from(function_ring.base_ring())): - raise ValueError('function ring base must coerce to base ring') + raise ValueError('function ring base must coerce into base ring') # Build the morphism that defines the category gamma = function_ring.hom([ore_polring_base(gen[0])]) From 3f542d8d72e398e7e6110402a656243ee04dda9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 23 Aug 2022 15:51:05 +0200 Subject: [PATCH 106/392] Check morphism is non zero in DrinfeldModules This is equivalent to asking the constant coefficient to be non zero. Goss asks the function-ring characteristic to be either 0 or prime, hence this change. --- src/sage/categories/drinfeld_modules.py | 3 +++ .../function_field/drinfeld_modules/drinfeld_module.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 4a7c4e6b155..7117c3c2835 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -176,6 +176,9 @@ def __init__(self, morphism, name='t'): self._base = K if not K.is_field(): raise TypeError('base must be a field') + # Check morphism is non zero + if gamma(X).is_zero(): + raise ValueError('the morphism must be non zero') # Build K{t} d = log(Fq.cardinality(), Fq.characteristic()) tau = K.frobenius_endomorphism(d) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index e2641335987..ac6011e9006 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -126,6 +126,13 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): ... ValueError: generator must have positive degree + The constant coefficient mudt be non zero:: + + sage: DrinfeldModule(FqX, [K(0), K(1)]) + Traceback (most recent call last): + ... + ValueError: the morphism must be non zero + The coefficients of the generator must live in some base field `K` that is the codomain of a morphism `\gamma: \Fq[X] \to K`, where `\Fq[X]` is the function ring:: From fd9126ca9b005b7f5aaec033cd767769d57b6e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 23 Aug 2022 16:32:19 +0200 Subject: [PATCH 107/392] Add various TESTS: in Drinfeld modules classes --- src/sage/categories/drinfeld_modules.py | 38 +++++++++- .../drinfeld_modules/drinfeld_module.py | 69 ++++++++++++++++++- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 7117c3c2835..ca4336b58d0 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -50,7 +50,7 @@ class DrinfeldModules(CategoryWithParameters): INPUT: a ring morphism from the function ring to the base ring - EXAMPLES: + .. RUBRIC:: Construction Generally, Drinfeld modules objects are created before their category, and the category is retrieved as an attribute of the @@ -152,13 +152,46 @@ class DrinfeldModules(CategoryWithParameters): True sage: rho.category() is cat True + + TESTS: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: from sage.categories.drinfeld_modules import DrinfeldModules + sage: gamma = Hom(FqX, K)(0) + sage: cat = DrinfeldModules(gamma) + Traceback (most recent call last): + ... + ValueError: the morphism must be non zero + + sage: gamma = Hom(FqX, FqX)(1) + sage: cat = DrinfeldModules(gamma) + Traceback (most recent call last): + ... + TypeError: base must be a field + + sage: gamma = 'I hate Rostropovitch' + sage: cat = DrinfeldModules(gamma) # known bug (blankline) + + Traceback (most recent call last): + ... + TypeError: input must be a ring morphism + + sage: ZZT. = ZZ[] + sage: gamma = Hom(ZZT, K)(1) + sage: cat = DrinfeldModules(gamma) # known bug (blankline) + + Traceback (most recent call last): + ... + TypeError: function ring base must be a finite field """ def __init__(self, morphism, name='t'): gamma = morphism # Check input is a ring Morphism if not isinstance(gamma, RingHomomorphism): - raise TypeError('category input must be a Ring morphism') + raise TypeError('input must be a Ring morphism') self._morphism = morphism self._function_ring = gamma.domain() # Check domain is Fq[X] @@ -246,7 +279,6 @@ def _latex_(self): sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: latex(cat) - sage: latex(cat) \text{Category{ }of{ }Drinfeld{ }modules{ }defined{ }by\begin{array}{l} \text{\texttt{Ring{ }morphism:}}\\ \text{\texttt{{ }{ }From:{ }Univariate{ }Polynomial{ }Ring{ }in{ }X{ }over{ }Finite{ }Field{ }of{ }size{ }11}}\\ diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index ac6011e9006..b152eaf5f16 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -488,6 +488,21 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: a = FqX.random_element() sage: phi.invert(phi(a)) == a True + + TESTS: + + sage: Fq = K = GF(2) + sage: FqX. = Fq[] + sage: phi = DrinfeldModule(FqX, [1, 1]) + Traceback (most recent call last): + ... + ValueError: function ring base must coerce into base ring + + sage: Fq = K = GF(2) + sage: FqX. = Fq[] + sage: phi = DrinfeldModule(FqX, [K(1), 1]) + sage: isinstance(phi.ore_polring(), OrePolynomialRing) + True """ @staticmethod @@ -567,6 +582,31 @@ def __call__(self, a): - ``a`` -- an element in the function ring OUTPUT: an element of the base ring + + TESTS: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + + sage: a = X^3 + 4*X + 2 + sage: phi(a) == phi(X)^3 + 4*phi(X) + 2 + True + sage: phi(a)[0] == p_root^3 + 4*p_root + 2 + True + + sage: phi(0) + 0 + sage: phi(1) + 1 + sage: phi(X) == phi._gen + True + + sage: a = FqX.random_element(5) + sage: phi(a)[0] == phi.category().morphism()(a) + True """ return self._morphism(a) @@ -574,6 +614,33 @@ def __call__(self, a): def __getitem__(self, n): r""" See method :meth:`coefficient`. + + TESTS: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi[0] == p_root + True + sage: phi[1] == z12^3 + True + sage: phi[2] == z12^5 + True + sage: phi[3] + Traceback (most recent call last): + ... + ValueError: input must be >= 0 and <= rank + sage: phi[-1] + Traceback (most recent call last): + ... + ValueError: input must be >= 0 and <= rank + sage: phi['I hate Dream Theater'] # known bug (blankline) + + Traceback (most recent call last): + ... + TypeErro: input must be an integer """ return self.coefficient(n) @@ -800,7 +867,7 @@ def change_ring(self, new_field, name=None): sage: phi_0.base_ring() is K0 True sage: phi.change_ring(K0).change_ring(K) # known bug - Traceback (most recent call last) + Traceback (most recent call last): ... TypeError: no coercion defined From a9152b7874836e39df5fe0f70b5e0c7009afc3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 29 Aug 2022 13:58:43 +0200 Subject: [PATCH 108/392] Change validity test in DrinfeldModules method __call__ Before, the constant coefficient of the input was tested to be a root of the characteristic. I changed this test to the following: we check that the constant coefficient is the image (gamma(X)) of the morphism (gamma) that defines the category. --- src/sage/categories/drinfeld_modules.py | 9 ++++++--- .../function_field/drinfeld_modules/drinfeld_module.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index ca4336b58d0..bb54c1d8c14 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -140,7 +140,7 @@ class DrinfeldModules(CategoryWithParameters): sage: cat([z, 1]) Traceback (most recent call last): ... - ValueError: constant coefficient must be a root of the characteristic + ValueError: constant coefficient must be the generator of the morphism that defines the category It is also possible to create a random object in the category, with a given rank:: @@ -256,8 +256,11 @@ def _call_(self, gen): from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule # If gen is not in the Ore polring, an exception is raised gen = self._ore_polring(gen) - if self.characteristic()(gen[0]) != 0: - raise ValueError('constant coefficient must be a root of the characteristic') + X = self._function_ring.gen() + gamma = self._morphism + if gen[0] != gamma(X): + raise ValueError('constant coefficient must be the generator ' \ + 'of the morphism that defines the category') return DrinfeldModule(self._function_ring, gen) # Somehow required for the class definition diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index b152eaf5f16..ff54ef4b656 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -183,7 +183,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: cat([1, 1, K(1)]) Traceback (most recent call last): ... - ValueError: constant coefficient must be a root of the characteristic + ValueError: constant coefficient must be the generator of the morphism that defines the category .. NOTE:: @@ -922,7 +922,7 @@ def constant_coefficient(self): sage: rho = cat(phi.constant_coefficient() + 1 + t^3) Traceback (most recent call last): ... - ValueError: constant coefficient must be a root of the characteristic + ValueError: constant coefficient must be the generator of the morphism that defines the category One can also retrieve the constant coefficient using ``phi[0]`:: From 52cb56719bdf0fd54e50424e394cee48ed19f316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 29 Aug 2022 14:12:39 +0200 Subject: [PATCH 109/392] Remove method __getitem__ in DrinfeldModule After introducing this method a few days ago, I realize that this syntax is a conflict with the more traditional syntax phi[a] for the a-torsion of phi. --- .../drinfeld_modules/drinfeld_module.py | 49 ++----------------- 1 file changed, 3 insertions(+), 46 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index ff54ef4b656..28d88faa6a4 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -345,11 +345,6 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: phi.coefficient(1) 1 - The ``[...]`` was introduced as a shortcut to ``phi(X)[...]``:: - - sage: phi[1] - 1 - .. RUBRIC:: Morphisms, isogenies A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore @@ -611,39 +606,6 @@ def __call__(self, a): return self._morphism(a) - def __getitem__(self, n): - r""" - See method :meth:`coefficient`. - - TESTS: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi[0] == p_root - True - sage: phi[1] == z12^3 - True - sage: phi[2] == z12^5 - True - sage: phi[3] - Traceback (most recent call last): - ... - ValueError: input must be >= 0 and <= rank - sage: phi[-1] - Traceback (most recent call last): - ... - ValueError: input must be >= 0 and <= rank - sage: phi['I hate Dream Theater'] # known bug (blankline) - - Traceback (most recent call last): - ... - TypeErro: input must be an integer - """ - return self.coefficient(n) - def _Hom_(self, other, category): r""" Return ``DrinfeldModuleHomset(self, other, category)``. @@ -923,13 +885,8 @@ def constant_coefficient(self): Traceback (most recent call last): ... ValueError: constant coefficient must be the generator of the morphism that defines the category - - One can also retrieve the constant coefficient using ``phi[0]`:: - - sage: phi.constant_coefficient() == phi[0] - True """ - return self[0] + return self.coefficient(0) def coefficient(self, n): r""" @@ -1226,8 +1183,8 @@ def j_invariant(self): NotImplementedError: rank must be 2 """ self._check_rank_two() - g = self[1] - delta = self[2] + g = self.coefficient(1) + delta = self.coefficient(2) q = self._Fq.order() return (g**(q+1)) / delta From 0f3d19ce271008ba521496bdf3db633831ca093a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 29 Aug 2022 15:00:57 +0200 Subject: [PATCH 110/392] Proofread docs in Drinfeld module classes --- src/sage/categories/drinfeld_modules.py | 36 ++- .../function_field/drinfeld_modules/action.py | 19 +- .../drinfeld_modules/drinfeld_module.py | 281 ++++++++---------- .../finite_drinfeld_module.py | 45 ++- .../function_field/drinfeld_modules/homset.py | 7 +- .../drinfeld_modules/morphism.py | 22 +- 6 files changed, 191 insertions(+), 219 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index bb54c1d8c14..f9d854de1d4 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -37,16 +37,22 @@ class DrinfeldModules(CategoryWithParameters): its function ring `\Fq[X]` and its base ring `K`. The `\Fq[X]`-field structure on `K` is given by a morphism `\gamma` - We say that `\Fq[X]` is the *function ring of the category*; `K` is the - *base of the category*, or simply its base ring or base field; `\Fq[X]` is - the *function ring of the category*; *K\{\tau\}* is the *Ore - polynomial ring of the category*; - `t` is the *Ore variable of the category*. The *constant coefficient - of the category* is `\gamma(X)`. + .. NOTE:: + + These notations will be used throughout this documentation. + + We say that `\Fq[X]` is the *function ring of the category*; `K` is + the *base of the category* (or simply its *base ring* or *base + field*); `\Fq[X]` is the *function ring of the category*; + *K\{\tau\}* is the *Ore polynomial ring of the category*; `t` is the + *Ore variable of the category*. The *constant coefficient of the + category* is `\gamma(X)`. The `\Fq[X]`-characteristic of the base + ring `K` can also be referred to as its *function + ring-characteristic*. .. NOTE:: - These notations will be used throughout this docstring. + The base is always a field. INPUT: a ring morphism from the function ring to the base ring @@ -107,6 +113,8 @@ class DrinfeldModules(CategoryWithParameters): Finite Field in z of size 11^4 sage: cat.base() is K True + sage: cat.base() is phi.base_ring() + True And the *function ring* is the polynomial ring `\Fq[X]`:: @@ -114,6 +122,8 @@ class DrinfeldModules(CategoryWithParameters): Univariate Polynomial Ring in X over Finite Field of size 11 sage: cat.function_ring() is FqX True + sage: cat.function_ring() is phi.function_ring() + True And as expected, the *Ore polynomial ring* is that of the Drinfeld modules in the category: @@ -142,8 +152,8 @@ class DrinfeldModules(CategoryWithParameters): ... ValueError: constant coefficient must be the generator of the morphism that defines the category - It is also possible to create a random object in the category, with - a given rank:: + It is also possible to create a random object in the category. The + input is the desired rank:: sage: rho = cat.random_object(2) sage: rho # random @@ -230,8 +240,8 @@ def __init__(self, morphism, name='t'): def _call_(self, gen): r""" - Return a Drinfeld module object, in the category, whose - generator is the input. + Return a Drinfeld module object in the category whose generator + is the input. INPUT: the generator of the Drinfeld module, given as an Ore polynomial or a list of coefficients @@ -376,7 +386,7 @@ def constant_coefficient(self): r""" Return the constant coefficient of the category. - OUTPUT: an element in the base + OUTPUT: a base element EXAMPLES: @@ -397,7 +407,7 @@ def function_ring(self): r""" Return the function ring of the category. - OUTPUT: the ring `\Fq[X]` + OUTPUT: a univariate polynomial ring EXAMPLES: diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 535474c3f57..0e5edfb74e6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -26,7 +26,6 @@ class DrinfeldModuleAction(Action): r""" - This class represents the module action induced by a Drinfeld module. @@ -34,10 +33,14 @@ class DrinfeldModuleAction(Action): ring `K`, whose `\Fq[X]`-field structure is given by a morphism `\gamma`. Let `L/K` be a field extension, let `x \in L`, let `a` be a function ring element; the action is defined as `(a, x) \mapsto - \phi_a(x)`. In this implementation, `L` is `K`. + \phi_a(x)`. + + .. NOTE:: + + In this implementation, `L` is `K`. - The action is instanciated as follows. Note that the user should - never explicitely instanciate the class `DrinfeldModuleAction`:: + The action is instantiated as follows. Note that the user should + never explicitly instantiate the class `DrinfeldModuleAction`:: INPUT: a Drinfeld module @@ -86,14 +89,14 @@ def __init__(self, drinfeld_module): def _act_(self, pol, x): r""" - Return ``pol * x``, where ``*`` is the action. + Return the action of ``pol`` on ``x``. INPUT: - ``pol`` -- a function ring element - - ``x`` -- an element in the base ring of the Drinfeld module + - ``x`` -- a base ring element - OUTPUT: an element in the base ring of the Drinfeld module. + OUTPUT: an element in the base ring of the Drinfeld module EXAMPLES: @@ -158,7 +161,7 @@ def _repr_(self): def drinfeld_module(self): r""" - Return the Drinfeld module associated to the action. + Return the Drinfeld module defining to the action. OUTPUT: a Drinfeld module diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 28d88faa6a4..8e9df953a43 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -3,6 +3,7 @@ This module provides the class :class:`sage.rings.function_field.drinfeld_module.drinfeld_module.DrinfeldModule`. + For *finite* Drinfeld modules and their theory of complex multiplication, see class :class:`sage.rings.function_field.drinfeld_module.finite_drinfeld_module.DrinfeldModule`. @@ -23,35 +24,33 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.rings.integer import Integer -from sage.structure.unique_representation import UniqueRepresentation -from sage.structure.category_object import CategoryObject - from sage.categories.drinfeld_modules import DrinfeldModules +from sage.matrix.constructor import Matrix +from sage.misc.latex import latex +from sage.modules.free_module_element import vector +from sage.rings.integer import Integer from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing from sage.rings.polynomial.polynomial_ring import PolynomialRing_general - -from sage.misc.latex import latex +from sage.structure.category_object import CategoryObject from sage.structure.sequence import Sequence -from sage.matrix.constructor import Matrix -from sage.modules.free_module_element import vector +from sage.structure.unique_representation import UniqueRepresentation + class DrinfeldModule(UniqueRepresentation, CategoryObject): r""" This class represents a Drinfeld module. - Let `\Fq` be a finite field with order `q`. Let `K` be a field - equiped a ring morphism `\gamma: \Fq[X] \to K` --- the field `K` is - said to be an *`\Fq[X]`-field*, and the monic polynomial that - generates `\Ker(\gamma)` is called the *`\Fq[X]`-characteristic of - the `\Fq[X]`-field `K`*. This characteristic plays the role of the - characteristic of a function field. + Let `\Fq` be a finite field with order `q` and let `K` be a field + equipped a ring morphism `\gamma: \Fq[X] \to K`. The field `K` is + called an *`\Fq[X]`-field*, and the monic generator of + `\Ker(\gamma)` is called the *`\Fq[X]`-characteristic of the + `\Fq[X]`-field `K`*. Let `K\{\tau\}` be the ring of Ore polynomials with coefficients in `K` and Frobenius variable `\tau: x \mapsto x^q`. A *Drinfeld - `\Fq[X]`-module over the `\Fq[X]`-field `K`* is a ring morphism - `\phi: \Fq[X] \to K\{\tau\}` such that: + `\Fq[X]`-module over the `\Fq[X]`-field `K`* is an `\Fq`-algebra + morphism `\phi: \Fq[X] \to K\{\tau\}` such that: 1. The image of `\phi` contains non-constant Ore polynomials. 2. For every element `a` in the function ring, the constant @@ -61,19 +60,22 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. NOTE:: - These notations will be used throughout this docstring. + These notations will be used throughout the documentation. We say that `\Fq[X]` is the *function ring of `\phi`*; `K` is the - *base ring of `\phi`*, or simply its base or base field; *K\{\tau\}* - is the *Ore polynomial ring of `\phi`*; `t` is the *Ore variable of - `\phi`*. The *generator of `\phi`* is `\phi_X`, its *constant - coefficient* is the constant coefficient of `\phi_X`. The - `\Fq[X]`-characteristic of the base ring `K` can also be referred to - as its *function ring-characteristic*. + *base ring of `\phi`* (or simply its *base* or *base field*); + *K\{\tau\}* is the *Ore polynomial ring of `\phi`*; `t` is the *Ore + variable of `\phi`*. Further, the *generator of `\phi`* is `\phi_X` + and its *constant coefficient* is the constant coefficient of + `\phi_X`. The `\Fq[X]`-characteristic of the base ring `K` can also + be referred to as its *function ring-characteristic*. + + .. NOTE:: + + The base ring is always a field. The Drinfeld module `\phi` is uniquely determined by the image - `\phi_X` of `X`. This Ore polynomial is an input of the class - constructor. + `\phi_X` of `X`, which is an input of the class. Classical references on Drinfeld modules include [Gos1998]_, [Rosen2002]_, [VS06]_ and [Gek1998]_. @@ -84,18 +86,18 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): polynomial ring `\Fq[X]` is replaced by a more general function ring: the ring of functions in `k` that are regular outside `\infty`, where `k` is a function field over `\Fq` with - transcendance degree `1` and `\infty` is a fixed place of `k`. + transcendence degree `1` and `\infty` is a fixed place of `k`. This is out of the scope of this implementation. INPUT: - - ``function_ring`` -- the polynomial ring with coefficients in the finite - field `\Fq` - - ``gen`` -- the generator `\phi_X`, as a list of coefficients or an - Ore polynomial - - ``name`` (optional) the name of the Ore variable + - ``function_ring`` -- a univariate polynomial ring whose base is a + finite field + - ``gen`` -- the generator of the Drinfeld module; as a list of + coefficients or an Ore polynomial + - ``name`` (optional) -- the name of the Ore variable - .. RUBRIC:: Construction and input + .. RUBRIC:: Construction A Drinfeld module object is constructed as follows:: @@ -126,16 +128,15 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): ... ValueError: generator must have positive degree - The constant coefficient mudt be non zero:: + The constant coefficient must be non zero:: sage: DrinfeldModule(FqX, [K(0), K(1)]) Traceback (most recent call last): ... ValueError: the morphism must be non zero - The coefficients of the generator must live in some base field `K` - that is the codomain of a morphism `\gamma: \Fq[X] \to K`, where - `\Fq[X]` is the function ring:: + The coefficients of the generator must lie in an `\Fq[X]`-field, + where `\Fq[X]` is the function ring of the Drinfeld module:: sage: DrinfeldModule(FqX, [z, QQ(1)]) Traceback (most recent call last): @@ -147,22 +148,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): ... ValueError: function ring base must coerce into base ring - If the coefficients are regular integers, an exception is raised. - One needs to manually cast them to the field of their choice:: - - sage: DrinfeldModule(FqX, [1, 1, 1]) - Traceback (most recent call last): - ... - ValueError: function ring base must coerce into base ring - - sage: DrinfeldModule(FqX, [K(1), 1, 1]) - Drinfeld module defined by X |--> t^2 + t + 1 over Finite Field in z of size 3^12 - - sage: DrinfeldModule(FqX, [Fq(1), 1, 1]) - Drinfeld module defined by X |--> t^2 + t + 1 over Finite Field in z2 of size 3^2 - The function ring must be an univariate polynomial ring whose - coefficients lie in a finite field:: + base is a finite field:: sage: DrinfeldModule(K, [z, 1, 1]) Traceback (most recent call last): @@ -175,9 +162,10 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): ... TypeError: function ring base must be a finite field - If you already defined a category of Drinfeld modules, you must - ensure that the constant coefficient is a root of the - function ring-characteristic of the category base:: + If you already defined a category of Drinfeld modules, and you + create a Drinfeld module through this category, you must + ensure that the constant coefficient is the generator of the algebra + morphism that defines the category:: sage: cat = phi.category() sage: cat([1, 1, K(1)]) @@ -193,13 +181,13 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): `\Fq[X]`-characteristic plays the role of the characteristic of a function field, and thus preexists Drinfeld modules. The base field `K` should rather be seen as an `\Fq[X]`-field, i.e. the - field `K` equiped with a morphism `\gamma: \Fq[X] \to K`, + field `K` equipped with a morphism `\gamma: \Fq[X] \to K`, instead of just a field. However, as the characteristic may be deduced from the constant - coefficient of the Drinfeld module, we chose to ommit the - characteristic in the input of the class in order to have - concise definitions. + coefficient of the Drinfeld module (it is its minimal polynomial + over the function ring), we chose to ommit the characteristic + in the input of the class in order to have concise definitions. .. RUBRIC:: Possible base rings @@ -216,9 +204,9 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: rho Drinfeld module defined by X |--> t^2 + t + z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z over Finite Field in z of size 3^12 - Then one can check that the morphisms `\gamma` are not the same for - ``phi`` and ``rho``, and that the `\gamma` associated to `\phi` is - surjective, while the other one is not:: + The morphisms `\gamma` are not the same for ``phi`` and ``rho``, and + that the `\gamma` associated to `\phi` is surjective, while the + other one is not:: sage: rho.category().morphism() Ring morphism: @@ -270,12 +258,33 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: char = phi.category().characteristic() - As the output of - :meth:`sage.rings.function_field.drinfeld_module.finite_drinfeld_module.DrinfeldModule.category` - suggests, the morphism `\gamma` uniquely determines the category of a Drinfeld - module. + As the output of :meth:`category` suggests, the morphism `\gamma` + uniquely determines the category. + + .. RUBRIC:: Basics - .. RUBRIC:: Getters and basic properties + Images under the Drinfeld module are computed by calling the object:: + + sage: phi(X) # phi_X + t^2 + t + z + sage: phi(X^3 + X + 1) # phi_X^3 +X + 1 + t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 + sage: phi(1) # phi_1 + 1 + + This is useful to quickly retrieve the generator of the Drinfeld + module. Furthermore, a Drinfeld `\Fq[X]`-module can be seen as an + Ore polynomial with positive degree and constant coefficient + `\gamma(X)`. This analogy is the motivation for the following + methods:: + + sage: phi.coefficients() + [z, 1, 1] + + sage: phi.coefficient(1) + 1 + sage: phi.coefficient(1) + 1 One can retrieve basic properties:: @@ -305,9 +314,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): To: Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) Defn: X |--> t^2 + t + z - .. RUBRIC:: Height, rank and j-invariant - - One can compute the rank and height:: + One can compute the rank and height (which is always `1`):: sage: phi.rank() 2 @@ -319,39 +326,12 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: phi.j_invariant() # j-invariant 1 - .. RUBRIC:: The generator of a Drinfeld module - - For a `a` in the function ring, `\phi_a` is computed by calling - `phi`:: - - sage: phi(X) # phi_X - t^2 + t + z - sage: phi(X^3 + X + 1) # phi_X^3 +X + 1 - t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 - sage: phi(1) # phi_1 - 1 - - This is especially useful to quickly retrive `\phi_X`, the generator - of the Drinfeld module. Furthermore, a Drinfeld `\Fq[X]`-module can - be seen as an Ore polynomial with positive degree and constant - coefficient `\gamma(X)`. This analogy is the motivation for the - following methods:: - - sage: phi.coefficients() - [z, 1, 1] - - sage: phi.coefficient(1) - 1 - sage: phi.coefficient(1) - 1 - .. RUBRIC:: Morphisms, isogenies A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for - every `a` in the function ring. In our specific case, this is - equivalent to verifying `f \phi_X = \psi_X f`. An *isogeny* is a - non-zero morphism. + every `a` in the function ring. In our case, this is equivalent to + `f \phi_X = \psi_X f`. An *isogeny* is a non-zero morphism. Use the ``in`` syntax to test if an Ore polynomial defines a morphism:: @@ -370,7 +350,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): True To create a SageMath object representing the morphism, call the - homset (``hom`` in the next example):: + homset (``hom``):: sage: hom = Hom(phi, phi) sage: frobenius_endomorphism = hom(t^6) @@ -392,15 +372,16 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): To: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 Defn: 0 - One can retrieve the underlying Ore polynomial with the method - :meth:`sage.rings.function_field.drinfeld_module.finite_drinfeld_module.DrinfeldModule.ore_polynomial`:: + The underlying Ore polynomial is retrieved with the method + :meth:`ore_polynomial`:: sage: frobenius_endomorphism.ore_polynomial() t^6 + sage: identity_morphism.ore_polynomial() + 1 - And one can easily check if a morphism defines an isogeny or an - isomorphism (i.e. an isogeny whose underlying Ore polynomial has - degree `0`):: + It is easy to check if a morphism is an isogeny, endomorphism or + isomorphism:: sage: frobenius_endomorphism.is_isogeny() True @@ -417,10 +398,9 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: The Vélu formula - Let ``ore_pol`` be a non-zero Ore polynomial. For Drinfeld module, - it is easy to decide if there exists a Drinfeld module ``psi`` such - that ``ore_pol`` is an isogeny from ``self`` to ``psi``. If so, we - find ``psi``:: + Let ``ore_pol`` be a non-zero Ore polynomial. We can decide if there + exists a Drinfeld module ``psi`` such that ``ore_pol`` is an isogeny + from ``self`` to ``psi``. If so, we find ``psi``:: sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z sage: psi = phi.velu(ore_pol) @@ -444,18 +424,21 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. RUBRIC:: The action of a Drinfeld module - An `\Fq[X]`-Drinfeld module `\phi` notoriously makes any field - extension `L/K` a left `\Fq[X]`-module. Let `x \in L`, let `a` be in - the function ring; the action is defined as `(a, x) \mapsto - \phi_a(x)`. The method :meth:`action` returns an ``Action`` object - representing the Drinfeld module action; in this implementation, `K - = L`. + The `\Fq[X]`-Drinfeld module `\phi` induces a special left + `\Fq[X]`-module structure on any field extension `L/K`. Let `x \in + L` and `a` be in the function ring; the action is defined as `(a, + x) \mapsto \phi_a(x)`. The method :meth:`action` returns an + ``Action`` object representing the Drinfeld module action. + + .. NOTE:: + + In this implementation, `L` is `L`. sage: action = phi.action() sage: action Action on Finite Field in z of size 3^12 induced by Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 - The action on elements is computed as follows:: + The action on elements is computed by calling the action object:: sage: P = X + 1 sage: a = z @@ -474,7 +457,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: extended_action Action on Finite Field in z60 of size 3^60 induced by Drinfeld module defined by X |--> t^2 + t + 2*z60^59 + z60^56 + 2*z60^55 + 2*z60^54 + 2*z60^53 + z60^49 + z60^48 + z60^47 + 2*z60^45 + z60^44 + 2*z60^41 + 2*z60^40 + 2*z60^39 + 2*z60^37 + 2*z60^36 + z60^34 + z60^33 + z60^32 + 2*z60^31 + 2*z60^30 + 2*z60^27 + 2*z60^25 + z60^23 + z60^22 + z60^21 + 2*z60^20 + z60^19 + z60^18 + z60^17 + z60^16 + z60^15 + 2*z60^14 + z60^12 + 2*z60^11 + 2*z60^10 + z60^8 + z60^6 + 2*z60^5 + z60^4 + z60^3 + z60 + 1 over Finite Field in z60 of size 3^60 - .. RUBRIC:: Inversion of the Drinfeld module + .. RUBRIC:: Inverting the Drinfeld module Given an Ore polynomial that equals `\phi_a` for some function ring elelement `a`, one can retrieve `a` (as a morphism, a Drinfeld @@ -569,14 +552,14 @@ def __init__(self, gen, category): def __call__(self, a): r""" Return the image of ``a`` by the morphism that defines the - Drinfeld module, i.e. `\phi_a` if the Drinfeld module is denoted + Drinfeld module; i.e. `\phi_a` if the Drinfeld module is denoted `phi`. INPUT: - - ``a`` -- an element in the function ring + - ``a`` -- a function ring element - OUTPUT: an element of the base ring + OUTPUT: a base ring element TESTS: @@ -603,7 +586,6 @@ def __call__(self, a): sage: phi(a)[0] == phi.category().morphism()(a) True """ - return self._morphism(a) def _Hom_(self, other, category): @@ -691,7 +673,9 @@ def _repr_(self): def action(self): r""" - Return the action object that represents the action on the base that is + Return the action object + (:class:`sage.rings.function_field.drinfeld_modules.action.Action`) + that represents the module action, on the base ring, that is induced by the Drinfeld module. OUTPUT: a Drinfeld module action object @@ -725,9 +709,6 @@ def base_ring(self): r""" Return the base ring of the Drinfeld module. - This is the Ore polynomial ring base. In particular, the base - ring is always a field. - OUTPUT: a field EXAMPLES: @@ -747,9 +728,13 @@ def base_ring(self): sage: phi.base_ring() is K True - Note that in the above example, the constant coefficient - generates a strict sub-extension of `K/\Fq`. In fact, the base - ring may also be the same as ``Fq``:: + The base ring can be infinite:: + + sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) + sage: sigma.base_ring() + Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + + Or it can be ``Fq``:: sage: psi = DrinfeldModule(FqX, [Fq(1), Fq.gen()]) sage: psi.base_ring() @@ -778,12 +763,11 @@ def base_ring(self): def change_ring(self, new_field, name=None): r""" - Return a Drinfeld module defined like the current one, but with + Return a Drinfeld module defined like the current one, but whose base ring ``new_field``. - The new base can either be a field extension of the base ring, - or field that has a coercion map from the field of definitions - of the coefficients of the generator. + The new base is valid whether it has a coercion map from the + current base. INPUT: @@ -848,7 +832,7 @@ def constant_coefficient(self): r""" Return the constant coefficient of the generator. - OUTPUT: an element in the base ring + OUTPUT: a base ring element EXAMPLES: @@ -896,7 +880,7 @@ def coefficient(self, n): - ``n`` -- a non-negative integer - OUTPUT: an element in the base ring + OUTPUT: a base ring element EXAMPLES: @@ -928,13 +912,14 @@ def coefficients(self, sparse=True): r""" Return the coefficients of the generator, as a list. - If the the flag ``sparse`` is ``True`` (default), only return the + If the flag ``sparse`` is ``True`` (default), only return the non-zero coefficients; otherwise, return all of them. INPUT: + - ``sparse`` -- a boolean - OUTPUT: a list of elements in the base ring + OUTPUT: a list of base ring elements EXAMPLES: @@ -968,10 +953,7 @@ def function_ring(self): r""" Return the function ring of the Drinfeld module. - In our case, the function ring is an univariate polynomial ring - whose coefficients lie in a finite field `\Fq`. - - OUTPUT: a polynomial ring + OUTPUT: a univariate polynomial ring EXAMPLES: @@ -989,12 +971,6 @@ def gen(self): r""" Return the generator of the Drinfeld module. - Let `\phi` denote the Drinfeld module, and `\Fq[X]` be the - function ring. This method returns `\phi_X`. - - This method makes sense the function ring is `\Fq`-generated by - a single element, whose image characterizes the Drinfeld module. - OUTPUT: an Ore polynomial EXAMPLES: @@ -1040,7 +1016,7 @@ def invert(self, ore_pol): - ``ore_pol`` -- the Ore polynomial whose preimage we want to compute - OUTPUT: an element in the function ring + OUTPUT: a function ring element EXAMPLES: @@ -1145,9 +1121,8 @@ def is_finite(self): def j_invariant(self): r""" - Return the j-invariant of the Drinfeld module; only the rank two - case has been implemented, a NotImplementedError is raised if - the rank is not two. + Return the j-invariant of the Drinfeld module if the rank is + two; raise a NotImplementedError otherwise. Assume the rank is two. Write the generator `\phi_X = \gamma(X) + g\tau + \Delta\tau^2`. The j-invariant is defined by @@ -1155,8 +1130,7 @@ def j_invariant(self): of the function ring. In our case, this base field is always finite. - OUTPUT: an element in the base ring if the rank is two; an - exception is raised otherwise + OUTPUT: a base ring element EXAMPLES: @@ -1192,7 +1166,7 @@ def morphism(self): r""" Return the morphism object that defines the Drinfeld module. - OUTPUT: a ring morphism, from the function ring to the Ore + OUTPUT: a ring morphism from the function ring to the Ore polynomial ring EXAMPLES: @@ -1278,9 +1252,6 @@ def ore_variable(self): r""" Return the Ore variable. - The Ore variable is defined as the generator of the Ore - polynomial ring. - OUTPUT: an Ore polynomial EXAMPLES: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index c7cd5c6d2f5..ce87148eb59 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -26,22 +26,20 @@ class FiniteDrinfeldModule(DrinfeldModule): r""" - This class represnets a finite Drinfeld module. + This class represents a finite Drinfeld module. A *finite Drinfeld module* is a Drinfeld module whose base ring is - finite. For general definitions and help on Drinfeld modules, see - class + finite. + + For general definitions and help on Drinfeld modules, see class :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. - In this specific documentation, we only present the specifics of - ``FiniteDrinfeldModle``. .. RUBRIC:: Construction: The user does not ever need to directly call - ``FiniteDrinfeldModule``, as it is the (meta)class - ``DrinfeldModule`` that is reponsible for instanciating - ``DrinfeldModule`` or ``FiniteDrinfeldModule`` depending on its - input:: + ``FiniteDrinfeldModule`` --- the metaclass ``DrinfeldModule`` is + responsible for instantiating ``DrinfeldModule`` or + ``FiniteDrinfeldModule`` depending on the input:: sage: Fq = GF(343) sage: FqX. = Fq[] @@ -113,9 +111,8 @@ def __init__(self, gen, category): def frobenius_endomorphism(self): r""" - Return the Frobenius endomorphism, as an instance of - ``DrinfeldModuleMorphism``, of the Drinfeld module, if the rank - is two; raise a NotImplementedError otherwise.. + Return the Frobenius endomorphism of the Drinfeld module as a + morphism object. Let `q` be the order of the base field of the function ring. The *Frobenius endomorphism* is defined as the endomorphism whose @@ -209,12 +206,9 @@ def frobenius_charpoly(self, var='T'): trace. This gives the Frobenius characteristic polynomial. See [SM2019]_, Section 4. - See docstrings of methods - :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.FiniteDrinfeldModule.frobenius_norm` - and - :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.FiniteDrinfeldModule.frobenius_trace` - for furthere details on the computation of the norm and of - the trace. + See docstrings of methods :meth:`frobenius_norm` and + :meth:`frobenius_trace` for furthere details on the + computation of the norm and of the trace. """ self._check_rank_two() A = self._function_ring # Fq[X] @@ -291,12 +285,11 @@ def frobenius_trace(self): Let `A(X)` denote the Frobenius trace and `B(X)` denote the Frobenius norm. We begin by computing `B(X)`, see docstring - of method - :meth:`sage.rings.function_fields.drinfeld_module.finite_drinfeld_module.FiniteDrinfeldModule.frobenius_norm` - for details. The characteristic polynomial of the Frobenius - yields `t^{2n} - \phi_A t^n + \phi_B = 0`, where `t^n` is - the Frobenius endomorphism. As `\phi_B` is now known, we can - compute `\phi_A = (t^{2n} + \phi_B) / t^n`. We get `A(X)` by + of method :meth:`frobenius_norm` for details. The + characteristic polynomial of the Frobenius yields `t^{2n} - + \phi_A t^n + \phi_B = 0`, where `t^n` is the Frobenius + endomorphism. As `\phi_B` is now known, we can compute + `\phi_A = (t^{2n} + \phi_B) / t^n`. We get `A(X)` by inverting this quantity, using the method :meth:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule.invert`, see its docstring for details. @@ -333,7 +326,7 @@ def is_ordinary(self): NotImplementedError if the rank is not two. A rank two finite Drinfeld module is *ordinary* if and only if - the `\Fq[X]-characteristic of the base ring does not devide the + the `\Fq[X]-characteristic of the base ring does not divide the Frobenius trace. A *supersingular* rank two finite Drinfeld module is a Drinfeld module that is not ordinary. @@ -372,7 +365,7 @@ def is_supersingular(self): NotImplementedError if the rank is not two. A rank two finite Drinfeld module is *supersingular* if and only - if the function field-characteristic of the base ring devides + if the function field-characteristic of the base ring divides the Frobenius trace. An *ordinary* rank two finite Drinfeld module is a Drinfeld module that is not supersingular. diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 8ec98e7562f..b442ec9fe0d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -176,8 +176,7 @@ def _repr_(self): def __contains__(self, x): r""" Implement the ``in`` operator for the homset; return ``True`` if - the input defines a morphism in the homset, return ``False`` - otherwise. + whether the input defines a morphism in the homset. INPUT: @@ -233,8 +232,8 @@ def __contains__(self, x): def _element_constructor_(self, *args, **kwds): r""" - Return a Drinfeld module morphism defined by the input, which is - an Ore polynomial. + Return the Drinfeld module morphism defined by the input Ore + polynomial. INPUT: an Ore polynomial diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 67f7d5a1aba..f5f274f51c2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -36,16 +36,12 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, and base ring `K`, whose `\Fq[X]`-field structure is given by a morphism `\gamma`. A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a - f` for every `a \in \Fq[X]`. In our case, this is equivalent to - verifying `f \phi_X = \psi_X f`. An *isogeny* is a non-zero - morphism. + f` for every `a \in \Fq[X]`. In our case, this is equivalent to `f + \phi_X = \psi_X f`. An *isogeny* is a non-zero morphism. - A Drinfeld module morphism is represented by instances of the class - `DrinfeldModuleMorphism`. - - To create a morphism object, do not explicitely use - `DrinfeldModuleMorphism`, but rather call the parent homset with the - defining Ore polynomial:: + To create a morphism object, the user should never explicitly + instantiate `DrinfeldModuleMorphism`, but rather call the parent + homset with the defining Ore polynomial:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -101,9 +97,9 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, .. NOTE:: - For the sake of completness, we explain how the user can - directly instanciate the class, even though this should never be - explicitely done:: + For the sake of completeness, we explain how the user can + directly instantiate the class, even though this should never be + explicitly done:: sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) @@ -235,7 +231,7 @@ def domain(self): def is_zero(self): r""" - Return ``True`` whethere the morphism is the zero morphism. + Return ``True`` whether the morphism is the zero morphism. EXAMPLES: From ea4a843f7832515cd008587e5f1747624f8bee2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 29 Aug 2022 17:31:14 +0200 Subject: [PATCH 111/392] Lint Drinfeld modules code --- src/sage/categories/drinfeld_modules.py | 16 +++--- .../function_field/drinfeld_modules/action.py | 10 ++-- .../drinfeld_modules/drinfeld_module.py | 50 +++++++++---------- .../finite_drinfeld_module.py | 4 +- .../function_field/drinfeld_modules/homset.py | 16 +++--- .../drinfeld_modules/morphism.py | 22 ++++---- 6 files changed, 62 insertions(+), 56 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index f9d854de1d4..4d4f851fc31 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -207,9 +207,11 @@ def __init__(self, morphism, name='t'): # Check domain is Fq[X] function_ring = self._function_ring if not isinstance(function_ring, PolynomialRing_general): - raise NotImplementedError('function ring must be a polynomial ring') + raise NotImplementedError('function ring must be a polynomial ' + 'ring') function_ring_base = function_ring.base_ring() - if not function_ring_base.is_field() or not function_ring_base.is_finite() : + if not function_ring_base.is_field() \ + or not function_ring_base.is_finite(): raise TypeError('function ring base must be a finite field') Fq = function_ring_base FqX = function_ring @@ -226,7 +228,7 @@ def __init__(self, morphism, name='t'): d = log(Fq.cardinality(), Fq.characteristic()) tau = K.frobenius_endomorphism(d) self._ore_polring = OrePolynomialRing(K, tau, names=name, - polcast=False) + polcast=False) # Create constant coefficient self._constant_coefficient = gamma(X) # Create characteristic @@ -269,8 +271,8 @@ def _call_(self, gen): X = self._function_ring.gen() gamma = self._morphism if gen[0] != gamma(X): - raise ValueError('constant coefficient must be the generator ' \ - 'of the morphism that defines the category') + raise ValueError('constant coefficient must be the generator ' + 'of the morphism that defines the category') return DrinfeldModule(self._function_ring, gen) # Somehow required for the class definition @@ -300,7 +302,7 @@ def _latex_(self): \end{array} """ return f'\\text{{Category{{ }}of{{ }}Drinfeld{{ }}modules{{ }}' \ - f'defined{{ }}by{latex(self._morphism)}' + f'defined{{ }}by{latex(self._morphism)}' def _repr_(self): r""" @@ -380,7 +382,7 @@ def characteristic(self): """ if self._characteristic is None: raise NotImplementedError - return self._characteristic + return self._characteristic def constant_coefficient(self): r""" diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 0e5edfb74e6..28b585cd5dc 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -1,5 +1,5 @@ r""" -The left-module action induced by a Drinfeld module +The module action induced by a Drinfeld module This module provides the class :class:`sage.rings.function_field.drinfeld_module.action.DrinfeldModuleAction`. @@ -85,7 +85,7 @@ def __init__(self, drinfeld_module): raise TypeError('input must be a DrinfeldModule') self._drinfeld_module = drinfeld_module super().__init__(drinfeld_module.function_ring(), - drinfeld_module.base_ring()) + drinfeld_module.base_ring()) def _act_(self, pol, x): r""" @@ -137,8 +137,8 @@ def _latex_(self): \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 11^2 """ return f'\\text{{Action{{ }}on{{ }}}}' \ - f'{latex(self._drinfeld_module.base_ring())}\\text{{{{ }}' \ - f'induced{{ }}by{{ }}}}{self._drinfeld_module}' + f'{latex(self._drinfeld_module.base_ring())}\\text{{{{ }}' \ + f'induced{{ }}by{{ }}}}{self._drinfeld_module}' def _repr_(self): r""" @@ -157,7 +157,7 @@ def _repr_(self): Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 11^2 """ return f'Action on {self._drinfeld_module.base_ring()} induced by ' \ - f'{self._drinfeld_module}' + f'{self._drinfeld_module}' def drinfeld_module(self): r""" diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 8e9df953a43..40bbfe4633a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -30,7 +30,6 @@ from sage.modules.free_module_element import vector from sage.rings.integer import Integer from sage.rings.polynomial.ore_polynomial_element import OrePolynomial -from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing from sage.rings.polynomial.polynomial_ring import PolynomialRing_general from sage.structure.category_object import CategoryObject from sage.structure.sequence import Sequence @@ -493,13 +492,12 @@ def __classcall_private__(cls, function_ring, gen, name='t'): # here and in the category constructor, which is not ideal. # Check domain is Fq[X] if not isinstance(function_ring, PolynomialRing_general): - raise NotImplementedError('function ring must be a polynomial ring') + raise NotImplementedError('function ring must be a polynomial ' + 'ring') function_ring_base = function_ring.base_ring() - if not function_ring_base.is_field() or not function_ring_base.is_finite() : + if not function_ring_base.is_field() \ + or not function_ring_base.is_finite(): raise TypeError('function ring base must be a finite field') - Fq = function_ring_base - FqX = function_ring - X = FqX.gen() # Check all possible input types for gen # `gen` is an Ore polynomial: @@ -512,11 +510,12 @@ def __classcall_private__(cls, function_ring, gen, name='t'): ore_polring = None ore_polring_base = Sequence(gen).universe() else: - raise TypeError('generator must be list of coefficients or Ore ' \ - 'polynomial') + raise TypeError('generator must be list of coefficients or Ore ' + 'polynomial') # The coefficients are in a base ring that has coercion from Fq: - if not (hasattr(ore_polring_base, 'has_coerce_map_from') and \ - ore_polring_base.has_coerce_map_from(function_ring.base_ring())): + if not (hasattr(ore_polring_base, 'has_coerce_map_from') + and ore_polring_base.has_coerce_map_from( + function_ring.base_ring())): raise ValueError('function ring base must coerce into base ring') # Build the morphism that defines the category @@ -625,11 +624,11 @@ def _Hom_(self, other, category): return DrinfeldModuleHomset(self, other, category) def _check_rank_two(self): - r""" - Raise ``NotImplementedError`` if the rank is not two. - """ - if self.rank() != 2: - raise NotImplementedError('rank must be 2') + r""" + Raise ``NotImplementedError`` if the rank is not two. + """ + if self.rank() != 2: + raise NotImplementedError('rank must be 2') def _latex_(self): r""" @@ -648,9 +647,9 @@ def _latex_(self): \text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }}\Bold{F}_{5^{12}} """ return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ - f'{latex(self._function_ring.gen())} '\ - f'\\mapsto {latex(self._gen)}' \ - f'\\text{{{{ }}over{{ }}}}{latex(self._base_ring)}' + f'{latex(self._function_ring.gen())} '\ + f'\\mapsto {latex(self._gen)}' \ + f'\\text{{{{ }}over{{ }}}}{latex(self._base_ring)}' def _repr_(self): r""" @@ -669,7 +668,7 @@ def _repr_(self): Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 """ return f'Drinfeld module defined by {self._function_ring.gen()} ' \ - f'|--> {self._gen} over {self._base_ring}' + f'|--> {self._gen} over {self._base_ring}' def action(self): r""" @@ -708,7 +707,7 @@ def action(self): def base_ring(self): r""" Return the base ring of the Drinfeld module. - + OUTPUT: a field EXAMPLES: @@ -824,7 +823,7 @@ def change_ring(self, new_field, name=None): """ coeffs = self._gen.coefficients() new_coeffs = list(map(new_field, coeffs)) - if name == None: + if name is None: name = self._ore_polring.variable_name() return DrinfeldModule(self._function_ring, new_coeffs, name=name) @@ -1073,12 +1072,13 @@ def invert(self, ore_pol): """ deg = ore_pol.degree() r = self.rank() - if not ore_pol in self._ore_polring: + if ore_pol not in self._ore_polring: raise TypeError('input must be an Ore polynomial') if ore_pol in self._base_ring: return self._Fq(ore_pol) if deg % r != 0: - raise ValueError('input must be in the image of the Drinfeld module') + raise ValueError('input must be in the image of the Drinfeld ' + 'module') k = deg // r X = self._function_ring.gen() @@ -1319,7 +1319,7 @@ def velu(self, isog): OUTPUT: a Drinfeld module ALGORITHM: - + The input defines an isogeny if only if: 1. The degree of the characteristic divides the height of the input. (The height of an Ore polynomial @@ -1376,7 +1376,7 @@ def velu(self, isog): ... ValueError: the input does not define an isogeny """ - if not isog in self.ore_polring(): + if isog not in self.ore_polring(): raise TypeError('input must be an Ore polynomial') e = ValueError('the input does not define an isogeny') if isog == 0: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index ce87148eb59..afb02c5eba6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -24,6 +24,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule + class FiniteDrinfeldModule(DrinfeldModule): r""" This class represents a finite Drinfeld module. @@ -263,7 +264,8 @@ def frobenius_norm(self): m = n // d delta = self._gen[2] norm = self._base_ring.over(self._Fq)(delta).norm() - self._frobenius_norm = ((-1)**n) * (self.characteristic()**m) / norm + char = self.characteristic() + self._frobenius_norm = ((-1)**n) * (char**m) / norm return self._frobenius_norm def frobenius_trace(self): diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index b442ec9fe0d..df176bfafa6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -20,11 +20,12 @@ #***************************************************************************** from sage.categories.drinfeld_modules import DrinfeldModules -from sage.categories.homset import Homset, Hom +from sage.categories.homset import Homset from sage.misc.latex import latex from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism from sage.structure.parent import Parent + class DrinfeldModuleHomset(Homset): r""" This class represents the set of morphisms between two Drinfeld @@ -113,7 +114,7 @@ class DrinfeldModuleHomset(Homset): sage: frobenius_endomorphism in hom False """ - + Element = DrinfeldModuleMorphism __contains__ = Parent.__contains__ @@ -123,7 +124,8 @@ def __init__(self, X, Y, category=None, check=True): if check: if X.category() != Y.category() \ or not isinstance(X.category(), DrinfeldModules): - raise NotImplementedError('Drinfeld modules must be in the same category') + raise NotImplementedError('Drinfeld modules must be in the ' + 'same category') if category != X.category(): raise NotImplementedError('category should be DrinfeldModules') base = category.base() @@ -147,8 +149,8 @@ def _latex_(self): \text{Set{ }of{ }Drinfeld{ }module{ }morphisms{ }from}\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto 2 t^{2} + z_{6} t + z_{6}\text{{ }over{ }}\Bold{F}_{3^{6}}\text{{ }to{ }}Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 """ return f'\\text{{Set{{ }}of{{ }}Drinfeld{{ }}module{{ }}morphisms' \ - f'{{ }}from}}{latex(self.domain())}\\text{{{{ }}to{{ }}}}' \ - f'{self.codomain()}' + f'{{ }}from}}{latex(self.domain())}\\text{{{{ }}to{{ }}}}' \ + f'{self.codomain()}' def _repr_(self): r""" @@ -170,8 +172,8 @@ def _repr_(self): To: Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 """ return f'Set of Drinfeld module morphisms:\n' \ - f' From: {self.domain()}\n' \ - f' To: {self.codomain()}' + f' From: {self.domain()}\n' \ + f' To: {self.codomain()}' def __contains__(self, x): r""" diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index f5f274f51c2..d8e8a7adc41 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -22,9 +22,6 @@ from sage.misc.latex import latex from sage.structure.element import Element from sage.structure.unique_representation import UniqueRepresentation -from sage.rings.polynomial.ore_polynomial_element import OrePolynomial -from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing -from sage.categories.drinfeld_modules import DrinfeldModules class DrinfeldModuleMorphism(UniqueRepresentation, Element, @@ -158,11 +155,14 @@ def _latex_(self): \end{array} """ return f'\\begin{{array}}{{l}}\n' \ - f'\\text{{Drinfeld{{ }}module{{ }}morphism:}}\\\\\n' \ - f'\\text{{{{ }}{{ }}From:{{ }}}}{latex(self._domain)}}}\\\\\n' \ - f'\\text{{{{ }}{{ }}To:{{ }}}}{{ }}{{ }}{latex(self._codomain)}\\\\\n' \ - f'\\text{{{{ }}{{ }}Defn:{{ }}}}{latex(self._ore_polynomial)}\n' \ - f'\\end{{array}}' + f'\\text{{Drinfeld{{ }}module{{ }}morphism:}}\\\\\n' \ + f'\\text{{{{ }}{{ }}From:{{ }}}}'\ + f'{latex(self._domain)}}}\\\\\n' \ + f'\\text{{{{ }}{{ }}To:{{ }}}}{{ }}{{ }}' \ + f'{latex(self._codomain)}\\\\\n' \ + f'\\text{{{{ }}{{ }}Defn:{{ }}}}' \ + f'{latex(self._ore_polynomial)}\n' \ + f'\\end{{array}}' def _repr_(self): r""" @@ -185,9 +185,9 @@ def _repr_(self): Defn: t + z6^5 + z6^2 + 1 """ return f'Drinfeld Module morphism:\n' \ - f' From: {self._domain}\n' \ - f' To: {self._codomain}\n' \ - f' Defn: {self._ore_polynomial}' + f' From: {self._domain}\n' \ + f' To: {self._codomain}\n' \ + f' Defn: {self._ore_polynomial}' def codomain(self): r""" From 5d21c5432cb4386d126b3da1098c7dde40d47683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 29 Aug 2022 17:34:43 +0200 Subject: [PATCH 112/392] Change sage.categories.drinfeld_modules title This is to comply with other modules in sage.categories. --- src/sage/categories/drinfeld_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 4d4f851fc31..a39228ce57c 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -1,5 +1,5 @@ r""" -Category of Drinfeld modules +Drinfeld modules over a base This module provides the class :class:`sage.category.drinfeld_modules.DrinfeldModules`. From 43c430f273a372e316a0061646f8bd4830837895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 29 Aug 2022 17:36:25 +0200 Subject: [PATCH 113/392] Refactor Drinfeld modules in reference Drinfeld modules now have their own directory (sage/src/doc/en/reference/drinfeld_modules), and they are indexed on the main entry point of the SageMath reference. --- src/doc/en/reference/drinfeld_modules/conf.py | 1 + .../en/reference/drinfeld_modules/index.rst | 42 +++++++++++++++++++ .../en/reference/function_fields/index.rst | 1 - src/doc/en/reference/index.rst | 1 + 4 files changed, 44 insertions(+), 1 deletion(-) create mode 120000 src/doc/en/reference/drinfeld_modules/conf.py create mode 100644 src/doc/en/reference/drinfeld_modules/index.rst diff --git a/src/doc/en/reference/drinfeld_modules/conf.py b/src/doc/en/reference/drinfeld_modules/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/drinfeld_modules/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/drinfeld_modules/index.rst b/src/doc/en/reference/drinfeld_modules/index.rst new file mode 100644 index 00000000000..316f50cb830 --- /dev/null +++ b/src/doc/en/reference/drinfeld_modules/index.rst @@ -0,0 +1,42 @@ +Drinfeld modules +==================================== + +Sage include facilities to manipulate Drinfeld modules and their morphisms. The +main entry point is the class +:class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. + +Drinfeld modules +---------------- + +.. toctree:: + :maxdepth: 2 + + sage/rings/function_field/drinfeld_modules/drinfeld_module + sage/rings/function_field/drinfeld_modules/finite_drinfeld_module + +Morphisms and isogenies +----------------------- + +.. toctree:: + :maxdepth: 2 + + sage/rings/function_field/drinfeld_modules/morphism + sage/rings/function_field/drinfeld_modules/homset + +The module action induced by a Drinfeld module +---------------------------------------------- + +.. toctree:: + :maxdepth: 2 + + sage/rings/function_field/drinfeld_modules/action + +The category of Drinfeld modules +-------------------------------- + +.. toctree:: + :maxdepth: 2 + + sage/categories/drinfeld_modules + +.. include:: ../footer.txt diff --git a/src/doc/en/reference/function_fields/index.rst b/src/doc/en/reference/function_fields/index.rst index bc2350116ad..50c04560a33 100644 --- a/src/doc/en/reference/function_fields/index.rst +++ b/src/doc/en/reference/function_fields/index.rst @@ -21,7 +21,6 @@ algebraic closure of `\QQ`. sage/rings/function_field/maps sage/rings/function_field/extensions sage/rings/function_field/constructor - sage/rings/function_field/drinfeld_modules/drinfeld_module A basic reference for the theory of algebraic function fields is [Stich2009]_. diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index 065bccac955..88f1bd814dd 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -111,6 +111,7 @@ Number Fields, Function Fields, and Valuations * :doc:`Number Fields ` * :doc:`Function Fields ` * :doc:`Discrete Valuations ` +* :doc:`Drinfeld Modules ` Number Theory ------------- From cf503d8d2c10fdd187cc5090f2724723dc12a0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 8 Sep 2022 13:37:38 +0200 Subject: [PATCH 114/392] Change _call_ to object in DrinfeldModules This is to emphasize that DrinfeldModules is not a parent. --- src/sage/categories/drinfeld_modules.py | 80 +++++++++---------- .../drinfeld_modules/drinfeld_module.py | 8 +- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index a39228ce57c..d87436233a5 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -135,10 +135,10 @@ class DrinfeldModules(CategoryWithParameters): .. RUBRIC:: Creating Drinfeld module objects from the category - Calling the category with an Ore polynomial creates a Drinfeld - module object in the category whose generator is the input:: + Calling :meth:`object` with an Ore polynomial creates a Drinfeld module + object in the category whose generator is the input:: - sage: psi = cat([p_root, 1]) + sage: psi = cat.object([p_root, 1]) sage: psi Drinfeld module defined by X |--> t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 sage: psi.category() is cat @@ -147,7 +147,7 @@ class DrinfeldModules(CategoryWithParameters): Of course, the constant coefficient of the input must be the same as the category':: - sage: cat([z, 1]) + sage: cat.object([z, 1]) Traceback (most recent call last): ... ValueError: constant coefficient must be the generator of the morphism that defines the category @@ -240,41 +240,6 @@ def __init__(self, morphism, name='t'): elif FqX.is_subring(K): self._characteristic = Integer(0) - def _call_(self, gen): - r""" - Return a Drinfeld module object in the category whose generator - is the input. - - INPUT: the generator of the Drinfeld module, given as an Ore - polynomial or a list of coefficients - - OUTPUT: a Drinfeld module in the category - - EXAMPLES: - - sage: Fq = GF(11) - sage: FqX. = Fq[] - sage: K. = Fq.extension(4) - sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: psi = cat([p_root, 0, 1]) - sage: psi - Drinfeld module defined by X |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 - sage: t = phi.ore_variable() - sage: cat(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi - True - """ - from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule - # If gen is not in the Ore polring, an exception is raised - gen = self._ore_polring(gen) - X = self._function_ring.gen() - gamma = self._morphism - if gen[0] != gamma(X): - raise ValueError('constant coefficient must be the generator ' - 'of the morphism that defines the category') - return DrinfeldModule(self._function_ring, gen) - # Somehow required for the class definition def _make_named_class_key(self, name): return self._function_ring.category() @@ -451,6 +416,41 @@ def morphism(self): """ return self._morphism + def object(self, gen): + r""" + Return a Drinfeld module object in the category whose generator + is the input. + + INPUT: the generator of the Drinfeld module, given as an Ore + polynomial or a list of coefficients + + OUTPUT: a Drinfeld module in the category + + EXAMPLES: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: psi = cat.object([p_root, 0, 1]) + sage: psi + Drinfeld module defined by X |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + sage: t = phi.ore_variable() + sage: cat.object(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi + True + """ + from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule + # If gen is not in the Ore polring, an exception is raised + gen = self._ore_polring(gen) + X = self._function_ring.gen() + gamma = self._morphism + if gen[0] != gamma(X): + raise ValueError('constant coefficient must be the generator ' + 'of the morphism that defines the category') + return DrinfeldModule(self._function_ring, gen) + def ore_polring(self): r""" Return the Ore polynomial ring of the category. @@ -529,7 +529,7 @@ def random_object(self, rank): dom_coeff = K.random_element() coeffs.append(dom_coeff) - return self(coeffs) + return self.object(coeffs) # Somehow required for the class definition def super_categories(self): diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 40bbfe4633a..4263bbd44f8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -167,7 +167,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): morphism that defines the category:: sage: cat = phi.category() - sage: cat([1, 1, K(1)]) + sage: cat.object([1, 1, K(1)]) Traceback (most recent call last): ... ValueError: constant coefficient must be the generator of the morphism that defines the category @@ -856,7 +856,7 @@ def constant_coefficient(self): same constant coefficient:: sage: t = phi.ore_variable() - sage: psi = cat(phi.constant_coefficient() + t^3) + sage: psi = cat.object(phi.constant_coefficient() + t^3) sage: psi Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 @@ -864,7 +864,7 @@ def constant_coefficient(self): this category if they do not share the same constant coefficient:: - sage: rho = cat(phi.constant_coefficient() + 1 + t^3) + sage: rho = cat.object(phi.constant_coefficient() + 1 + t^3) Traceback (most recent call last): ... ValueError: constant coefficient must be the generator of the morphism that defines the category @@ -1387,4 +1387,4 @@ def velu(self, isog): or rem != 0: raise e else: - return self.category()(quo) + return self.category().object(quo) From f98c9686008acb0024d3be1bb562beaea42c4912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 8 Sep 2022 14:31:26 +0200 Subject: [PATCH 115/392] Change base for Drinfeld modules classes The so-called base of a Drinfeld module (or its category) was a field (the field of coefficients of the Ore polring). This was fundamentally misleading, as the good notion for the base is that of a morphism Fq[X] -> K. The base is now this morphism. Method change_ring was deleted. Doc is updated; tests pass. Some changes were made to other _repr_ and _latex_ methods. --- src/sage/categories/drinfeld_modules.py | 187 ++++---- .../function_field/drinfeld_modules/action.py | 42 +- .../drinfeld_modules/drinfeld_module.py | 398 +++++++----------- .../finite_drinfeld_module.py | 47 ++- .../function_field/drinfeld_modules/homset.py | 60 ++- .../drinfeld_modules/morphism.py | 67 +-- 6 files changed, 342 insertions(+), 459 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index d87436233a5..27480adb460 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -33,28 +33,26 @@ class DrinfeldModules(CategoryWithParameters): This class represents the category of Drinfeld modules on a given base. - Let `\phi` be a Drinfeld module in the present category. We denote - its function ring `\Fq[X]` and its base ring `K`. The `\Fq[X]`-field - structure on `K` is given by a morphism `\gamma` + The category is uniquely defined by its base, which is a ring + morphism from the function ring `\Fq[X]` to a field `K`. Note that + the base is a morphism, but not a field. The base is often denoted + `\gamma`, and we call `K` an *`\Fq[X]-field`*. + + The monic polynomial that generates the kernel of the base is called + the *`\Fq[X]`-characteristic of the `\Fq[X]`-field `K`*. .. NOTE:: These notations will be used throughout this documentation. - We say that `\Fq[X]` is the *function ring of the category*; `K` is - the *base of the category* (or simply its *base ring* or *base - field*); `\Fq[X]` is the *function ring of the category*; + We say that `\Fq[X]` is the *function ring of the category*; *K\{\tau\}* is the *Ore polynomial ring of the category*; `t` is the *Ore variable of the category*. The *constant coefficient of the - category* is `\gamma(X)`. The `\Fq[X]`-characteristic of the base - ring `K` can also be referred to as its *function - ring-characteristic*. - - .. NOTE:: - - The base is always a field. + category* is the image of `X` under the base. The + `\Fq[X]`-characteristic of the `\Fq[X]`-field `K` can also be + referred to as its *function ring-characteristic*. - INPUT: a ring morphism from the function ring to the base ring + INPUT: the base, a ring morphism .. RUBRIC:: Construction @@ -69,20 +67,20 @@ class DrinfeldModules(CategoryWithParameters): sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat - Category of Drinfeld modules defined by Ring morphism: + Category of Drinfeld modules defined over base Ring morphism: From: Univariate Polynomial Ring in X over Finite Field of size 11 To: Finite Field in z of size 11^4 Defn: X |--> z^3 + 7*z^2 + 6*z + 10 - The output tells the user that the category is only defined by the - ring morphism `\gamma`. + The output tells the user that the category is only defined by its + base. .. RUBRIC:: Properties of the category - The defining morphism is retrieved using the method + The base, which is a morphism, is retrieved using the method :meth:`morphism`:: - sage: cat.morphism() + sage: cat.base() Ring morphism: From: Univariate Polynomial Ring in X over Finite Field of size 11 To: Finite Field in z of size 11^4 @@ -94,44 +92,35 @@ class DrinfeldModules(CategoryWithParameters): sage: cat.constant_coefficient() z^3 + 7*z^2 + 6*z + 10 - sage: cat.morphism()(X) == cat.constant_coefficient() + sage: cat.base()(X) == cat.constant_coefficient() True Similarly, the *function ring-characteristic* of the category is either `0` or the unique monic polynomial in `\Fq[X]` that generates - `\mathrm{Ker}(\gamma)`:: + the kernel of the base:: sage: cat.characteristic() X^2 + 7*X + 2 - sage: cat.morphism()(cat.characteristic()) + sage: cat.base()(cat.characteristic()) 0 - Like for its Drinfeld modules, the *base* of the category is the - field `K`:: + The base, function ring and Ore polynomial ring are the + same for the category and its objects:: - sage: cat.base() - Finite Field in z of size 11^4 - sage: cat.base() is K + sage: cat.base() is phi.base() True - sage: cat.base() is phi.base_ring() - True - - And the *function ring* is the polynomial ring `\Fq[X]`:: + sage: cat.function_ring() is phi.function_ring() + True sage: cat.function_ring() Univariate Polynomial Ring in X over Finite Field of size 11 - sage: cat.function_ring() is FqX - True - sage: cat.function_ring() is phi.function_ring() + sage: cat.function_ring() is cat.base().domain() True - And as expected, the *Ore polynomial ring* is that of - the Drinfeld modules in the category: - - sage: cat.ore_polring() - Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11 sage: cat.ore_polring() is phi.ore_polring() True + sage: cat.ore_polring() + Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11 .. RUBRIC:: Creating Drinfeld module objects from the category @@ -140,7 +129,10 @@ class DrinfeldModules(CategoryWithParameters): sage: psi = cat.object([p_root, 1]) sage: psi - Drinfeld module defined by X |--> t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + Drinfeld module defined by X |--> t + z^3 + 7*z^2 + 6*z + 10 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^4 + Defn: X |--> z^3 + 7*z^2 + 6*z + 10 sage: psi.category() is cat True @@ -169,42 +161,41 @@ class DrinfeldModules(CategoryWithParameters): sage: FqX. = Fq[] sage: K. = Fq.extension(4) sage: from sage.categories.drinfeld_modules import DrinfeldModules - sage: gamma = Hom(FqX, K)(0) - sage: cat = DrinfeldModules(gamma) + sage: base = Hom(FqX, K)(0) + sage: cat = DrinfeldModules(base) Traceback (most recent call last): ... - ValueError: the morphism must be non zero + ValueError: base must be a non zero morphism - sage: gamma = Hom(FqX, FqX)(1) - sage: cat = DrinfeldModules(gamma) + sage: base = Hom(FqX, FqX)(1) + sage: cat = DrinfeldModules(base) Traceback (most recent call last): ... - TypeError: base must be a field + TypeError: base codomain must be a field - sage: gamma = 'I hate Rostropovitch' - sage: cat = DrinfeldModules(gamma) # known bug (blankline) + sage: base = 'I hate Rostropovitch' + sage: cat = DrinfeldModules(base) # known bug (blankline) Traceback (most recent call last): ... TypeError: input must be a ring morphism sage: ZZT. = ZZ[] - sage: gamma = Hom(ZZT, K)(1) - sage: cat = DrinfeldModules(gamma) # known bug (blankline) + sage: base = Hom(ZZT, K)(1) + sage: cat = DrinfeldModules(base) # known bug (blankline) Traceback (most recent call last): ... TypeError: function ring base must be a finite field """ - def __init__(self, morphism, name='t'): - gamma = morphism + def __init__(self, base, name='t'): # Check input is a ring Morphism - if not isinstance(gamma, RingHomomorphism): + if not isinstance(base, RingHomomorphism): raise TypeError('input must be a Ring morphism') - self._morphism = morphism - self._function_ring = gamma.domain() - # Check domain is Fq[X] + self._base = base + self._function_ring = base.domain() + # Check domain of base is Fq[X] function_ring = self._function_ring if not isinstance(function_ring, PolynomialRing_general): raise NotImplementedError('function ring must be a polynomial ' @@ -216,27 +207,26 @@ def __init__(self, morphism, name='t'): Fq = function_ring_base FqX = function_ring X = FqX.gen() - # Check codomain of gamma is field - K = gamma.codomain() - self._base = K + # Check codomain of base is a field + K = base.codomain() if not K.is_field(): - raise TypeError('base must be a field') - # Check morphism is non zero - if gamma(X).is_zero(): - raise ValueError('the morphism must be non zero') + raise TypeError('base codomain must be a field') + # Check base is a non zero morphism + if base(X).is_zero(): + raise ValueError('base must be a non zero morphism') # Build K{t} d = log(Fq.cardinality(), Fq.characteristic()) tau = K.frobenius_endomorphism(d) self._ore_polring = OrePolynomialRing(K, tau, names=name, polcast=False) # Create constant coefficient - self._constant_coefficient = gamma(X) + self._constant_coefficient = base(X) # Create characteristic self._characteristic = None if K.is_finite(): - f = gamma * FqX.coerce_map_from(Fq) # Fq -> K + f = base * FqX.coerce_map_from(Fq) # Fq -> K E = K.over(f) - self._characteristic = FqX(E(gamma(X)).minpoly()) + self._characteristic = FqX(E(base(X)).minpoly()) elif FqX.is_subring(K): self._characteristic = Integer(0) @@ -259,7 +249,7 @@ def _latex_(self): sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: latex(cat) - \text{Category{ }of{ }Drinfeld{ }modules{ }defined{ }by\begin{array}{l} + \text{Category{ }of{ }Drinfeld{ }modules{ }defined{ }over{ }base{ }\begin{array}{l} \text{\texttt{Ring{ }morphism:}}\\ \text{\texttt{{ }{ }From:{ }Univariate{ }Polynomial{ }Ring{ }in{ }X{ }over{ }Finite{ }Field{ }of{ }size{ }11}}\\ \text{\texttt{{ }{ }To:{ }{ }{ }Finite{ }Field{ }in{ }z{ }of{ }size{ }11{\char`\^}4}}\\ @@ -267,7 +257,7 @@ def _latex_(self): \end{array} """ return f'\\text{{Category{{ }}of{{ }}Drinfeld{{ }}modules{{ }}' \ - f'defined{{ }}by{latex(self._morphism)}' + f'defined{{ }}over{{ }}base{{ }}{latex(self._base)}' def _repr_(self): r""" @@ -284,12 +274,12 @@ def _repr_(self): sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat - Category of Drinfeld modules defined by Ring morphism: + Category of Drinfeld modules defined over base Ring morphism: From: Univariate Polynomial Ring in X over Finite Field of size 11 To: Finite Field in z of size 11^4 Defn: X |--> z^3 + 7*z^2 + 6*z + 10 """ - return f'Category of Drinfeld modules defined by {self._morphism}' + return f'Category of Drinfeld modules defined over base {self._base}' # Somehow required for the class definition def Homsets(self): @@ -303,7 +293,7 @@ def base(self): r""" Return the base of the category. - OUTPUT: a ring + OUTPUT: a ring morphism EXAMPLES: @@ -313,9 +303,13 @@ def base(self): sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() - sage: cat.base() - Finite Field in z of size 11^4 - sage: cat.base() is K + sage: base = cat.base() + sage: base + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^4 + Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + sage: base(X) == cat.constant_coefficient() True """ return self._base @@ -340,7 +334,10 @@ def characteristic(self): sage: L = Frac(FqX) sage: psi = DrinfeldModule(FqX, [L.gen(), 1]) sage: psi - Drinfeld module defined by X |--> t + X over Fraction Field of Univariate Polynomial Ring in X over Finite Field of size 11 + Drinfeld module defined by X |--> t + X over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Fraction Field of Univariate Polynomial Ring in X over Finite Field of size 11 + Defn: X |--> X sage: fox = psi.category() sage: fox.characteristic() 0 @@ -353,7 +350,7 @@ def constant_coefficient(self): r""" Return the constant coefficient of the category. - OUTPUT: a base element + OUTPUT: an element in the base codomain EXAMPLES: @@ -365,7 +362,7 @@ def constant_coefficient(self): sage: cat = phi.category() sage: cat.constant_coefficient() z^3 + 7*z^2 + 6*z + 10 - sage: cat.constant_coefficient() == cat.morphism()(X) + sage: cat.constant_coefficient() == cat.base()(X) True """ return self._constant_coefficient @@ -391,31 +388,6 @@ def function_ring(self): """ return self._function_ring - def morphism(self): - r""" - Return the morphism that defines the category. - - OUTPUT: a ring morphism - - EXAMPLES: - - sage: Fq = GF(11) - sage: FqX. = Fq[] - sage: K. = Fq.extension(4) - sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: gamma = cat.morphism() - sage: gamma - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Finite Field in z of size 11^4 - Defn: X |--> z^3 + 7*z^2 + 6*z + 10 - sage: gamma(X) == cat.constant_coefficient() - True - """ - return self._morphism - def object(self, gen): r""" Return a Drinfeld module object in the category whose generator @@ -436,7 +408,10 @@ def object(self, gen): sage: cat = phi.category() sage: psi = cat.object([p_root, 0, 1]) sage: psi - Drinfeld module defined by X |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + Drinfeld module defined by X |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^4 + Defn: X |--> z^3 + 7*z^2 + 6*z + 10 sage: t = phi.ore_variable() sage: cat.object(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi True @@ -445,8 +420,8 @@ def object(self, gen): # If gen is not in the Ore polring, an exception is raised gen = self._ore_polring(gen) X = self._function_ring.gen() - gamma = self._morphism - if gen[0] != gamma(X): + base = self._base + if gen[0] != base(X): raise ValueError('constant coefficient must be the generator ' 'of the morphism that defines the category') return DrinfeldModule(self._function_ring, gen) @@ -520,7 +495,7 @@ def random_object(self, rank): if rank <= 0: raise ValueError('rank must be a positive integer') - K = self._base + K = self._base.codomain() coeffs = [self._constant_coefficient] for _ in range(rank-1): coeffs.append(K.random_element()) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 28b585cd5dc..fbc1bbfe47b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -52,7 +52,10 @@ class DrinfeldModuleAction(Action): sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 11^2 + Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by X |--> t^3 + z over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^2 + Defn: X |--> z The action on elements is computed as follows:: @@ -66,13 +69,6 @@ class DrinfeldModuleAction(Action): sage: action(FqX.random_element(), 0) 0 - To act on a field larger than `K`, one can change the ring of the - Drinfeld module, then create the action:: - - sage: extended_action = phi.change_ring(K.extension(2)).action() - sage: extended_action - Action on Finite Field in z4 of size 11^4 induced by Drinfeld module defined by X |--> t + 10*z4^3 + 4*z4^2 + 5*z4 + 5 over Finite Field in z4 of size 11^4 - Finally, given a Drinfeld module action, it is easy to recover the corresponding Drinfeld module:: @@ -84,8 +80,8 @@ def __init__(self, drinfeld_module): if not isinstance(drinfeld_module, DrinfeldModule): raise TypeError('input must be a DrinfeldModule') self._drinfeld_module = drinfeld_module - super().__init__(drinfeld_module.function_ring(), - drinfeld_module.base_ring()) + self._field = drinfeld_module.base().codomain() + super().__init__(drinfeld_module.function_ring(), self._field) def _act_(self, pol, x): r""" @@ -94,9 +90,9 @@ def _act_(self, pol, x): INPUT: - ``pol`` -- a function ring element - - ``x`` -- a base ring element + - ``x`` -- an element in the field acted upon - OUTPUT: an element in the base ring of the Drinfeld module + OUTPUT: an element in the base field of the Drinfeld module EXAMPLES: @@ -116,8 +112,8 @@ def _act_(self, pol, x): """ if pol not in self._drinfeld_module.function_ring(): raise TypeError('first input must be in the function ring') - if x not in self._drinfeld_module.base_ring(): - raise TypeError('second input must be in the base ring') + if x not in self._field: + raise TypeError('second input must be in the field acted upon') return self._drinfeld_module(pol)(x) def _latex_(self): @@ -134,11 +130,16 @@ def _latex_(self): sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) sage: action = phi.action() sage: latex(action) - \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 11^2 + \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto t^{3} + z\text{{ }over{ }base{ }}\begin{array}{l} + \text{\texttt{Ring{ }morphism:}}\\ + \text{\texttt{{ }{ }From:{ }Univariate{ }Polynomial{ }Ring{ }in{ }X{ }over{ }Finite{ }Field{ }of{ }size{ }11}}\\ + \text{\texttt{{ }{ }To:{ }{ }{ }Finite{ }Field{ }in{ }z{ }of{ }size{ }11{\char`\^}2}}\\ + \text{\texttt{{ }{ }Defn:{ }X{ }|{-}{-}>{ }z}} + \end{array} """ return f'\\text{{Action{{ }}on{{ }}}}' \ - f'{latex(self._drinfeld_module.base_ring())}\\text{{{{ }}' \ - f'induced{{ }}by{{ }}}}{self._drinfeld_module}' + f'{latex(self._field)}\\text{{{{ }}' \ + f'induced{{ }}by{{ }}}}{latex(self._drinfeld_module)}' def _repr_(self): r""" @@ -154,9 +155,12 @@ def _repr_(self): sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 11^2 + Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by X |--> t^3 + z over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^2 + Defn: X |--> z """ - return f'Action on {self._drinfeld_module.base_ring()} induced by ' \ + return f'Action on {self._field} induced by ' \ f'{self._drinfeld_module}' def drinfeld_module(self): diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 4263bbd44f8..63313a25d1b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -40,15 +40,22 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): r""" This class represents a Drinfeld module. - Let `\Fq` be a finite field with order `q` and let `K` be a field - equipped a ring morphism `\gamma: \Fq[X] \to K`. The field `K` is - called an *`\Fq[X]`-field*, and the monic generator of - `\Ker(\gamma)` is called the *`\Fq[X]`-characteristic of the - `\Fq[X]`-field `K`*. + Let `\Fq[X]` be a polynomial ring with coefficients in a finite + field `\Fq` and let `K` be a field. We fix a ring morphism `\gamma: + \Fq[X] \to K`, which we call the *base* of the Drinfeld module. + We also call `K` an *`\Fq[X]`-field*. + + .. NOTE:: + + The base of the Drinfeld module is the base of the category of + the Drinfeld module. + + The monic polynomial that generates the kernel of the base is called + the *`\Fq[X]`-characteristic of the `\Fq[X]`-field `K`*. Let `K\{\tau\}` be the ring of Ore polynomials with coefficients in `K` and Frobenius variable `\tau: x \mapsto x^q`. A *Drinfeld - `\Fq[X]`-module over the `\Fq[X]`-field `K`* is an `\Fq`-algebra + `\Fq[X]`-module over the base `\gamma`* is an `\Fq`-algebra morphism `\phi: \Fq[X] \to K\{\tau\}` such that: 1. The image of `\phi` contains non-constant Ore polynomials. @@ -57,25 +64,16 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): For `a` in the function ring, `\phi(a)` is denoted `\phi_a`. - .. NOTE:: - - These notations will be used throughout the documentation. - - We say that `\Fq[X]` is the *function ring of `\phi`*; `K` is the - *base ring of `\phi`* (or simply its *base* or *base field*); - *K\{\tau\}* is the *Ore polynomial ring of `\phi`*; `t` is the *Ore - variable of `\phi`*. Further, the *generator of `\phi`* is `\phi_X` - and its *constant coefficient* is the constant coefficient of - `\phi_X`. The `\Fq[X]`-characteristic of the base ring `K` can also - be referred to as its *function ring-characteristic*. - - .. NOTE:: - - The base ring is always a field. - The Drinfeld module `\phi` is uniquely determined by the image `\phi_X` of `X`, which is an input of the class. + We say that `\Fq[X]` is the *function ring of `\phi`*; *K\{\tau\}* + is the *Ore polynomial ring of `\phi`*; `t` is the *Ore variable of + `\phi`*. Further, the *generator of `\phi`* is `\phi_X` and its + *constant coefficient* is the constant coefficient of `\phi_X`. The + `\Fq[X]`-characteristic of the `\Fq[X]`-field `K` can also be referred to as + its *function ring-characteristic*. + Classical references on Drinfeld modules include [Gos1998]_, [Rosen2002]_, [VS06]_ and [Gek1998]_. @@ -105,7 +103,10 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z, 1, 1]) sage: phi - Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + Drinfeld module defined by X |--> t^2 + t + z over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z In this example, we used a list of coefficients (``[z, 1, 1]``) to represent the generator `\phi_X = z + t + t^2`, `K = \Fq(z)`. One can @@ -116,7 +117,10 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: psi_X = z + t^3 sage: psi = DrinfeldModule(FqX, psi_X) sage: psi - Drinfeld module defined by X |--> t^3 + z over Finite Field in z of size 3^12 + Drinfeld module defined by X |--> t^3 + z over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z sage: psi(X) == psi_X True @@ -132,7 +136,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: DrinfeldModule(FqX, [K(0), K(1)]) Traceback (most recent call last): ... - ValueError: the morphism must be non zero + ValueError: base must be a non zero morphism The coefficients of the generator must lie in an `\Fq[X]`-field, where `\Fq[X]` is the function ring of the Drinfeld module:: @@ -140,12 +144,12 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: DrinfeldModule(FqX, [z, QQ(1)]) Traceback (most recent call last): ... - ValueError: function ring base must coerce into base ring + ValueError: function ring base must coerce into base codomain sage: DrinfeldModule(FqX, [1, QQ(1)]) Traceback (most recent call last): ... - ValueError: function ring base must coerce into base ring + ValueError: function ring base must coerce into base codomain The function ring must be an univariate polynomial ring whose base is a finite field:: @@ -175,44 +179,36 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): .. NOTE:: The reader may think that it is odd to build a Drinfeld module - without specifying the `\Fq[X]`-characteristic of the base (or, - more generally, its parent category). Indeed, the - `\Fq[X]`-characteristic plays the role of the characteristic of - a function field, and thus preexists Drinfeld modules. The base - field `K` should rather be seen as an `\Fq[X]`-field, i.e. the - field `K` equipped with a morphism `\gamma: \Fq[X] \to K`, - instead of just a field. - - However, as the characteristic may be deduced from the constant - coefficient of the Drinfeld module (it is its minimal polynomial - over the function ring), we chose to ommit the characteristic - in the input of the class in order to have concise definitions. - - .. RUBRIC:: Possible base rings - - The morphism `\gamma` does not need be surjective like in the above - examples. This is equivalent to say that the constant coefficient of - the Drinfeld module may be different to the generator of `K` over - `\Fq`. In the following example, `K` is still a degree six extension - of `\Fq`, but `\gamma` is a projection over a degree two extension - with modulus `X^3 + (z_2 + 2)X^2 + (6*z_2 + 1)X + 3z_2 + 5`:: + without explicitly specifying the base. However, the base can be + deduced from the generator, and we omit the base in the input + of the class for conciseness. + + .. RUBRIC:: Possible bases + + The base does not need be surjective like in the above examples. In + the following example, the base codomain is still a degree six + extension of `\Fq`, but the base is a projection over a degree two + extension with modulus `X^3 + (z_2 + 2)X^2 + (6*z_2 + 1)X + 3z_2 + + 5`:: sage: p = X^2 + z2 + 2 sage: p_root = z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z sage: rho = DrinfeldModule(FqX, [p_root, 1, 1]) sage: rho - Drinfeld module defined by X |--> t^2 + t + z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z over Finite Field in z of size 3^12 + Drinfeld module defined by X |--> t^2 + t + z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z - The morphisms `\gamma` are not the same for ``phi`` and ``rho``, and - that the `\gamma` associated to `\phi` is surjective, while the - other one is not:: + Drinfeld modules `\phi` and `\rho` have different based. That of + `\phi` is surjective while that of `\rho` is note:: - sage: rho.category().morphism() + sage: rho.category().base() Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 To: Finite Field in z of size 3^12 Defn: X |--> z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z - sage: phi.category().morphism() + sage: phi.category().base() Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 To: Finite Field in z of size 3^12 @@ -223,27 +219,22 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1, 1]) sage: sigma - Drinfeld module defined by X |--> t^2 + t + X over Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + Drinfeld module defined by X |--> t^2 + t + X over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + Defn: X |--> X sage: sigma.is_finite() False sage: phi.is_finite() True - It is possible to change the base ring:: - - sage: L = K.extension(2) - sage: phi_rebased = phi.change_ring(L) - sage: Ltau = phi_rebased.ore_polring() - sage: Ltau(phi(X)) == phi_rebased(X) - True - .. RUBRIC:: The category of Drinfeld modules Drinfeld modules have their own category (see class :class:`sage.categories.drinfeld_modules.DrinfeldModules`):: sage: phi.category() - Category of Drinfeld modules defined by Ring morphism: + Category of Drinfeld modules defined over base Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 To: Finite Field in z of size 3^12 Defn: X |--> z @@ -257,8 +248,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: char = phi.category().characteristic() - As the output of :meth:`category` suggests, the morphism `\gamma` - uniquely determines the category. + As the output of :meth:`category` suggests, the base uniquely + determines the category. .. RUBRIC:: Basics @@ -274,8 +265,8 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): This is useful to quickly retrieve the generator of the Drinfeld module. Furthermore, a Drinfeld `\Fq[X]`-module can be seen as an Ore polynomial with positive degree and constant coefficient - `\gamma(X)`. This analogy is the motivation for the following - methods:: + `\gamma(X)`, where `\gamma` is the base. This analogy is the + motivation for the following methods:: sage: phi.coefficients() [z, 1, 1] @@ -287,8 +278,11 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): One can retrieve basic properties:: - sage: phi.base_ring() # K - Finite Field in z of size 3^12 + sage: phi.base() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z sage: phi.ore_polring() # K{t} Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) @@ -357,19 +351,19 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: zero_morphism = hom(0) sage: frobenius_endomorphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 - To: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 - Defn: t^6 + From (gen): t^2 + t + z + To (gen): t^2 + t + z + Defn: t^6 sage: identity_morphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 - To: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 - Defn: 1 + From (gen): t^2 + t + z + To (gen): t^2 + t + z + Defn: 1 sage: zero_morphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 - To: Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 - Defn: 0 + From (gen): t^2 + t + z + To (gen): t^2 + t + z + Defn: 0 The underlying Ore polynomial is retrieved with the method :meth:`ore_polynomial`:: @@ -404,7 +398,10 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z sage: psi = phi.velu(ore_pol) sage: psi - Drinfeld module defined by X |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over Finite Field in z of size 3^12 + Drinfeld module defined by X |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z sage: ore_pol in Hom(phi, psi) True sage: ore_pol * phi(X) == psi(X) * ore_pol @@ -435,7 +432,10 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: action = phi.action() sage: action - Action on Finite Field in z of size 3^12 induced by Drinfeld module defined by X |--> t^2 + t + z over Finite Field in z of size 3^12 + Action on Finite Field in z of size 3^12 induced by Drinfeld module defined by X |--> t^2 + t + z over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z The action on elements is computed by calling the action object:: @@ -449,13 +449,6 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: action(FqX.random_element(), 0) 0 - To act on a field larger than `K`, one can change the ring of the - Drinfeld module, then create the action:: - - sage: extended_action = phi.change_ring(K.extension(5)).action() - sage: extended_action - Action on Finite Field in z60 of size 3^60 induced by Drinfeld module defined by X |--> t^2 + t + 2*z60^59 + z60^56 + 2*z60^55 + 2*z60^54 + 2*z60^53 + z60^49 + z60^48 + z60^47 + 2*z60^45 + z60^44 + 2*z60^41 + 2*z60^40 + 2*z60^39 + 2*z60^37 + 2*z60^36 + z60^34 + z60^33 + z60^32 + 2*z60^31 + 2*z60^30 + 2*z60^27 + 2*z60^25 + z60^23 + z60^22 + z60^21 + 2*z60^20 + z60^19 + z60^18 + z60^17 + z60^16 + z60^15 + 2*z60^14 + z60^12 + 2*z60^11 + 2*z60^10 + z60^8 + z60^6 + 2*z60^5 + z60^4 + z60^3 + z60 + 1 over Finite Field in z60 of size 3^60 - .. RUBRIC:: Inverting the Drinfeld module Given an Ore polynomial that equals `\phi_a` for some function ring @@ -473,7 +466,7 @@ class DrinfeldModule(UniqueRepresentation, CategoryObject): sage: phi = DrinfeldModule(FqX, [1, 1]) Traceback (most recent call last): ... - ValueError: function ring base must coerce into base ring + ValueError: function ring base must coerce into base codomain sage: Fq = K = GF(2) sage: FqX. = Fq[] @@ -516,13 +509,13 @@ def __classcall_private__(cls, function_ring, gen, name='t'): if not (hasattr(ore_polring_base, 'has_coerce_map_from') and ore_polring_base.has_coerce_map_from( function_ring.base_ring())): - raise ValueError('function ring base must coerce into base ring') + raise ValueError('function ring base must coerce into base codomain') # Build the morphism that defines the category - gamma = function_ring.hom([ore_polring_base(gen[0])]) + base = function_ring.hom([ore_polring_base(gen[0])]) # Other checks in the category definition - category = DrinfeldModules(gamma, name=name) + category = DrinfeldModules(base, name=name) # Check gen as Ore polynomial if ore_polring is not None and \ @@ -541,7 +534,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): def __init__(self, gen, category): super().__init__(category=category) - self._base_ring = category.base() + self._base = category.base() self._function_ring = category.function_ring() self._gen = gen self._morphism = category._function_ring.hom([gen]) @@ -558,7 +551,7 @@ def __call__(self, a): - ``a`` -- a function ring element - OUTPUT: a base ring element + OUTPUT: an element in the base codomain TESTS: @@ -582,7 +575,7 @@ def __call__(self, a): True sage: a = FqX.random_element(5) - sage: phi(a)[0] == phi.category().morphism()(a) + sage: phi(a)[0] == phi.category().base()(a) True """ return self._morphism(a) @@ -644,12 +637,17 @@ def _latex_(self): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: latex(phi) - \text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }}\Bold{F}_{5^{12}} + \text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }base{ }}\begin{array}{l} + \text{\texttt{Ring{ }morphism:}}\\ + \text{\texttt{{ }{ }From:{ }Univariate{ }Polynomial{ }Ring{ }in{ }X{ }over{ }Finite{ }Field{ }in{ }z2{ }of{ }size{ }5{\char`\^}2}}\\ + \text{\texttt{{ }{ }To:{ }{ }{ }Finite{ }Field{ }in{ }z12{ }of{ }size{ }5{\char`\^}12}}\\ + \text{\texttt{{ }{ }Defn:{ }X{ }|{-}{-}>{ }2*z12{\char`\^}11{ }+{ }2*z12{\char`\^}10{ }+{ }z12{\char`\^}9{ }+{ }3*z12{\char`\^}8{ }+{ }z12{\char`\^}7{ }+{ }2*z12{\char`\^}5{ }+{ }2*z12{\char`\^}4{ }+{ }3*z12{\char`\^}3{ }+{ }z12{\char`\^}2{ }+{ }2*z12}} + \end{array} """ return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ f'{latex(self._function_ring.gen())} '\ f'\\mapsto {latex(self._gen)}' \ - f'\\text{{{{ }}over{{ }}}}{latex(self._base_ring)}' + f'\\text{{{{ }}over{{ }}base{{ }}}}{latex(self._base)}' def _repr_(self): r""" @@ -665,16 +663,19 @@ def _repr_(self): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: phi - Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 """ return f'Drinfeld module defined by {self._function_ring.gen()} ' \ - f'|--> {self._gen} over {self._base_ring}' + f'|--> {self._gen} over base {self._base}' def action(self): r""" Return the action object (:class:`sage.rings.function_field.drinfeld_modules.action.Action`) - that represents the module action, on the base ring, that is + that represents the module action, on the base codomain, that is induced by the Drinfeld module. OUTPUT: a Drinfeld module action object @@ -688,7 +689,10 @@ def action(self): sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: action = phi.action() sage: action - Action on Finite Field in z12 of size 5^12 induced by Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + Action on Finite Field in z12 of size 5^12 induced by Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 The action on elements is computed as follows:: @@ -704,11 +708,11 @@ def action(self): from sage.rings.function_field.drinfeld_modules.action import DrinfeldModuleAction return DrinfeldModuleAction(self) - def base_ring(self): + def base(self): r""" - Return the base ring of the Drinfeld module. + Return the base of the Drinfeld module. - OUTPUT: a field + OUTPUT: a ring morphism EXAMPLES: @@ -717,29 +721,29 @@ def base_ring(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.base_ring() - Finite Field in z12 of size 5^12 - - This is always true:: - - sage: phi.base_ring() is phi.ore_polring().base_ring() - True - sage: phi.base_ring() is K - True + sage: phi.base() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - The base ring can be infinite:: + The base codomain can be infinite:: sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) - sage: sigma.base_ring() - Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + sage: sigma.base() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + Defn: X |--> X - Or it can be ``Fq``:: + And it can also be the base field of the function ring:: sage: psi = DrinfeldModule(FqX, [Fq(1), Fq.gen()]) - sage: psi.base_ring() - Finite Field in z2 of size 5^2 - sage: psi.base_ring() is Fq - True + sage: psi.base() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z2 of size 5^2 + Defn: X |--> 1 In this case the Ore polynomial ring is isomorphic to a regular polynomial ring:: @@ -754,84 +758,17 @@ def base_ring(self): sage: psi.ore_polring().twisting_morphism().is_identity() True - sage: psi.base_ring() is psi.function_ring().base_ring() + sage: psi.base().codomain() is psi.function_ring().base_ring() True """ - return self._base_ring - - def change_ring(self, new_field, name=None): - r""" - Return a Drinfeld module defined like the current one, but whose - base ring ``new_field``. - - The new base is valid whether it has a coercion map from the - current base. - - INPUT: - - - ``new_field`` -- the field extension of the base ring that - serves as base ring for the new Drinfeld module - - OUTPUT: a Drinfeld module - - EXAMPLES: - - The new ring can be an extension of the base ring:: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, p_root^3, 2]) - sage: K2 = K.extension(2) - sage: phi_2 = phi.change_ring(K2) - sage: phi_2 - Drinfeld module defined by X |--> 2*t^2 + (3*z24^23 + 2*z24^22 + 2*z24^20 + z24^19 + 4*z24^18 + 3*z24^17 + 4*z24^15 + 2*z24^13 + 4*z24^12 + 4*z24^11 + 3*z24^10 + 3*z24^9 + 4*z24^8 + 4*z24^6 + 3*z24^5 + 4*z24^4 + 4*z24^3 + 2*z24)*t + 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 over Finite Field in z24 of size 5^24 - - And one can check various things:: - - sage: phi.change_ring(K2).change_ring(K) is phi - True - sage: phi_2.base_ring() is K2 - True - - Naturally, the category has changed:: - - sage: phi_2.category() - Category of Drinfeld modules defined by Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z24 of size 5^24 - Defn: X |--> 2*z24^23 + 2*z24^22 + z24^21 + 2*z24^20 + z24^19 + 2*z24^18 + 3*z24^17 + 2*z24^16 + 4*z24^12 + 3*z24^11 + 4*z24^10 + z24^9 + z24^8 + 3*z24^7 + 2*z24^6 + z24^4 + 4*z24^3 + 3*z24^2 + 3*z24 + 2 - - One can also change the base ring to a subfield, even though some things - do not work as expected:: - - sage: K0 = Fq.extension(2) - sage: phi_0 = phi.change_ring(K0) - sage: phi_0.base_ring() is K0 - True - sage: phi.change_ring(K0).change_ring(K) # known bug - Traceback (most recent call last): - ... - TypeError: no coercion defined - - Furthermore:: - - sage: phi.change_ring(K) is phi - True - """ - coeffs = self._gen.coefficients() - new_coeffs = list(map(new_field, coeffs)) - if name is None: - name = self._ore_polring.variable_name() - return DrinfeldModule(self._function_ring, new_coeffs, name=name) + return self._base def constant_coefficient(self): r""" Return the constant coefficient of the generator. - OUTPUT: a base ring element + OUTPUT: an element in the base codomain EXAMPLES: @@ -843,13 +780,13 @@ def constant_coefficient(self): sage: phi.constant_coefficient() == p_root True - Let `\Fq[X]` be the function ring, and let `\gamma` the morphism - defining the `\Fq[X]`-field structure of the base ring. The - constant coefficient equals `\gamma(X)`:: + Let `\Fq[X]` be the function ring, and let `\gamma` the base of + the Drinfeld module. The constant coefficient equals + `\gamma(X)`:: sage: cat = phi.category() - sage: gamma = cat.morphism() - sage: gamma(X) == phi.constant_coefficient() + sage: base = cat.base() + sage: base(X) == phi.constant_coefficient() True Naturally, two Drinfeld modules in the same category have the @@ -858,7 +795,10 @@ def constant_coefficient(self): sage: t = phi.ore_variable() sage: psi = cat.object(phi.constant_coefficient() + t^3) sage: psi - Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 Reciprocally, it is impossible to create two Drinfeld modules in this category if they do not share the same constant @@ -879,7 +819,7 @@ def coefficient(self, n): - ``n`` -- a non-negative integer - OUTPUT: a base ring element + OUTPUT: an element in the base codomain EXAMPLES: @@ -918,7 +858,7 @@ def coefficients(self, sparse=True): - ``sparse`` -- a boolean - OUTPUT: a list of base ring elements + OUTPUT: a list of elements in the base codomain EXAMPLES: @@ -1074,7 +1014,7 @@ def invert(self, ore_pol): r = self.rank() if ore_pol not in self._ore_polring: raise TypeError('input must be an Ore polynomial') - if ore_pol in self._base_ring: + if ore_pol in self._base.codomain(): return self._Fq(ore_pol) if deg % r != 0: raise ValueError('input must be in the image of the Drinfeld ' @@ -1124,13 +1064,12 @@ def j_invariant(self): Return the j-invariant of the Drinfeld module if the rank is two; raise a NotImplementedError otherwise. - Assume the rank is two. Write the generator `\phi_X = \gamma(X) - + g\tau + \Delta\tau^2`. The j-invariant is defined by + Assume the rank is two. Write the generator `\phi_X = \omega + + g\tau + \Delta\tau^2`. The j-invariant is defined by `\frac{g^{q+1}}{\Delta}`, `q` being the order of the base field - of the function ring. In our case, this base field is always - finite. + of the function ring. In our case, this field is always finite. - OUTPUT: a base ring element + OUTPUT: an element in the base codomain EXAMPLES: @@ -1163,50 +1102,6 @@ def j_invariant(self): return (g**(q+1)) / delta def morphism(self): - r""" - Return the morphism object that defines the Drinfeld module. - - OUTPUT: a ring morphism from the function ring to the Ore - polynomial ring - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.morphism() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) - Defn: X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: from sage.rings.morphism import RingHomomorphism - sage: isinstance(phi.morphism(), RingHomomorphism) - True - - Actually, the ``DrinfeldModule`` method ``__call__`` simply - class the ``__call__`` method of this morphism:: - - sage: phi.morphism()(X) == phi(X) - True - sage: a = FqX.random_element() - sage: phi.morphism()(a) == phi(a) - True - - And many methods of the Drinfeld module have a counterpart in - the morphism object:: - - sage: m = phi.morphism() - sage: m.domain() is phi.function_ring() - True - sage: m.codomain() is phi.ore_polring() - True - sage: m.im_gens() - [z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12] - sage: phi(X) == m.im_gens()[0] - True - """ return self._morphism def ore_polring(self): @@ -1349,7 +1244,10 @@ def velu(self, isog): sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(isog) sage: psi - Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: isog in Hom(phi, psi) True diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index afb02c5eba6..18003d62d3c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -29,7 +29,7 @@ class FiniteDrinfeldModule(DrinfeldModule): r""" This class represents a finite Drinfeld module. - A *finite Drinfeld module* is a Drinfeld module whose base ring is + A *finite Drinfeld module* is a Drinfeld module whose base field is finite. For general definitions and help on Drinfeld modules, see class @@ -70,9 +70,9 @@ class FiniteDrinfeldModule(DrinfeldModule): sage: frobenius_endomorphism = phi.frobenius_endomorphism() sage: frobenius_endomorphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> 5*t^2 + z6 over Finite Field in z6 of size 7^6 - To: Drinfeld module defined by X |--> 5*t^2 + z6 over Finite Field in z6 of size 7^6 - Defn: t^2 + From (gen): 5*t^2 + z6 + To (gen): 5*t^2 + z6 + Defn: t^2 Its characteristic polynomial can be computed:: @@ -129,15 +129,15 @@ def frobenius_endomorphism(self): sage: phi = DrinfeldModule(FqX, [1, 0, z6]) sage: phi.frobenius_endomorphism() Drinfeld Module morphism: - From: Drinfeld module defined by X |--> z6*t^2 + 1 over Finite Field in z6 of size 7^6 - To: Drinfeld module defined by X |--> z6*t^2 + 1 over Finite Field in z6 of size 7^6 - Defn: t^2 + From (gen): z6*t^2 + 1 + To (gen): z6*t^2 + 1 + Defn: t^2 sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism sage: isinstance(phi.frobenius_endomorphism(), DrinfeldModuleMorphism) True """ t = self.ore_variable() - L = self._base_ring + L = self._base.codomain() Fq = self._function_ring.base_ring() deg = L.over(Fq).degree(Fq) return self._Hom_(self, category=self.category())(t**deg) @@ -160,7 +160,7 @@ def frobenius_charpoly(self, var='T'): Let `\chi = T^2 - A(X)T + B(X)` be the characteristic polynomial of the Frobenius endomorphism, let `t^n` be the Ore polynomial that defines the Frobenius endomorphism of `\phi`; by - definition, `n` is the degree of the base ring over `\Fq`. We + definition, `n` is the degree over `\Fq` of the base codomain. We have `\chi(t^n)(\phi(X)) = t^{2n} - \phi_A t^n + \phi_B = 0`, with `\deg(A) \leq \frac{n}{2}` and `\deg(B) = n`. @@ -195,7 +195,7 @@ def frobenius_charpoly(self, var='T'): sage: B (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 - sage: n = 2 # Degree of the base ring over Fq + sage: n = 2 # Degree over Fq of the base codomain sage: A.degree() <= n/2 True sage: B.degree() == n @@ -229,7 +229,7 @@ def frobenius_norm(self): Frobenius endomorphism. The *Frobenius norm* is defined as the polynomial `B(X) \in \Fq[X]`. - Let `n` be the degree of the base ring over `\Fq`. Then the + Let `n` be the degree over `\Fq` of the base codomain. Then the Frobenius norm has degree `n`. OUTPUT: an element in the function ring @@ -244,7 +244,7 @@ def frobenius_norm(self): sage: B (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 - sage: n = 2 # Degree of the base ring over Fq + sage: n = 2 # Degree over Fq of the base codomain sage: B.degree() == n True @@ -257,13 +257,14 @@ def frobenius_norm(self): Gekeler, given in [SM2019]_, Section 4, Proposition 3. """ self._check_rank_two() + L = self._base.codomain().over(self._Fq) # Notations from Schost-Musleh: if self._frobenius_norm is None: - n = self._base_ring.over(self._Fq).degree_over(self._Fq) + n = L.degree_over(self._Fq) d = self.characteristic().degree() m = n // d delta = self._gen[2] - norm = self._base_ring.over(self._Fq)(delta).norm() + norm = L(delta).norm() char = self.characteristic() self._frobenius_norm = ((-1)**n) * (char**m) / norm return self._frobenius_norm @@ -278,7 +279,7 @@ def frobenius_trace(self): Frobenius endomorphism. The *Frobenius norm* is defined as the polynomial `B(X) \in \Fq[X]`. - Let `n` be the degree of the base ring over `\Fq`. Then the + Let `n` be the degree over `\Fq` of the base codomain. Then the Frobenius trace has degree `\leq \frac{n}{2}`. OUTPUT: an element in the function ring @@ -306,7 +307,7 @@ def frobenius_trace(self): sage: A (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 - sage: n = 2 # Degree of the base ring over Fq + sage: n = 2 # Degree over Fq of the base codomain sage: A.degree() <= n/2 True @@ -316,7 +317,7 @@ def frobenius_trace(self): self._check_rank_two() # Notations from Schost-Musleh: if self._frobenius_trace is None: - n = self._base_ring.over(self._Fq).degree_over(self._Fq) + n = self._base.codomain().over(self._Fq).degree_over(self._Fq) B = self.frobenius_norm() t = self.ore_polring().gen() self._frobenius_trace = self.invert(t**n + (self(B) // t**n)) @@ -328,9 +329,9 @@ def is_ordinary(self): NotImplementedError if the rank is not two. A rank two finite Drinfeld module is *ordinary* if and only if - the `\Fq[X]-characteristic of the base ring does not divide the - Frobenius trace. A *supersingular* rank two finite Drinfeld - module is a Drinfeld module that is not ordinary. + the function field-characteristic does not divide the Frobenius + trace. A *supersingular* rank two finite Drinfeld module is a + Drinfeld module that is not ordinary. A rnak two Drinfeld module is *ordinary* if and only if it is note supersingular; see :meth:`is_supersingular`. @@ -367,9 +368,9 @@ def is_supersingular(self): NotImplementedError if the rank is not two. A rank two finite Drinfeld module is *supersingular* if and only - if the function field-characteristic of the base ring divides - the Frobenius trace. An *ordinary* rank two finite Drinfeld - module is a Drinfeld module that is not supersingular. + if the function field-characteristic divides the Frobenius + trace. An *ordinary* rank two finite Drinfeld module is a + Drinfeld module that is not supersingular. OUTPUT: a boolean diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index df176bfafa6..e70a06db2b3 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -45,9 +45,7 @@ class DrinfeldModuleHomset(Homset): sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: hom - Set of Drinfeld module morphisms: - From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - To: Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 + Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 sage: from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset sage: isinstance(hom, DrinfeldModuleHomset) @@ -57,9 +55,7 @@ class DrinfeldModuleHomset(Homset): sage: end = End(phi) sage: end - Set of Drinfeld module morphisms: - From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 + Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + z6*t + z6 sage: end is Hom(phi, phi) True @@ -69,23 +65,23 @@ class DrinfeldModuleHomset(Homset): sage: identity_morphism = end(1) sage: identity_morphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - Defn: 1 + From (gen): 2*t^2 + z6*t + z6 + To (gen): 2*t^2 + z6*t + z6 + Defn: 1 sage: frobenius_endomorphism = end(t^6) sage: frobenius_endomorphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - Defn: t^6 + From (gen): 2*t^2 + z6*t + z6 + To (gen): 2*t^2 + z6*t + z6 + Defn: t^6 sage: isogeny = hom(t + 1) sage: isogeny Drinfeld Module morphism: - From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - To: Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 - Defn: t + 1 + From (gen): 2*t^2 + z6*t + z6 + To (gen): 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 + Defn: t + 1 And one can test if an Ore polynomial defines a morphism using the ``in`` syntax:: @@ -146,11 +142,12 @@ def _latex_(self): sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: latex(hom) - \text{Set{ }of{ }Drinfeld{ }module{ }morphisms{ }from}\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto 2 t^{2} + z_{6} t + z_{6}\text{{ }over{ }}\Bold{F}_{3^{6}}\text{{ }to{ }}Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 + \text{Set{ }of{ }Drinfeld{ }module{ }morphisms{ }from{ }(gen){ }}2 t^{2} + z_{6} t + z_{6}\text{{ }to{ }(gen){ }}2 t^{2} + \left(2 z_{6}^{5} + 2 z_{6}^{4} + 2 z_{6} + 1\right) t + z_{6} """ return f'\\text{{Set{{ }}of{{ }}Drinfeld{{ }}module{{ }}morphisms' \ - f'{{ }}from}}{latex(self.domain())}\\text{{{{ }}to{{ }}}}' \ - f'{self.codomain()}' + f'{{ }}from{{ }}(gen){{ }}}}{latex(self.domain().gen())}' \ + f'\\text{{{{ }}to{{ }}(gen){{ }}}}'\ + f'{latex(self.codomain().gen())}' def _repr_(self): r""" @@ -167,13 +164,10 @@ def _repr_(self): sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: hom - Set of Drinfeld module morphisms: - From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - To: Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 + Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 """ - return f'Set of Drinfeld module morphisms:\n' \ - f' From: {self.domain()}\n' \ - f' To: {self.codomain()}' + return f'Set of Drinfeld module morphisms from (gen) '\ + f'{self.domain().gen()} to (gen) {self.codomain().gen()}' def __contains__(self, x): r""" @@ -254,23 +248,23 @@ def _element_constructor_(self, *args, **kwds): sage: identity_morphism = end(1) sage: identity_morphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - Defn: 1 + From (gen): 2*t^2 + z6*t + z6 + To (gen): 2*t^2 + z6*t + z6 + Defn: 1 sage: frobenius_endomorphism = end(t^6) sage: frobenius_endomorphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - To: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - Defn: t^6 + From (gen): 2*t^2 + z6*t + z6 + To (gen): 2*t^2 + z6*t + z6 + Defn: t^6 sage: isogeny = hom(t + 1) sage: isogeny Drinfeld Module morphism: - From: Drinfeld module defined by X |--> 2*t^2 + z6*t + z6 over Finite Field in z6 of size 3^6 - To: Drinfeld module defined by X |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 over Finite Field in z6 of size 3^6 - Defn: t + 1 + From (gen): 2*t^2 + z6*t + z6 + To (gen): 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 + Defn: t + 1 """ # NOTE: This used to be self.element_class(self, ...), but this # would call __init__ instead of __classcall_private__. This diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index d8e8a7adc41..503c9a8fd6b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -29,12 +29,11 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, r""" This class represents a Drinfeld module morphism. - Let `\phi, \psi` be two Drinfeld modules with function ring `\Fq[X]` - and base ring `K`, whose `\Fq[X]`-field structure is given by a - morphism `\gamma`. A *morphism of Drinfeld modules `\phi \to \psi`* - is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a - f` for every `a \in \Fq[X]`. In our case, this is equivalent to `f - \phi_X = \psi_X f`. An *isogeny* is a non-zero morphism. + Let `\phi, \psi` be two Drinfeld modules with base `\Fq[X] \to K`. A + *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial + `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a \in + \Fq[X]`. In our case, this is equivalent to `f \phi_X = \psi_X f`. + An *isogeny* is a non-zero morphism. To create a morphism object, the user should never explicitly instantiate `DrinfeldModuleMorphism`, but rather call the parent @@ -51,19 +50,25 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, sage: morphism = Hom(phi, psi)(ore_pol) sage: morphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 - To: Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 - Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + From (gen): z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + To (gen): (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 We can get basic data on the morphism:: sage: morphism.domain() - Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: morphism.domain() is phi True sage: morphism.codomain() - Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 + Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: morphism.codomain() is psi True @@ -101,9 +106,9 @@ class DrinfeldModuleMorphism(UniqueRepresentation, Element, sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) Drinfeld Module morphism: - From: Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 - To: Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 - Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + From (gen): z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + To (gen): (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) is morphism True """ @@ -149,17 +154,17 @@ def _latex_(self): sage: latex(morphism) \begin{array}{l} \text{Drinfeld{ }module{ }morphism:}\\ - \text{{ }{ }From:{ }}\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto t^{2} + t + z_{6}\text{{ }over{ }}\Bold{F}_{2^{6}}}\\ - \text{{ }{ }To:{ }}{ }{ }\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto t^{2} + \left(z_{6}^{4} + z_{6}^{2} + 1\right) t + z_{6}\text{{ }over{ }}\Bold{F}_{2^{6}}\\ + \text{{ }{ }From (gen):{ }}t^{2} + t + z_{6}\\ + \text{{ }{ }To (gen):{ }}{ }{ }t^{2} + \left(z_{6}^{4} + z_{6}^{2} + 1\right) t + z_{6}\\ \text{{ }{ }Defn:{ }}t + z_{6}^{5} + z_{6}^{2} + 1 \end{array} """ return f'\\begin{{array}}{{l}}\n' \ f'\\text{{Drinfeld{{ }}module{{ }}morphism:}}\\\\\n' \ - f'\\text{{{{ }}{{ }}From:{{ }}}}'\ - f'{latex(self._domain)}}}\\\\\n' \ - f'\\text{{{{ }}{{ }}To:{{ }}}}{{ }}{{ }}' \ - f'{latex(self._codomain)}\\\\\n' \ + f'\\text{{{{ }}{{ }}From (gen):{{ }}}}'\ + f'{latex(self._domain.gen())}\\\\\n' \ + f'\\text{{{{ }}{{ }}To (gen):{{ }}}}{{ }}{{ }}' \ + f'{latex(self._codomain.gen())}\\\\\n' \ f'\\text{{{{ }}{{ }}Defn:{{ }}}}' \ f'{latex(self._ore_polynomial)}\n' \ f'\\end{{array}}' @@ -180,14 +185,14 @@ def _repr_(self): sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism Drinfeld Module morphism: - From: Drinfeld module defined by X |--> t^2 + t + z6 over Finite Field in z6 of size 2^6 - To: Drinfeld module defined by X |--> t^2 + (z6^4 + z6^2 + 1)*t + z6 over Finite Field in z6 of size 2^6 - Defn: t + z6^5 + z6^2 + 1 + From (gen): t^2 + t + z6 + To (gen): t^2 + (z6^4 + z6^2 + 1)*t + z6 + Defn: t + z6^5 + z6^2 + 1 """ return f'Drinfeld Module morphism:\n' \ - f' From: {self._domain}\n' \ - f' To: {self._codomain}\n' \ - f' Defn: {self._ore_polynomial}' + f' From (gen): {self._domain.gen()}\n' \ + f' To (gen): {self._codomain.gen()}\n' \ + f' Defn: {self._ore_polynomial}' def codomain(self): r""" @@ -203,7 +208,10 @@ def codomain(self): sage: t = phi.ore_variable() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism.codomain() - Drinfeld module defined by X |--> t^2 + (z6^4 + z6^2 + 1)*t + z6 over Finite Field in z6 of size 2^6 + Drinfeld module defined by X |--> t^2 + (z6^4 + z6^2 + 1)*t + z6 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 2 (using GF2X) + To: Finite Field in z6 of size 2^6 + Defn: X |--> z6 sage: morphism.codomain() is psi True """ @@ -223,7 +231,10 @@ def domain(self): sage: t = phi.ore_variable() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism.domain() - Drinfeld module defined by X |--> t^2 + t + z6 over Finite Field in z6 of size 2^6 + Drinfeld module defined by X |--> t^2 + t + z6 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 2 (using GF2X) + To: Finite Field in z6 of size 2^6 + Defn: X |--> z6 sage: morphism.domain() is phi True """ From cfad9600155f0c1ae0cc95c83cc86f9a3b464bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 8 Sep 2022 18:00:27 +0200 Subject: [PATCH 116/392] Use Parent instead of CategoryObject in DrinfeldModule --- .../function_field/drinfeld_modules/drinfeld_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 63313a25d1b..7a2fb2c3b8d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -31,12 +31,12 @@ from sage.rings.integer import Integer from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.polynomial_ring import PolynomialRing_general -from sage.structure.category_object import CategoryObject +from sage.structure.parent import Parent from sage.structure.sequence import Sequence from sage.structure.unique_representation import UniqueRepresentation -class DrinfeldModule(UniqueRepresentation, CategoryObject): +class DrinfeldModule(Parent, UniqueRepresentation): r""" This class represents a Drinfeld module. @@ -533,13 +533,13 @@ def __classcall_private__(cls, function_ring, gen, name='t'): return cls.__classcall__(cls, gen, category) def __init__(self, gen, category): - super().__init__(category=category) self._base = category.base() self._function_ring = category.function_ring() self._gen = gen self._morphism = category._function_ring.hom([gen]) self._ore_polring = gen.parent() self._Fq = self._function_ring.base_ring() # Must be last + super().__init__(base=self._base, category=category) def __call__(self, a): r""" From dbcbaf74fc617f8605466fce3223abccc8874053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 8 Sep 2022 18:00:49 +0200 Subject: [PATCH 117/392] Implement base_ring in DrinfeldModule Why? See docstring of the method. --- .../drinfeld_modules/drinfeld_module.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 7a2fb2c3b8d..bc6fc8e76ac 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -764,6 +764,18 @@ def base(self): """ return self._base + def base_ring(self): + r""" + Raise an exception. + + The base of a Drinfeld module is a ring morphism, not a ring. + + This method is implemented in CategoryObjects, of which + Parent inherits. It returns the base of the category. I + overloaded it to avoid confusion. + """ + raise TypeError('the base of a Drinfeld module is a morphism') + def constant_coefficient(self): r""" Return the constant coefficient of the generator. From 771f406ec3e9d4fb0317da89af4b3b0abc2bf788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 8 Sep 2022 19:08:15 +0200 Subject: [PATCH 118/392] Use Category_over_base instead of CategoryWithParameters for DrinfeldModules --- src/sage/categories/drinfeld_modules.py | 36 ++++--------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 27480adb460..5a85c3e31d3 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -18,8 +18,9 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from sage.categories.category import CategoryWithParameters +from sage.categories.category_types import Category_over_base from sage.categories.homsets import Homsets +from sage.misc.cachefunc import cached_method from sage.misc.functional import log from sage.misc.latex import latex from sage.rings.integer import Integer @@ -28,7 +29,7 @@ from sage.rings.polynomial.polynomial_ring import PolynomialRing_general -class DrinfeldModules(CategoryWithParameters): +class DrinfeldModules(Category_over_base): r""" This class represents the category of Drinfeld modules on a given base. @@ -229,10 +230,7 @@ def __init__(self, base, name='t'): self._characteristic = FqX(E(base(X)).minpoly()) elif FqX.is_subring(K): self._characteristic = Integer(0) - - # Somehow required for the class definition - def _make_named_class_key(self, name): - return self._function_ring.category() + super().__init__(base=base) def _latex_(self): r""" @@ -289,31 +287,6 @@ def Homsets(self): def Endsets(self): return Homsets() - def base(self): - r""" - Return the base of the category. - - OUTPUT: a ring morphism - - EXAMPLES: - - sage: Fq = GF(11) - sage: FqX. = Fq[] - sage: K. = Fq.extension(4) - sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: base = cat.base() - sage: base - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Finite Field in z of size 11^4 - Defn: X |--> z^3 + 7*z^2 + 6*z + 10 - sage: base(X) == cat.constant_coefficient() - True - """ - return self._base - def characteristic(self): r""" Return the function ring-characteristic of the category. @@ -507,6 +480,7 @@ def random_object(self, rank): return self.object(coeffs) # Somehow required for the class definition + @cached_method def super_categories(self): return [] From 674faeb30a406932eb67c44a126b561a7cf03e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 8 Sep 2022 19:27:39 +0200 Subject: [PATCH 119/392] Make DrinfeldModuleMorphism inherit Morphism As DrinfeldModule now inherits Parent, this is possible. --- .../drinfeld_modules/morphism.py | 82 +------------------ 1 file changed, 3 insertions(+), 79 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 503c9a8fd6b..391aa44855a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -20,12 +20,12 @@ from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass from sage.misc.latex import latex -from sage.structure.element import Element +from sage.categories.morphism import Morphism from sage.structure.unique_representation import UniqueRepresentation -class DrinfeldModuleMorphism(UniqueRepresentation, Element, - metaclass=InheritComparisonClasscallMetaclass): +class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, + metaclass=InheritComparisonClasscallMetaclass): r""" This class represents a Drinfeld module morphism. @@ -194,52 +194,6 @@ def _repr_(self): f' To (gen): {self._codomain.gen()}\n' \ f' Defn: {self._ore_polynomial}' - def codomain(self): - r""" - Return the codomain of the morphism. - - EXAMPLES: - - sage: Fq = GF(2) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_variable() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) - sage: morphism.codomain() - Drinfeld module defined by X |--> t^2 + (z6^4 + z6^2 + 1)*t + z6 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 2 (using GF2X) - To: Finite Field in z6 of size 2^6 - Defn: X |--> z6 - sage: morphism.codomain() is psi - True - """ - return self._codomain - - def domain(self): - r""" - Return the codomain of the morphism. - - EXAMPLES: - - sage: Fq = GF(2) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_variable() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) - sage: morphism.domain() - Drinfeld module defined by X |--> t^2 + t + z6 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 2 (using GF2X) - To: Finite Field in z6 of size 2^6 - Defn: X |--> z6 - sage: morphism.domain() is phi - True - """ - return self._domain - def is_zero(self): r""" Return ``True`` whether the morphism is the zero morphism. @@ -262,36 +216,6 @@ def is_zero(self): """ return self._ore_polynomial.is_zero() - def is_endomorphism(self): - r""" - Return ``True`` whether the morphism is an endomorphism. - - EXAMPLES: - - sage: Fq = GF(2) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_variable() - sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) - sage: morphism.is_endomorphism() - False - - sage: zero_morphism = End(phi)(0) - sage: zero_morphism.is_endomorphism() - True - - sage: identity_morphism = End(phi)(1) - sage: identity_morphism.is_endomorphism() - True - - sage: frobenius_endomorphism = phi.frobenius_endomorphism() - sage: frobenius_endomorphism.is_endomorphism() - True - """ - return self._domain is self._codomain - def is_isogeny(self): r""" Return ``True`` whether the morphism is an isogeny. From d597953e9c51bd2474c263d7c0c49730511125f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 9 Sep 2022 10:52:01 +0200 Subject: [PATCH 120/392] Remove methods ore_variable from Drinfeld module classes --- src/sage/categories/drinfeld_modules.py | 32 ++------- .../drinfeld_modules/drinfeld_module.py | 65 ++++--------------- .../finite_drinfeld_module.py | 2 +- .../function_field/drinfeld_modules/homset.py | 6 +- .../drinfeld_modules/morphism.py | 14 ++-- 5 files changed, 27 insertions(+), 92 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 5a85c3e31d3..5ca42f87ce4 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -47,11 +47,10 @@ class DrinfeldModules(Category_over_base): These notations will be used throughout this documentation. We say that `\Fq[X]` is the *function ring of the category*; - *K\{\tau\}* is the *Ore polynomial ring of the category*; `t` is the - *Ore variable of the category*. The *constant coefficient of the - category* is the image of `X` under the base. The - `\Fq[X]`-characteristic of the `\Fq[X]`-field `K` can also be - referred to as its *function ring-characteristic*. + *K\{\tau\}* is the *Ore polynomial ring of the category*. The + *constant coefficient of the category* is the image of `X` under the + base. The `\Fq[X]`-characteristic of the `\Fq[X]`-field `K` can also + be referred to as its *function ring-characteristic*. INPUT: the base, a ring morphism @@ -385,7 +384,7 @@ def object(self, gen): From: Univariate Polynomial Ring in X over Finite Field of size 11 To: Finite Field in z of size 11^4 Defn: X |--> z^3 + 7*z^2 + 6*z + 10 - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: cat.object(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi True """ @@ -420,27 +419,6 @@ def ore_polring(self): """ return self._ore_polring - def ore_variable(self): - r""" - Return the Ore variable of the category. - - OUTPUT: an Ore polynomial - - EXAMPLES: - - sage: Fq = GF(11) - sage: FqX. = Fq[] - sage: K. = Fq.extension(4) - sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat.ore_variable() - t - sage: cat.ore_variable() is phi.ore_variable() - True - """ - return self._ore_polring.gen() - def random_object(self, rank): r""" Return a random Drinfeld module in the category, whose rank is diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index bc6fc8e76ac..1740c3cdbfa 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -68,11 +68,11 @@ class DrinfeldModule(Parent, UniqueRepresentation): `\phi_X` of `X`, which is an input of the class. We say that `\Fq[X]` is the *function ring of `\phi`*; *K\{\tau\}* - is the *Ore polynomial ring of `\phi`*; `t` is the *Ore variable of - `\phi`*. Further, the *generator of `\phi`* is `\phi_X` and its - *constant coefficient* is the constant coefficient of `\phi_X`. The - `\Fq[X]`-characteristic of the `\Fq[X]`-field `K` can also be referred to as - its *function ring-characteristic*. + is the *Ore polynomial ring of `\phi`*. Further, the *generator of + `\phi`* is `\phi_X` and its *constant coefficient* is the constant + coefficient of `\phi_X`. The `\Fq[X]`-characteristic of the + `\Fq[X]`-field `K` can also be referred to as its *function + ring-characteristic*. Classical references on Drinfeld modules include [Gos1998]_, [Rosen2002]_, [VS06]_ and [Gek1998]_. @@ -92,7 +92,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): finite field - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` (optional) -- the name of the Ore variable + - ``name`` (optional) -- the name of the Ore polynomial ring gen .. RUBRIC:: Construction @@ -113,7 +113,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): also use regular Ore polynomials:: sage: ore_polring = phi.ore_polring() - sage: t = phi.ore_variable() # same as ore_polring.gen() + sage: t = phi.ore_polring().gen() sage: psi_X = z + t^3 sage: psi = DrinfeldModule(FqX, psi_X) sage: psi @@ -287,9 +287,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.ore_polring() # K{t} Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) - sage: phi.ore_variable() # t - t - sage: phi.function_ring() # Fq[X] Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 @@ -603,7 +600,7 @@ def _Hom_(self, other, category): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(isog) sage: hom = phi._Hom_(psi, category=phi.category()) @@ -804,7 +801,7 @@ def constant_coefficient(self): Naturally, two Drinfeld modules in the same category have the same constant coefficient:: - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: psi = cat.object(phi.constant_coefficient() + t^3) sage: psi Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: @@ -987,7 +984,7 @@ def invert(self, ore_pol): When the input is not in the image of the Drinfeld module, an exception is raised:: - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: phi.invert(t + 1) Traceback (most recent call last): ... @@ -1144,49 +1141,9 @@ def ore_polring(self): sage: phi(X) in ore_polring True - - The Ore variable is just the generator of the Ore polynomial - ring:: - - sage: ore_polring.gen() - t - sage: phi.ore_variable() is ore_polring.gen() - True """ return self._ore_polring - def ore_variable(self): - r""" - Return the Ore variable. - - OUTPUT: an Ore polynomial - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.ore_variable() - t - sage: phi.ore_variable() is phi.ore_polring().gen() - True - - One can use the Ore variable to instanciate new Drinfeld - modules...:: - - sage: t = phi.ore_variable() - sage: psi_X = phi.constant_coefficient() + 3*t + 2*t^4 - sage: psi = DrinfeldModule(FqX, psi_X) - - ...or morphisms and isogenies:: - - sage: t^6 in End(phi) # Frobenius endomorphism - True - """ - return self._ore_polring.gen() - def rank(self): r""" Return the rank of the Drinfeld module. @@ -1252,7 +1209,7 @@ def velu(self, isog): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(isog) sage: psi diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 18003d62d3c..6d02da7dff9 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -136,7 +136,7 @@ def frobenius_endomorphism(self): sage: isinstance(phi.frobenius_endomorphism(), DrinfeldModuleMorphism) True """ - t = self.ore_variable() + t = self.ore_polring().gen() L = self._base.codomain() Fq = self._function_ring.base_ring() deg = L.over(Fq).degree(Fq) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index e70a06db2b3..43cd91849f7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -61,7 +61,7 @@ class DrinfeldModuleHomset(Homset): One can create morphism objects by calling the homset:: - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: identity_morphism = end(1) sage: identity_morphism Drinfeld Module morphism: @@ -191,7 +191,7 @@ def __contains__(self, x): sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: end = End(phi) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: 1 in hom False @@ -244,7 +244,7 @@ def _element_constructor_(self, *args, **kwds): sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: end = End(phi) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: identity_morphism = end(1) sage: identity_morphism Drinfeld Module morphism: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 391aa44855a..1aa36eba277 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -44,7 +44,7 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: ore_pol = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(ore_pol) sage: morphism = Hom(phi, psi)(ore_pol) @@ -149,7 +149,7 @@ def _latex_(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: latex(morphism) \begin{array}{l} @@ -181,7 +181,7 @@ def _repr_(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism Drinfeld Module morphism: @@ -205,7 +205,7 @@ def is_zero(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism.is_zero() False @@ -227,7 +227,7 @@ def is_isogeny(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism.is_isogeny() True @@ -257,7 +257,7 @@ def is_isomorphism(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism.is_isomorphism() False @@ -287,7 +287,7 @@ def ore_polynomial(self): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) - sage: t = phi.ore_variable() + sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: ore_pol = morphism.ore_polynomial() sage: ore_pol From 2ca21832b445169e246edce63498f01e53f76a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 9 Sep 2022 10:54:22 +0200 Subject: [PATCH 121/392] Remove `class ElementMethods` from DrinfeldModules This seems to change nothing. --- src/sage/categories/drinfeld_modules.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 5ca42f87ce4..c2fdf902cc7 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -467,7 +467,3 @@ class ParentMethods: def characteristic(self): return self.category().characteristic() - - # Somehow required for the class definition - class ElementMethods: - pass From 15efff9de6413a48c7f8bf000466c341485fb4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 9 Sep 2022 10:58:23 +0200 Subject: [PATCH 122/392] (Fix) Add doc of morphism method in DrinfeldModule It was deleted by accident. --- .../drinfeld_modules/drinfeld_module.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 1740c3cdbfa..b3007bc171b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -1111,6 +1111,50 @@ def j_invariant(self): return (g**(q+1)) / delta def morphism(self): + r""" + Return the morphism object that defines the Drinfeld module. + + OUTPUT: a ring morphism from the function ring to the Ore + polynomial ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + Defn: X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: from sage.rings.morphism import RingHomomorphism + sage: isinstance(phi.morphism(), RingHomomorphism) + True + + Actually, the ``DrinfeldModule`` method ``__call__`` simply + class the ``__call__`` method of this morphism:: + + sage: phi.morphism()(X) == phi(X) + True + sage: a = FqX.random_element() + sage: phi.morphism()(a) == phi(a) + True + + And many methods of the Drinfeld module have a counterpart in + the morphism object:: + + sage: m = phi.morphism() + sage: m.domain() is phi.function_ring() + True + sage: m.codomain() is phi.ore_polring() + True + sage: m.im_gens() + [z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12] + sage: phi(X) == m.im_gens()[0] + True + """ return self._morphism def ore_polring(self): From 2e7745256a9c152d975b77de812bb31efed9414d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 9 Sep 2022 11:17:45 +0200 Subject: [PATCH 123/392] Enhance base and base ring distinctions in doc --- src/sage/categories/drinfeld_modules.py | 17 +++++++++++------ .../function_field/drinfeld_modules/action.py | 10 ++++------ .../drinfeld_modules/drinfeld_module.py | 8 +++++--- .../drinfeld_modules/finite_drinfeld_module.py | 13 +++++++------ .../function_field/drinfeld_modules/morphism.py | 10 +++++----- 5 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index c2fdf902cc7..a3e7f8d6c9d 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -34,10 +34,14 @@ class DrinfeldModules(Category_over_base): This class represents the category of Drinfeld modules on a given base. - The category is uniquely defined by its base, which is a ring - morphism from the function ring `\Fq[X]` to a field `K`. Note that - the base is a morphism, but not a field. The base is often denoted - `\gamma`, and we call `K` an *`\Fq[X]-field`*. + Let `\Fq[X]` be a polynomial ring with coefficients in a finite + field `\Fq` and let `K` be a field. We fix a ring morphism `\gamma: + \Fq[X] \to K`, which we call the *base* of the category (it is the + base of the Drinfeld modules in the category). + Please note that the base is not a ring; in particular, it is + not the field `K`. We also call `K` an *`\Fq[X]`-field*. + + The category is uniquely defined by its base. The monic polynomial that generates the kernel of the base is called the *`\Fq[X]`-characteristic of the `\Fq[X]`-field `K`*. @@ -50,7 +54,8 @@ class DrinfeldModules(Category_over_base): *K\{\tau\}* is the *Ore polynomial ring of the category*. The *constant coefficient of the category* is the image of `X` under the base. The `\Fq[X]`-characteristic of the `\Fq[X]`-field `K` can also - be referred to as its *function ring-characteristic*. + be referred to as its *function ring-characteristic*. Finally, `K` + is just refered to as the codomain base. INPUT: the base, a ring morphism @@ -288,7 +293,7 @@ def Endsets(self): def characteristic(self): r""" - Return the function ring-characteristic of the category. + Return the function ring-characteristic. OUTPUT: `0` or a monic prime polynomial in the function ring diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index fbc1bbfe47b..4b348bff6e2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -29,11 +29,9 @@ class DrinfeldModuleAction(Action): This class represents the module action induced by a Drinfeld module. - Let `\phi` be a Drinfeld module with function ring `\Fq[X]` and base - ring `K`, whose `\Fq[X]`-field structure is given by a morphism - `\gamma`. Let `L/K` be a field extension, let `x \in L`, let `a` be - a function ring element; the action is defined as `(a, x) \mapsto - \phi_a(x)`. + Let `\phi` be a Drinfeld module with base `\gamma: \Fq[X] \to K`. + Let `L/K` be a field extension, let `x \in L`, let `a` be a function + ring element; the action is defined as `(a, x) \mapsto \phi_a(x)`. .. NOTE:: @@ -92,7 +90,7 @@ def _act_(self, pol, x): - ``pol`` -- a function ring element - ``x`` -- an element in the field acted upon - OUTPUT: an element in the base field of the Drinfeld module + OUTPUT: an element in the base codomain EXAMPLES: diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index b3007bc171b..c1e61416d1e 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -43,7 +43,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): Let `\Fq[X]` be a polynomial ring with coefficients in a finite field `\Fq` and let `K` be a field. We fix a ring morphism `\gamma: \Fq[X] \to K`, which we call the *base* of the Drinfeld module. - We also call `K` an *`\Fq[X]`-field*. + Please note that the base is not a ring; in particular, it is + not the field `K`. We also call `K` an *`\Fq[X]`-field*. .. NOTE:: @@ -72,7 +73,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): `\phi`* is `\phi_X` and its *constant coefficient* is the constant coefficient of `\phi_X`. The `\Fq[X]`-characteristic of the `\Fq[X]`-field `K` can also be referred to as its *function - ring-characteristic*. + ring-characteristic*. Finally, `K` is just refered to as the + codomain base. Classical references on Drinfeld modules include [Gos1998]_, [Rosen2002]_, [VS06]_ and [Gek1998]_. @@ -1075,7 +1077,7 @@ def j_invariant(self): Assume the rank is two. Write the generator `\phi_X = \omega + g\tau + \Delta\tau^2`. The j-invariant is defined by - `\frac{g^{q+1}}{\Delta}`, `q` being the order of the base field + `\frac{g^{q+1}}{\Delta}`, `q` being the order of the base ring of the function ring. In our case, this field is always finite. OUTPUT: an element in the base codomain diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 6d02da7dff9..3ccb00cd361 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -29,8 +29,9 @@ class FiniteDrinfeldModule(DrinfeldModule): r""" This class represents a finite Drinfeld module. - A *finite Drinfeld module* is a Drinfeld module whose base field is - finite. + A *finite Drinfeld module* is a Drinfeld module whose base, which is + a morphism, is surjective. This is equivalent to say that the base + codomain is a finite field. For general definitions and help on Drinfeld modules, see class :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. @@ -115,7 +116,7 @@ def frobenius_endomorphism(self): Return the Frobenius endomorphism of the Drinfeld module as a morphism object. - Let `q` be the order of the base field of the function ring. The + Let `q` be the order of the base ring of the function ring. The *Frobenius endomorphism* is defined as the endomorphism whose defining Ore polynomial is `t^q`. @@ -148,7 +149,7 @@ def frobenius_charpoly(self, var='T'): endomorphism, if the rank is two; raise a NotImplementedError otherwise. - Let `\Fq` be the base field of the function ring. The + Let `\Fq` be the base ring of the function ring. The *characteristic polynomial `\chi` of the Frobenius endomorphism* is defined in [Gek1991]_. An important feature of this polynomial is that it is a monic univariate polynomial with @@ -329,7 +330,7 @@ def is_ordinary(self): NotImplementedError if the rank is not two. A rank two finite Drinfeld module is *ordinary* if and only if - the function field-characteristic does not divide the Frobenius + the function ring-characteristic does not divide the Frobenius trace. A *supersingular* rank two finite Drinfeld module is a Drinfeld module that is not ordinary. @@ -368,7 +369,7 @@ def is_supersingular(self): NotImplementedError if the rank is not two. A rank two finite Drinfeld module is *supersingular* if and only - if the function field-characteristic divides the Frobenius + if the function ring-characteristic divides the Frobenius trace. An *ordinary* rank two finite Drinfeld module is a Drinfeld module that is not supersingular. diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 1aa36eba277..d80ff19643b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -29,11 +29,11 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, r""" This class represents a Drinfeld module morphism. - Let `\phi, \psi` be two Drinfeld modules with base `\Fq[X] \to K`. A - *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial - `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a \in - \Fq[X]`. In our case, this is equivalent to `f \phi_X = \psi_X f`. - An *isogeny* is a non-zero morphism. + Let `\phi, \psi` be two Drinfeld modules with base `\gamma: \Fq[X] + \to K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore + polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for + every `a \in \Fq[X]`. In our case, this is equivalent to `f \phi_X = + \psi_X f`. An *isogeny* is a non-zero morphism. To create a morphism object, the user should never explicitly instantiate `DrinfeldModuleMorphism`, but rather call the parent From 4412ad191092e9d5bab82d1ad73a669dc83d6f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Fri, 9 Sep 2022 11:29:58 +0200 Subject: [PATCH 124/392] Move 'category methods' from DrinfeldModule to DrinfeldModules Methods of DrinfeldModule that are a getter to a property of the category (e.g. base or function_ring) are now declared in the `class ParentMethods` part of the category. --- src/sage/categories/drinfeld_modules.py | 186 ++++++++++++++++++ .../drinfeld_modules/drinfeld_module.py | 164 --------------- 2 files changed, 186 insertions(+), 164 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index a3e7f8d6c9d..485372a96ab 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -470,5 +470,191 @@ def super_categories(self): # Somehow required for the class definition class ParentMethods: + def base(self): + r""" + Return the base of the Drinfeld module. + + OUTPUT: a ring morphism + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.base() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + + The base codomain can be infinite:: + + sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) + sage: sigma.base() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + Defn: X |--> X + + And it can also be the base field of the function ring:: + + sage: psi = DrinfeldModule(FqX, [Fq(1), Fq.gen()]) + sage: psi.base() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z2 of size 5^2 + Defn: X |--> 1 + + In this case the Ore polynomial ring is isomorphic to a regular + polynomial ring:: + + sage: psi.ore_polring() + Ore Polynomial Ring in t over Finite Field in z2 of size 5^2 twisted by Identity + sage: psi.ore_polring().twisting_morphism() + Identity endomorphism of Finite Field in z2 of size 5^2 + + TESTS:: + + sage: psi.ore_polring().twisting_morphism().is_identity() + True + + sage: psi.base().codomain() is psi.function_ring().base_ring() + True + + """ + return self.category().base() + + def base_ring(self): + r""" + Raise an exception. + + The base of a Drinfeld module is a ring morphism, not a ring. + + This method is implemented in CategoryObjects, of which + Parent inherits. It returns the base of the category. I + overloaded it to avoid confusion. + """ + raise TypeError('the base of a Drinfeld module is a morphism') + def characteristic(self): + r""" + Return the function ring-characteristic. + + OUTPUT: a univariate polynomial ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.characteristic() + X^2 + (4*z2 + 2)*X + 2 + sage: phi.characteristic()(phi.constant_coefficient()) + 0 + + sage: L = Frac(FqX) + sage: psi = DrinfeldModule(FqX, [L(1), 0, 0, L(1)]) + sage: psi.characteristic() + 0 + """ return self.category().characteristic() + + def function_ring(self): + r""" + Return the function ring of the Drinfeld module. + + OUTPUT: a univariate polynomial ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.function_ring() is FqX + True + """ + return self.category().function_ring() + + def constant_coefficient(self): + r""" + Return the constant coefficient of the generator. + + OUTPUT: an element in the base codomain + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.constant_coefficient() == p_root + True + + Let `\Fq[X]` be the function ring, and let `\gamma` the base of + the Drinfeld module. The constant coefficient equals + `\gamma(X)`:: + + sage: cat = phi.category() + sage: base = cat.base() + sage: base(X) == phi.constant_coefficient() + True + + Naturally, two Drinfeld modules in the same category have the + same constant coefficient:: + + sage: t = phi.ore_polring().gen() + sage: psi = cat.object(phi.constant_coefficient() + t^3) + sage: psi + Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + + Reciprocally, it is impossible to create two Drinfeld modules in + this category if they do not share the same constant + coefficient:: + + sage: rho = cat.object(phi.constant_coefficient() + 1 + t^3) + Traceback (most recent call last): + ... + ValueError: constant coefficient must be the generator of the morphism that defines the category + """ + return self.category().constant_coefficient() + + def ore_polring(self): + r""" + Return the Ore polynomial ring of the Drinfeld module. + + OUTPUT: an Ore polynomial ring + + EXAMPLES: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: ore_polring = phi.ore_polring() + sage: ore_polring + Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + + The Ore polynomial ring can also be retrieved from the category + of the Drinfeld module:: + + sage: ore_polring is phi.category().ore_polring() + True + + The generator of the Drinfeld module is in the Ore polynomial + ring:: + + sage: phi(X) in ore_polring + True + """ + return self.category().ore_polring() diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index c1e61416d1e..29de3ba191b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -707,121 +707,6 @@ def action(self): from sage.rings.function_field.drinfeld_modules.action import DrinfeldModuleAction return DrinfeldModuleAction(self) - def base(self): - r""" - Return the base of the Drinfeld module. - - OUTPUT: a ring morphism - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.base() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z12 of size 5^12 - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - - The base codomain can be infinite:: - - sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) - sage: sigma.base() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - Defn: X |--> X - - And it can also be the base field of the function ring:: - - sage: psi = DrinfeldModule(FqX, [Fq(1), Fq.gen()]) - sage: psi.base() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z2 of size 5^2 - Defn: X |--> 1 - - In this case the Ore polynomial ring is isomorphic to a regular - polynomial ring:: - - sage: psi.ore_polring() - Ore Polynomial Ring in t over Finite Field in z2 of size 5^2 twisted by Identity - sage: psi.ore_polring().twisting_morphism() - Identity endomorphism of Finite Field in z2 of size 5^2 - - TESTS:: - - sage: psi.ore_polring().twisting_morphism().is_identity() - True - - sage: psi.base().codomain() is psi.function_ring().base_ring() - True - - """ - return self._base - - def base_ring(self): - r""" - Raise an exception. - - The base of a Drinfeld module is a ring morphism, not a ring. - - This method is implemented in CategoryObjects, of which - Parent inherits. It returns the base of the category. I - overloaded it to avoid confusion. - """ - raise TypeError('the base of a Drinfeld module is a morphism') - - def constant_coefficient(self): - r""" - Return the constant coefficient of the generator. - - OUTPUT: an element in the base codomain - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.constant_coefficient() == p_root - True - - Let `\Fq[X]` be the function ring, and let `\gamma` the base of - the Drinfeld module. The constant coefficient equals - `\gamma(X)`:: - - sage: cat = phi.category() - sage: base = cat.base() - sage: base(X) == phi.constant_coefficient() - True - - Naturally, two Drinfeld modules in the same category have the - same constant coefficient:: - - sage: t = phi.ore_polring().gen() - sage: psi = cat.object(phi.constant_coefficient() + t^3) - sage: psi - Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z12 of size 5^12 - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - - Reciprocally, it is impossible to create two Drinfeld modules in - this category if they do not share the same constant - coefficient:: - - sage: rho = cat.object(phi.constant_coefficient() + 1 + t^3) - Traceback (most recent call last): - ... - ValueError: constant coefficient must be the generator of the morphism that defines the category - """ - return self.coefficient(0) - def coefficient(self, n): r""" Return the n-th coefficient of the generator. @@ -899,24 +784,6 @@ def coefficients(self, sparse=True): """ return self._gen.coefficients(sparse=sparse) - def function_ring(self): - r""" - Return the function ring of the Drinfeld module. - - OUTPUT: a univariate polynomial ring - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.function_ring() is FqX - True - """ - return self._function_ring - def gen(self): r""" Return the generator of the Drinfeld module. @@ -1159,37 +1026,6 @@ class the ``__call__`` method of this morphism:: """ return self._morphism - def ore_polring(self): - r""" - Return the Ore polynomial ring of the Drinfeld module. - - OUTPUT: an Ore polynomial ring - - EXAMPLES: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: ore_polring = phi.ore_polring() - sage: ore_polring - Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) - - The Ore polynomial ring can also be retrieved from the category - of the Drinfeld module:: - - sage: ore_polring is phi.category().ore_polring() - True - - The generator of the Drinfeld module is in the Ore polynomial - ring:: - - sage: phi(X) in ore_polring - True - """ - return self._ore_polring - def rank(self): r""" Return the rank of the Drinfeld module. From 52e6d06336bc0802624670c4a4c6f43c6e24961b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 20 Sep 2022 13:37:40 +0200 Subject: [PATCH 125/392] (minor) Change a comment in a doctstring --- .../function_field/drinfeld_modules/finite_drinfeld_module.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 3ccb00cd361..82e2d304048 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -383,8 +383,7 @@ def is_supersingular(self): sage: phi = DrinfeldModule(FqX, [1, 0, z6]) sage: phi.is_supersingular() True - sage: phi_p = phi(phi.characteristic()) - sage: phi_p # Purely inseparable + sage: phi(phi.characteristic()) # Purely inseparable z6*t^2 ALGORITHM: From d6174065977780a9fdaa8d7de4131f1c52e421c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 20 Sep 2022 13:31:59 +0200 Subject: [PATCH 126/392] Add a better error message in DrinfeldModules when the characteristic is not implemented --- src/sage/categories/drinfeld_modules.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 485372a96ab..61d0482af48 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -320,7 +320,8 @@ def characteristic(self): 0 """ if self._characteristic is None: - raise NotImplementedError + raise NotImplementedError('function ring characteristic not' \ + 'implemented in this case') return self._characteristic def constant_coefficient(self): From 1eab9ee6a594ee1f50148325cbdc23632d1ffc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 20 Sep 2022 13:33:31 +0200 Subject: [PATCH 127/392] Fix height method The height used to return always 1. This was a mistake (despite being the generic case) that is now corrected. For example, in rank two, a Drinfeld module is supersingular if and only if its height is 2. --- .../drinfeld_modules/drinfeld_module.py | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 29de3ba191b..31aff8ced91 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -306,7 +306,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): To: Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) Defn: X |--> t^2 + t + z - One can compute the rank and height (which is always `1`):: + One can compute the rank and height:: sage: phi.rank() 2 @@ -804,9 +804,23 @@ def gen(self): def height(self): r""" - Return the height of the Drinfeld module. + Return the height of the Drinfeld module if the function field + characteristic is a prime ideal; raise ValueError otherwise. - In our case, the height is always 1. + The height of a Drinfeld module is defined when the function + field characteristic is a prime ideal. In our case, this ideal + is even generated by a monic polynomial `\mathfrak{p}` in the + function field. Write `\phi_\mathfrak{p} = a_s \tau^s + \dots + + \tau^{r*\deg(\mathfrak{p})}`. The *height* of the Drinfeld + module is the well-defined positive integer `h = + \frac{s}{\deg(\mathfrak{p})}`. + + .. NOTE:: + + See [Gos1998]_, Definition 4.5.8 for the general definition. + + A rank two Drinfeld module is supersingular if and only if its + height equals its rank. OUTPUT: an integer @@ -819,8 +833,35 @@ def height(self): sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: phi.height() == 1 True + sage: phi.is_ordinary() + True + + sage: L = Frac(FqX) + sage: phi = DrinfeldModule(FqX, [L(2), L(1)]) + sage: phi.height() + Traceback (most recent call last): + ... + ValueError: height is defined for prime function field characteristic + + sage: Fq = GF(343) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi.height() + 2 + sage: phi.is_supersingular() + True + """ - return Integer(1) + try: + if self.characteristic().is_zero(): + raise ValueError('height is defined for prime ' \ + 'function field characteristic') + else: + p = self.characteristic() + return Integer((self(p).valuation()) // (p.degree())) + except NotImplementedError: + raise NotImplementedError('height not implemented in this case') def invert(self, ore_pol): r""" From bcdfb60284efc88d246625b61b72ae86773ee6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 20 Sep 2022 13:37:07 +0200 Subject: [PATCH 128/392] Fix definition of finite drinfeld module in FiniteDrinfeldModule --- .../drinfeld_modules/finite_drinfeld_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 82e2d304048..a64af067a34 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -29,9 +29,9 @@ class FiniteDrinfeldModule(DrinfeldModule): r""" This class represents a finite Drinfeld module. - A *finite Drinfeld module* is a Drinfeld module whose base, which is - a morphism, is surjective. This is equivalent to say that the base - codomain is a finite field. + A *finite Drinfeld module* is a Drinfeld module whose base codomain + is a finite field. In this case, the function field characteristic + is a prime ideal. For general definitions and help on Drinfeld modules, see class :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. From 0cdd80f5c8b694e67151b97b9eeef36e5340e086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 21 Sep 2022 12:13:15 +0200 Subject: [PATCH 129/392] (fix) Fix function_field/all.py Thank you David, see Ticket 33713, comment 125. --- src/sage/rings/function_field/all.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index 6904f3ef4fb..17cf87636e3 100644 --- a/src/sage/rings/function_field/all.py +++ b/src/sage/rings/function_field/all.py @@ -1 +1,2 @@ +from .constructor import FunctionField from .drinfeld_modules.all import * From 8052550f00cd9ae9e8ba0e83a5c84022d462b361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 22 Sep 2022 14:46:26 +0200 Subject: [PATCH 130/392] Remove .all import See ticket 33713, comment 247. --- src/sage/rings/function_field/all.py | 2 +- src/sage/rings/function_field/drinfeld_modules/all.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 src/sage/rings/function_field/drinfeld_modules/all.py diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index 17cf87636e3..5557bbb349f 100644 --- a/src/sage/rings/function_field/all.py +++ b/src/sage/rings/function_field/all.py @@ -1,2 +1,2 @@ from .constructor import FunctionField -from .drinfeld_modules.all import * +from .drinfeld_modules.drinfeld_module import DrinfeldModule diff --git a/src/sage/rings/function_field/drinfeld_modules/all.py b/src/sage/rings/function_field/drinfeld_modules/all.py deleted file mode 100644 index 71d56f75a35..00000000000 --- a/src/sage/rings/function_field/drinfeld_modules/all.py +++ /dev/null @@ -1,3 +0,0 @@ -from sage.misc.lazy_import import lazy_import - -lazy_import('sage.rings.function_field.drinfeld_modules.drinfeld_module', 'DrinfeldModule') From 1e158033ab0ad13a6694dac9118e9d108d258e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 27 Sep 2022 16:38:58 +0200 Subject: [PATCH 131/392] (minor) Fix typo in docstring --- .../function_field/drinfeld_modules/finite_drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index a64af067a34..db4e1052fd5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -166,7 +166,7 @@ def frobenius_charpoly(self, var='T'): with `\deg(A) \leq \frac{n}{2}` and `\deg(B) = n`. Note that the *Frobenius trace* is defined as `A(X)` and the - *Frobenius norm` is defined as `B(X)`. + *Frobenius norm* is defined as `B(X)`. INPUT: From 5fe2745a4edb559dab0fb6ba1dd72ac7a62a9806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Thu, 29 Sep 2022 10:17:59 +0200 Subject: [PATCH 132/392] Fix lint error E265 This error was due to using \#*** \#*** and not \# *** \# *** for the license preambule in .py files. --- src/sage/categories/drinfeld_modules.py | 12 ++++++------ .../function_field/drinfeld_modules/action.py | 16 ++++++++-------- .../drinfeld_modules/drinfeld_module.py | 16 ++++++++-------- .../drinfeld_modules/finite_drinfeld_module.py | 16 ++++++++-------- .../function_field/drinfeld_modules/homset.py | 16 ++++++++-------- .../function_field/drinfeld_modules/morphism.py | 16 ++++++++-------- 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 61d0482af48..f222795f947 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -10,13 +10,13 @@ - Xavier Caruso (2022-06) """ -#***************************************************************************** -# Copyright (C) 2022 Xavier Caruso -# Antoine Leudière +# ***************************************************************************** +# Copyright (C) 2022 Xavier Caruso +# Antoine Leudière # -# Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#****************************************************************************** +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +# ****************************************************************************** from sage.categories.category_types import Category_over_base from sage.categories.homsets import Homsets diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 4b348bff6e2..b3c1b8a8c5d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -9,15 +9,15 @@ - Antoine Leudière (2022-04) """ -#***************************************************************************** -# Copyright (C) 2022 Antoine Leudière +# ***************************************************************************** +# Copyright (C) 2022 Antoine Leudière # -# 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. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** from sage.categories.action import Action from sage.misc.latex import latex diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 31aff8ced91..dc3bfaa5944 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -14,15 +14,15 @@ - Xavier Caruso (2022-06) """ -#***************************************************************************** -# Copyright (C) 2022 Antoine Leudière +# ***************************************************************************** +# Copyright (C) 2022 Antoine Leudière # -# 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. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** from sage.categories.drinfeld_modules import DrinfeldModules from sage.matrix.constructor import Matrix diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index db4e1052fd5..1b51ed079ad 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -11,15 +11,15 @@ - Antoine Leudière (2022-04) """ -#***************************************************************************** -# Copyright (C) 2022 Antoine Leudière +# ***************************************************************************** +# Copyright (C) 2022 Antoine Leudière # -# 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. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 43cd91849f7..1b8f0004e1b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -9,15 +9,15 @@ - Antoine Leudière (2022-04) """ -#***************************************************************************** -# Copyright (C) 2022 Antoine Leudière +# ***************************************************************************** +# Copyright (C) 2022 Antoine Leudière # -# 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. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** from sage.categories.drinfeld_modules import DrinfeldModules from sage.categories.homset import Homset diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index d80ff19643b..c96ca7b438d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -8,15 +8,15 @@ - Antoine Leudière (2022-04) """ -#***************************************************************************** -# Copyright (C) 2022 Antoine Leudière +# ***************************************************************************** +# Copyright (C) 2022 Antoine Leudière # -# 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. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# 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. +# http://www.gnu.org/licenses/ +# ***************************************************************************** from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass from sage.misc.latex import latex From 1e0cfc8989beebeeb4353344795ef47d0df10242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 3 Oct 2022 11:28:47 +0200 Subject: [PATCH 133/392] (minor) Remove a blank line in DrinfeldModules --- src/sage/categories/drinfeld_modules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index f222795f947..7df87ad55ef 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -193,7 +193,6 @@ class DrinfeldModules(Category_over_base): ... TypeError: function ring base must be a finite field """ - def __init__(self, base, name='t'): # Check input is a ring Morphism if not isinstance(base, RingHomomorphism): From e0b22d4d2d137e3dee9413ce36088eb6f637a779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 19:11:56 +0200 Subject: [PATCH 134/392] (minor) Change Sage to SageMath in Drinfeld modules reference --- src/doc/en/reference/drinfeld_modules/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/en/reference/drinfeld_modules/index.rst b/src/doc/en/reference/drinfeld_modules/index.rst index 316f50cb830..d7485c9762e 100644 --- a/src/doc/en/reference/drinfeld_modules/index.rst +++ b/src/doc/en/reference/drinfeld_modules/index.rst @@ -1,7 +1,7 @@ Drinfeld modules ==================================== -Sage include facilities to manipulate Drinfeld modules and their morphisms. The +SageMath include facilities to manipulate Drinfeld modules and their morphisms. The main entry point is the class :class:`sage.rings.function_field.drinfeld_modules.drinfeld_module.DrinfeldModule`. From e9c99e843c73f06b53184bc21202f463affc4197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 16:44:57 +0200 Subject: [PATCH 135/392] (minor) Remove unused import --- src/sage/categories/drinfeld_modules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 7df87ad55ef..9aec38e4347 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -20,7 +20,6 @@ from sage.categories.category_types import Category_over_base from sage.categories.homsets import Homsets -from sage.misc.cachefunc import cached_method from sage.misc.functional import log from sage.misc.latex import latex from sage.rings.integer import Integer From a687972e1158c18330edf577fbed2bf484531b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 5 Oct 2022 11:12:46 +0200 Subject: [PATCH 136/392] (minor) Remove useless comment --- src/sage/categories/drinfeld_modules.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 9aec38e4347..8c20fbdaea0 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -466,7 +466,6 @@ def random_object(self, rank): def super_categories(self): return [] - # Somehow required for the class definition class ParentMethods: def base(self): From 15df1104c93a76642889d1eb57081320760bfda3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 3 Oct 2022 11:30:14 +0200 Subject: [PATCH 137/392] (fix) Address first part of ticket 33713 comment 123 Thank you David. All suggestions of comment 123 were addressed. The last one, concerning non finite Drinfeld modules in doctesting is for another commit. In particular, the `if` statement (cf. comment) was removed. --- .../drinfeld_modules/drinfeld_module.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index dc3bfaa5944..f5d3b977f21 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -60,7 +60,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): morphism `\phi: \Fq[X] \to K\{\tau\}` such that: 1. The image of `\phi` contains non-constant Ore polynomials. - 2. For every element `a` in the function ring, the constant + 2. For every element `a` in the `\Fq[X]`, the constant coefficient `\phi(a)` is `\gamma(a)`. For `a` in the function ring, `\phi(a)` is denoted `\phi_a`. @@ -202,7 +202,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): To: Finite Field in z of size 3^12 Defn: X |--> z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z - Drinfeld modules `\phi` and `\rho` have different based. That of + Drinfeld modules `\phi` and `\rho` have different bases. That of `\phi` is surjective while that of `\rho` is note:: sage: rho.category().base() @@ -427,7 +427,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. NOTE:: - In this implementation, `L` is `L`. + In this implementation, `L` is `K`. sage: action = phi.action() sage: action @@ -517,9 +517,6 @@ def __classcall_private__(cls, function_ring, gen, name='t'): category = DrinfeldModules(base, name=name) # Check gen as Ore polynomial - if ore_polring is not None and \ - ore_polring != category.ore_polring(): - raise ValueError(f'generator must lie in {category.ore_polring()}') ore_polring = category.ore_polring() # Sanity cast gen = ore_polring(gen) if gen.degree() <= 0: @@ -709,7 +706,7 @@ def action(self): def coefficient(self, n): r""" - Return the n-th coefficient of the generator. + Return the `n`-th coefficient of the generator. INPUT: @@ -1091,7 +1088,7 @@ def rank(self): sage: rho.rank() 4 """ - return self.gen().degree() + return self._gen.degree() def velu(self, isog): r""" From f55ebfe55d9d02fbdc93cce53cc746e2673df3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 3 Oct 2022 18:23:29 +0200 Subject: [PATCH 138/392] Emphasis on K infinite in DrinfeldModule docstring In the DrinfeldModule doctest, emphasis that the field K can be any extension of Fq. See ticket 33713, comment 123. --- .../drinfeld_modules/drinfeld_module.py | 123 +++++++++--------- 1 file changed, 61 insertions(+), 62 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index f5d3b977f21..f193f805a29 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -43,13 +43,14 @@ class DrinfeldModule(Parent, UniqueRepresentation): Let `\Fq[X]` be a polynomial ring with coefficients in a finite field `\Fq` and let `K` be a field. We fix a ring morphism `\gamma: \Fq[X] \to K`, which we call the *base* of the Drinfeld module. - Please note that the base is not a ring; in particular, it is - not the field `K`. We also call `K` an *`\Fq[X]`-field*. .. NOTE:: + The base is not a ring. Specifically, it is not the field `K`. We say + however that `K` is an *`\Fq[X]`-field*. + The base of the Drinfeld module is the base of the category of - the Drinfeld module. + the Drinfeld module. The monic polynomial that generates the kernel of the base is called the *`\Fq[X]`-characteristic of the `\Fq[X]`-field `K`*. @@ -66,14 +67,42 @@ class DrinfeldModule(Parent, UniqueRepresentation): For `a` in the function ring, `\phi(a)` is denoted `\phi_a`. The Drinfeld module `\phi` is uniquely determined by the image - `\phi_X` of `X`, which is an input of the class. + `\phi_X` of `X`. This serves as input of the class. + + Despite an emphasis on the finite case, the base codomain can be any + extension of the field `\Fq`:: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z, 4, 1]) + sage: phi + Drinfeld module defined by X |--> t^2 + 4*t + z over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z of size 5^12 + Defn: X |--> z + + sage: Fq = GF(49) + sage: FqX. = Fq[] + sage: K. = Frac(FqX) + sage: phi = DrinfeldModule(FqX, [z, X+1]) + sage: phi + Drinfeld module defined by X |--> (X + 1)*t + X over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 + To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 + Defn: X |--> X + + .. NOTE:: + + In the first case, the Drinfeld module is said to be *finite*. See + :class:`sage.rings.function_field.drinfeld_modules.finite_drinfeld_module`. We say that `\Fq[X]` is the *function ring of `\phi`*; *K\{\tau\}* is the *Ore polynomial ring of `\phi`*. Further, the *generator of `\phi`* is `\phi_X` and its *constant coefficient* is the constant coefficient of `\phi_X`. The `\Fq[X]`-characteristic of the `\Fq[X]`-field `K` can also be referred to as its *function - ring-characteristic*. Finally, `K` is just refered to as the + ring-characteristic*. Finally, `K` is just referred to as the codomain base. Classical references on Drinfeld modules include [Gos1998]_, @@ -98,7 +127,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. RUBRIC:: Construction - A Drinfeld module object is constructed as follows:: + A Drinfeld module object is constructed by precising the function + ring and the generator:: sage: Fq. = GF(3^2) sage: FqX. = Fq[] @@ -110,20 +140,34 @@ class DrinfeldModule(Parent, UniqueRepresentation): To: Finite Field in z of size 3^12 Defn: X |--> z - In this example, we used a list of coefficients (``[z, 1, 1]``) to - represent the generator `\phi_X = z + t + t^2`, `K = \Fq(z)`. One can - also use regular Ore polynomials:: + The above Drinfeld module is finite; it can also be infinite:: + + sage: L = Frac(FqX) + sage: psi = DrinfeldModule(FqX, [L(X), 1, X^3 + X + 1]) + sage: psi + Drinfeld module defined by X |--> (X^3 + X + 1)*t^2 + t + X over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + Defn: X |--> X + sage: phi.is_finite() + True + sage: psi.is_finite() + False + + In those examples, we used a list of coefficients (``[z, 1, 1]``) to + represent the generator `\phi_X = z + t + t^2`. One can also use + regular Ore polynomials:: sage: ore_polring = phi.ore_polring() sage: t = phi.ore_polring().gen() - sage: psi_X = z + t^3 - sage: psi = DrinfeldModule(FqX, psi_X) - sage: psi + sage: rho_X = z + t^3 + sage: rho = DrinfeldModule(FqX, rho_X) + sage: rho Drinfeld module defined by X |--> t^3 + z over base Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 To: Finite Field in z of size 3^12 Defn: X |--> z - sage: psi(X) == psi_X + sage: rho(X) == rho_X True The generator must have positive degree:: @@ -185,51 +229,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): deduced from the generator, and we omit the base in the input of the class for conciseness. - .. RUBRIC:: Possible bases - - The base does not need be surjective like in the above examples. In - the following example, the base codomain is still a degree six - extension of `\Fq`, but the base is a projection over a degree two - extension with modulus `X^3 + (z_2 + 2)X^2 + (6*z_2 + 1)X + 3z_2 + - 5`:: - - sage: p = X^2 + z2 + 2 - sage: p_root = z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z - sage: rho = DrinfeldModule(FqX, [p_root, 1, 1]) - sage: rho - Drinfeld module defined by X |--> t^2 + t + z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z - - Drinfeld modules `\phi` and `\rho` have different bases. That of - `\phi` is surjective while that of `\rho` is note:: - - sage: rho.category().base() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z^10 + 2*z^9 + z^8 + z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z - sage: phi.category().base() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z - - Note that ``phi`` and ``psi`` are *finite* Drinfeld modules, in the - sense that `K` is finite. But `K` can be infinite:: - - sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1, 1]) - sage: sigma - Drinfeld module defined by X |--> t^2 + t + X over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - Defn: X |--> X - sage: sigma.is_finite() - False - sage: phi.is_finite() - True - .. RUBRIC:: The category of Drinfeld modules Drinfeld modules have their own category (see class @@ -241,9 +240,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): To: Finite Field in z of size 3^12 Defn: X |--> z sage: phi.category() is psi.category() - True - sage: phi.category() is sigma.category() False + sage: phi.category() is rho.category() + True This category holds crucial information, like the function ring-characteristic of the base:: @@ -334,11 +333,11 @@ class DrinfeldModule(Parent, UniqueRepresentation): True sage: t^5 + 2*t^3 + 1 in Hom(phi, phi) False - sage: 1 in Hom(phi, psi) + sage: 1 in Hom(phi, rho) False sage: 1 in Hom(phi, phi) True - sage: 0 in Hom(phi, psi) + sage: 0 in Hom(phi, rho) True To create a SageMath object representing the morphism, call the From a207fdd64ed4fcd964f72e1f73d9c7e8733dba35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 3 Oct 2022 19:13:23 +0200 Subject: [PATCH 139/392] Add 100% doctest coverage Thank you Travis, see ticket 33713, comment 259. I added docstrings and doctests for the following methods: - DrinfeldModule.__classcall_private__ - DrinfeldModule.__init__ - DrinfeldModule._check_rank_two - FiniteDrinfeldModule.__init__ - DrinfeldModuleAction.__init__ - DrinfeldModuleHomset.__init__ - DrinfeldModuleMorphism.__init__ - DrinfeldModuleMorphism.__classcall_private__ - DrinfeldModules.__init__ - DrinfeldModules.Homsets - DrinfeldModules.Endsets - DrinfeldModules.super_categories --- src/sage/categories/drinfeld_modules.py | 82 +++++++++++++++++-- .../function_field/drinfeld_modules/action.py | 17 ++++ .../drinfeld_modules/drinfeld_module.py | 78 ++++++++++++++++++ .../finite_drinfeld_module.py | 26 ++++++ .../function_field/drinfeld_modules/homset.py | 39 +++++++++ .../drinfeld_modules/morphism.py | 62 ++++++++++++++ 6 files changed, 299 insertions(+), 5 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 8c20fbdaea0..89ae3e4bf2c 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -56,7 +56,7 @@ class DrinfeldModules(Category_over_base): be referred to as its *function ring-characteristic*. Finally, `K` is just refered to as the codomain base. - INPUT: the base, a ring morphism + INPUT: the base ring morphism .. RUBRIC:: Construction @@ -193,6 +193,36 @@ class DrinfeldModules(Category_over_base): TypeError: function ring base must be a finite field """ def __init__(self, base, name='t'): + r""" + Initialize `self`. + + INPUT: + + - ``base`` -- the base ring morphism + - ``name`` (default: `'t'`) -- the name of the Ore polynomial + ring generator + + TESTS:: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: ore_polring. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: cat._ore_polring is ore_polring + True + sage: base = Hom(FqX, K)(p_root) + sage: cat._base == base + True + sage: cat._function_ring is FqX + True + sage: cat._constant_coefficient == base(X) + True + sage: cat._characteristic(cat._constant_coefficient) + 0 + """ # Check input is a ring Morphism if not isinstance(base, RingHomomorphism): raise TypeError('input must be a Ring morphism') @@ -281,12 +311,44 @@ def _repr_(self): """ return f'Category of Drinfeld modules defined over base {self._base}' - # Somehow required for the class definition def Homsets(self): + r""" + Return the category of homsets. + + OUTPUT: the category of homsets + + EXAMPLE:: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: from sage.categories.homsets import Homsets + sage: cat.Homsets() is Homsets() + True + """ return Homsets() - # Somehow required for the class definition def Endsets(self): + r""" + Return the category of endsets. + + OUTPUT: the category of endsets + + EXAMPLE:: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: from sage.categories.homsets import Endsets + sage: cat.Endsets() is Endsets() + True + """ return Homsets() def characteristic(self): @@ -461,9 +523,19 @@ def random_object(self, rank): return self.object(coeffs) - # Somehow required for the class definition - @cached_method def super_categories(self): + """ + EXAMPLES:: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat.super_categories() + [] + """ return [] class ParentMethods: diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index b3c1b8a8c5d..0c85caea7bf 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -75,6 +75,23 @@ class DrinfeldModuleAction(Action): """ def __init__(self, drinfeld_module): + """ + Initialize `self`. + + INPUT: the Drinfeld module + + TESTS: + + sage: Fq. = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: action = phi.action() + sage: action._drinfeld_module is phi + True + sage: action._field is phi.base().codomain() + True + """ if not isinstance(drinfeld_module, DrinfeldModule): raise TypeError('input must be a DrinfeldModule') self._drinfeld_module = drinfeld_module diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index f193f805a29..c4992558db6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -475,6 +475,36 @@ class DrinfeldModule(Parent, UniqueRepresentation): @staticmethod def __classcall_private__(cls, function_ring, gen, name='t'): + """ + Check input validity and return a `DrinfeldModule` or + `FiniteDrinfeldModule` object accordingly. + + INPUT: + + - ``function_ring`` -- a univariate polynomial ring whose base + is a finite field + - ``gen`` -- the generator of the Drinfeld module; as a list of + coefficients or an Ore polynomial + - ``name`` (optional) -- the name of the Ore polynomial ring gen + + OUTPUT: a DrinfeldModule or FiniteDrinfeldModule + + TESTS: + + sage: from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: isinstance(phi, FiniteDrinfeldModule) + True + + sage: K = Frac(FqX) + sage: phi = DrinfeldModule(FqX, [K(X), 1]) + sage: isinstance(psi, FiniteDrinfeldModule) + False + """ # FIXME: function_ring must be checked before calling base_ring # on it. But then it is checked twice: firstly here, secondly in @@ -528,6 +558,40 @@ def __classcall_private__(cls, function_ring, gen, name='t'): return cls.__classcall__(cls, gen, category) def __init__(self, gen, category): + """ + Initialize `self`. + + Validity of the input is checked in `__classcall_private__`. The + `__init__` just saves attributes. + + INPUT: + + - ``function_ring`` -- a univariate polynomial ring whose base + is a finite field + - ``gen`` -- the generator of the Drinfeld module; as a list of + coefficients or an Ore polynomial + - ``name`` (optional) -- the name of the Ore polynomial ring gen + + TESTS: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: gen = [p_root, z12^3, z12^5] + sage: phi = DrinfeldModule(FqX, gen) + sage: ore_polring = phi.ore_polring() + sage: phi._base == phi.category().base() + True + sage: phi._function_ring == FqX + True + sage: phi._gen == ore_polring(gen) + True + sage: phi._ore_polring == ore_polring + True + sage: phi._morphism == Hom(FqX, ore_polring)(phi._gen) + True + """ self._base = category.base() self._function_ring = category.function_ring() self._gen = gen @@ -614,6 +678,20 @@ def _Hom_(self, other, category): def _check_rank_two(self): r""" Raise ``NotImplementedError`` if the rank is not two. + + TESTS: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi._check_rank_two() + sage: phi = DrinfeldModule(FqX, [p_root, 1]) + sage: phi._check_rank_two() + Traceback (most recent call last): + ... + NotImplementedError: rank must be 2 """ if self.rank() != 2: raise NotImplementedError('rank must be 2') diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 1b51ed079ad..1044fc4a618 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -102,6 +102,32 @@ class FiniteDrinfeldModule(DrinfeldModule): """ def __init__(self, gen, category): + """ + Initialize `self`. + + Validity of the input is checked in `__classcall_private__`. The + `__init__` just saves attributes. + + INPUT: + + - ``function_ring`` -- a univariate polynomial ring whose base + is a finite field + - ``gen`` -- the generator of the Drinfeld module; as a list of + coefficients or an Ore polynomial + - ``name`` (optional) -- the name of the Ore polynomial ring gen + + TESTS: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: gen = [p_root, z12^3, z12^5] + sage: phi = DrinfeldModule(FqX, gen) + sage: ore_polring = phi.ore_polring() + sage: phi._gen == ore_polring(gen) + True + """ # NOTE: There used to be no __init__ here (which was fine). I # added one to ensure that FiniteDrinfeldModule would always diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 1b8f0004e1b..51b69fab180 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -59,6 +59,21 @@ class DrinfeldModuleHomset(Homset): sage: end is Hom(phi, phi) True + The domain and codomain must have the same Drinfeld modules + category:: + + sage: rho = DrinfeldModule(FqX, [Frac(FqX)(X), 1]) + sage: Hom(phi, rho) + Traceback (most recent call last): + ... + ValueError: Drinfeld modules must be in the same category + + sage: sigma = DrinfeldModule(FqX, [1, z6, 2]) + sage: Hom(phi, sigma) + Traceback (most recent call last): + ... + ValueError: Drinfeld modules must be in the same category + One can create morphism objects by calling the homset:: sage: t = phi.ore_polring().gen() @@ -115,6 +130,30 @@ class DrinfeldModuleHomset(Homset): __contains__ = Parent.__contains__ def __init__(self, X, Y, category=None, check=True): + """ + Initialize `self`. + + INPUT: + + - ``X`` -- the domain of the homset + - ``Y`` -- the codomain of the homset + - ``category`` (default: None) -- the Drinfeld modules category of + the domain and codomain + - ``check`` (default: True) -- check the validity of the category + + TESTS:: + + sage: Fq = GF(27) + sage: FqX. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) + sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: hom = Hom(phi, psi) + sage: hom.domain() is phi + True + sage: hom.codomain() is psi + True + """ if category is None: category = X.category() if check: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index c96ca7b438d..4f8fd2fa1e7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -54,6 +54,13 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, To (gen): (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + The input Ore polynomial must indeed define a morphism:: + + sage: morphism = Hom(phi, psi)(1) + Traceback (most recent call last): + ... + ValueError: Ore polynomial does not define a morphism + We can get basic data on the morphism:: sage: morphism.domain() @@ -115,6 +122,36 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, @staticmethod def __classcall_private__(cls, parent, x): + """ + Create the morphism. + + INPUT: + + - ``cls`` -- DrinfeldModuleMorphism + - ``parent`` -- The Drinfeld module homset + - ``x`` -- the Ore polynomial defining the morphism or a + DrinfeldModuleMorphism + + OUTPUT: the morphism object + + TESTS:: + + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_polring().gen() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism is Hom(phi, psi)(morphism) + True + + sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism + sage: morphism = DrinfeldModuleMorphism(Sets(), t + 1) + Traceback (most recent call last): + ... + TypeError: parent should be a DrinfeldModuleHomset + """ from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset if not isinstance(parent, DrinfeldModuleHomset): raise TypeError('parent should be a DrinfeldModuleHomset') @@ -132,6 +169,31 @@ def __classcall_private__(cls, parent, x): return cls.__classcall__(cls, parent, ore_pol) def __init__(self, parent, ore_pol): + r""" + Initialize `self`. + + INPUT: + + - ``parent`` -- The Drinfeld module homset + - ``ore_pol`` -- The Ore polynomial that defines the morphism + + TESTS:: + + sage: Fq = GF(2) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) + sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_polring().gen() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism._domain is phi + True + sage: morphism._codomain is psi + True + sage: morphism._ore_polynomial == t + z6^5 + z6^2 + 1 + True + """ + super().__init__(parent) self._domain = parent.domain() self._codomain = parent.codomain() From e042ae219c56574cef4ac7a9deb663fc8ba270b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 11:11:13 +0200 Subject: [PATCH 140/392] State that the code is for Drinfeld Fq[X]-modules in DrinfeldModule docstring --- .../function_field/drinfeld_modules/drinfeld_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index c4992558db6..592a3f9c4c4 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -38,7 +38,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): r""" - This class represents a Drinfeld module. + This class represents a Drinfeld `\Fq[X]`-module. Let `\Fq[X]` be a polynomial ring with coefficients in a finite field `\Fq` and let `K` be a field. We fix a ring morphism `\gamma: @@ -66,8 +66,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): For `a` in the function ring, `\phi(a)` is denoted `\phi_a`. - The Drinfeld module `\phi` is uniquely determined by the image - `\phi_X` of `X`. This serves as input of the class. + The Drinfeld `\Fq[X]`-module `\phi` is uniquely determined by the + image `\phi_X` of `X`. This serves as input of the class. Despite an emphasis on the finite case, the base codomain can be any extension of the field `\Fq`:: From 89fa35188ec3a89ac332ac7f7a27f8bb774c6f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 13:58:59 +0200 Subject: [PATCH 141/392] (fix) Change a NotImplementedError to ValueError in DrinfeldModuleHomset --- src/sage/rings/function_field/drinfeld_modules/homset.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 51b69fab180..7eed56a5326 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -159,10 +159,10 @@ def __init__(self, X, Y, category=None, check=True): if check: if X.category() != Y.category() \ or not isinstance(X.category(), DrinfeldModules): - raise NotImplementedError('Drinfeld modules must be in the ' - 'same category') + raise ValueError('Drinfeld modules must be in the same ' + 'category') if category != X.category(): - raise NotImplementedError('category should be DrinfeldModules') + raise ValueError('category should be DrinfeldModules') base = category.base() super().__init__(X, Y, category=category, base=base, check=check) From e0846a39cfddc8abe13912c692d5a3b674c04132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 14:03:16 +0200 Subject: [PATCH 142/392] (fix) In INPUT docstring fields, change `default` to `optional: ...` --- src/sage/rings/function_field/drinfeld_modules/action.py | 2 +- .../function_field/drinfeld_modules/drinfeld_module.py | 8 +++++--- .../drinfeld_modules/finite_drinfeld_module.py | 7 +++---- .../rings/function_field/drinfeld_modules/morphism.py | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 0c85caea7bf..911fd11a26c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -40,7 +40,7 @@ class DrinfeldModuleAction(Action): The action is instantiated as follows. Note that the user should never explicitly instantiate the class `DrinfeldModuleAction`:: - INPUT: a Drinfeld module + INPUT: the Drinfeld module EXAMPLES: diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 592a3f9c4c4..fe8dbb0b5a7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -123,7 +123,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): finite field - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` (optional) -- the name of the Ore polynomial ring gen + - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring + generator .. RUBRIC:: Construction @@ -485,7 +486,8 @@ def __classcall_private__(cls, function_ring, gen, name='t'): is a finite field - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` (optional) -- the name of the Ore polynomial ring gen + - ``name`` (default: `'t'`) -- the name of the Ore polynomial + ring gen OUTPUT: a DrinfeldModule or FiniteDrinfeldModule @@ -570,7 +572,7 @@ def __init__(self, gen, category): is a finite field - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` (optional) -- the name of the Ore polynomial ring gen + - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring gen TESTS: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 1044fc4a618..1ef152573d1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -114,7 +114,8 @@ def __init__(self, gen, category): is a finite field - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` (optional) -- the name of the Ore polynomial ring gen + - ``name`` (default: `'t'`) -- the name of the Ore polynomial + ring gen TESTS: @@ -194,9 +195,7 @@ def frobenius_charpoly(self, var='T'): Note that the *Frobenius trace* is defined as `A(X)` and the *Frobenius norm* is defined as `B(X)`. - INPUT: - - - ``var`` -- (optional) the name of the second variable + INPUT: (default: `'T'`) the name of the second variable OUTPUT: an univariate polynomial with coefficients in the function ring diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 4f8fd2fa1e7..d0acab5b98a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -128,7 +128,7 @@ def __classcall_private__(cls, parent, x): INPUT: - ``cls`` -- DrinfeldModuleMorphism - - ``parent`` -- The Drinfeld module homset + - ``parent`` -- the Drinfeld module homset - ``x`` -- the Ore polynomial defining the morphism or a DrinfeldModuleMorphism @@ -174,8 +174,8 @@ def __init__(self, parent, ore_pol): INPUT: - - ``parent`` -- The Drinfeld module homset - - ``ore_pol`` -- The Ore polynomial that defines the morphism + - ``parent`` -- the Drinfeld module homset + - ``ore_pol`` -- the Ore polynomial that defines the morphism TESTS:: From f71aff4637d5e3546bfe529c72d76d25c6ea88ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 15:29:39 +0200 Subject: [PATCH 143/392] (fix) Move base_ring from DrinfeldModules.ParentMethods to DrinfeldModule This is because calling `phi.base_ring()` used to call the `base_ring` method of `CategoryObject` and not the one defined in `DrinfeldModules.ParentMethod`. This works, but any better solution would be welcome. --- src/sage/categories/drinfeld_modules.py | 22 ++++++++--------- .../drinfeld_modules/drinfeld_module.py | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 89ae3e4bf2c..c6f5ace355d 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -596,18 +596,6 @@ def base(self): """ return self.category().base() - def base_ring(self): - r""" - Raise an exception. - - The base of a Drinfeld module is a ring morphism, not a ring. - - This method is implemented in CategoryObjects, of which - Parent inherits. It returns the base of the category. I - overloaded it to avoid confusion. - """ - raise TypeError('the base of a Drinfeld module is a morphism') - def characteristic(self): r""" Return the function ring-characteristic. @@ -728,3 +716,13 @@ def ore_polring(self): True """ return self.category().ore_polring() + + # FIXME + # The parent method `base_ring` is defined not here, as it + # should be, but in `DrinfeldModule`. + # + # This is because calling `phi.base_ring()` calls the + # `base_ring` method of `CategoryObject` and not the one defined + # here. + # + # This works, but any better solution would be welcome. diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index fe8dbb0b5a7..74a6444c6ef 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -602,6 +602,30 @@ def __init__(self, gen, category): self._Fq = self._function_ring.base_ring() # Must be last super().__init__(base=self._base, category=category) + def base_ring(self): + r""" + Raise an exception. + + The base of a Drinfeld module is a ring morphism, not a ring. + + This method is implemented in CategoryObjects, of which + Parent inherits. It returns the base of the category. I + overloaded it to avoid confusion. + + TESTS:: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.base_ring() + Traceback (most recent call last): + ... + AttributeError: the base of a Drinfeld module is a morphism + """ + raise AttributeError('the base of a Drinfeld module is a morphism') + def __call__(self, a): r""" Return the image of ``a`` by the morphism that defines the From bf5a4a44e64ffb48a7d24fe487a7bd36dcf9b82d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 16:45:47 +0200 Subject: [PATCH 144/392] (fix) Replace TESTS: by TESTS:: and EXAMPLES: by EXAMPLES:: This was the cause for `make doc` errors. --- src/sage/categories/drinfeld_modules.py | 32 +++++++-------- .../function_field/drinfeld_modules/action.py | 12 +++--- .../drinfeld_modules/drinfeld_module.py | 40 +++++++++---------- .../finite_drinfeld_module.py | 14 +++---- .../function_field/drinfeld_modules/homset.py | 10 ++--- .../drinfeld_modules/morphism.py | 14 ++++--- 6 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index c6f5ace355d..4212e6b063c 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -159,7 +159,7 @@ class DrinfeldModules(Category_over_base): sage: rho.category() is cat True - TESTS: + TESTS:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -270,7 +270,7 @@ def _latex_(self): OUTPUT: a string - EXAMPLE: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -295,7 +295,7 @@ def _repr_(self): OUTPUT: a string - EXAMPLE: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -317,7 +317,7 @@ def Homsets(self): OUTPUT: the category of homsets - EXAMPLE:: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -337,7 +337,7 @@ def Endsets(self): OUTPUT: the category of endsets - EXAMPLE:: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -357,7 +357,7 @@ def characteristic(self): OUTPUT: `0` or a monic prime polynomial in the function ring - EXAMPLES: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -390,7 +390,7 @@ def constant_coefficient(self): OUTPUT: an element in the base codomain - EXAMPLES: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -411,7 +411,7 @@ def function_ring(self): OUTPUT: a univariate polynomial ring - EXAMPLES: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -436,7 +436,7 @@ def object(self, gen): OUTPUT: a Drinfeld module in the category - EXAMPLES: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -470,7 +470,7 @@ def ore_polring(self): OUTPUT: an Ore polynomial ring - EXAMPLES: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -494,7 +494,7 @@ def random_object(self, rank): OUTPUT: a Drinfeld module in the category - EXAMPLES: + EXAMPLES:: sage: Fq = GF(11) sage: FqX. = Fq[] @@ -546,7 +546,7 @@ def base(self): OUTPUT: a ring morphism - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -602,7 +602,7 @@ def characteristic(self): OUTPUT: a univariate polynomial ring - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -627,7 +627,7 @@ def function_ring(self): OUTPUT: a univariate polynomial ring - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -645,7 +645,7 @@ def constant_coefficient(self): OUTPUT: an element in the base codomain - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -692,7 +692,7 @@ def ore_polring(self): OUTPUT: an Ore polynomial ring - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 911fd11a26c..558c102b244 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -42,7 +42,7 @@ class DrinfeldModuleAction(Action): INPUT: the Drinfeld module - EXAMPLES: + EXAMPLES:: sage: Fq. = GF(11) sage: FqX. = Fq[] @@ -80,7 +80,7 @@ def __init__(self, drinfeld_module): INPUT: the Drinfeld module - TESTS: + TESTS:: sage: Fq. = GF(11) sage: FqX. = Fq[] @@ -109,7 +109,7 @@ def _act_(self, pol, x): OUTPUT: an element in the base codomain - EXAMPLES: + EXAMPLES:: sage: Fq. = GF(11) sage: FqX. = Fq[] @@ -137,7 +137,7 @@ def _latex_(self): OUTPUT: a string - EXAMPLES: + EXAMPLES:: sage: Fq. = GF(11) sage: FqX. = Fq[] @@ -162,7 +162,7 @@ def _repr_(self): OUTPUT: a string - EXAMPLES: + EXAMPLES:: sage: Fq. = GF(11) sage: FqX. = Fq[] @@ -184,7 +184,7 @@ def drinfeld_module(self): OUTPUT: a Drinfeld module - EXAMPLES: + EXAMPLES:: sage: Fq. = GF(11) sage: FqX. = Fq[] diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 74a6444c6ef..d83acc3d73a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -458,7 +458,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.invert(phi(a)) == a True - TESTS: + TESTS:: sage: Fq = K = GF(2) sage: FqX. = Fq[] @@ -491,7 +491,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): OUTPUT: a DrinfeldModule or FiniteDrinfeldModule - TESTS: + TESTS:: sage: from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule sage: Fq = GF(25) @@ -574,7 +574,7 @@ def __init__(self, gen, category): coefficients or an Ore polynomial - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring gen - TESTS: + TESTS:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -638,7 +638,7 @@ def __call__(self, a): OUTPUT: an element in the base codomain - TESTS: + TESTS:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -681,7 +681,7 @@ def _Hom_(self, other, category): OUTPUT: an homset - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -704,8 +704,8 @@ def _Hom_(self, other, category): def _check_rank_two(self): r""" Raise ``NotImplementedError`` if the rank is not two. - - TESTS: + + TESTS:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -728,7 +728,7 @@ def _latex_(self): OUTPUT: a string - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -754,7 +754,7 @@ def _repr_(self): OUTPUT: a string - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -779,7 +779,7 @@ def action(self): OUTPUT: a Drinfeld module action object - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -817,7 +817,7 @@ def coefficient(self, n): OUTPUT: an element in the base codomain - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -856,7 +856,7 @@ def coefficients(self, sparse=True): OUTPUT: a list of elements in the base codomain - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -890,7 +890,7 @@ def gen(self): OUTPUT: an Ore polynomial - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -924,7 +924,7 @@ def height(self): OUTPUT: an integer - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -976,7 +976,7 @@ def invert(self, ore_pol): OUTPUT: a function ring element - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -1061,7 +1061,7 @@ def is_finite(self): OUTPUT: a boolean - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -1090,7 +1090,7 @@ def j_invariant(self): OUTPUT: an element in the base codomain - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -1127,7 +1127,7 @@ def morphism(self): OUTPUT: a ring morphism from the function ring to the Ore polynomial ring - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -1175,7 +1175,7 @@ def rank(self): OUTPUT: an integer - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -1225,7 +1225,7 @@ def velu(self, isog): Another possible algorithm is to recursively solve a system, see :arxiv:`2203.06970`, eq. 1.1. - EXAMPLES: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 1ef152573d1..1cfd2af886f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -117,7 +117,7 @@ def __init__(self, gen, category): - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring gen - TESTS: + TESTS:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -149,7 +149,7 @@ def frobenius_endomorphism(self): OUTPUT: a Drinfeld module morphism - EXAMPLES: + EXAMPLES:: sage: Fq = GF(343) sage: FqX. = Fq[] @@ -200,7 +200,7 @@ def frobenius_charpoly(self, var='T'): OUTPUT: an univariate polynomial with coefficients in the function ring - EXAMPLES: + EXAMPLES:: sage: Fq = GF(343) sage: FqX. = Fq[] @@ -260,7 +260,7 @@ def frobenius_norm(self): OUTPUT: an element in the function ring - EXAMPLES: + EXAMPLES:: sage: Fq = GF(343) sage: FqX. = Fq[] @@ -323,7 +323,7 @@ def frobenius_trace(self): :meth:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule.invert`, see its docstring for details. - EXAMPLES: + EXAMPLES:: sage: Fq = GF(343) sage: FqX. = Fq[] @@ -364,7 +364,7 @@ def is_ordinary(self): OUTPUT: a boolean - EXAMPLES: + EXAMPLES:: sage: Fq = GF(343) sage: FqX. = Fq[] @@ -400,7 +400,7 @@ def is_supersingular(self): OUTPUT: a boolean - EXAMPLES: + EXAMPLES:: sage: Fq = GF(343) sage: FqX. = Fq[] diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 7eed56a5326..dc3484c759a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -36,7 +36,7 @@ class DrinfeldModuleHomset(Homset): - ``X`` -- the domain - ``Y`` -- the codomain - EXAMPLES: + EXAMPLES:: sage: Fq = GF(27) sage: FqX. = Fq[] @@ -172,7 +172,7 @@ def _latex_(self): OUTPUT: a string - EXAMPLES: + EXAMPLES:: sage: Fq = GF(27) sage: FqX. = Fq[] @@ -194,7 +194,7 @@ def _repr_(self): OUTPUT: a string - EXAMPLES: + EXAMPLES:: sage: Fq = GF(27) sage: FqX. = Fq[] @@ -219,7 +219,7 @@ def __contains__(self, x): OUTPUT: a boolean - EXAMPLES: + EXAMPLES:: In the next examples, the input is an Ore polynomial:: @@ -274,7 +274,7 @@ def _element_constructor_(self, *args, **kwds): OUTPUT: a Drinfeld module morphism - EXAMPLES: + EXAMPLES:: sage: Fq = GF(27) sage: FqX. = Fq[] diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index d0acab5b98a..bf511bc7df1 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -205,7 +205,8 @@ def _latex_(self): OUTPUT: a string - EXAMPLES: + EXAMPLES:: + sage: Fq = GF(2) sage: FqX. = Fq[] sage: K. = Fq.extension(6) @@ -237,7 +238,8 @@ def _repr_(self): OUTPUT: a string - EXAMPLES: + EXAMPLES:: + sage: Fq = GF(2) sage: FqX. = Fq[] sage: K. = Fq.extension(6) @@ -260,7 +262,7 @@ def is_zero(self): r""" Return ``True`` whether the morphism is the zero morphism. - EXAMPLES: + EXAMPLES:: sage: Fq = GF(2) sage: FqX. = Fq[] @@ -282,7 +284,7 @@ def is_isogeny(self): r""" Return ``True`` whether the morphism is an isogeny. - EXAMPLES: + EXAMPLES:: sage: Fq = GF(2) sage: FqX. = Fq[] @@ -312,7 +314,7 @@ def is_isomorphism(self): r""" Return ``True`` whether the morphism is an isomorphism. - EXAMPLES: + EXAMPLES:: sage: Fq = GF(2) sage: FqX. = Fq[] @@ -342,7 +344,7 @@ def ore_polynomial(self): r""" Return the Ore polynomial that defines the morphism. - EXAMPLES: + EXAMPLES:: sage: Fq = GF(2) sage: FqX. = Fq[] From c821b208b0840b3d83407e8b2332fe4ff63daee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 18:10:55 +0200 Subject: [PATCH 145/392] (fix) Fix various small issues in doc and `make doc` (see description) Fixes: - fix some broken references; - change some `:` to `::`; - change some ` to ``; - change `\Fq` to `\mathbb{F}_q`; - remove some italics (`*...*`) in inline definitions as Mathjax in italics is not rendered properly. --- src/sage/categories/drinfeld_modules.py | 42 ++--- .../function_field/drinfeld_modules/action.py | 13 +- .../drinfeld_modules/drinfeld_module.py | 151 +++++++++--------- .../finite_drinfeld_module.py | 46 +++--- .../function_field/drinfeld_modules/homset.py | 6 +- .../drinfeld_modules/morphism.py | 12 +- 6 files changed, 140 insertions(+), 130 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 4212e6b063c..7feba60d0b0 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -33,28 +33,30 @@ class DrinfeldModules(Category_over_base): This class represents the category of Drinfeld modules on a given base. - Let `\Fq[X]` be a polynomial ring with coefficients in a finite - field `\Fq` and let `K` be a field. We fix a ring morphism `\gamma: - \Fq[X] \to K`, which we call the *base* of the category (it is the - base of the Drinfeld modules in the category). - Please note that the base is not a ring; in particular, it is - not the field `K`. We also call `K` an *`\Fq[X]`-field*. + Let `\mathbb{F}_q[X]` be a polynomial ring with coefficients in a + finite field `\mathbb{F}_q` and let `K` be a field. We fix a ring + morphism `\gamma: \mathbb{F}_q[X] \to K`, which we call the *base* + of the category (it is the base of the Drinfeld modules in the + category). Please note that the base is not a ring; in particular, + it is not the field `K`. We also call `K` an + `\mathbb{F}_q[X]`-field. The category is uniquely defined by its base. The monic polynomial that generates the kernel of the base is called - the *`\Fq[X]`-characteristic of the `\Fq[X]`-field `K`*. + the `\mathbb{F}_q[X]`-characteristic of the `\mathbb{F}_q[X]`-field + `K`. .. NOTE:: These notations will be used throughout this documentation. - We say that `\Fq[X]` is the *function ring of the category*; - *K\{\tau\}* is the *Ore polynomial ring of the category*. The - *constant coefficient of the category* is the image of `X` under the - base. The `\Fq[X]`-characteristic of the `\Fq[X]`-field `K` can also - be referred to as its *function ring-characteristic*. Finally, `K` - is just refered to as the codomain base. + We say that `\mathbb{F}_q[X]` is the function ring of the category; + `K\{\tau\}` is the re polynomial ring of the category. The constant + coefficient of the category is the image of `X` under the base. The + `\mathbb{F}_q[X]`-characteristic of the `\mathbb{F}_q[X]`-field `K` + can also be referred to as its function ring-characteristic. + Finally, `K` is just refered to as the codomain base. INPUT: the base ring morphism @@ -90,7 +92,7 @@ class DrinfeldModules(Category_over_base): To: Finite Field in z of size 11^4 Defn: X |--> z^3 + 7*z^2 + 6*z + 10 - The so-called *constant coefficient* --- which is the same for all + The so-called constant coefficient --- which is the same for all Drinfeld modules in the category --- is simply the image of `X` by this morphism: @@ -99,9 +101,9 @@ class DrinfeldModules(Category_over_base): sage: cat.base()(X) == cat.constant_coefficient() True - Similarly, the *function ring-characteristic* of the category is either - `0` or the unique monic polynomial in `\Fq[X]` that generates - the kernel of the base:: + Similarly, the function ring-characteristic of the category is + either `0` or the unique monic polynomial in `\mathbb{F}_q[X]` that + generates the kernel of the base:: sage: cat.characteristic() X^2 + 7*X + 2 @@ -655,9 +657,9 @@ def constant_coefficient(self): sage: phi.constant_coefficient() == p_root True - Let `\Fq[X]` be the function ring, and let `\gamma` the base of - the Drinfeld module. The constant coefficient equals - `\gamma(X)`:: + Let `\mathbb{F}_q[X]` be the function ring, and let `\gamma` + the base of the Drinfeld module. The constant coefficient + equals `\gamma(X)`:: sage: cat = phi.category() sage: base = cat.base() diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 558c102b244..096b5428059 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -29,16 +29,17 @@ class DrinfeldModuleAction(Action): This class represents the module action induced by a Drinfeld module. - Let `\phi` be a Drinfeld module with base `\gamma: \Fq[X] \to K`. - Let `L/K` be a field extension, let `x \in L`, let `a` be a function - ring element; the action is defined as `(a, x) \mapsto \phi_a(x)`. + Let `\phi` be a Drinfeld module with base `\gamma: \mathbb{F}_q[X] + \to K`. Let `L/K` be a field extension, let `x \in L`, let `a` be a + function ring element; the action is defined as `(a, x) \mapsto + \phi_a(x)`. .. NOTE:: In this implementation, `L` is `K`. - The action is instantiated as follows. Note that the user should - never explicitly instantiate the class `DrinfeldModuleAction`:: + The user should never explicitly instantiate the class + `DrinfeldModuleAction`. INPUT: the Drinfeld module @@ -76,7 +77,7 @@ class DrinfeldModuleAction(Action): def __init__(self, drinfeld_module): """ - Initialize `self`. + Initialize ``self``. INPUT: the Drinfeld module diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index d83acc3d73a..b63b5aaef7c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -4,7 +4,7 @@ This module provides the class :class:`sage.rings.function_field.drinfeld_module.drinfeld_module.DrinfeldModule`. -For *finite* Drinfeld modules and their theory of complex multiplication, see +For finite Drinfeld modules and their theory of complex multiplication, see class :class:`sage.rings.function_field.drinfeld_module.finite_drinfeld_module.DrinfeldModule`. @@ -38,39 +38,42 @@ class DrinfeldModule(Parent, UniqueRepresentation): r""" - This class represents a Drinfeld `\Fq[X]`-module. + This class represents a Drinfeld `\mathbb{F}_q[X]`-module. - Let `\Fq[X]` be a polynomial ring with coefficients in a finite - field `\Fq` and let `K` be a field. We fix a ring morphism `\gamma: - \Fq[X] \to K`, which we call the *base* of the Drinfeld module. + Let `\mathbb{F}_q[X]` be a polynomial ring with coefficients in a + finite field `\mathbb{F}_q` and let `K` be a field. We fix a ring + morphism `\gamma: \mathbb{F}_q[X] \to K`, which we call the base of + the Drinfeld module. .. NOTE:: - The base is not a ring. Specifically, it is not the field `K`. We say - however that `K` is an *`\Fq[X]`-field*. + The base is not a ring. Specifically, it is not the field `K`. + We say however that `K` is an `\mathbb{F}_q[X]`-field. The base of the Drinfeld module is the base of the category of the Drinfeld module. The monic polynomial that generates the kernel of the base is called - the *`\Fq[X]`-characteristic of the `\Fq[X]`-field `K`*. + the `\mathbb{F}_q[X]`-characteristic of the `\mathbb{F}_q[X]`-field + `K`. Let `K\{\tau\}` be the ring of Ore polynomials with coefficients in - `K` and Frobenius variable `\tau: x \mapsto x^q`. A *Drinfeld - `\Fq[X]`-module over the base `\gamma`* is an `\Fq`-algebra - morphism `\phi: \Fq[X] \to K\{\tau\}` such that: + `K` and Frobenius variable `\tau: x \mapsto x^q`. A Drinfeld + `\mathbb{F}_q[X]`-module over the base `\gamma` is an + `\mathbb{F}_q`-algebra morphism `\phi: \mathbb{F}_q[X] \to + K\{\tau\}` such that: - 1. The image of `\phi` contains non-constant Ore polynomials. - 2. For every element `a` in the `\Fq[X]`, the constant - coefficient `\phi(a)` is `\gamma(a)`. + 1. The image of `\phi` contains non-constant Ore polynomials. + 2. For every element `a` in the `\mathbb{F}_q[X]`, the constant + coefficient `\phi(a)` is `\gamma(a)`. For `a` in the function ring, `\phi(a)` is denoted `\phi_a`. - The Drinfeld `\Fq[X]`-module `\phi` is uniquely determined by the - image `\phi_X` of `X`. This serves as input of the class. + The Drinfeld `\mathbb{F}_q[X]`-module `\phi` is uniquely determined + by the image `\phi_X` of `X`. This serves as input of the class. Despite an emphasis on the finite case, the base codomain can be any - extension of the field `\Fq`:: + extension of the field `\mathbb{F}_q`:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -94,28 +97,29 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. NOTE:: - In the first case, the Drinfeld module is said to be *finite*. See + In the first case, the Drinfeld module is said to be finite. See :class:`sage.rings.function_field.drinfeld_modules.finite_drinfeld_module`. - We say that `\Fq[X]` is the *function ring of `\phi`*; *K\{\tau\}* - is the *Ore polynomial ring of `\phi`*. Further, the *generator of - `\phi`* is `\phi_X` and its *constant coefficient* is the constant - coefficient of `\phi_X`. The `\Fq[X]`-characteristic of the - `\Fq[X]`-field `K` can also be referred to as its *function - ring-characteristic*. Finally, `K` is just referred to as the - codomain base. + We say that `\mathbb{F}_q[X]` is the function ring of `\phi`; + `K\{\tau\}` is the Ore polynomial ring of `\phi`. Further, the + generator of `\phi` is `\phi_X` and its constant coefficient is the + constant coefficient of `\phi_X`. The + `\mathbb{F}_q[X]`-characteristic of the `\mathbb{F}_q[X]`-field `K` + can also be referred to as its function ring-characteristic. + Finally, `K` is just referred to as the codomain base. Classical references on Drinfeld modules include [Gos1998]_, - [Rosen2002]_, [VS06]_ and [Gek1998]_. + [Rosen2002]_, [VS06]_ and [Gek1991]_. .. NOTE:: Drinfeld modules are defined in a larger setting, in which the - polynomial ring `\Fq[X]` is replaced by a more general function - ring: the ring of functions in `k` that are regular outside - `\infty`, where `k` is a function field over `\Fq` with - transcendence degree `1` and `\infty` is a fixed place of `k`. - This is out of the scope of this implementation. + polynomial ring `\mathbb{F}_q[X]` is replaced by a more general + function ring: the ring of functions in `k` that are regular + outside `\infty`, where `k` is a function field over + `\mathbb{F}_q` with transcendence degree `1` and `\infty` is a + fixed place of `k`. This is out of the scope of this + implementation. INPUT: @@ -123,7 +127,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): finite field - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring + - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring generator .. RUBRIC:: Construction @@ -185,8 +189,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): ... ValueError: base must be a non zero morphism - The coefficients of the generator must lie in an `\Fq[X]`-field, - where `\Fq[X]` is the function ring of the Drinfeld module:: + The coefficients of the generator must lie in an + `\mathbb{F}_q[X]`-field, where `\mathbb{F}_q[X]` is the function + ring of the Drinfeld module:: sage: DrinfeldModule(FqX, [z, QQ(1)]) Traceback (most recent call last): @@ -259,14 +264,14 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi(X) # phi_X t^2 + t + z - sage: phi(X^3 + X + 1) # phi_X^3 +X + 1 + sage: phi(X^3 + X + 1) # phi_(X^3 +X + 1) t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 sage: phi(1) # phi_1 1 This is useful to quickly retrieve the generator of the Drinfeld - module. Furthermore, a Drinfeld `\Fq[X]`-module can be seen as an - Ore polynomial with positive degree and constant coefficient + module. Furthermore, a Drinfeld `\mathbb{F}_q[X]`-module can be seen + as an Ore polynomial with positive degree and constant coefficient `\gamma(X)`, where `\gamma` is the base. This analogy is the motivation for the following methods:: @@ -320,10 +325,10 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. RUBRIC:: Morphisms, isogenies - A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore - polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for - every `a` in the function ring. In our case, this is equivalent to - `f \phi_X = \psi_X f`. An *isogeny* is a non-zero morphism. + A morphism of Drinfeld modules `\phi \to \psi` is an Ore polynomial + `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a` in + the function ring. In our case, this is equivalent to `f \phi_X = + \psi_X f`. An isogeny is a non-zero morphism. Use the ``in`` syntax to test if an Ore polynomial defines a morphism:: @@ -419,22 +424,22 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. RUBRIC:: The action of a Drinfeld module - The `\Fq[X]`-Drinfeld module `\phi` induces a special left - `\Fq[X]`-module structure on any field extension `L/K`. Let `x \in - L` and `a` be in the function ring; the action is defined as `(a, - x) \mapsto \phi_a(x)`. The method :meth:`action` returns an + The `\mathbb{F}_q[X]`-Drinfeld module `\phi` induces a special left + `\mathbb{F}_q[X]`-module structure on any field extension `L/K`. Let + `x \in L` and `a` be in the function ring; the action is defined as + `(a, x) \mapsto \phi_a(x)`. The method :meth:`action` returns an ``Action`` object representing the Drinfeld module action. .. NOTE:: - In this implementation, `L` is `K`. + In this implementation, `L` is `K`:: - sage: action = phi.action() - sage: action - Action on Finite Field in z of size 3^12 induced by Drinfeld module defined by X |--> t^2 + t + z over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z + sage: action = phi.action() + sage: action + Action on Finite Field in z of size 3^12 induced by Drinfeld module defined by X |--> t^2 + t + z over base Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 + Defn: X |--> z The action on elements is computed by calling the action object:: @@ -477,8 +482,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): @staticmethod def __classcall_private__(cls, function_ring, gen, name='t'): """ - Check input validity and return a `DrinfeldModule` or - `FiniteDrinfeldModule` object accordingly. + Check input validity and return a ``DrinfeldModule`` or + ``FiniteDrinfeldModule`` object accordingly. INPUT: @@ -486,7 +491,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): is a finite field - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` (default: `'t'`) -- the name of the Ore polynomial + - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring gen OUTPUT: a DrinfeldModule or FiniteDrinfeldModule @@ -561,10 +566,10 @@ def __classcall_private__(cls, function_ring, gen, name='t'): def __init__(self, gen, category): """ - Initialize `self`. + Initialize ``self``. - Validity of the input is checked in `__classcall_private__`. The - `__init__` just saves attributes. + Validity of the input is checked in ``__classcall_private__``. + The ``__init__`` just saves attributes. INPUT: @@ -572,7 +577,8 @@ def __init__(self, gen, category): is a finite field - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring gen + - ``name`` (default: ``'t'``) -- the name of the Ore polynomial + ring gen TESTS:: @@ -628,7 +634,7 @@ def base_ring(self): def __call__(self, a): r""" - Return the image of ``a`` by the morphism that defines the + Return the image of input ``a`` by the morphism that defines the Drinfeld module; i.e. `\phi_a` if the Drinfeld module is denoted `phi`. @@ -677,7 +683,7 @@ def _Hom_(self, other, category): - ``other`` -- the codomain of the homset - ``category`` -- the category in which we consider the - morphisms, usually `self.category()` + morphisms, usually ``self.category()`` OUTPUT: an homset @@ -911,8 +917,8 @@ def height(self): field characteristic is a prime ideal. In our case, this ideal is even generated by a monic polynomial `\mathfrak{p}` in the function field. Write `\phi_\mathfrak{p} = a_s \tau^s + \dots + - \tau^{r*\deg(\mathfrak{p})}`. The *height* of the Drinfeld - module is the well-defined positive integer `h = + \tau^{r*\deg(\mathfrak{p})}`. The height of the Drinfeld module + is the well-defined positive integer `h = \frac{s}{\deg(\mathfrak{p})}`. .. NOTE:: @@ -1143,7 +1149,7 @@ def morphism(self): sage: isinstance(phi.morphism(), RingHomomorphism) True - Actually, the ``DrinfeldModule`` method ``__call__`` simply + Actually, the ``DrinfeldModule`` method :meth:`__call__` simply class the ``__call__`` method of this morphism:: sage: phi.morphism()(X) == phi(X) @@ -1208,12 +1214,13 @@ def velu(self, isog): ALGORITHM: The input defines an isogeny if only if: - 1. The degree of the characteristic divides the height - of the input. (The height of an Ore polynomial - `P(t)` is the maximum `n` such that `t^n` right-divides - `P(t)`.) - 2. The input right-divides the generator, which can - be tested with Euclidean division. + + 1. The degree of the characteristic divides the height of + the input. (The height of an Ore polynomial `P(\tau)` is the + maximum `n` such that `\tau^n` right-divides `P(\tau)`.) + + 2. The input right-divides the generator, which can + be tested with Euclidean division. We test if the input is an isogeny, and, if it is, we return the quotient of the Euclidean division. @@ -1223,7 +1230,7 @@ def velu(self, isog): :class:`sage.rings.polynomial.ore_polynomial_element.OrePolynomial`. Another possible algorithm is to recursively solve a system, - see :arxiv:`2203.06970`, eq. 1.1. + see :arxiv:`2203.06970`, Eq. 1.1. EXAMPLES:: @@ -1251,7 +1258,7 @@ def velu(self, isog): True The following inputs do not define isogenies, and the method - returns None:: + returns ``None``:: sage: phi.velu(0) Traceback (most recent call last): diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 1cfd2af886f..2e8fa854a7e 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -176,7 +176,7 @@ def frobenius_charpoly(self, var='T'): endomorphism, if the rank is two; raise a NotImplementedError otherwise. - Let `\Fq` be the base ring of the function ring. The + Let `\mathbb{F}_q` be the base ring of the function ring. The *characteristic polynomial `\chi` of the Frobenius endomorphism* is defined in [Gek1991]_. An important feature of this polynomial is that it is a monic univariate polynomial with @@ -188,14 +188,14 @@ def frobenius_charpoly(self, var='T'): Let `\chi = T^2 - A(X)T + B(X)` be the characteristic polynomial of the Frobenius endomorphism, let `t^n` be the Ore polynomial that defines the Frobenius endomorphism of `\phi`; by - definition, `n` is the degree over `\Fq` of the base codomain. We - have `\chi(t^n)(\phi(X)) = t^{2n} - \phi_A t^n + \phi_B = 0`, - with `\deg(A) \leq \frac{n}{2}` and `\deg(B) = n`. + definition, `n` is the degree over `\mathbb{F}_q` of the base + codomain. We have `\chi(t^n)(\phi(X)) = t^{2n} - \phi_A t^n + + \phi_B = 0`, with `\deg(A) \leq \frac{n}{2}` and `\deg(B) = n`. Note that the *Frobenius trace* is defined as `A(X)` and the *Frobenius norm* is defined as `B(X)`. - INPUT: (default: `'T'`) the name of the second variable + INPUT: (default: ``'T'``) the name of the second variable OUTPUT: an univariate polynomial with coefficients in the function ring @@ -231,7 +231,7 @@ def frobenius_charpoly(self, var='T'): We compute the Frobenius norm, and with it the Frobenius trace. This gives the Frobenius characteristic polynomial. - See [SM2019]_, Section 4. + See [MS2019]_, Section 4. See docstrings of methods :meth:`frobenius_norm` and :meth:`frobenius_trace` for furthere details on the @@ -250,13 +250,13 @@ def frobenius_norm(self): Return Frobenius norm of the Drinfeld module, if the rank is two; raise a NotImplementedError otherwise. - Let `\Fq[X]` be the function ring, write `\chi = T^2 - A(X)T + - B(X) \in \Fq[X][T]` for the characteristic polynomial of the - Frobenius endomorphism. The *Frobenius norm* is defined as the - polynomial `B(X) \in \Fq[X]`. + Let `\mathbb{F}_q[X]` be the function ring, write `\chi = T^2 - + A(X)T + B(X) \in \mathbb{F}_q[X][T]` for the characteristic + polynomial of the Frobenius endomorphism. The *Frobenius norm* + is defined as the polynomial `B(X) \in \mathbb{F}_q[X]`. - Let `n` be the degree over `\Fq` of the base codomain. Then the - Frobenius norm has degree `n`. + Let `n` be the degree over `\mathbb{F}_q` of the base codomain. + Then the Frobenius norm has degree `n`. OUTPUT: an element in the function ring @@ -280,7 +280,7 @@ def frobenius_norm(self): ALGORITHM: The Frobenius norm is computed using the formula, by - Gekeler, given in [SM2019]_, Section 4, Proposition 3. + Gekeler, given in [MS2019]_, Section 4, Proposition 3. """ self._check_rank_two() L = self._base.codomain().over(self._Fq) @@ -300,13 +300,13 @@ def frobenius_trace(self): Return Frobenius norm of the Drinfeld module, if the rank is two; raise a NotImplementedError otherwise. - Let `\Fq[X]` be the function ring, write `\chi = T^2 - A(X)T + - B(X) \in \Fq[X][T]` for the characteristic polynomial of the - Frobenius endomorphism. The *Frobenius norm* is defined as the - polynomial `B(X) \in \Fq[X]`. + Let `\mathbb{F}_q[X]` be the function ring, write `\chi = T^2 - + A(X)T + B(X) \in \mathbb{F}_q[X][T]` for the characteristic + polynomial of the Frobenius endomorphism. The *Frobenius norm* + is defined as the polynomial `B(X) \in \mathbb{F}_q[X]`. - Let `n` be the degree over `\Fq` of the base codomain. Then the - Frobenius trace has degree `\leq \frac{n}{2}`. + Let `n` be the degree over `\mathbb{F}_q` of the base codomain. + Then the Frobenius trace has degree `\leq \frac{n}{2}`. OUTPUT: an element in the function ring @@ -378,12 +378,12 @@ def is_ordinary(self): ALGORITHM: - Compute the Frobenius trace and test if the `\Fq[X]` - characteristic divides it. + Compute the Frobenius trace and test if the + `\mathbb{F}_q[X]` characteristic divides it. We could also test if the image of the - `\Fq[X]`-characteristic under the Drinfeld module is purely - inseparable; see [Gek1991]_, Proposition 4.1. + `\mathbb{F}_q[X]`-characteristic under the Drinfeld module + is purely inseparable; see [Gek1991]_, Proposition 4.1. """ self._check_rank_two() return not self.is_supersingular() diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index dc3484c759a..f70bc0d9de5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -131,15 +131,15 @@ class DrinfeldModuleHomset(Homset): def __init__(self, X, Y, category=None, check=True): """ - Initialize `self`. + Initialize ``self``. INPUT: - ``X`` -- the domain of the homset - ``Y`` -- the codomain of the homset - - ``category`` (default: None) -- the Drinfeld modules category of + - ``category`` (default: ``None``) -- the Drinfeld modules category of the domain and codomain - - ``check`` (default: True) -- check the validity of the category + - ``check`` (default: ``True``) -- check the validity of the category TESTS:: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index bf511bc7df1..847ce0233bb 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -29,14 +29,14 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, r""" This class represents a Drinfeld module morphism. - Let `\phi, \psi` be two Drinfeld modules with base `\gamma: \Fq[X] + Let `\phi, \psi` be two Drinfeld modules with base `\gamma: \mathbb{F}_q[X] \to K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore - polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for - every `a \in \Fq[X]`. In our case, this is equivalent to `f \phi_X = - \psi_X f`. An *isogeny* is a non-zero morphism. + polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a + \in \mathbb{F}_q[X]`. In our case, this is equivalent to `f \phi_X = \psi_X + f`. An *isogeny* is a non-zero morphism. To create a morphism object, the user should never explicitly - instantiate `DrinfeldModuleMorphism`, but rather call the parent + instantiate :class:`DrinfeldModuleMorphism`, but rather call the parent homset with the defining Ore polynomial:: sage: Fq = GF(25) @@ -170,7 +170,7 @@ def __classcall_private__(cls, parent, x): def __init__(self, parent, ore_pol): r""" - Initialize `self`. + Initialize ``self``. INPUT: From c2691352c3322851cccefc089119cb69d0a60534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 18:42:03 +0200 Subject: [PATCH 146/392] (fix) Fix DrinfeldModule.base_ring docstring --- .../function_field/drinfeld_modules/drinfeld_module.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index b63b5aaef7c..cbcf0cc0a96 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -610,15 +610,15 @@ def __init__(self, gen, category): def base_ring(self): r""" - Raise an exception. + Raise exception ``AttributeError``. The base of a Drinfeld module is a ring morphism, not a ring. - This method is implemented in CategoryObjects, of which - Parent inherits. It returns the base of the category. I - overloaded it to avoid confusion. + This method is implemented in ``CategoryObject``, of which Parent + inherits. It returns the base of the category. I overloaded it + to avoid confusion. - TESTS:: + EXAMPLES:: sage: Fq = GF(25) sage: FqX. = Fq[] From b59d83624afeda23ed07c16115dd37f889b569b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 5 Oct 2022 10:46:57 +0200 Subject: [PATCH 147/392] (fix) Fix DrinfeldModules.Endsets It used to return Homsets(). Now it returns Homsets().Endsets(). --- src/sage/categories/drinfeld_modules.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 7feba60d0b0..01dc332fc27 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -347,11 +347,11 @@ def Endsets(self): sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() - sage: from sage.categories.homsets import Endsets - sage: cat.Endsets() is Endsets() + sage: from sage.categories.homsets import Homsets + sage: cat.Endsets() is Homsets().Endsets() True """ - return Homsets() + return Homsets().Endsets() def characteristic(self): r""" From b7638276a45bbefb066b5da8ee3d58eb37303c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 4 Oct 2022 19:06:56 +0200 Subject: [PATCH 148/392] Split some code blocks using :: Change sage: foo sage: bar to sage: foo :: sage: bar --- src/sage/categories/drinfeld_modules.py | 5 +++- .../drinfeld_modules/drinfeld_module.py | 27 +++++++++++++++-- .../finite_drinfeld_module.py | 14 +++++++++ .../function_field/drinfeld_modules/homset.py | 13 ++++++++- .../drinfeld_modules/morphism.py | 29 +++++++++++++++++-- 5 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 01dc332fc27..7f88954ef96 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -115,7 +115,6 @@ class DrinfeldModules(Category_over_base): sage: cat.base() is phi.base() True - sage: cat.function_ring() is phi.function_ring() True sage: cat.function_ring() @@ -370,6 +369,8 @@ def characteristic(self): sage: cat.characteristic() X^2 + 7*X + 2 + :: + sage: L = Frac(FqX) sage: psi = DrinfeldModule(FqX, [L.gen(), 1]) sage: psi @@ -592,6 +593,8 @@ def base(self): sage: psi.ore_polring().twisting_morphism().is_identity() True + :: + sage: psi.base().codomain() is psi.function_ring().base_ring() True diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index cbcf0cc0a96..df68ab80502 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -85,6 +85,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): To: Finite Field in z of size 5^12 Defn: X |--> z + :: + sage: Fq = GF(49) sage: FqX. = Fq[] sage: K. = Frac(FqX) @@ -198,6 +200,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): ... ValueError: function ring base must coerce into base codomain + :: + sage: DrinfeldModule(FqX, [1, QQ(1)]) Traceback (most recent call last): ... @@ -211,6 +215,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): ... NotImplementedError: function ring must be a polynomial ring + :: + sage: FqXY. = FqX[] sage: DrinfeldModule(FqXY, [z, 1, 1]) Traceback (most recent call last): @@ -278,8 +284,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.coefficients() [z, 1, 1] - sage: phi.coefficient(1) - 1 + :: + sage: phi.coefficient(1) 1 @@ -291,20 +297,30 @@ class DrinfeldModule(Parent, UniqueRepresentation): To: Finite Field in z of size 3^12 Defn: X |--> z + :: + sage: phi.ore_polring() # K{t} Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) + :: + sage: phi.function_ring() # Fq[X] Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + :: + sage: phi.gen() # phi_X t^2 + t + z sage: phi.gen() == phi(X) True + :: + sage: phi.constant_coefficient() # Constant coefficient of phi_X z + :: + sage: phi.morphism() # The Drinfeld module as a morphism Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 @@ -507,6 +523,8 @@ def __classcall_private__(cls, function_ring, gen, name='t'): sage: isinstance(phi, FiniteDrinfeldModule) True + :: + sage: K = Frac(FqX) sage: phi = DrinfeldModule(FqX, [K(X), 1]) sage: isinstance(psi, FiniteDrinfeldModule) @@ -652,12 +670,15 @@ def __call__(self, a): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + :: sage: a = X^3 + 4*X + 2 sage: phi(a) == phi(X)^3 + 4*phi(X) + 2 True sage: phi(a)[0] == p_root^3 + 4*p_root + 2 True + :: + sage: phi(0) 0 sage: phi(1) @@ -665,6 +686,8 @@ def __call__(self, a): sage: phi(X) == phi._gen True + :: + sage: a = FqX.random_element(5) sage: phi(a)[0] == phi.category().base()(a) True diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 2e8fa854a7e..0991a343b35 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -210,10 +210,14 @@ def frobenius_charpoly(self, var='T'): sage: chi T^2 + ((3*z3^2 + z3 + 4)*X + 4*z3^2 + 6*z3 + 3)*T + (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 + :: + sage: frob_pol = phi.frobenius_endomorphism().ore_polynomial() sage: chi(frob_pol, phi(X)) 0 + :: + sage: A = phi.frobenius_trace() sage: A (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 @@ -221,6 +225,8 @@ def frobenius_charpoly(self, var='T'): sage: B (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 + :: + sage: n = 2 # Degree over Fq of the base codomain sage: A.degree() <= n/2 True @@ -270,10 +276,14 @@ def frobenius_norm(self): sage: B (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 + :: + sage: n = 2 # Degree over Fq of the base codomain sage: B.degree() == n True + :: + sage: B == phi.frobenius_charpoly()[0] True @@ -333,10 +343,14 @@ def frobenius_trace(self): sage: A (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 + :: + sage: n = 2 # Degree over Fq of the base codomain sage: A.degree() <= n/2 True + :: + sage: A == -phi.frobenius_charpoly()[1] True """ diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index f70bc0d9de5..53cd6719b79 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -47,6 +47,8 @@ class DrinfeldModuleHomset(Homset): sage: hom Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 + :: + sage: from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset sage: isinstance(hom, DrinfeldModuleHomset) True @@ -68,6 +70,8 @@ class DrinfeldModuleHomset(Homset): ... ValueError: Drinfeld modules must be in the same category + :: + sage: sigma = DrinfeldModule(FqX, [1, z6, 2]) sage: Hom(phi, sigma) Traceback (most recent call last): @@ -84,6 +88,8 @@ class DrinfeldModuleHomset(Homset): To (gen): 2*t^2 + z6*t + z6 Defn: 1 + :: + sage: frobenius_endomorphism = end(t^6) sage: frobenius_endomorphism Drinfeld Module morphism: @@ -91,6 +97,8 @@ class DrinfeldModuleHomset(Homset): To (gen): 2*t^2 + z6*t + z6 Defn: t^6 + :: + sage: isogeny = hom(t + 1) sage: isogeny Drinfeld Module morphism: @@ -231,7 +239,6 @@ def __contains__(self, x): sage: hom = Hom(phi, psi) sage: end = End(phi) sage: t = phi.ore_polring().gen() - sage: 1 in hom False sage: t^6 in hom @@ -291,6 +298,8 @@ def _element_constructor_(self, *args, **kwds): To (gen): 2*t^2 + z6*t + z6 Defn: 1 + :: + sage: frobenius_endomorphism = end(t^6) sage: frobenius_endomorphism Drinfeld Module morphism: @@ -298,6 +307,8 @@ def _element_constructor_(self, *args, **kwds): To (gen): 2*t^2 + z6*t + z6 Defn: t^6 + :: + sage: isogeny = hom(t + 1) sage: isogeny Drinfeld Module morphism: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 847ce0233bb..459b2fdf7b3 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -61,7 +61,7 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, ... ValueError: Ore polynomial does not define a morphism - We can get basic data on the morphism:: + One can get basic data on the morphism:: sage: morphism.domain() Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: @@ -71,6 +71,8 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: morphism.domain() is phi True + :: + sage: morphism.codomain() Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 @@ -79,12 +81,17 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: morphism.codomain() is psi True + :: + sage: morphism.ore_polynomial() t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + + :: + sage: morphism.ore_polynomial() is ore_pol True - We can check various properties:: + One can check various properties:: sage: morphism.is_zero() False @@ -146,6 +153,8 @@ def __classcall_private__(cls, parent, x): sage: morphism is Hom(phi, psi)(morphism) True + :: + sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism sage: morphism = DrinfeldModuleMorphism(Sets(), t + 1) Traceback (most recent call last): @@ -274,6 +283,8 @@ def is_zero(self): sage: morphism.is_zero() False + :: + sage: zero_morphism = End(phi)(0) sage: zero_morphism.is_zero() True @@ -296,14 +307,20 @@ def is_isogeny(self): sage: morphism.is_isogeny() True + :: + sage: zero_morphism = End(phi)(0) sage: zero_morphism.is_isogeny() False + :: + sage: identity_morphism = End(phi)(1) sage: identity_morphism.is_isogeny() True + :: + sage: frobenius_endomorphism = phi.frobenius_endomorphism() sage: frobenius_endomorphism.is_isogeny() True @@ -326,14 +343,20 @@ def is_isomorphism(self): sage: morphism.is_isomorphism() False + :: + sage: zero_morphism = End(phi)(0) sage: zero_morphism.is_isomorphism() False + :: + sage: identity_morphism = End(phi)(1) sage: identity_morphism.is_isomorphism() True + :: + sage: frobenius_endomorphism = phi.frobenius_endomorphism() sage: frobenius_endomorphism.is_isomorphism() False @@ -357,6 +380,8 @@ def ore_polynomial(self): sage: ore_pol t + z6^5 + z6^2 + 1 + :: + sage: ore_pol * phi(X) == psi(X) * ore_pol True """ From 7d8e358cde69671bee9978ea4793fc32cfae7965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Wed, 5 Oct 2022 17:25:40 +0200 Subject: [PATCH 149/392] (fix) Fix lint check Github job See https://github.com/sagemath/sagetrac-mirror/actions/runs/3190135097/jobs/5204830911#step:5:23. --- src/sage/rings/function_field/drinfeld_modules/homset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 53cd6719b79..690d35653b8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -227,7 +227,7 @@ def __contains__(self, x): OUTPUT: a boolean - EXAMPLES:: + EXAMPLES: In the next examples, the input is an Ore polynomial:: From 26fe42659d8bed43835721f8a9d40303f4dca6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 10 Oct 2022 18:21:04 +0200 Subject: [PATCH 150/392] (fix) Adress typos from comment 264 Thanks David. I changed the first typo and the nonXYZ typos. --- src/sage/categories/drinfeld_modules.py | 8 ++++---- .../drinfeld_modules/drinfeld_module.py | 16 ++++++++-------- .../function_field/drinfeld_modules/morphism.py | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 7f88954ef96..d6c4ca17c6a 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -52,7 +52,7 @@ class DrinfeldModules(Category_over_base): These notations will be used throughout this documentation. We say that `\mathbb{F}_q[X]` is the function ring of the category; - `K\{\tau\}` is the re polynomial ring of the category. The constant + `K\{\tau\}` is the polynomial ring of the category. The constant coefficient of the category is the image of `X` under the base. The `\mathbb{F}_q[X]`-characteristic of the `\mathbb{F}_q[X]`-field `K` can also be referred to as its function ring-characteristic. @@ -170,7 +170,7 @@ class DrinfeldModules(Category_over_base): sage: cat = DrinfeldModules(base) Traceback (most recent call last): ... - ValueError: base must be a non zero morphism + ValueError: base must be a nonzero morphism sage: base = Hom(FqX, FqX)(1) sage: cat = DrinfeldModules(base) @@ -245,9 +245,9 @@ def __init__(self, base, name='t'): K = base.codomain() if not K.is_field(): raise TypeError('base codomain must be a field') - # Check base is a non zero morphism + # Check base is a nonzero morphism if base(X).is_zero(): - raise ValueError('base must be a non zero morphism') + raise ValueError('base must be a nonzero morphism') # Build K{t} d = log(Fq.cardinality(), Fq.characteristic()) tau = K.frobenius_endomorphism(d) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index df68ab80502..9b4830488e6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -63,7 +63,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): `\mathbb{F}_q`-algebra morphism `\phi: \mathbb{F}_q[X] \to K\{\tau\}` such that: - 1. The image of `\phi` contains non-constant Ore polynomials. + 1. The image of `\phi` contains nonconstant Ore polynomials. 2. For every element `a` in the `\mathbb{F}_q[X]`, the constant coefficient `\phi(a)` is `\gamma(a)`. @@ -184,12 +184,12 @@ class DrinfeldModule(Parent, UniqueRepresentation): ... ValueError: generator must have positive degree - The constant coefficient must be non zero:: + The constant coefficient must be nonzero:: sage: DrinfeldModule(FqX, [K(0), K(1)]) Traceback (most recent call last): ... - ValueError: base must be a non zero morphism + ValueError: base must be a nonzero morphism The coefficients of the generator must lie in an `\mathbb{F}_q[X]`-field, where `\mathbb{F}_q[X]` is the function @@ -344,7 +344,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): A morphism of Drinfeld modules `\phi \to \psi` is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a` in the function ring. In our case, this is equivalent to `f \phi_X = - \psi_X f`. An isogeny is a non-zero morphism. + \psi_X f`. An isogeny is a nonzero morphism. Use the ``in`` syntax to test if an Ore polynomial defines a morphism:: @@ -411,7 +411,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. RUBRIC:: The Vélu formula - Let ``ore_pol`` be a non-zero Ore polynomial. We can decide if there + Let ``ore_pol`` be a nonzero Ore polynomial. We can decide if there exists a Drinfeld module ``psi`` such that ``ore_pol`` is an isogeny from ``self`` to ``psi``. If so, we find ``psi``:: @@ -842,7 +842,7 @@ def coefficient(self, n): INPUT: - - ``n`` -- a non-negative integer + - ``n`` -- a nonnegative integer OUTPUT: an element in the base codomain @@ -877,7 +877,7 @@ def coefficients(self, sparse=True): Return the coefficients of the generator, as a list. If the flag ``sparse`` is ``True`` (default), only return the - non-zero coefficients; otherwise, return all of them. + nonzero coefficients; otherwise, return all of them. INPUT: @@ -897,7 +897,7 @@ def coefficients(self, sparse=True): z12^3, z12^5] - Careful, the method only returns the non-zero coefficients, + Careful, the method only returns the nonzero coefficients, unless otherwise specified:: sage: rho = DrinfeldModule(FqX, [p_root, 0, 0, 0, 1]) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 459b2fdf7b3..9f36cdeca13 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -33,7 +33,7 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, \to K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a \in \mathbb{F}_q[X]`. In our case, this is equivalent to `f \phi_X = \psi_X - f`. An *isogeny* is a non-zero morphism. + f`. An *isogeny* is a nonzero morphism. To create a morphism object, the user should never explicitly instantiate :class:`DrinfeldModuleMorphism`, but rather call the parent From bee20a07453c5b18ac51a36835e6d20c1e33a1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 11 Oct 2022 18:48:18 +0200 Subject: [PATCH 151/392] Add details on the base definition in DrinfeldModule doc See comments 264 and 265 for context. --- .../drinfeld_modules/drinfeld_module.py | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 9b4830488e6..efac5f6cdb5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -72,8 +72,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): The Drinfeld `\mathbb{F}_q[X]`-module `\phi` is uniquely determined by the image `\phi_X` of `X`. This serves as input of the class. - Despite an emphasis on the finite case, the base codomain can be any - extension of the field `\mathbb{F}_q`:: + A Drinfeld module is saif to be finite if the base ring codomain is + a finite field. Despite an emphasis on this case, the base codomain + can be any extension of the field `\mathbb{F}_q`:: sage: Fq = GF(25) sage: FqX. = Fq[] @@ -99,7 +100,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. NOTE:: - In the first case, the Drinfeld module is said to be finite. See + Finite Drinfeld modules are implemented in the class :class:`sage.rings.function_field.drinfeld_modules.finite_drinfeld_module`. We say that `\mathbb{F}_q[X]` is the function ring of `\phi`; @@ -147,6 +148,19 @@ class DrinfeldModule(Parent, UniqueRepresentation): To: Finite Field in z of size 3^12 Defn: X |--> z + Note that the definition of the base morphism is implicit; it is + defined as the `\mathbb{F}_q`-algebra morphism `\mathbb{F}_q[X] \to + K` which maps `X` to the constant coefficient of the generator, and + where `K` is the compositum of all the parents of the coefficients:: + + sage: phi.base().codomain() is K + True + + .. NOTE:: + + Formally, `K` is defined as `Sequence(gen).universe()`, where + `gen` is the generator of the Drinfeld module. + The above Drinfeld module is finite; it can also be infinite:: sage: L = Frac(FqX) @@ -493,6 +507,48 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi = DrinfeldModule(FqX, [K(1), 1]) sage: isinstance(phi.ore_polring(), OrePolynomialRing) True + + Test that the base morphism is correct:: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K = Frac(Fq) + sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(1)]) + sage: phi.base().codomain() is K + True + + :: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: k = Frac(Fq) + sage: kT. = k[] + sage: K = k.extension(T^3 + T + 1) + sage: phi = DrinfeldModule(FqX, [Fq.gen(), K.gen()]) + sage: phi.base().codomain() is K + True + + In particular, note that the field `K` may not be the smallest field + of definition of the coefficients:: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: k = Frac(Fq) + sage: kT. = k[] + sage: K = k.extension(T^3 + T + 1) + sage: phi = DrinfeldModule(FqX, [K(k.gen()), 1]) + sage: phi.base().codomain() is K + True + + :: + + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(Fq.gen())]) + sage: phi.base().codomain() is K + True + """ @staticmethod From 78c99e05e5b12568dcb2def70da21cfc0d8fa870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 11 Oct 2022 18:54:43 +0200 Subject: [PATCH 152/392] Enhance "base ring comment" See comment 264 for context. --- src/sage/categories/drinfeld_modules.py | 10 ---------- .../function_field/drinfeld_modules/drinfeld_module.py | 7 +++++++ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index d6c4ca17c6a..dc11284e7fa 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -721,13 +721,3 @@ def ore_polring(self): True """ return self.category().ore_polring() - - # FIXME - # The parent method `base_ring` is defined not here, as it - # should be, but in `DrinfeldModule`. - # - # This is because calling `phi.base_ring()` calls the - # `base_ring` method of `CategoryObject` and not the one defined - # here. - # - # This works, but any better solution would be welcome. diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index efac5f6cdb5..cbc1c8ac8f2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -704,6 +704,13 @@ def base_ring(self): ... AttributeError: the base of a Drinfeld module is a morphism """ + # FIXME + # This method depends on the sole category of the Drinfeld + # module. It should therefore be implemented in the + # `ParentMethods` bloc of `DrinfeldModule`. This is not possible + # as the method `base_ring` from `CategoryObject` --- of which + # `Parent` and so `DrinfeldModule inherit --- is called instead. + # Any better way would be welcome. raise AttributeError('the base of a Drinfeld module is a morphism') def __call__(self, a): From 6d7ab9c14e94bffa959e9c310ee3d16529006d77 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 18 Oct 2022 14:56:08 +0200 Subject: [PATCH 153/392] initial commit --- src/doc/en/reference/combinat/module_list.rst | 1 + src/sage/combinat/all.py | 4 + src/sage/combinat/bijectionist.py | 2720 +++++++++++++++++ 3 files changed, 2725 insertions(+) create mode 100644 src/sage/combinat/bijectionist.py diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index e42e3891d45..cee2984b1e0 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -22,6 +22,7 @@ Comprehensive Module List sage/combinat/alternating_sign_matrix sage/combinat/backtrack sage/combinat/baxter_permutations + sage/combinat/bijectionist sage/combinat/binary_recurrence_sequences sage/combinat/binary_tree sage/combinat/blob_algebra diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index e0450a1fbe3..af3af86a1a8 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -27,6 +27,7 @@ - :ref:`sage.combinat.designs.all` - :ref:`sage.combinat.posets.all` - :ref:`sage.combinat.words` +- :ref:`sage.combinat.bijectionist` Utilities --------- @@ -298,3 +299,6 @@ # Path Tableaux lazy_import('sage.combinat.path_tableaux', 'catalog', as_='path_tableaux') + +# Bijectionist +lazy_import('sage.combinat.bijectionist', 'Bijectionist') diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py new file mode 100644 index 00000000000..452a013b470 --- /dev/null +++ b/src/sage/combinat/bijectionist.py @@ -0,0 +1,2720 @@ +# -*- coding: utf-8 -*- +# pylint: disable=all + +# TODO: (high): +# +# check whether it makes sense to keep a list of solutions, and keep +# a global MILP up to date with this list + +# TODO: (medium): +# +# can we somehow tweak gurobi so that +# minimal_subdistributions_iterator considers the minimal +# subdistributions with "smallest" (e.g., in the sorting order +# defined above) elements first? + +r""" +A bijectionist's toolkit + +AUTHORS: + +- Alexander Grosz, Tobias Kietreiber, Stephan Pfannerer and Martin + Rubey (2020): Initial version + +Quick reference +=============== + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~Bijectionist.set_intertwining_relations` | Set + :meth:`~Bijectionist.set_constant_blocks` | Set + :meth:`~Bijectionist.set_statistics` | Set + :meth:`~Bijectionist.set_value_restrictions` | Set + :meth:`~Bijectionist.set_distributions` | Set + + :meth:`~Bijectionist.statistics_table` | Return + :meth:`~Bijectionist.statistics_fibers` | Return + + :meth:`~Bijectionist.constant_blocks` | Return + :meth:`~Bijectionist.solutions_iterator` | Return + :meth:`~Bijectionist.possible_values` | Return + :meth:`~Bijectionist.minimal_subdistributions_iterator` | Return + :meth:`~Bijectionist.minimal_subdistributions_blocks_iterator` | Return + +A guided tour +============= + + EXAMPLES: + + We find a statistic `s` such that + `(s, wex, fix) \sim (llis, des, adj)`:: + + sage: N = 3 + sage: As = [list(Permutations(n)) for n in range(N+1)] + sage: A = B = sum(As, []) + sage: alpha1 = lambda p: len(p.weak_excedences()) + sage: alpha2 = lambda p: len(p.fixed_points()) + sage: beta1 = lambda p: len(p.descents(final_descent=True)) if p else 0 + sage: beta2 = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: tau = Permutation.longest_increasing_subsequence_length + sage: def rotate_permutation(p): + ....: cycle = Permutation(tuple(range(1, len(p)+1))) + ....: return Permutation([cycle.inverse()(p(cycle(i))) for i in range(1, len(p)+1)]) + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((len, len), (alpha1, beta1), (alpha2, beta2)) + sage: a, b = bij.statistics_table() + sage: table(a, header_row=True, frame=True) + +-----------+--------+--------+--------+ + | a | α_1(a) | α_2(a) | α_3(a) | + +===========+========+========+========+ + | [] | 0 | 0 | 0 | + +-----------+--------+--------+--------+ + | [1] | 1 | 1 | 1 | + +-----------+--------+--------+--------+ + | [1, 2] | 2 | 2 | 2 | + +-----------+--------+--------+--------+ + | [2, 1] | 2 | 1 | 0 | + +-----------+--------+--------+--------+ + | [1, 2, 3] | 3 | 3 | 3 | + +-----------+--------+--------+--------+ + | [1, 3, 2] | 3 | 2 | 1 | + +-----------+--------+--------+--------+ + | [2, 1, 3] | 3 | 2 | 1 | + +-----------+--------+--------+--------+ + | [2, 3, 1] | 3 | 2 | 0 | + +-----------+--------+--------+--------+ + | [3, 1, 2] | 3 | 1 | 0 | + +-----------+--------+--------+--------+ + | [3, 2, 1] | 3 | 2 | 1 | + +-----------+--------+--------+--------+ + + sage: table(b, header_row=True, frame=True) + +-----------+---+--------+--------+--------+ + | b | τ | β_1(b) | β_2(b) | β_3(b) | + +===========+===+========+========+========+ + | [] | 0 | 0 | 0 | 0 | + +-----------+---+--------+--------+--------+ + | [1] | 1 | 1 | 1 | 1 | + +-----------+---+--------+--------+--------+ + | [1, 2] | 2 | 2 | 1 | 0 | + +-----------+---+--------+--------+--------+ + | [2, 1] | 1 | 2 | 2 | 2 | + +-----------+---+--------+--------+--------+ + | [1, 2, 3] | 3 | 3 | 1 | 0 | + +-----------+---+--------+--------+--------+ + | [1, 3, 2] | 2 | 3 | 2 | 1 | + +-----------+---+--------+--------+--------+ + | [2, 1, 3] | 2 | 3 | 2 | 1 | + +-----------+---+--------+--------+--------+ + | [2, 3, 1] | 2 | 3 | 2 | 1 | + +-----------+---+--------+--------+--------+ + | [3, 1, 2] | 2 | 3 | 2 | 0 | + +-----------+---+--------+--------+--------+ + | [3, 2, 1] | 1 | 3 | 3 | 3 | + +-----------+---+--------+--------+--------+ + + sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition + sage: bij.set_constant_blocks(sum([orbit_decomposition(A, rotate_permutation) for A in As], [])) + sage: bij.constant_blocks() + {{[1, 3, 2], [2, 1, 3], [3, 2, 1]}} + sage: next(bij.solutions_iterator()) + {[]: 0, + [1]: 1, + [1, 2]: 1, + [1, 2, 3]: 1, + [1, 3, 2]: 2, + [2, 1]: 2, + [2, 1, 3]: 2, + [2, 3, 1]: 2, + [3, 1, 2]: 3, + [3, 2, 1]: 2} + + There is no rotation invariant statistic on non crossing set partitions which is equidistributed + with the Strahler number on ordered trees:: + + sage: N=8; As = [[SetPartition(d.to_noncrossing_partition()) for d in DyckWords(n)] for n in range(N)] + sage: A = sum(As, []) + sage: B = sum([list(OrderedTrees(n)) for n in range(1, N+1)], []) + sage: theta = lambda m: SetPartition([[i % m.size() + 1 for i in b] for b in m]) + + The following code is equivalent to ``tau = findstat(397)``:: + + sage: def tau(T): + ....: if len(T) == 0: + ....: return 1 + ....: else: + ....: l = [tau(S) for S in T] + ....: m = max(l) + ....: if l.count(m) == 1: + ....: return m + ....: else: + ....: return m+1 + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((lambda a: a.size(), lambda b: b.node_number()-1)) + sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition + sage: bij.set_constant_blocks(sum([orbit_decomposition(A_n, theta) for A_n in As], [])) + sage: list(bij.solutions_iterator()) + [] + + An example identifying `s` and `S`:: + + sage: N = 4 + sage: A = [dyck_word for n in range(1, N) for dyck_word in DyckWords(n)] + sage: B = [binary_tree for n in range(1, N) for binary_tree in BinaryTrees(n)] + sage: concat_path = lambda D1, D2: DyckWord(list(D1) + list(D2)) + sage: concat_tree = lambda B1, B2: concat_path(B1.to_dyck_word(), + ....: B2.to_dyck_word()).to_binary_tree() + sage: bij = Bijectionist(A, B) + sage: bij.set_intertwining_relations((2, concat_path, concat_tree)) + sage: bij.set_statistics((lambda d: d.semilength(), lambda t: t.node_number())) + sage: for D in bij.minimal_subdistributions_iterator(): + ....: ascii_art(D) + ( [ /\ ], [ o ] ) + ( [ o ] ) + ( [ \ ] ) + ( [ /\/\ ], [ o ] ) + ( [ o ] ) + ( [ /\ ] [ / ] ) + ( [ / \ ], [ o ] ) + ( [ o ] ) + ( [ \ ] ) + ( [ o ] ) + ( [ \ ] ) + ( [ /\/\/\ ], [ o ] ) + ( [ o ] ) + ( [ \ ] ) + ( [ o ] ) + ( [ /\ ] [ / ] ) + ( [ /\/ \ ], [ o ] ) + ( [ o ] ) + ( [ /\ ] [ / \ ] ) + ( [ / \/\ ], [ o o ] ) + ( [ o, o ] ) + ( [ / / ] ) + ( [ /\ ] [ o o ] ) + ( [ /\/\ / \ ] [ \ / ] ) + ( [ / \, / \ ], [ o o ] ) + + TESTS: + + The following failed before commit c6d4d2e8804aa42afa08c72c887d50c725cc1a91:: + + sage: N=4; A = B = [permutation for n in range(N) for permutation in Permutations(n)] + sage: theta = lambda pi: Permutation([x+1 if x != len(pi) else 1 for x in pi[-1:]+pi[:-1]]) + sage: def tau(pi): + ....: n = len(pi) + ....: return sum([1 for i in range(1, n+1) for j in range(1, n+1) + ....: if i +# Stephan Pfannerer +# Tobias Kietreiber +# Alexander Grosz +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# The full text of the GPL is available at: +# +# https://www.gnu.org/licenses/ +# *************************************************************************** +import itertools +from collections import namedtuple +from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException +from sage.rings.integer_ring import ZZ +from sage.combinat.set_partition import SetPartition +from sage.sets.disjoint_set import DisjointSet +from sage.structure.sage_object import SageObject +from copy import copy, deepcopy +from sage.misc.verbose import get_verbose + +# TODO: (low) für sagemath sollten wir Zeilen möglichst auf 79 +# Zeichen beschränken, das ist zwar nicht streng, wird aber lieber +# gesehen. + +# TODO: (low) we frequently need variable names for subsets of A, B, +# Z. In LaTeX, we mostly call them \tilde A, \tilde Z, etc. now. It +# would be good to have a standard name in code, too. + +# TODO: (medium) wann immer möglich, sollten die Tests in einer +# Methode nur diese eine Methode testen. Wir haben in fast allen +# Methoden "system tests", das ist unpraktisch, wenn man größere +# Änderungen durchführt. + + +class Bijectionist(SageObject): + r"""Solver class for bijection-statistic problems. + + INPUT: + + - ``A``, ``B`` -- sets of equal size, given as a list + + - ``tau`` (optional, default: ``None``) -- a function from ``B`` + to ``Z``, in case of ``None``, the identity map ``lambda x: x`` + is used + + - ``alpha`` (optional) -- a statistic from ``A`` to ``W`` + + - ``beta`` (optional) -- a statistic from ``B`` to ``W`` + + - ``P`` (optional) -- a partition of ``A`` + + - ``pi_rho`` (optional) -- a triple ``(k, pi, rho)`` where + + - ``pi`` is a ``k``-ary operation composing objects in ``A`` + and + + - ``rho`` is a ``k``-ary function composing statistic values + in `Z` + + ``W`` and ``Z`` can be arbitrary sets. As a natural example we + may think of the natural numbers or tuples of integers. + + We are looking for a statistic `s: A\to Z` and a bijection `S: + A\to B` such that + + - `s = \tau \circ S`: the statistics `s` and `\tau` are + equidistributed and `S` is an intertwining bijection. + + - `\alpha = \beta \circ S`: the statistics `\alpha` and `\beta` + are equidistributed and `S` is an intertwining bijection. + + - `s` is constant on the blocks of `P`. + + - `s(\pi(a_1,\dots, a_k)) = \rho(s(a_1),\dots, s(a_k))`. + + Additionally, we may require that + + - `s(a)\in Z_a` for specified sets `Z_a\subseteq Z`, and + + - `s|_{\tilde A}` has a specified distribution for specified sets + `\tilde A \subset A`. + + If `\tau` is the identity, the two unknown functions `s` and `S` + coincide. Although we do not exclude other bijective choices for + `\tau`, they probably do not make sense. + + If we want that `S` is graded, i.e. if elements of `A` and `B` + have a notion of size and `S` should preserve this size, we can + add grading statistics as `\alpha` and `\beta`. Since `\alpha` + and `\beta` will be equidistributed with `S` as an intertwining + bijection, `S` will then also be graded. + + In summary, we have the following two commutative diagrams, where + `s` and `S` are unknown functions. + + .. MATH:: + + \begin{array}{rrl} + & A \\ + {\scriptstyle\alpha}\swarrow & {\scriptstyle S}\downarrow & \searrow{\scriptstyle s}\\ + W \overset{\beta}{\leftarrow} & B & \overset{\tau}{\rightarrow} Z + \end{array} + \qquad + \begin{array}{lcl} + A^k &\overset{\pi}{\rightarrow} & A\\ + \downarrow{\scriptstyle s^k} & & \downarrow{\scriptstyle s}\\ + Z^k &\overset{\rho}{\rightarrow} & Z\\ + \end{array} + + .. NOTE:: + + If `\tau` is the identity map, the partition `P` of `A` + necessarily consists only of singletons. + + .. NOTE:: + + The order of invocation of the methods with prefix ``set``, + i.e., :meth:`set_statistics`, + :meth:`set_intertwining_relations`, + :meth:`set_constant_blocks`, etc., is irrelevant. Calling + any of these methods a second time overrides the previous + specification. + + """ + def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), elements_distributions=tuple(), a_values=tuple(), solver=None, key=None): + # glossary of standard letters: + # A, B, Z, W ... finite sets + # ???? tilde_A, tilde_Z, ..., subsets? + # P ... set partition of A + # a in A, b in B, p in P + # S: A -> B + # alpha: A -> W, beta: B -> W + # s: A -> Z, tau: B -> Z + # k arity of pi and rho + # pi: A^k -> A, rho: Z^k -> Z + # a_tuple in A^k + + assert len(A) == len(set(A)), "A must have distinct items" + assert len(B) == len(set(B)), "B must have distinct items" + self._A = A + self._B = B + self._sorter = {} + self._sorter["A"] = lambda x: sorted(x, key=self._A.index) + self._sorter["B"] = lambda x: sorted(x, key=self._B.index) + + if tau is None: + self._tau = {b: b for b in self._B} + else: + self._tau = {b: tau(b) for b in self._B} + self._Z = set(self._tau.values()) + if key is not None and "Z" in key: + self._sorter["Z"] = lambda x: sorted(x, key=key["Z"]) + self._Z = self._sorter["Z"](self._Z) + else: + try: + self._Z = sorted(self._Z) + self._sorter["Z"] = lambda x: sorted(x) + except TypeError: + self._sorter["Z"] = lambda x: list(x) + self._Z = list(self._Z) + + # set optional inputs + self.set_statistics(*alpha_beta) + self.set_value_restrictions(*a_values) + self.set_distributions(*elements_distributions) + self.set_intertwining_relations(*pi_rho) + self.set_constant_blocks(P) + + self._solver = solver + + def set_constant_blocks(self, P): + r""" + Declare that `s: A\to Z` is constant on each block of `P`. + + .. WARNING:: + + Any restriction imposed by a previous invocation of + :meth:`set_constant_blocks` will be overwritten, + including restrictions discovered by + :meth:`set_intertwining_relations` and + :meth:`solutions_iterator`! + + A common example is to use the orbits of a bijection acting + on `A`. This can be achieved using the function + :meth:`~sage.combinat.cyclic_sieving_phenomenon.orbit_decomposition`. + + INPUT: + + - ``P`` -- a set partition of `A`, singletons may be omitted + + EXAMPLES: + + Initially the partitions are set to singleton blocks. The + current partition can be reviewed using + :meth:`constant_blocks`:: + + sage: A = B = list('abcd') + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) + sage: bij.constant_blocks() + {} + + sage: bij.set_constant_blocks([['a', 'c']]) + sage: bij.constant_blocks() + {{'a', 'c'}} + + We now add a map that combines some blocks:: + + sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] + sage: rho = lambda s1, s2: (s1 + s2) % 2 + sage: bij.set_intertwining_relations((2, pi, rho)) + sage: list(bij.solutions_iterator()) + [{'a': 0, 'b': 1, 'c': 0, 'd': 1}] + sage: bij.constant_blocks() + {{'a', 'c'}, {'b', 'd'}} + + Setting constant blocks overrides any previous assignment:: + + sage: bij.set_constant_blocks([['a', 'b']]) + sage: bij.constant_blocks() + {{'a', 'b'}} + + If there is no solution, and the coarsest partition is + requested, an error is raised:: + + sage: bij.constant_blocks(optimal=True) + Traceback (most recent call last): + ... + MIPSolverException: ... + + """ + self._P = DisjointSet(self._A) + P = sorted(self._sorter["A"](p) for p in P) + for p in P: + for a in p: + self._P.union(p[0], a) + + self._compute_possible_block_values() + + def constant_blocks(self, singletons=False, optimal=False): + r""" + Return the set partition `P` of `A` such that `s: A\to Z` is + known to be constant on the blocks of `P`. + + INPUT: + + - ``singletons`` (optional, default: ``False``) -- whether or + not to include singleton blocks in the output + + - ``optimal`` (optional, default: ``False``) -- whether or + not to compute the coarsest possible partition + + .. NOTE:: + + computing the coarsest possible partition may be + computationally expensive, but may speed up generating + solutions. + + EXAMPLES:: + + sage: A = B = ["a", "b", "c"] + sage: bij = Bijectionist(A, B, lambda x: 0) + sage: bij.set_constant_blocks([["a", "b"]]) + sage: bij.constant_blocks() + {{'a', 'b'}} + + sage: bij.constant_blocks(singletons=True) + {{'a', 'b'}, {'c'}} + + """ + if optimal: + self._forced_constant_blocks() + if singletons: + return SetPartition(self._P) + return SetPartition(p for p in self._P if len(p) > 1) + + def set_statistics(self, *alpha_beta): + r""" + Set constraints of the form `\alpha = \beta\circ S`. + + .. WARNING:: + + Any restriction imposed by a previous invocation of + :meth:`set_statistics` will be overwritten! + + INPUT: + + - ``alpha_beta`` -- one or more pairs `(\alpha: A\to W, + \beta: B\to W)` + + If the statistics `\alpha` and `\beta` are not + equidistributed, an error is raised. + + EXAMPLES: + + We look for bijections `S` on permutations such that the + number of weak exceedences of `S(\pi)` equals the number of + descents of `\pi`, and statistics `s`, such that the number + of fixed points of `S(\pi)` equals `s(\pi)`:: + + sage: N = 4; A = B = [permutation for n in range(N) for permutation in Permutations(n)] + sage: wex = lambda p: len(p.weak_excedences()) + sage: fix = lambda p: len(p.fixed_points()) + sage: des = lambda p: len(p.descents(final_descent=True)) if p else 0 + sage: adj = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: bij = Bijectionist(A, B, fix) + sage: bij.set_statistics((wex, des), (len, len)) + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 0, [2, 1, 3]: 0, [2, 3, 1]: 1, [3, 1, 2]: 3, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 0, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 3, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 0, [2, 1, 3]: 1, [2, 3, 1]: 1, [3, 1, 2]: 3, [3, 2, 1]: 0} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 1, [2, 1, 3]: 0, [2, 3, 1]: 0, [3, 1, 2]: 3, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 1, [2, 1, 3]: 0, [2, 3, 1]: 1, [3, 1, 2]: 3, [3, 2, 1]: 0} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 1, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 3, [3, 2, 1]: 0} + + sage: bij = Bijectionist(A, B, fix) + sage: bij.set_statistics((wex, des), (fix, adj), (len, len)) + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 0, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 3, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 1, [2, 1, 3]: 0, [2, 3, 1]: 0, [3, 1, 2]: 3, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 1, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 3, [3, 2, 1]: 0} + + Calling this with non-equidistributed statistics yields an error:: + + sage: bij = Bijectionist(A, B, fix) + sage: bij.set_statistics((wex, fix)) + Traceback (most recent call last): + ... + ValueError: Statistics alpha and beta are not equidistributed! + + TESTS: + + Calling ``set_statistics`` without arguments should restore the previous state.:: + + sage: N = 3; A = B = [permutation for n in range(N) for permutation in Permutations(n)] + sage: wex = lambda p: len(p.weak_excedences()) + sage: fix = lambda p: len(p.fixed_points()) + sage: des = lambda p: len(p.descents(final_descent=True)) if p else 0 + sage: bij = Bijectionist(A, B, fix) + sage: bij.set_statistics((wex, des), (len, len)) + sage: for solution in bij.solutions_iterator(): + ....: print(solution) + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2} + sage: bij.set_statistics() + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + {[]: 0, [1]: 0, [1, 2]: 1, [2, 1]: 2} + {[]: 0, [1]: 0, [1, 2]: 2, [2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0} + {[]: 0, [1]: 2, [1, 2]: 0, [2, 1]: 1} + {[]: 0, [1]: 2, [1, 2]: 1, [2, 1]: 0} + {[]: 1, [1]: 0, [1, 2]: 0, [2, 1]: 2} + {[]: 1, [1]: 0, [1, 2]: 2, [2, 1]: 0} + {[]: 1, [1]: 2, [1, 2]: 0, [2, 1]: 0} + {[]: 2, [1]: 0, [1, 2]: 0, [2, 1]: 1} + {[]: 2, [1]: 0, [1, 2]: 1, [2, 1]: 0} + {[]: 2, [1]: 1, [1, 2]: 0, [2, 1]: 0} + + """ + # reset values + self._statistics_possible_values = {a: set(self._Z) for a in self._A} + + self._n_statistics = len(alpha_beta) + # TODO: (low) do we really want to recompute statistics every time? + self._alpha = lambda p: tuple(arg[0](p) for arg in alpha_beta) + self._beta = lambda p: tuple(arg[1](p) for arg in alpha_beta) + + # generate fibers + self._statistics_fibers = {} + for a in self._A: + v = self._alpha(a) + if v not in self._statistics_fibers: + self._statistics_fibers[v] = ([], []) + self._statistics_fibers[v][0].append(a) + + for b in self._B: + v = self._beta(b) + if v not in self._statistics_fibers: + raise ValueError(f"Statistics alpha and beta do not have the same image, {v} is not a value of alpha, but of beta!") + self._statistics_fibers[v][1].append(b) + + # check compatibility + if not all(len(fiber[0]) == len(fiber[1]) + for fiber in self._statistics_fibers.values()): + raise ValueError("Statistics alpha and beta are not equidistributed!") + + self._W = list(self._statistics_fibers) + + # the possible values of s(a) are tau(beta^{-1}(alpha(a))) + self._statistics_possible_values = {a: set(self._tau[b] + for b in self._statistics_fibers[self._alpha(a)][1]) + for a in self._A} + + def statistics_fibers(self): + r""" + Return a dictionary mapping statistic values in `W` to their + preimages in `A` and `B`. + + This is a (computationally) fast way to obtain a first + impression which objects in `A` should be mapped to which + objects in `B`. + + EXAMPLES:: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: wex = lambda p: len(p.weak_excedences()) + sage: fix = lambda p: len(p.fixed_points()) + sage: des = lambda p: len(p.descents(final_descent=True)) if p else 0 + sage: adj = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((len, len), (wex, des), (fix, adj)) + sage: table([[key, AB[0], AB[1]] for key, AB in bij.statistics_fibers().items()]) + (0, 0, 0) [[]] [[]] + (1, 1, 1) [[1]] [[1]] + (2, 2, 2) [[1, 2]] [[2, 1]] + (2, 1, 0) [[2, 1]] [[1, 2]] + (3, 3, 3) [[1, 2, 3]] [[3, 2, 1]] + (3, 2, 1) [[1, 3, 2], [2, 1, 3], [3, 2, 1]] [[1, 3, 2], [2, 1, 3], [2, 3, 1]] + (3, 2, 0) [[2, 3, 1]] [[3, 1, 2]] + (3, 1, 0) [[3, 1, 2]] [[1, 2, 3]] + + """ + return self._statistics_fibers + + def statistics_table(self, header=True): + r""" + Provide information about all elements of `A` with corresponding + `\alpha` values and all elements of `B` with corresponding + `\beta` and `\tau` values. + + INPUT: + + - ``header`` (optional, default: ``True``) -- whether to + include a header with the standard greek letters. + + OUTPUT: + + A pair of lists suitable for :class:`~sage.misc.table.table`, + where + + - the first contains the elements of `A` together with the + values of `\alpha` + + - the second contains the elements of `B` together with the + values of `\tau` and `\beta` + + EXAMPLES:: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: wex = lambda p: len(p.weak_excedences()) + sage: fix = lambda p: len(p.fixed_points()) + sage: des = lambda p: len(p.descents(final_descent=True)) if p else 0 + sage: adj = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((wex, des), (fix, adj)) + sage: a, b = bij.statistics_table() + sage: table(a, header_row=True, frame=True) + +-----------+--------+--------+ + | a | α_1(a) | α_2(a) | + +===========+========+========+ + | [] | 0 | 0 | + +-----------+--------+--------+ + | [1] | 1 | 1 | + +-----------+--------+--------+ + | [1, 2] | 2 | 2 | + +-----------+--------+--------+ + | [2, 1] | 1 | 0 | + +-----------+--------+--------+ + | [1, 2, 3] | 3 | 3 | + +-----------+--------+--------+ + | [1, 3, 2] | 2 | 1 | + +-----------+--------+--------+ + | [2, 1, 3] | 2 | 1 | + +-----------+--------+--------+ + | [2, 3, 1] | 2 | 0 | + +-----------+--------+--------+ + | [3, 1, 2] | 1 | 0 | + +-----------+--------+--------+ + | [3, 2, 1] | 2 | 1 | + +-----------+--------+--------+ + sage: table(b, header_row=True, frame=True) + +-----------+---+--------+--------+ + | b | τ | β_1(b) | β_2(b) | + +===========+===+========+========+ + | [] | 0 | 0 | 0 | + +-----------+---+--------+--------+ + | [1] | 1 | 1 | 1 | + +-----------+---+--------+--------+ + | [1, 2] | 2 | 1 | 0 | + +-----------+---+--------+--------+ + | [2, 1] | 1 | 2 | 2 | + +-----------+---+--------+--------+ + | [1, 2, 3] | 3 | 1 | 0 | + +-----------+---+--------+--------+ + | [1, 3, 2] | 2 | 2 | 1 | + +-----------+---+--------+--------+ + | [2, 1, 3] | 2 | 2 | 1 | + +-----------+---+--------+--------+ + | [2, 3, 1] | 2 | 2 | 1 | + +-----------+---+--------+--------+ + | [3, 1, 2] | 2 | 2 | 0 | + +-----------+---+--------+--------+ + | [3, 2, 1] | 1 | 3 | 3 | + +-----------+---+--------+--------+ + + TESTS: + + If no statistics are given, the table should still be able to be generated:: + + sage: A = B = [permutation for n in range(3) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: a, b = bij.statistics_table() + sage: table(a, header_row=True, frame=True) + +--------+ + | a | + +========+ + | [] | + +--------+ + | [1] | + +--------+ + | [1, 2] | + +--------+ + | [2, 1] | + +--------+ + sage: table(b, header_row=True, frame=True) + +--------+---+ + | b | τ | + +========+===+ + | [] | 0 | + +--------+---+ + | [1] | 1 | + +--------+---+ + | [1, 2] | 2 | + +--------+---+ + | [2, 1] | 1 | + +--------+---+ + + We can omit the header:: + + sage: bij.statistics_table(header=True)[1] + [['b', 'τ'], [[], 0], [[1], 1], [[1, 2], 2], [[2, 1], 1]] + sage: bij.statistics_table(header=False)[1] + [[[], 0], [[1], 1], [[1, 2], 2], [[2, 1], 1]] + + """ + # table for alpha + n_statistics = self._n_statistics + if header: + output_alphas = [["a"] + ["\u03b1_" + str(i) + "(a)" + for i in range(1, n_statistics + 1)]] + else: + output_alphas = [] + + for a in self._A: + if n_statistics > 0: + output_alphas.append([a] + list(self._alpha(a))) + else: + output_alphas.append([a]) + + # table for beta and tau + if header: + output_tau_betas = [["b", "\u03c4"] + ["\u03b2_" + str(i) + "(b)" + for i in range(1, n_statistics + 1)]] + else: + output_tau_betas = [] + for b in self._B: + if n_statistics > 0: + output_tau_betas.append([b, self._tau[b]] + list(self._beta(b))) + else: + output_tau_betas.append([b, self._tau[b]]) + + return output_alphas, output_tau_betas + + def set_value_restrictions(self, *a_values): + r""" + Restrict the set of possible values `s(a)` for a given element + `a`. + + .. WARNING:: + + Any restriction imposed by a previous invocation of + :meth:`set_value_restrictions` will be overwritten! + + INPUT: + + - ``a_values`` -- one or more pairs `(a\in A, \tilde + Z\subseteq Z)` + + EXAMPLES: + + We may want to restrict the value of a given element to a + single or multiple values. We do not require that the + specified values are in the image of `\tau`. In some + cases, the restriction may not be able to provide a better + solution, as for size 3 in the following example. :: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((len, len)) + sage: bij.set_value_restrictions((Permutation([1, 2]), [1]), + ....: (Permutation([3, 2, 1]), [2, 3, 4])) + sage: for sol in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(sol) + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 3} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 3, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 3, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 2, [2, 1, 3]: 3, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 1, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 3} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 1, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 3, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 1, [2, 1, 3]: 2, [2, 3, 1]: 3, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 1, [2, 1, 3]: 3, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 2, [2, 1, 3]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 3} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 2, [2, 1, 3]: 1, [2, 3, 1]: 2, [3, 1, 2]: 3, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 2, [2, 1, 3]: 1, [2, 3, 1]: 3, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 1, [3, 1, 2]: 2, [3, 2, 1]: 3} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 1, [3, 1, 2]: 3, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 3} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 3, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 2, [2, 1, 3]: 3, [2, 3, 1]: 1, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 2, [2, 1, 3]: 3, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 1, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 1, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + + However, an error occurs if the set of possible values is + empty. In this example, the image of `\tau` under any + legal bijection is disjoint to the specified values. :: TODO: we now have to call _compute_possible_block_values() for the error message. Is this intended behaviour? + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_value_restrictions((Permutation([1, 2]), [4, 5])) + sage: bij._compute_possible_block_values() + Traceback (most recent call last): + ... + ValueError: No possible values found for singleton block [[1, 2]] + + TESTS:: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_constant_blocks([[permutation for permutation in Permutations(n)] for n in range(4)]) + sage: bij.set_value_restrictions((Permutation([1, 2]), [4, 5])) + sage: bij._compute_possible_block_values() + Traceback (most recent call last): + ... + ValueError: No possible values found for block [[1, 2], [2, 1]] + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_value_restrictions(((1, 2), [4, 5, 6])) + Traceback (most recent call last): + ... + AssertionError: Element (1, 2) was not found in A + + """ + # reset values + self._restrictions_possible_values = {a: set(self._Z) for a in self._A} + for a, values in a_values: + assert a in self._A, f"Element {a} was not found in A" + self._restrictions_possible_values[a].intersection_update(values) + + def _compute_possible_block_values(self): + r""" + Update the dictionary of possible values of each block. + """ + self._possible_block_values = {} # P -> Power(Z) + for p, block in self._P.root_to_elements_dict().items(): + self._possible_block_values[p] = set.intersection(*[self._restrictions_possible_values[a] for a in block], *[self._statistics_possible_values[a] for a in block]) + if not self._possible_block_values[p]: + if len(block) == 1: + raise ValueError(f"No possible values found for singleton block {block}") + else: + raise ValueError(f"No possible values found for block {block}") + + def set_distributions(self, *elements_distributions): + r""" + Specify the distribution of `s` for a subset of elements. + + .. WARNING:: + + Any restriction imposed by a previous invocation of + :meth:`set_distributions` will be overwritten! + + INPUT: + + - one or more pairs of `(\tilde A\subseteq A, \tilde Z)`, + where `\tilde Z` is a list of values in `Z` of the same + size as `\tilde A` + + This method specifies that `\{s(a) | a\in\tilde A\}` equals + ``\tilde Z`` as a multiset for each of the pairs. + + When specifying several distributions, the subsets of `A` do + not have to be disjoint. + + EXAMPLES:: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((len, len)) + sage: bij.set_distributions(([Permutation([1, 2, 3]), Permutation([1, 3, 2])], [1, 3])) + sage: for sol in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(sol) + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 1, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + + sage: bij.constant_blocks(optimal=True) + {{[2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]}} + sage: sorted(bij.minimal_subdistributions_blocks_iterator(), key=lambda d: (len(d[0]), d[0])) + [([[]], [0]), + ([[1]], [1]), + ([[2, 1, 3]], [2]), + ([[1, 2], [2, 1]], [1, 2]), + ([[1, 2, 3], [1, 3, 2]], [1, 3])] + + We may also specify multiple, possibly overlapping distributions:: + + sage: bij.set_distributions(([Permutation([1, 2, 3]), Permutation([1, 3, 2])], [1, 3]), + ....: ([Permutation([1, 3, 2]), Permutation([3, 2, 1]), + ....: Permutation([2, 1, 3])], [1, 2, 2])) + sage: for sol in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(sol) + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + + sage: bij.constant_blocks(optimal=True) + {{[1], [1, 3, 2]}, {[2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]}} + sage: sorted(bij.minimal_subdistributions_blocks_iterator(), key=lambda d: (len(d[0]), d[0])) + [([[]], [0]), + ([[1]], [1]), + ([[1, 2, 3]], [3]), + ([[2, 1, 3]], [2]), + ([[1, 2], [2, 1]], [1, 2])] + + TESTS: + + Because of the current implementation of the output + calculation, we do not improve our solution if we do not gain + any unique solutions.:: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((len, len)) + sage: bij.set_distributions(([Permutation([1, 2, 3]), Permutation([1, 3, 2])], [2, 3])) + sage: for sol in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(sol) + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 1, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 1, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 1, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 1, [3, 1, 2]: 2, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} + + Another example with statistics:: + + sage: bij = Bijectionist(A, B, tau) + sage: alpha = lambda p: p(1) if len(p) > 0 else 0 + sage: beta = lambda p: p(1) if len(p) > 0 else 0 + sage: bij.set_statistics((alpha, beta), (len, len)) + sage: for sol in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(sol) + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} + + The solution above is not unique. We can add a feasible distribution to force uniqueness:: + + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((alpha, beta), (len, len)) + sage: bij.set_distributions(([Permutation([1, 2, 3]), Permutation([3, 2, 1])], [1, 3])) + sage: for sol in bij.solutions_iterator(): + ....: print(sol) + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} + + Let us try to add a distribution that cannot be satisfied, + because there is no solution where a permutation that starts + with 1 is mapped onto 1:: + + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((alpha, beta), (len, len)) + sage: bij.set_distributions(([Permutation([1, 2, 3]), Permutation([1, 3, 2])], [1, 3])) + sage: list(bij.solutions_iterator()) + [] + + The specified elements have to be in `A` and have to be of the same size:: + + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((len, len)) + sage: bij.set_distributions(([Permutation([1, 2, 3, 4])], [1])) + Traceback (most recent call last): + ... + ValueError: Element [1, 2, 3, 4] was not found in A! + sage: bij.set_distributions(([Permutation([1, 2, 3])], [-1])) + Traceback (most recent call last): + ... + ValueError: Value -1 was not found in tau(A)! + + Note that the same error occurs when an element that is not the first element of the list is + not in `A`. + + """ + for elements, values in elements_distributions: + assert len(elements) == len(values), f"{elements} and {values} are not of the same size!" + for a, z in zip(elements, values): + if a not in self._A: + raise ValueError(f"Element {a} was not found in A!") + if z not in self._Z: + raise ValueError(f"Value {z} was not found in tau(A)!") + self._elements_distributions = elements_distributions + + def set_intertwining_relations(self, *pi_rho): + r""" + Add restrictions of the form `s(\pi(a_1,\dots, a_k)) = + \rho(s(a_1),\dots, s(a_k))`. + + .. WARNING:: + + Any restriction imposed by a previous invocation of + :meth:`set_intertwining_relations` will be overwritten! + + INPUT: + + - ``pi_rho`` -- one or more tuples `(k, \pi: A^k\to A, \rho: + Z^k\to Z, \tilde A)` where `\tilde A` (optional) is an + `k`-ary function that returns true if and only if an + `k`-tuple of objects in `A` is in the domain of `\pi` + + EXAMPLES: + + We can concatenate two permutations, by increasing the values + of the second permutation by the length of the first + permutation:: + + sage: concat = lambda p1, p2: Permutation(p1 + [i + len(p1) for i in p2]) + + We may be interested in statistics on permutations which are + equidistributed with the number of fixed points, such that + concatenating permutations corresponds to adding statistic + values:: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: bij = Bijectionist(A, B, Permutation.number_of_fixed_points) + sage: bij.set_statistics((len, len)) + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + ... + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 0, [2, 1, 3]: 0, [2, 3, 1]: 1, [3, 1, 2]: 1, [3, 2, 1]: 3} + ... + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 1, [2, 1, 3]: 3, [2, 3, 1]: 0, [3, 1, 2]: 0, [3, 2, 1]: 1} + ... + + sage: bij.set_intertwining_relations((2, concat, lambda x, y: x + y)) + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 0, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 1, [3, 2, 1]: 0} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 1, [2, 3, 1]: 1, [3, 1, 2]: 0, [3, 2, 1]: 0} + + The domain of the composition may be restricted. E.g., if we + concatenate only permutations starting with a 1, we obtain + fewer forced elements:: + + sage: in_domain = lambda p1, p2: (not p1 or p1(1) == 1) and (not p2 or p2(1) == 1) + sage: bij.set_intertwining_relations((2, concat, lambda x, y: x + y, in_domain)) + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 0, [2, 1, 3]: 0, [2, 3, 1]: 1, [3, 1, 2]: 1, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 0, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 1, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 0, [2, 1, 3]: 1, [2, 3, 1]: 1, [3, 1, 2]: 0, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 0, [2, 1, 3]: 1, [2, 3, 1]: 1, [3, 1, 2]: 1, [3, 2, 1]: 0} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 0, [2, 3, 1]: 0, [3, 1, 2]: 1, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 0, [2, 3, 1]: 1, [3, 1, 2]: 0, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 0, [2, 3, 1]: 1, [3, 1, 2]: 1, [3, 2, 1]: 0} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 0, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 1, [3, 2, 1]: 0} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 0, [1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 1, [2, 3, 1]: 1, [3, 1, 2]: 0, [3, 2, 1]: 0} + + We can also restrict according to several composition + functions. For example, we may additionally concatenate + permutations by incrementing the elements of the first:: + + sage: skew_concat = lambda p1, p2: Permutation([i + len(p2) for i in p1] + list(p2)) + sage: bij.set_intertwining_relations((2, skew_concat, lambda x, y: x + y)) + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 0, [1, 3, 2]: 0, [2, 1, 3]: 1, [2, 3, 1]: 1, [3, 1, 2]: 1, [3, 2, 1]: 3} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 0, [1, 3, 2]: 1, [2, 1, 3]: 0, [2, 3, 1]: 1, [3, 1, 2]: 1, [3, 2, 1]: 3} + {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 0, [2, 1, 3]: 0, [2, 3, 1]: 1, [3, 1, 2]: 1, [3, 2, 1]: 3} + + However, this yields no solution:: + + sage: bij.set_intertwining_relations((2, concat, lambda x, y: x + y), (2, skew_concat, lambda x, y: x + y)) + sage: list(bij.solutions_iterator()) + [] + + """ + Pi_Rho = namedtuple("Pi_Rho", "numargs pi rho domain") + self._pi_rho = [] + + for pi_rho_tuple in pi_rho: + if len(pi_rho_tuple) == 3: + k, pi, rho = pi_rho_tuple + domain = None + else: + k, pi, rho, domain = pi_rho_tuple + + self._pi_rho.append(Pi_Rho(numargs=k, pi=pi, rho=rho, domain=domain)) + + def _forced_constant_blocks(self): + r""" + Modify current partition into blocks to the coarsest possible + one, meaning that after calling this function for every two + distinct blocks `p_1`, `p_2` there exists a solution `s` with + `s(p_1)\neq s(p_2)`. + + ALGORITHM: + + First we generate an initial solution. For all blocks i, j + that have the same value under this initial solution, we add + the constraint `x[i, z] + x[j, z] <= 1` for all possible + values `z\in Z`. This constraint ensures that the `s` differs + on the two blocks. If this modified problem does not have a + solution, we know that the two blocks always have the same + value and join them. Then we save all values of this new + solution and continue looking at pairs of blocks that had the + same value under all calculated solutions, until no blocks + can be joined anymore. + + EXAMPLES: + + The easiest example is given by a constant `tau`, so everything + is forced to be the same value: + + sage: A = B = [permutation for n in range(3) for permutation in Permutations(n)] + sage: bij = Bijectionist(A, B, lambda x: 0) + sage: bij.constant_blocks() + {} + sage: bij.constant_blocks(optimal=True) + {{[], [1], [1, 2], [2, 1]}} + + In this other example we look at permutations with length 2 and 3:: + + sage: N = 4 + sage: A = B = [permutation for n in range(2, N) for permutation in Permutations(n)] + sage: tau = lambda p: p[0] if len(p) else 0 + sage: add_n = lambda p1: Permutation(p1 + [1 + len(p1)]) + sage: add_1 = lambda p1: Permutation([1] + [1 + i for i in p1]) + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_intertwining_relations((1, add_n, lambda x: x + 1), (1, add_1, lambda x: x + 1)) + sage: bij.set_statistics((len, len)) + + sage: bij.constant_blocks() + {} + sage: bij.constant_blocks(optimal=True) + {{[1, 3, 2], [2, 1, 3]}} + + Indeed, ``[1,3,2]`` and ``[2,1,3]`` have the same value in + all solutions, but different values are possible:: + + sage: pi1 = Permutation([1,3,2]); pi2 = Permutation([2,1,3]); + sage: set([(solution[pi1], solution[pi2]) for solution in bij.solutions_iterator()]) + {(2, 2), (3, 3)} + + Another example involving the cycle type of permutations:: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: bij = Bijectionist(A, B, lambda x: x.cycle_type()) + + Let us require that each permutation has the same value as its inverse:: + + sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition + sage: P = orbit_decomposition([permutation for n in range(4) for permutation in Permutations(n)], Permutation.inverse) + sage: bij.set_constant_blocks(P) + sage: bij.constant_blocks() + {{[2, 3, 1], [3, 1, 2]}} + + sage: concat = lambda p1, p2: Permutation(p1 + [i + len(p1) for i in p2]) + sage: union = lambda p1, p2: Partition(sorted(list(p1) + list(p2), reverse=True)) + sage: bij.set_intertwining_relations((2, concat, union)) + + In this case we do not discover constant blocks by looking at the intertwining_relations only:: + + sage: next(bij.solutions_iterator()) + ... + sage: bij.constant_blocks() + {{[2, 3, 1], [3, 1, 2]}} + + sage: bij.constant_blocks(optimal=True) + {{[1, 3, 2], [2, 1, 3], [3, 2, 1]}, {[2, 3, 1], [3, 1, 2]}} + + TESTS:: + + sage: N = 4 + sage: A = B = [permutation for n in range(N + 1) for permutation in Permutations(n)] + sage: alpha1 = lambda p: len(p.weak_excedences()) + sage: alpha2 = lambda p: len(p.fixed_points()) + sage: beta1 = lambda p: len(p.descents(final_descent=True)) if p else 0 + sage: beta2 = lambda p: len([e for (e, f) in zip(p, p[1:] + [0]) if e == f + 1]) + sage: tau = Permutation.longest_increasing_subsequence_length + sage: def rotate_permutation(p): + ....: cycle = Permutation(tuple(range(1, len(p) + 1))) + ....: return Permutation([cycle.inverse()(p(cycle(i))) for i in range(1, len(p) + 1)]) + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((alpha1, beta1), (alpha2, beta2)) + sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition + sage: bij.set_constant_blocks(orbit_decomposition(A, rotate_permutation)) + sage: for p in bij.constant_blocks(): print(list(p)) + [[2, 1, 3, 4], [1, 2, 4, 3], [1, 3, 2, 4], [4, 2, 3, 1]] + [[3, 2, 1], [1, 3, 2], [2, 1, 3]] + [[2, 4, 3, 1], [3, 2, 4, 1], [2, 3, 1, 4], [1, 3, 4, 2]] + [[1, 4, 2, 3], [3, 1, 2, 4], [4, 2, 1, 3], [4, 1, 3, 2]] + [[1, 4, 3, 2], [3, 2, 1, 4]] + [[2, 1, 4, 3], [4, 3, 2, 1]] + [[2, 4, 1, 3], [3, 4, 2, 1], [4, 3, 1, 2], [3, 1, 4, 2]] + + sage: for p in bij.constant_blocks(optimal=True): sorted(p, key=len) + [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]] + [[1, 3, 2], + [2, 1, 3], + [3, 2, 1], + [2, 3, 4, 1], + [1, 3, 4, 2], + [2, 1, 3, 4], + [1, 3, 2, 4], + [2, 3, 1, 4], + [1, 2, 4, 3], + [3, 2, 4, 1], + [2, 1, 4, 3], + [2, 4, 3, 1], + [4, 2, 3, 1], + [4, 3, 2, 1], + [1, 4, 3, 2], + [3, 2, 1, 4]] + [[1, 4, 2, 3], + [4, 2, 1, 3], + [2, 4, 1, 3], + [4, 3, 1, 2], + [4, 1, 3, 2], + [3, 4, 2, 1], + [3, 1, 2, 4], + [3, 1, 4, 2]] + + The permutation `[2, 1]` is in none of these blocks:: + + sage: bij.set_constant_blocks(orbit_decomposition(A, rotate_permutation)) + sage: all(s[Permutation([2, 1])] == s[Permutation([1])] for s in bij.solutions_iterator()) + False + + sage: all(s[Permutation([2, 1])] == s[Permutation([1, 3, 2])] for s in bij.solutions_iterator()) + False + + sage: all(s[Permutation([2, 1])] == s[Permutation([1, 4, 2, 3])] for s in bij.solutions_iterator()) + False + + + + sage: A = B = ["a", "b", "c", "d", "e", "f"] + sage: tau = {"a": 1, "b": 1, "c": 3, "d": 4, "e": 5, "f": 6}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_distributions((["a", "b"], [1, 1]), (["c", "d", "e"], [3, 4, 5])) + sage: bij.constant_blocks() + {} + sage: bij.constant_blocks(optimal=True) + {{'a', 'b'}} + + sage: A = B = ["a", "b", "c", "d", "e", "f"] + sage: tau = {"a": 1, "b": 1, "c": 5, "d": 4, "e": 4, "f": 6}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_distributions((["a", "b"], [1, 1]), (["d", "e"], [4, 4])) + sage: bij.constant_blocks(optimal=True) + {{'a', 'b'}, {'d', 'e'}} + + sage: A = B = ["a", "b", "c", "d"] + sage: tau = {"a": 1, "b": 1, "c": 2, "d": 2}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.constant_blocks(optimal=True) + {} + sage: bij.set_constant_blocks([["a", "b"]]) + sage: bij.constant_blocks() + {{'a', 'b'}} + sage: bij.constant_blocks(optimal=True) + {{'a', 'b'}, {'c', 'd'}} + + """ + bmilp = self._generate_and_solve_initial_bmilp() # may throw Exception + + # generate blockwise preimage to determine which blocks have the same image + solution = self._solution_by_blocks(bmilp) + multiple_preimages = {(value,): preimages + for value, preimages in _invert_dict(solution).items() + if len(preimages) > 1} + + # check for each pair of blocks if a solution with different values on these block exists + # if yes, use the new solution to update the multiple_preimages dictionary, restart the check + # if no, the two blocks can be joined + + # _P has to be copied to not mess with the solution-process + # since we do not want to regenerate the bmilp in each step, so blocks + # have to stay consistent during the whole process + tmp_P = deepcopy(self._P) + updated_preimages = True + while updated_preimages: + updated_preimages = False + for values in copy(multiple_preimages): # copy to be able to modify dict + if updated_preimages: + break + for i, j in itertools.combinations(copy(multiple_preimages[values]), r=2): # copy to be able to modify list + bmilp_veto = deepcopy(bmilp) # adding constraints to a simple copy adds them to the original instance, too + try: + # veto the two blocks having the same value + for z in self._possible_block_values[i]: + if z in self._possible_block_values[j]: # intersection + bmilp_veto.milp.add_constraint(bmilp_veto._x[i, z] + bmilp_veto._x[j, z] <= 1) + bmilp_veto.milp.solve() + + # solution exists, update dictionary + solution = self._solution_by_blocks(bmilp_veto) + updated_multiple_preimages = {} + for values in multiple_preimages: + for p in multiple_preimages[values]: + solution_tuple = (*values, solution[p]) # tuple so actual solutions were equal in lookup + if solution_tuple not in updated_multiple_preimages: + updated_multiple_preimages[solution_tuple] = [] + updated_multiple_preimages[solution_tuple].append(p) + updated_preimages = True + multiple_preimages = updated_multiple_preimages + break + except MIPSolverException: + # no solution exists, join blocks + tmp_P.union(i, j) + if i in multiple_preimages[values] and j in multiple_preimages[values]: # only one of the joined blocks should remain in the list + multiple_preimages[values].remove(j) + if len(multiple_preimages[values]) == 1: + del multiple_preimages[values] + break + + self.set_constant_blocks(tmp_P) + + def possible_values(self, p=None, optimal=False): + r""" + Return for each block the values of `s` compatible with the + imposed restrictions. + + TODO: should this method update and return ``self._possible_block_values``? + + INPUT: + + - ``p`` (optional, default: ``None``) -- a block of `P`, or + an element of a block of `P`, or a list of these + + - ``optimal`` (optional, default: ``False``) -- whether or + not to compute the minimal possible set of statistic values, + throws a MIPSolverException if no solution is found. + + .. NOTE:: + + computing the minimal possible set of statistic values + may be computationally expensive. + + TESTS:: + + sage: A = B = ["a", "b", "c", "d"] + sage: tau = {"a": 1, "b": 1, "c": 2, "d": 2}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_constant_blocks([["a", "b"]]) + + Test if all formats are really possible:: + + sage: bij.possible_values(p="a") + {'a': {1, 2}, 'b': {1, 2}} + sage: bij.possible_values(p=["a", "b"]) + {'a': {1, 2}, 'b': {1, 2}} + sage: bij.possible_values(p=[["a", "b"]]) + {'a': {1, 2}, 'b': {1, 2}} + sage: bij.possible_values(p=[["a", "b"], ["c"]]) + {'a': {1, 2}, 'b': {1, 2}, 'c': {1, 2}} + + Test optimal:: + + sage: bij.possible_values(p=["a", "c"], optimal=True) + {'a': {1, 2}, 'b': {1, 2}, 'c': {1, 2}} + + Verify by listing all solutions:: + + sage: sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))) + [{'a': 1, 'b': 1, 'c': 2, 'd': 2}, {'a': 2, 'b': 2, 'c': 1, 'd': 1}] + + Test if MIPSolverException is thrown:: + + sage: A = B = list('ab') + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) + sage: bij.set_constant_blocks([['a', 'b']]) + sage: bij.possible_values(p="a") + {'a': {0, 1}, 'b': {0, 1}} + sage: bij.possible_values(p="a", optimal=True) + Traceback (most recent call last): + ... + sage.numerical.mip.MIPSolverException: ... + + Another example:: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: alpha = lambda p: p(1) if len(p) > 0 else 0 + sage: beta = lambda p: p(1) if len(p) > 0 else 0 + sage: bij.set_statistics((alpha, beta), (len, len)) + sage: for sol in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(sol) + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} + sage: bij.possible_values(p=[Permutation([1]), Permutation([1, 2, 3]), Permutation([3, 1, 2])], optimal=True) + {[1]: {1}, [1, 2, 3]: {2, 3}, [3, 1, 2]: {1, 2}} + + Another example:: + + sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] + sage: tau = lambda D: D.number_of_touch_points() + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 1, [1, 1, 0, 0]: 2} + {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} + sage: bij.possible_values(p=[DyckWord([]), DyckWord([1, 0]), DyckWord([1, 0, 1, 0]), DyckWord([1, 1, 0, 0])], optimal=True) + {[]: {0}, [1, 0]: {1}, [1, 0, 1, 0]: {1, 2}, [1, 1, 0, 0]: {1, 2}} + + .. TODO: + + test der zeigt, dass die Lösung für alle Blöcke nicht + langsamer ist als mit solutions_iterator + + """ + # convert input to set of block representatives + blocks = set() + if p in self._A: + blocks.add(self._P.find(p)) + elif type(p) is list: + for p1 in p: + if p1 in self._A: + blocks.add(self._P.find(p1)) + elif type(p1) is list: + for p2 in p1: + blocks.add(self._P.find(p2)) + + if optimal: + # function adding a solution to dict of solutions + def add_solution(solutions, solution): + for p, value in solution.items(): + if p not in solutions: + solutions[p] = set() + solutions[p].add(value) + + # generate initial solution, solution dict and add solution + bmilp = self._generate_and_solve_initial_bmilp() + solution = self._solution(bmilp) + solutions = {} + add_solution(solutions, solution) + + # iterate through blocks and generate all values + for p in blocks: + veto_bmilp = deepcopy(bmilp) # adding constraints to a simple copy adds them to the original instance, too + for value in solutions[p]: + veto_bmilp.milp.add_constraint(veto_bmilp._x[p, value] == 0) + while True: + try: + veto_bmilp.milp.solve() + # problem has a solution, so new value was found + solution = self._solution(veto_bmilp) + add_solution(solutions, solution) + # veto new value and try again + veto_bmilp.milp.add_constraint(veto_bmilp._x[p, solution[p]] == 0) + except MIPSolverException: + # no solution, so all possible values have been found + break + + # TODO: update possible block values if wanted + + # create dictionary to return + possible_values = {} + for p in blocks: + for a in self._P.root_to_elements_dict()[p]: # TODO: is this the format we want to return in or possible_values[block]? + if optimal: + possible_values[a] = solutions[p] + else: + possible_values[a] = self._possible_block_values[p] + + return possible_values + + def minimal_subdistributions_iterator(self, tA=None): + r""" + Return all minimal subsets `\tilde A` of `A` containing `tA` + together with submultisets `\tilde Z` with `s(\tilde A) = + \tilde Z` as multisets. + + TODO: should this method interact with ``self._elements_distributions``? + + INPUT: + + - ``tA`` (optional, default: ``None``) -- a subset of `A` TODO: add this + + If ``tA`` is not ``None``, return an iterator of the + subdistributions containing ``tA``. + + TESTS:: + + sage: A = B = [permutation for n in range(3) for permutation in Permutations(n)] + sage: bij = Bijectionist(A, B, len) + sage: bij.set_statistics((len, len)) + sage: for sol in bij.solutions_iterator(): + ....: print(sol) + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 2} + sage: sorted(bij.minimal_subdistributions_iterator()) + [([[]], [0]), ([[1]], [1]), ([[1, 2]], [2]), ([[2, 1]], [2])] + + Another example:: + + sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] + sage: tau = lambda D: D.number_of_touch_points() + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 1, [1, 1, 0, 0]: 2} + {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} + sage: for subdistribution in bij.minimal_subdistributions_iterator(): + ....: print(subdistribution) + ([[]], [0]) + ([[1, 0]], [1]) + ([[1, 0, 1, 0], [1, 1, 0, 0]], [1, 2]) + + An example with two elements of the same block in a subdistribution:: + + sage: A = B = ["a", "b", "c", "d", "e"] + sage: tau = {"a": 1, "b": 1, "c": 2, "d": 2, "e": 3}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_constant_blocks([["a", "b"]]) + sage: bij.set_value_restrictions(("a", [1, 2])) + sage: bij.constant_blocks(optimal=True) + {{'a', 'b'}} + sage: list(bij.minimal_subdistributions_iterator()) + [(['a', 'b', 'c', 'd', 'e'], [1, 1, 2, 2, 3])] + """ + # see + # https://mathoverflow.net/questions/406751/find-a-subdistribution/406975 + # and + # https://gitlab.com/mantepse/bijection-tools/-/issues/29 + + minimal_subdistribution = MixedIntegerLinearProgram(maximization=False, solver=self._solver) + D = minimal_subdistribution.new_variable(binary=True) # the subset of elements + V = minimal_subdistribution.new_variable(integer=True) # the subdistribution + minimal_subdistribution.set_objective(sum(D[a] for a in self._A)) + minimal_subdistribution.add_constraint(sum(D[a] for a in self._A) >= 1) + + try: + bmilp = self._generate_and_solve_initial_bmilp() + except MIPSolverException: + return + s = self._solution(bmilp) + while True: + for v in self._Z: + minimal_subdistribution.add_constraint(sum(D[a] for a in self._A if s[a] == v) == V[v]) + try: + minimal_subdistribution.solve() + except MIPSolverException: + return + d = minimal_subdistribution.get_values(D) # a dict from A to {0, 1} + new_s = self._find_counter_example(bmilp, s, d) + if new_s is None: + values = self._sorter["Z"](s[a] for a in self._A if d[a]) + yield ([a for a in self._A if d[a]], values) + + # get all variables with value 1 + active_vars = [D[a] for a in self._A + if minimal_subdistribution.get_values(D[a])] + + # add constraint that not all of these can be 1, thus vetoing + # the current solution + minimal_subdistribution.add_constraint(sum(active_vars) <= len(active_vars) - 1, + name="veto") + # TODO: can we ignore that in the next step the same constraint is added again? + else: + s = new_s + + def _find_counter_example(self, bmilp, s0, d): + r""" + Return a solution `s` such that ``d`` is not a subdistribution of + `s0`. + + TODO: better name + + INPUT: + + - ``bmilp``, the mixed linear integer program + + - ``s0``, a solution + + - ``d``, a subset of `A`, in the form of a dict from `A` to `\{0, 1\}` + """ + for v in self._Z: + v_in_d_count = sum(d[a] for a in self._A if s0[a] == v) + if not v_in_d_count: + continue + + veto_bmilp = deepcopy(bmilp) # adding constraints to a simple copy adds them to the original instance, too + # try to find a solution which has a different + # subdistribution on d than s0 + v_in_d = sum(d[a] * veto_bmilp._x[self._P.find(a), v] + for a in self._A + if v in self._possible_block_values[self._P.find(a)]) + + # it is sufficient to require that v occurs less often as + # a value among {a | d[a] == 1} than it does in + # v_in_d_count, because, if the distributions are + # different, one such v must exist + veto_bmilp.milp.add_constraint(v_in_d <= v_in_d_count - 1) + try: + veto_bmilp.milp.solve() + return self._solution(veto_bmilp) + except MIPSolverException: + pass + return + + def minimal_subdistributions_blocks_iterator(self, p=None): + r"""Return all representatives of minimal subsets `\tilde P` + of `P` containing `p` together with submultisets `\tilde Z` + with `s(\tilde P) = \tilde Z` as multisets. + + .. WARNING:: + + If there are several solutions with the same support + (i.e., the sets of block representatives are the same), + only one of these will be found, even if the + distributions are different, see the doctest below. To + find all solutions, use + :meth:`minimal_subdistributions_iterator`, which is, + however, computationally more expensive. + + TODO: should this method interact with ``self._elements_distributions``? + + INPUT: + + - ``p`` (optional, default: ``None``) -- a subset of `P` TODO: add this + + If ``p`` is not ``None``, return an iterator of the + subdistributions containing ``p``. + + EXAMPLES:: + + sage: A = B = [permutation for n in range(3) for permutation in Permutations(n)] + sage: bij = Bijectionist(A, B, len) + sage: bij.set_statistics((len, len)) + sage: for sol in bij.solutions_iterator(): + ....: print(sol) + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 2} + sage: sorted(bij.minimal_subdistributions_blocks_iterator()) + [([[]], [0]), ([[1]], [1]), ([[1, 2]], [2]), ([[2, 1]], [2])] + + Another example:: + + sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] + sage: tau = lambda D: D.number_of_touch_points() + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) + sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): + ....: print(solution) + {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 1, [1, 1, 0, 0]: 2} + {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} + sage: for subdistribution in bij.minimal_subdistributions_blocks_iterator(): + ....: print(subdistribution) + ([[]], [0]) + ([[1, 0]], [1]) + ([[1, 0, 1, 0], [1, 1, 0, 0]], [1, 2]) + + An example with two elements of the same block in a subdistribution:: + + sage: A = B = ["a", "b", "c", "d", "e"] + sage: tau = {"a": 1, "b": 1, "c": 2, "d": 2, "e": 3}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_constant_blocks([["a", "b"]]) + sage: bij.set_value_restrictions(("a", [1, 2])) + sage: bij.constant_blocks(optimal=True) + {{'a', 'b'}} + sage: list(bij.minimal_subdistributions_blocks_iterator()) + [(['a', 'a', 'c', 'd', 'e'], [1, 1, 2, 2, 3])] + + An example with overlapping minimal subdistributions:: + + sage: A = B = ["a", "b", "c", "d", "e"] + sage: tau = {"a": 1, "b": 1, "c": 2, "d": 2, "e": 3}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_distributions((["a", "b"], [1, 2]), (["a", "c", "d"], [1, 2, 3])) + sage: sorted(bij.solutions_iterator(), key=lambda d: tuple(sorted(d.items()))) + [{'a': 1, 'b': 2, 'c': 2, 'd': 3, 'e': 1}, + {'a': 1, 'b': 2, 'c': 3, 'd': 2, 'e': 1}, + {'a': 2, 'b': 1, 'c': 1, 'd': 3, 'e': 2}, + {'a': 2, 'b': 1, 'c': 3, 'd': 1, 'e': 2}] + sage: bij.constant_blocks(optimal=True) + {{'a', 'e'}} + sage: list(bij.minimal_subdistributions_blocks_iterator()) + [(['a', 'b'], [1, 2]), (['a', 'c', 'd'], [1, 2, 3])] + + Fedor Petrov's example from https://mathoverflow.net/q/424187:: + + sage: A = B = ["a"+str(i) for i in range(1, 9)] + ["b"+str(i) for i in range(3, 9)] + ["d"] + sage: tau = {b: 0 if i < 10 else 1 for i, b in enumerate(B)}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_constant_blocks([["a"+str(i), "b"+str(i)] for i in range(1, 9) if "b"+str(i) in A]) + sage: d = [0]*8+[1]*4 + sage: bij.set_distributions((A[:8] + A[8+2:-1], d), (A[:8] + A[8:-3], d)) + sage: sorted([s[a] for a in A] for s in bij.solutions_iterator()) + [[0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1], + [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0], + [0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], + [0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0], + [1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0], + [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0], + [1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0], + [1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0], + [1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1], + [1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1]] + + sage: sorted(bij.minimal_subdistributions_blocks_iterator()) + [(['a1', 'a2', 'a3', 'a4', 'a5', 'a5', 'a6', 'a6', 'a7', 'a7', 'a8', 'a8'], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]), + (['a3', 'a4', 'd'], [0, 0, 1]), + (['a7', 'a8', 'd'], [0, 0, 1])] + + The following solution is not found, because it happens to + have the same support as the other:: + + sage: D = set(A).difference(['b7', 'b8', 'd']) + sage: sorted(a.replace("b", "a") for a in D) + ['a1', 'a2', 'a3', 'a3', 'a4', 'a4', 'a5', 'a5', 'a6', 'a6', 'a7', 'a8'] + sage: set(tuple(sorted(s[a] for a in D)) for s in bij.solutions_iterator()) + {(0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1)} + + But it is, by design, included here:: + + sage: sorted(D) in [d for d, _ in bij.minimal_subdistributions_iterator()] + True + + """ + # see + # https://mathoverflow.net/questions/406751/find-a-subdistribution/406975 + # and + # https://gitlab.com/mantepse/bijection-tools/-/issues/29 + # see https://mathoverflow.net/q/424187 for Fedor Petrov's example + + minimal_subdistribution = MixedIntegerLinearProgram(maximization=False, solver=self._solver) + D = minimal_subdistribution.new_variable(integer=True, nonnegative=True) # the submultiset of elements + X = minimal_subdistribution.new_variable(binary=True) # the support of D + V = minimal_subdistribution.new_variable(integer=True, nonnegative=True) # the subdistribution + P = _disjoint_set_roots(self._P) + minimal_subdistribution.set_objective(sum(D[p] for p in P)) + minimal_subdistribution.add_constraint(sum(D[p] for p in P) >= 1) + for p in P: + minimal_subdistribution.add_constraint(D[p] <= len(self._P.root_to_elements_dict()[p])) + minimal_subdistribution.add_constraint(X[p]*len(self._P.root_to_elements_dict()[p]) >= D[p] >= X[p]) + + def add_counter_example_constraint(s): + for v in self._Z: + minimal_subdistribution.add_constraint(sum(D[p] for p in P + if s[p] == v) == V[v]) + + try: + bmilp = self._generate_and_solve_initial_bmilp() + except MIPSolverException: + return + s = self._solution_by_blocks(bmilp) + add_counter_example_constraint(s) + while True: + try: + minimal_subdistribution.solve() + except MIPSolverException: + return + d = minimal_subdistribution.get_values(D) # a dict from P to multiplicities + new_s = self._find_counter_example2(bmilp, P, s, d) + if new_s is None: + yield ([p for p in P for _ in range(ZZ(d[p]))], + self._sorter["Z"](s[p] + for p in P + for _ in range(ZZ(d[p])))) + + support = [X[p] for p in P if d[p]] + # add constraint that the support is different + minimal_subdistribution.add_constraint(sum(support) <= len(support) - 1, + name="veto") + else: + s = new_s + add_counter_example_constraint(s) + + def _find_counter_example2(self, bmilp, P, s0, d): + r""" + Return a solution `s` such that ``d`` is not a subdistribution of + `s0`. + + TODO: better name + + INPUT: + + - ``bmilp``, the mixed linear integer program + + - ``P``, the representatives of the blocks + + - ``s0``, a solution + + - ``d``, a subset of `A`, in the form of a dict from `A` to `\{0, 1\}` + """ + for v in self._Z: + v_in_d_count = sum(d[p] for p in P if s0[p] == v) + if not v_in_d_count: + continue + + veto_bmilp = deepcopy(bmilp) # adding constraints to a simple copy adds them to the original instance, too + # try to find a solution which has a different + # subdistribution on d than s0 + v_in_d = sum(d[p] * veto_bmilp._x[p, v] + for p in P + if v in self._possible_block_values[p]) + + # it is sufficient to require that v occurs less often as + # a value among {a | d[a] == 1} than it does in + # v_in_d_count, because, if the distributions are + # different, one such v must exist + veto_bmilp.milp.add_constraint(v_in_d <= v_in_d_count - 1) + try: + veto_bmilp.milp.solve() + return self._solution_by_blocks(veto_bmilp) + except MIPSolverException: + pass + return + + def _preprocess_intertwining_relations(self): + r""" + TODO: (medium) untangle side effect and return value if possible + + Make `self._P` be the finest set partition coarser than `self._P` + such that composing elements preserves blocks. + + Suppose that `p_1`, `p_2` are blocks of `P`, and `a_1, a'_1 + \in p_1` and `a_2, a'_2\in p_2`. Then, + + .. MATH: + + s(\pi(a_1, a_2)) + = \rho(s(a_1), s(a_2)) + = \rho(s(a'_1), s(a'_2)) + = s(\pi(a'_1, a'_2)). + + Therefore, `\pi(a_1, a_2)` and `\pi(a'_1, a'_2)` are in the + same block. + + In other words, `s(\pi(a_1,\dots,a_k))` only depends on the + blocks of `a_1,\dots,a_k`. + + TESTS:: + + TODO: create one test with one and one test with two + intertwining_relations + + """ + images = {} # A^k -> A, a_1,...,a_k to pi(a_1,...,a_k), for all pi + origins_by_elements = [] # (pi/rho, pi(a_1,...,a_k), a_1,...,a_k) + for composition_index, pi_rho in enumerate(self._pi_rho): + for a_tuple in itertools.product(*([self._A]*pi_rho.numargs)): + if pi_rho.domain is not None and not pi_rho.domain(*a_tuple): + continue + a = pi_rho.pi(*a_tuple) + if a in self._A: + if a in images: + # this happens if there are several pi's of the same arity + images[a_tuple].add(a) # TODO: (low) wouldn't self._P.find(a) be more efficient here? + else: + images[a_tuple] = set((a,)) # TODO: (low) wouldn't self._P.find(a) be more efficient here? + origins_by_elements.append((composition_index, a, a_tuple)) + + # merge blocks + something_changed = True + while something_changed: + something_changed = False + # collect (preimage, image) pairs by (representatives) of + # the blocks of the elements of the preimage + updated_images = {} # (p_1,...,p_k) to {a_1,....} + for a_tuple, image_set in images.items(): + representatives = tuple(self._P.find(a) for a in a_tuple) + if representatives in updated_images: + updated_images[representatives].update(image_set) + else: + updated_images[representatives] = image_set + + # merge blocks + for a_tuple, image_set in updated_images.items(): + image = image_set.pop() + while image_set: + self._P.union(image, image_set.pop()) + something_changed = True + # we keep a representative + image_set.add(image) + + images = updated_images + + origins = set() + for composition_index, image, preimage in origins_by_elements: + origins.add((composition_index, + self._P.find(image), + tuple(self._P.find(a) for a in preimage))) + return origins + + def solutions_iterator(self): + r""" + An iterator over all solutions of the problem. + + OUTPUT: An iterator over all possible mappings `s: A\to Z` + + ALGORITHM: + + We solve an integer linear program with a binary variable + `x_{p, z}` for each partition block `p\in P` and each + statistic value `z\in Z`: + + - `x_{p, z} = 1` if and only if `s(a) = z` for all `a\in p`. + + Then we add the constraint `\sum_{x\in V} x<|V|`, where `V` + is the set containing all `x` with `x = 1`, that is, those + indicator variables representing the current solution. + Therefore, a solution of this new program must be different + from all those previously obtained. + + INTEGER LINEAR PROGRAM: + + * Let `m_w(p)`, for a block `p` of `P`, be the multiplicity + of the value `w` in `W` under `\alpha`, that is, the number + of elements `a \in p` with `\alpha(a)=w`. + + * Let `n_w(z)` be the number of elements `b \in B` with + `\beta(b)=w` and `\tau(b)=z` for `w \in W`, `z \in Z`. + + * Let `k` be the arity of a pair `(\pi, \rho)` in an + intertwining relation. + + and the following constraints: + + * because every block is assigned precisely one value, for + all `p\in P`, + + .. MATH:: + + \sum_z x_{p, z} = 1. + + * because the statistics `s` and `\tau` and also `\alpha` and + `\beta` are equidistributed, for all `w\in W` and `z\in Z`, + + .. MATH:: + + \sum_p m_w(p) x_{p, z} = n_w(z). + + * for each intertwining relation `s(\pi(a_1,\dots, a_k)) = + \rho(s(a_1),\dots, s(a_r))`, and for all `k`-combinations + of blocks `p_i\in P` such that there exist `(a_1,\dots, + a_k)\in p_1\times\dots\times p_k` with `\pi(a_1,\dots, + a_k)\in W` and `z = \rho(z_1,\dots, z_k)`, + + .. MATH:: + + x_{p, z} \geq 1-k + \sum_{i=1}^k x_{p_i, z_i}. + + * for each distribution restriction, i.e. a set of elements + `e` and a distribution of values given by integers `d_z` + representing the multiplicity of each `z \in Z`, and `r_p = + |p \cap e|` indicating the relative size of block `p` in + the set of elements of the distribution, + + .. MATH:: + + \sum_p r_p x_{p, z} = d_z. + + EXAMPLES:: + + sage: A = B = list('abc') + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2, solver="GLPK") + sage: next(bij.solutions_iterator()) + {'a': 0, 'b': 1, 'c': 0} + + sage: list(bij.solutions_iterator()) + [{'a': 0, 'b': 1, 'c': 0}, + {'a': 1, 'b': 0, 'c': 0}, + {'a': 0, 'b': 0, 'c': 1}] + + sage: N = 4 + sage: A = B = [permutation for n in range(N) for permutation in Permutations(n)] + + Let `\tau` be the number of non-left-to-right-maxima of a + permutation:: + + sage: def tau(pi): + ....: pi = list(pi) + ....: i = count = 0 + ....: for j in range(len(pi)): + ....: if pi[j] > i: + ....: i = pi[j] + ....: else: + ....: count += 1 + ....: return count + + We look for a statistic which is constant on conjugacy classes:: + + sage: P = [list(a) for n in range(N) for a in Permutations(n).conjugacy_classes()] + + sage: bij = Bijectionist(A, B, tau, solver="GLPK") + sage: bij.set_statistics((len, len)) + sage: bij.set_constant_blocks(P) + sage: for solution in bij.solutions_iterator(): + ....: print(solution) + {[]: 0, [1]: 0, [1, 2]: 1, [2, 1]: 0, [1, 2, 3]: 0, [1, 3, 2]: 1, [2, 1, 3]: 1, [3, 2, 1]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2} + {[]: 0, [1]: 0, [1, 2]: 0, [2, 1]: 1, [1, 2, 3]: 0, [1, 3, 2]: 1, [2, 1, 3]: 1, [3, 2, 1]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2} + + Setting the verbosity prints the MILP which is solved:: + + sage: set_verbose(2) + sage: _ = list(bij.solutions_iterator()) + Constraints are: + block []: 1 <= x_0 <= 1 + block [1]: 1 <= x_1 <= 1 + block [1, 2]: 1 <= x_2 + x_3 <= 1 + block [2, 1]: 1 <= x_4 + x_5 <= 1 + block [1, 2, 3]: 1 <= x_6 + x_7 + x_8 <= 1 + block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 + block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 + statistics: 1 <= x_0 <= 1 + statistics: 1 <= x_1 <= 1 + statistics: 1 <= x_2 + x_4 <= 1 + statistics: 1 <= x_3 + x_5 <= 1 + statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 + statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 + statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 + Variables are: + x_0: s([]) = 0 + x_1: s([1]) = 0 + x_2: s([1, 2]) = 0 + x_3: s([1, 2]) = 1 + x_4: s([2, 1]) = 0 + x_5: s([2, 1]) = 1 + x_6: s([1, 2, 3]) = 0 + x_7: s([1, 2, 3]) = 1 + x_8: s([1, 2, 3]) = 2 + x_9: s([1, 3, 2]) = s([2, 1, 3]) = s([3, 2, 1]) = 0 + x_10: s([1, 3, 2]) = s([2, 1, 3]) = s([3, 2, 1]) = 1 + x_11: s([1, 3, 2]) = s([2, 1, 3]) = s([3, 2, 1]) = 2 + x_12: s([2, 3, 1]) = s([3, 1, 2]) = 0 + x_13: s([2, 3, 1]) = s([3, 1, 2]) = 1 + x_14: s([2, 3, 1]) = s([3, 1, 2]) = 2 + after vetoing + Constraints are: + block []: 1 <= x_0 <= 1 + block [1]: 1 <= x_1 <= 1 + block [1, 2]: 1 <= x_2 + x_3 <= 1 + block [2, 1]: 1 <= x_4 + x_5 <= 1 + block [1, 2, 3]: 1 <= x_6 + x_7 + x_8 <= 1 + block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 + block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 + statistics: 1 <= x_0 <= 1 + statistics: 1 <= x_1 <= 1 + statistics: 1 <= x_2 + x_4 <= 1 + statistics: 1 <= x_3 + x_5 <= 1 + statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 + statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 + statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 + veto: x_0 + x_1 + x_3 + x_4 + x_6 + x_10 + x_14 <= 6 + after vetoing + Constraints are: + block []: 1 <= x_0 <= 1 + block [1]: 1 <= x_1 <= 1 + block [1, 2]: 1 <= x_2 + x_3 <= 1 + block [2, 1]: 1 <= x_4 + x_5 <= 1 + block [1, 2, 3]: 1 <= x_6 + x_7 + x_8 <= 1 + block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 + block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 + statistics: 1 <= x_0 <= 1 + statistics: 1 <= x_1 <= 1 + statistics: 1 <= x_2 + x_4 <= 1 + statistics: 1 <= x_3 + x_5 <= 1 + statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 + statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 + statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 + veto: x_0 + x_1 + x_3 + x_4 + x_6 + x_10 + x_14 <= 6 + veto: x_0 + x_1 + x_2 + x_5 + x_6 + x_10 + x_14 <= 6 + + sage: set_verbose(0) + + TESTS: + + An unfeasible problem:: + + sage: A = ["a", "b", "c", "d"]; B = [1, 2, 3, 4] + sage: bij = Bijectionist(A, B) + sage: bij.set_value_restrictions(("a", [1, 2]), ("b", [1, 2]), ("c", [1, 3]), ("d", [2, 3])) + sage: list(bij.solutions_iterator()) + [] + + """ + try: + bmilp = self._generate_and_solve_initial_bmilp() + except MIPSolverException: + return + while True: + yield self._solution(bmilp) + bmilp.veto_current_solution() + if get_verbose() >= 2: + print("after vetoing") + self._show_bmilp(bmilp, variables=False) + try: + bmilp.milp.solve() + except MIPSolverException: + return + + def _solution(self, bmilp): + """ + Return the bmilp solution as a dictionary from `A` to + `Z`. + + """ + map = {} # A -> Z, a +-> s(a) + for p, block in self._P.root_to_elements_dict().items(): + for z in self._possible_block_values[p]: + if bmilp.milp.get_values(bmilp._x[p, z]) == 1: + for a in block: + map[a] = z + break + return map + + def _solution_by_blocks(self, bmilp): + """ + Return the bmilp solution as a dictionary from block + representatives of `P` to `Z`. + + """ + map = {} # P -> Z, a +-> s(a) + for p in _disjoint_set_roots(self._P): + for z in self._possible_block_values[p]: + if bmilp.milp.get_values(bmilp._x[p, z]) == 1: + map[p] = z + break + return map + + def _show_bmilp(self, bmilp, variables=True): + """ + Print the constraints and variables of the current MILP + together with some explanations. + + """ + print("Constraints are:") + b = bmilp.milp.get_backend() + varid_name = {} + for i in range(b.ncols()): + s = b.col_name(i) + default_name = str(bmilp.milp.linear_functions_parent()({i: 1})) + if s and s != default_name: + varid_name[i] = s + else: + varid_name[i] = default_name + for i, (lb, (indices, values), ub) in enumerate(bmilp.milp.constraints()): + if b.row_name(i): + print(" "+b.row_name(i)+":", end=" ") + if lb is not None: + print(str(ZZ(lb))+" <=", end=" ") + first = True + for j, c in sorted(zip(indices, values)): + c = ZZ(c) + if c == 0: + continue + print((("+ " if (not first and c > 0) else "") + + ("" if c == 1 else + ("- " if c == -1 else + (str(c) + " " if first and c < 0 else + ("- " + str(abs(c)) + " " if c < 0 else str(c) + " ")))) + + varid_name[j]), end=" ") + first = False + # Upper bound + print("<= "+str(ZZ(ub)) if ub is not None else "") + + if variables: + print("Variables are:") + for (p, z), v in bmilp._x.items(): + print(f" {v}: " + "".join([f"s({a}) = " + for a in self._P.root_to_elements_dict()[p]]) + f"{z}") + + def _generate_and_solve_initial_bmilp(self): + r""" + Generate a _BijectionistMILP, add all relevant constraints and call MILP.solve(). + """ + preimage_blocks = self._preprocess_intertwining_relations() + self._compute_possible_block_values() + + bmilp = _BijectionistMILP(self) + n = bmilp.milp.number_of_variables() + bmilp.add_alpha_beta_constraints() + bmilp.add_distribution_constraints() + bmilp.add_interwining_relation_constraints(preimage_blocks) + if get_verbose() >= 2: + self._show_bmilp(bmilp) + assert n == bmilp.milp.number_of_variables(), "The number of variables increased." + bmilp.milp.solve() + return bmilp + + +class _BijectionistMILP(SageObject): + r""" + Wrapper class for the MixedIntegerLinearProgram (MILP). This class is used to manage the MILP, + add constraints, solve the problem and check for uniqueness of solution values. + """ + def __init__(self, bijectionist: Bijectionist): + # TODO: it would be cleaner not to pass the full bijectionist + # instance, but only those attributes we actually use: + # _possible_block_values + # _elements_distributions + # _W, _Z, _A, _B, _P, _alpha, _beta, _tau, _pi_rho + self.milp = MixedIntegerLinearProgram(solver=bijectionist._solver) + self.milp.set_objective(None) + self._x = self.milp.new_variable(binary=True) # indexed by P x Z + + self._bijectionist = bijectionist + + for p in _disjoint_set_roots(bijectionist._P): + name = f"block {p}" + self.milp.add_constraint(sum(self._x[p, z] + for z in bijectionist._possible_block_values[p]) == 1, + name=name[:50]) + + def add_alpha_beta_constraints(self): + r""" + Add constraints enforcing that `(alpha, s)` is equidistributed + with `(beta, tau)` and `S` is the intertwining bijection. + + We do this by adding + + .. MATH:: + + \sum_{a\in A, z\in Z} x_{p(a), z} s^z t^{\alpha(a)} + = \sum_{b\in B} s^{\tau(b)} t(\beta(b)) + + as a matrix equation. + + """ + W = self._bijectionist._W + Z = self._bijectionist._Z + AZ_matrix = [[ZZ(0)]*len(W) for _ in range(len(Z))] + B_matrix = [[ZZ(0)]*len(W) for _ in range(len(Z))] + + W_dict = {w: i for i, w in enumerate(W)} + Z_dict = {z: i for i, z in enumerate(Z)} + + for a in self._bijectionist._A: + p = self._bijectionist._P.find(a) + for z in self._bijectionist._possible_block_values[p]: + w_index = W_dict[self._bijectionist._alpha(a)] + z_index = Z_dict[z] + AZ_matrix[z_index][w_index] += self._x[p, z] + + for b in self._bijectionist._B: + w_index = W_dict[self._bijectionist._beta(b)] + z_index = Z_dict[self._bijectionist._tau[b]] + B_matrix[z_index][w_index] += 1 + + # TODO: (low) I am not sure that this is the best way to + # filter out empty conditions + for w in range(len(W)): + for z in range(len(Z)): + c = AZ_matrix[z][w] - B_matrix[z][w] + if c.is_zero(): + continue + if c in ZZ: + raise MIPSolverException + self.milp.add_constraint(c == 0, name="statistics") + + def add_distribution_constraints(self): + r""" + Add constraints so the distributions given by + :meth:`~Bijectionist.set_distributions` are fulfilled. + + To accomplish this we add + + .. MATH:: + + \sum_{a\in elements} x_{p(a), z}t^z = \sum_{z\in values} t^z, + + where `p(a)` is the block containing `a`, for each given + distribution as a vector equation. + + """ + Z = self._bijectionist._Z + Z_dict = {z: i for i, z in enumerate(Z)} + for elements, values in self._bijectionist._elements_distributions: + elements_sum = [ZZ(0)]*len(Z_dict) + values_sum = [ZZ(0)]*len(Z_dict) + for a in elements: + p = self._bijectionist._P.find(a) + for z in self._bijectionist._possible_block_values[p]: + elements_sum[Z_dict[z]] += self._x[p, z] + for z in values: + values_sum[Z_dict[z]] += 1 + + # TODO: (low) I am not sure that this is the best way to + # filter out empty conditions + for element, value in zip(elements_sum, values_sum): + c = element - value + if c.is_zero(): + continue + if c in ZZ: + raise MIPSolverException + self.milp.add_constraint(c == 0, name=f"d: {element} == {value}") + + def add_interwining_relation_constraints(self, origins): + r""" + Add constraints corresponding to the given intertwining + relations. + + This adds the constraints imposed by + :meth:`~Bijectionist.set_intertwining_relations`. + + .. MATH:: + + s(\pi(a_1,\dots, a_k)) = \rho(s(a_1),\dots, s(a_k))` + + for each pair `(\pi, \rho)`. The relation implies + immediately that `s(\pi(a_1,\dots, a_k))` only depends on the + blocks of `a_1,\dots, a_k`. + + The MILP formulation is as follows. Let `a_1,\dots,a_k \in + A` and let `a = \pi(a_1,\dots,a_k)`. Let `z_1,\dots,z_k \in + Z` and let `z = \rho(z_1,\dots,z_k)`. Suppose that `a_i\in + p_i` for all `i` and that `a\in p`. + + We then want to model the implication + + .. MATH:: + + x_{p_1, z_1} = 1,\dots, x_{p_k, z_k} = 1 \Rightarrow x_{p, z} = 1. + + We achieve this by requiring + + .. MATH:: + + x_{p, z}\geq 1 - k + \sum_{i=1}^k x_{p_i, z_i}. + + Not that `z` must be a possible value of `p` and each `z_i` + must be a possible value of `p_i`. + + INPUT: + + - origins, a list of triples `((\pi/\rho, p, + (p_1,\dots,p_k))`, where `p` is the block of + `\rho(s(a_1),\dots, s(a_k))`, for any `a_i\in p_i`. + + TODO: TESTS + + """ + for composition_index, image_block, preimage_blocks in origins: + pi_rho = self._bijectionist._pi_rho[composition_index] + # iterate over all possible value combinations of the origin blocks + for z_tuple in itertools.product(*[self._bijectionist._possible_block_values[p] + for p in preimage_blocks]): + rhs = 1 - pi_rho.numargs + sum(self._x[p_i, z_i] + for p_i, z_i in zip(preimage_blocks, z_tuple)) + z = pi_rho.rho(*z_tuple) + if z in self._bijectionist._possible_block_values[image_block]: + c = self._x[image_block, z] - rhs + if c.is_zero(): + continue + self.milp.add_constraint(c >= 0, + name=f"pi/rho({composition_index})") + else: + self.milp.add_constraint(rhs <= 0, + name=f"pi/rho({composition_index})") + + def veto_current_solution(self): + r""" + Add a constraint vetoing the current solution. + + This adds a constraint such that the next call to + :meth:`MixedIntegerLinearProgram.solve()` must return a + solution different from the current one. + + We require that the MILP currently has a solution. + + .. WARNING:: + + The underlying MILP will be modified! + + ALGORITHM: + + We add the constraint `\sum_{x\in V} x < |V|`` where `V` is + the set of variables `x_{p, z}` with value 1, that is, the + set of variables indicating the current solution. + + """ + # get all variables with value 1 + active_vars = [self._x[p, z] + for p in _disjoint_set_roots(self._bijectionist._P) + for z in self._bijectionist._possible_block_values[p] + if self.milp.get_values(self._x[p, z])] + + # add constraint that not all of these can be 1, thus vetoing + # the current solution + self.milp.add_constraint(sum(active_vars) <= len(active_vars) - 1, + name="veto") + + +def _invert_dict(d): + """ + Return the dictionary whose keys are the values of the input and + whose values are the lists of preimages. + """ + preimages = {} + for k, v in d.items(): + preimages[v] = preimages.get(v, []) + [k] + return preimages + + +def _disjoint_set_roots(d): + """ + Return the representatives of the blocks of the disjoint set. + """ + return d.root_to_elements_dict().keys() + + +""" +TESTS:: + + sage: As = Bs = [[], + ....: [(1,i,j) for i in [-1,0,1] for j in [-1,1]], + ....: [(2,i,j) for i in [-1,0,1] for j in [-1,1]], + ....: [(3,i,j) for i in [-2,-1,0,1,2] for j in [-1,1]]] + + # adding [(2,-2,-1), (2,2,-1), (2,-2,1), (2,2,1)] makes it take (seemingly) forever + + sage: c1 = lambda a, b: (a[0]+b[0], a[1]*b[1], a[2]*b[2]) + sage: c2 = lambda a: (a[0], -a[1], a[2]) + + sage: bij = Bijectionist(sum(As, []), sum(Bs, [])) + sage: bij.set_statistics((lambda x: x[0], lambda x: x[0])) + sage: bij.set_intertwining_relations((2, c1, c1), (1, c2, c2)) + sage: l = list(bij.solutions_iterator()); len(l) + 64 + +A brute force check would be difficult:: + + sage: prod([factorial(len(A)) for A in As]) + 1881169920000 + +Let us try a smaller example:: + + sage: As = Bs = [[], + ....: [(1,i,j) for i in [-1,0,1] for j in [-1,1]], + ....: [(2,i,j) for i in [-1,1] for j in [-1,1]], + ....: [(3,i,j) for i in [-1,1] for j in [-1,1]]] + + sage: bij = Bijectionist(sum(As, []), sum(Bs, [])) + sage: bij.set_statistics((lambda x: x[0], lambda x: x[0])) + sage: bij.set_intertwining_relations((2, c1, c1), (1, c2, c2)) + sage: l1 = list(bij.solutions_iterator()); len(l1) + 16 + sage: prod([factorial(len(A)) for A in As]) + 414720 + + sage: pis = cartesian_product([Permutations(len(A)) for A in As]) + sage: it = ({a: Bs[n][pi[n][i]-1] for n, A in enumerate(As) for i, a in enumerate(A)} for pi in pis) + sage: A = sum(As, []) + sage: respects_c1 = lambda s: all(c1(a1, a2) not in A or s[c1(a1, a2)] == c1(s[a1], s[a2]) for a1 in A for a2 in A) + sage: respects_c2 = lambda s: all(c2(a1) not in A or s[c2(a1)] == c2(s[a1]) for a1 in A) + sage: l2 = [s for s in it if respects_c1(s) and respects_c2(s)] + sage: sorted(l1, key=lambda s: tuple(s.items())) == l2 + True + +""" + + +""" +Our benchmark example:: + + sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition + sage: alpha1 = lambda p: len(p.weak_excedences()) + sage: alpha2 = lambda p: len(p.fixed_points()) + sage: beta1 = lambda p: len(p.descents(final_descent=True)) if p else 0 + sage: beta2 = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: gamma = Permutation.longest_increasing_subsequence_length + sage: def rotate_permutation(p): + ....: cycle = Permutation(tuple(range(1, len(p)+1))) + ....: return Permutation([cycle.inverse()(p(cycle(i))) for i in range(1, len(p)+1)]) + + sage: N=5; As = [list(Permutations(n)) for n in range(N+1)]; A = B = sum(As, []); bij = Bijectionist(A, B, gamma); bij.set_statistics((len, len), (alpha1, beta1), (alpha2, beta2)); bij.set_constant_blocks(sum([orbit_decomposition(A, rotate_permutation) for A in As], [])) + + sage: P = bij.constant_blocks(optimal=True) + sage: P = [sorted(p, key=lambda p: (len(p), p)) for p in P] + sage: P = sorted(P, key=lambda p: (len(next(iter(p))), len(p))) + sage: for p in P: + ....: print(p) + [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]] + [[2, 1], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 2, 1], ... + [[3, 1, 2], [1, 4, 2, 3], [2, 4, 1, 3], [3, 1, 2, 4], [3, 1, 4, 2], ... + [[4, 1, 2, 3], [1, 5, 2, 3, 4], [4, 1, 2, 3, 5], [4, 5, 1, 2, 3], [5, 1, 2, 4, 3], ... + [[1, 3, 2, 5, 4], [2, 1, 3, 5, 4], [2, 1, 4, 3, 5], [5, 2, 4, 3, 1], [5, 3, 2, 4, 1]] + [[1, 3, 5, 2, 4], [2, 4, 1, 3, 5], [3, 5, 2, 4, 1], [4, 1, 3, 5, 2], [5, 2, 4, 1, 3]] + ... + + sage: for d in sorted(bij.minimal_subdistributions_blocks_iterator(), key=lambda d: (len(d[0]), d[0])): + ....: print(d) + ([[]], [0]) + ([[1]], [1]) + ([[2, 1]], [2]) + ([[3, 1, 2]], [3]) + ([[4, 1, 2, 3]], [4]) + ([[5, 1, 2, 3, 4]], [5]) + ([[2, 1, 4, 5, 3], [2, 3, 5, 1, 4], [2, 4, 1, 5, 3], [2, 4, 5, 1, 3]], [2, 3, 3, 3]) + ([[2, 1, 5, 3, 4], [2, 5, 1, 3, 4], [3, 1, 5, 2, 4], [3, 5, 1, 2, 4]], [3, 3, 4, 4]) + ([[1, 3, 2, 5, 4], [1, 3, 5, 2, 4], [1, 4, 2, 5, 3], [1, 4, 5, 2, 3], [1, 4, 5, 3, 2], [1, 5, 4, 2, 3], [1, 5, 4, 3, 2]], [2, 2, 3, 3, 3, 3, 3]) + + sage: l = list(bij.solutions_iterator()); len(l) # long time + 504 + + sage: for a, d in bij.minimal_subdistributions_iterator(): # long time + ....: print(sorted(a), sorted(d)) +""" From 6149eb25ff2301cd9ac7f41f8c2c6a6da367dccc Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 18 Oct 2022 15:23:48 +0200 Subject: [PATCH 154/392] make the linter happier --- src/sage/combinat/bijectionist.py | 76 ++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 452a013b470..12ce11ba64f 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -383,18 +383,13 @@ from copy import copy, deepcopy from sage.misc.verbose import get_verbose -# TODO: (low) für sagemath sollten wir Zeilen möglichst auf 79 -# Zeichen beschränken, das ist zwar nicht streng, wird aber lieber -# gesehen. - # TODO: (low) we frequently need variable names for subsets of A, B, # Z. In LaTeX, we mostly call them \tilde A, \tilde Z, etc. now. It # would be good to have a standard name in code, too. -# TODO: (medium) wann immer möglich, sollten die Tests in einer -# Methode nur diese eine Methode testen. Wir haben in fast allen -# Methoden "system tests", das ist unpraktisch, wenn man größere -# Änderungen durchführt. +# TODO: (medium) whenever possible, doctests of a method should only +# test this method. Currently we have very many system tests, which +# is inconvenient when modifying the design substantially. class Bijectionist(SageObject): @@ -1000,7 +995,13 @@ def set_value_restrictions(self, *a_values): However, an error occurs if the set of possible values is empty. In this example, the image of `\tau` under any - legal bijection is disjoint to the specified values. :: TODO: we now have to call _compute_possible_block_values() for the error message. Is this intended behaviour? + legal bijection is disjoint to the specified values. + + .. TODO:: + + we now have to call + :meth:`_compute_possible_block_values` for the error + message. Is this intended behaviour? sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: tau = Permutation.longest_increasing_subsequence_length @@ -1044,7 +1045,8 @@ def _compute_possible_block_values(self): """ self._possible_block_values = {} # P -> Power(Z) for p, block in self._P.root_to_elements_dict().items(): - self._possible_block_values[p] = set.intersection(*[self._restrictions_possible_values[a] for a in block], *[self._statistics_possible_values[a] for a in block]) + self._possible_block_values[p] = set.intersection(*[self._restrictions_possible_values[a] for a in block], + *[self._statistics_possible_values[a] for a in block]) if not self._possible_block_values[p]: if len(block) == 1: raise ValueError(f"No possible values found for singleton block {block}") @@ -1531,11 +1533,13 @@ def _forced_constant_blocks(self): self.set_constant_blocks(tmp_P) def possible_values(self, p=None, optimal=False): - r""" - Return for each block the values of `s` compatible with the + r"""Return for each block the values of `s` compatible with the imposed restrictions. - TODO: should this method update and return ``self._possible_block_values``? + .. TODO:: + + should this method update and return + ``self._possible_block_values``? INPUT: @@ -1621,10 +1625,10 @@ def possible_values(self, p=None, optimal=False): sage: bij.possible_values(p=[DyckWord([]), DyckWord([1, 0]), DyckWord([1, 0, 1, 0]), DyckWord([1, 1, 0, 0])], optimal=True) {[]: {0}, [1, 0]: {1}, [1, 0, 1, 0]: {1, 2}, [1, 1, 0, 0]: {1, 2}} - .. TODO: + .. TODO:: - test der zeigt, dass die Lösung für alle Blöcke nicht - langsamer ist als mit solutions_iterator + test to show that the solution for all blocks is not more + expensive than using :meth:`solutions_iterator` """ # convert input to set of block representatives @@ -1689,7 +1693,9 @@ def minimal_subdistributions_iterator(self, tA=None): together with submultisets `\tilde Z` with `s(\tilde A) = \tilde Z` as multisets. - TODO: should this method interact with ``self._elements_distributions``? + .. TODO:: + + should this method interact with ``self._elements_distributions``? INPUT: @@ -1832,15 +1838,21 @@ def minimal_subdistributions_blocks_iterator(self, p=None): :meth:`minimal_subdistributions_iterator`, which is, however, computationally more expensive. - TODO: should this method interact with ``self._elements_distributions``? + .. TODO:: + + should this method interact with ``self._elements_distributions``? INPUT: - - ``p`` (optional, default: ``None``) -- a subset of `P` TODO: add this + - ``p`` (optional, default: ``None``) -- a subset of `P` If ``p`` is not ``None``, return an iterator of the subdistributions containing ``p``. + .. TODO:: + + the optional argument is not yet supported + EXAMPLES:: sage: A = B = [permutation for n in range(3) for permutation in Permutations(n)] @@ -1992,7 +2004,10 @@ def _find_counter_example2(self, bmilp, P, s0, d): Return a solution `s` such that ``d`` is not a subdistribution of `s0`. - TODO: better name + .. TODO:: + + find a better name - possibly not relevant if we + implement the cache of solutions INPUT: @@ -2003,6 +2018,7 @@ def _find_counter_example2(self, bmilp, P, s0, d): - ``s0``, a solution - ``d``, a subset of `A`, in the form of a dict from `A` to `\{0, 1\}` + """ for v in self._Z: v_in_d_count = sum(d[p] for p in P if s0[p] == v) @@ -2030,7 +2046,10 @@ def _find_counter_example2(self, bmilp, P, s0, d): def _preprocess_intertwining_relations(self): r""" - TODO: (medium) untangle side effect and return value if possible + + .. TODO:: + + (medium) untangle side effect and return value if possible Make `self._P` be the finest set partition coarser than `self._P` such that composing elements preserves blocks. @@ -2051,10 +2070,10 @@ def _preprocess_intertwining_relations(self): In other words, `s(\pi(a_1,\dots,a_k))` only depends on the blocks of `a_1,\dots,a_k`. - TESTS:: + .. TODO:: - TODO: create one test with one and one test with two - intertwining_relations + create one test with one and one test with two + intertwining_relations """ images = {} # A^k -> A, a_1,...,a_k to pi(a_1,...,a_k), for all pi @@ -2549,8 +2568,6 @@ def add_interwining_relation_constraints(self, origins): (p_1,\dots,p_k))`, where `p` is the block of `\rho(s(a_1),\dots, s(a_k))`, for any `a_i\in p_i`. - TODO: TESTS - """ for composition_index, image_block, preimage_blocks in origins: pi_rho = self._bijectionist._pi_rho[composition_index] @@ -2685,7 +2702,12 @@ def _disjoint_set_roots(d): ....: cycle = Permutation(tuple(range(1, len(p)+1))) ....: return Permutation([cycle.inverse()(p(cycle(i))) for i in range(1, len(p)+1)]) - sage: N=5; As = [list(Permutations(n)) for n in range(N+1)]; A = B = sum(As, []); bij = Bijectionist(A, B, gamma); bij.set_statistics((len, len), (alpha1, beta1), (alpha2, beta2)); bij.set_constant_blocks(sum([orbit_decomposition(A, rotate_permutation) for A in As], [])) + sage: N=5 + sage: As = [list(Permutations(n)) for n in range(N+1)] + sage: A = B = sum(As, []) + sage: bij = Bijectionist(A, B, gamma) + sage: bij.set_statistics((len, len), (alpha1, beta1), (alpha2, beta2)) + sage: bij.set_constant_blocks(sum([orbit_decomposition(A, rotate_permutation) for A in As], [])) sage: P = bij.constant_blocks(optimal=True) sage: P = [sorted(p, key=lambda p: (len(p), p)) for p in P] From 9d4bfb97b1667fc7fc7e58527a9d8beae9e1f621 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 18 Oct 2022 18:10:06 +0200 Subject: [PATCH 155/392] remove useless assignment --- src/sage/combinat/bijectionist.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 12ce11ba64f..ac41442e414 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -719,9 +719,6 @@ def set_statistics(self, *alpha_beta): {[]: 2, [1]: 1, [1, 2]: 0, [2, 1]: 0} """ - # reset values - self._statistics_possible_values = {a: set(self._Z) for a in self._A} - self._n_statistics = len(alpha_beta) # TODO: (low) do we really want to recompute statistics every time? self._alpha = lambda p: tuple(arg[0](p) for arg in alpha_beta) @@ -1034,7 +1031,8 @@ def set_value_restrictions(self, *a_values): """ # reset values - self._restrictions_possible_values = {a: set(self._Z) for a in self._A} + set_Z = set(self._Z) + self._restrictions_possible_values = {a: set_Z.copy() for a in self._A} for a, values in a_values: assert a in self._A, f"Element {a} was not found in A" self._restrictions_possible_values[a].intersection_update(values) From 6ee34e1d761af4b8ec23c721a12a0d123318661a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 19 Oct 2022 00:36:54 +0200 Subject: [PATCH 156/392] make initialisation more efficient --- src/sage/combinat/bijectionist.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index ac41442e414..95637fcae32 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -483,6 +483,16 @@ class Bijectionist(SageObject): """ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), elements_distributions=tuple(), a_values=tuple(), solver=None, key=None): + """ + Initialize the bijectionist. + + TESTS: + + Check that large input sets are handled well:: + + sage: A = B = list(range(7000)) + sage: bij = Bijectionist(A, B) + """ # glossary of standard letters: # A, B, Z, W ... finite sets # ???? tilde_A, tilde_Z, ..., subsets? @@ -746,9 +756,14 @@ def set_statistics(self, *alpha_beta): self._W = list(self._statistics_fibers) # the possible values of s(a) are tau(beta^{-1}(alpha(a))) - self._statistics_possible_values = {a: set(self._tau[b] - for b in self._statistics_fibers[self._alpha(a)][1]) - for a in self._A} + tau_beta_inverse = {} + self._statistics_possible_values = {} + for a in self._A: + alpha_a = self._alpha(a) + if alpha_a not in tau_beta_inverse: + tau_beta_inverse[alpha_a] = set(self._tau[b] + for b in self._statistics_fibers[alpha_a][1]) + self._statistics_possible_values[a] = tau_beta_inverse[alpha_a] def statistics_fibers(self): r""" @@ -1032,10 +1047,10 @@ def set_value_restrictions(self, *a_values): """ # reset values set_Z = set(self._Z) - self._restrictions_possible_values = {a: set_Z.copy() for a in self._A} + self._restrictions_possible_values = {a: set_Z for a in self._A} for a, values in a_values: assert a in self._A, f"Element {a} was not found in A" - self._restrictions_possible_values[a].intersection_update(values) + self._restrictions_possible_values[a] = self._restrictions_possible_values[a].intersection(values) def _compute_possible_block_values(self): r""" From 1a14bcfcbd8202fb81481a0017c1df0974547797 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 19 Oct 2022 00:50:14 +0200 Subject: [PATCH 157/392] consistently name variable --- src/sage/combinat/bijectionist.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 95637fcae32..10c076858dc 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -759,11 +759,11 @@ def set_statistics(self, *alpha_beta): tau_beta_inverse = {} self._statistics_possible_values = {} for a in self._A: - alpha_a = self._alpha(a) - if alpha_a not in tau_beta_inverse: - tau_beta_inverse[alpha_a] = set(self._tau[b] - for b in self._statistics_fibers[alpha_a][1]) - self._statistics_possible_values[a] = tau_beta_inverse[alpha_a] + v = self._alpha(a) + if v not in tau_beta_inverse: + tau_beta_inverse[v] = set(self._tau[b] + for b in self._statistics_fibers[v][1]) + self._statistics_possible_values[a] = tau_beta_inverse[v] def statistics_fibers(self): r""" From 0901ec7a2dd6de58de2307489e844dd2978d79bb Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 19 Oct 2022 17:02:14 +0200 Subject: [PATCH 158/392] slightly improve documentation --- src/sage/combinat/bijectionist.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 10c076858dc..0124994c022 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -393,7 +393,9 @@ class Bijectionist(SageObject): - r"""Solver class for bijection-statistic problems. + r""" + A toolbox to list all possible bijections between two finite sets + under various constraints. INPUT: @@ -517,6 +519,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), ele self._tau = {b: b for b in self._B} else: self._tau = {b: tau(b) for b in self._B} + # we store Z as a list to keep an order self._Z = set(self._tau.values()) if key is not None and "Z" in key: self._sorter["Z"] = lambda x: sorted(x, key=key["Z"]) @@ -526,8 +529,8 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), ele self._Z = sorted(self._Z) self._sorter["Z"] = lambda x: sorted(x) except TypeError: - self._sorter["Z"] = lambda x: list(x) self._Z = list(self._Z) + self._sorter["Z"] = lambda x: list(x) # set optional inputs self.set_statistics(*alpha_beta) @@ -1045,7 +1048,10 @@ def set_value_restrictions(self, *a_values): AssertionError: Element (1, 2) was not found in A """ - # reset values + # it might be much cheaper to construct the sets as subsets + # of _statistics_possible_values - however, we do not want to + # insist that set_value_restrictions is called after + # set_statistics set_Z = set(self._Z) self._restrictions_possible_values = {a: set_Z for a in self._A} for a, values in a_values: @@ -1055,6 +1061,15 @@ def set_value_restrictions(self, *a_values): def _compute_possible_block_values(self): r""" Update the dictionary of possible values of each block. + + This has to be called whenever `self._P` was modified. + + .. TODO:: + + If `self._Z` is large, this is very memory expensive. In + this case it would be good if equal values of the dictionary + `self._possible_block_values` would share memory. + """ self._possible_block_values = {} # P -> Power(Z) for p, block in self._P.root_to_elements_dict().items(): @@ -1546,7 +1561,8 @@ def _forced_constant_blocks(self): self.set_constant_blocks(tmp_P) def possible_values(self, p=None, optimal=False): - r"""Return for each block the values of `s` compatible with the + r""" + Return for each block the values of `s` compatible with the imposed restrictions. .. TODO:: @@ -1837,7 +1853,8 @@ def _find_counter_example(self, bmilp, s0, d): return def minimal_subdistributions_blocks_iterator(self, p=None): - r"""Return all representatives of minimal subsets `\tilde P` + r""" + Return all representatives of minimal subsets `\tilde P` of `P` containing `p` together with submultisets `\tilde Z` with `s(\tilde P) = \tilde Z` as multisets. @@ -2416,7 +2433,9 @@ def _show_bmilp(self, bmilp, variables=True): def _generate_and_solve_initial_bmilp(self): r""" - Generate a _BijectionistMILP, add all relevant constraints and call MILP.solve(). + Generate a ``_BijectionistMILP``, add all relevant constraints + and call ``MILP.solve()``. + """ preimage_blocks = self._preprocess_intertwining_relations() self._compute_possible_block_values() From c39d653071d1d00b73fca4bf961bab8d4a7ba360 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 2 Nov 2022 10:55:37 +0100 Subject: [PATCH 159/392] non-copying intersection, to save memory when there are almost no restrictions on the values of a block --- src/sage/combinat/bijectionist.py | 51 +++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 0124994c022..4b4b52089d6 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -13,6 +13,12 @@ # subdistributions with "smallest" (e.g., in the sorting order # defined above) elements first? +# Priorities: + +# 1) for an all-knowing user, code should be as fast as possible +# 2) code should be easy to understand +# 3) anticipate that a user computes things in a bad order + r""" A bijectionist's toolkit @@ -492,7 +498,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), ele Check that large input sets are handled well:: - sage: A = B = list(range(7000)) + sage: A = B = list(range(20000)) sage: bij = Bijectionist(A, B) """ # glossary of standard letters: @@ -1062,19 +1068,13 @@ def _compute_possible_block_values(self): r""" Update the dictionary of possible values of each block. - This has to be called whenever `self._P` was modified. - - .. TODO:: - - If `self._Z` is large, this is very memory expensive. In - this case it would be good if equal values of the dictionary - `self._possible_block_values` would share memory. - + This has to be called whenever ``self._P`` was modified. """ self._possible_block_values = {} # P -> Power(Z) for p, block in self._P.root_to_elements_dict().items(): - self._possible_block_values[p] = set.intersection(*[self._restrictions_possible_values[a] for a in block], - *[self._statistics_possible_values[a] for a in block]) + sets = ([self._restrictions_possible_values[a] for a in block] + + [self._statistics_possible_values[a] for a in block]) + self._possible_block_values[p] = _non_copying_intersection(sets) if not self._possible_block_values[p]: if len(block) == 1: raise ValueError(f"No possible values found for singleton block {block}") @@ -2435,7 +2435,6 @@ def _generate_and_solve_initial_bmilp(self): r""" Generate a ``_BijectionistMILP``, add all relevant constraints and call ``MILP.solve()``. - """ preimage_blocks = self._preprocess_intertwining_relations() self._compute_possible_block_values() @@ -2670,6 +2669,34 @@ def _disjoint_set_roots(d): return d.root_to_elements_dict().keys() +def _non_copying_intersection(sets): + """ + Return the intersection of the sets. + + If the intersection is equal to one of the sets, return this + set. + + EXAMPLES:: + + sage: from sage.combinat.bijectionist import _non_copying_intersection + sage: A = set(range(7000)); B = set(range(8000)); + sage: _non_copying_intersection([A, B]) is A + True + + """ + sets = sorted(sets, key=len) + result = set.intersection(*sets) + n = len(result) + if n < len(sets[0]): + return result + for s in sets: + N = len(s) + if N > n: + return result + if s == result: + return s + + """ TESTS:: From 0374b6d900171ca54f946e71f52481e6dca2ae43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Tue, 15 Nov 2022 17:08:23 +0100 Subject: [PATCH 160/392] (breaking) Change base morphism to base ring for Drinfeld modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WARNING: THIS IS EXTREMELY BUGGY! The base of the category was a ring morphism, it is now a ring extension. This change was discussed in comment 268 of the ticket: https://trac.sagemath.org/ticket/33713#comment:268. I updated both the code, documentation, and doctests. Many tests are unfortunately tagued with `# todo: not tested`, as several bugs need be corrected. Those are the bugs. 1. The first bug is https://trac.sagemath.org/ticket/34752. 2. One cannot create ring extensions using 'exotic' morphisms:: sage: Fq = GF(11) sage: FqX. = Fq[] sage: K = Frac(FqX) sage: f = Hom(FqX, K)(K(1)) sage: K_over = K.over(f) # Exception This a problem to define Drinfeld modules with arbitrary constant coefficient. 3. One cannot see the base ring — which is a ring extension over Fq[X] — as a ring extension over Fq:: sage: Fq = GF(25) sage: FqX. = Fq[] sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z12, 0, 1]) sage: K_over = phi.base_ring() sage: K_over.over(Fq) # Exception This would be very helpful to compute the function-ring characteristic and the Frobenius endomorphism (it requires the degree of the base field over Fq). 4. This other coercion problem causes problem to the ``invert`` method:: Fq = GF(25) FqX. = Fq[] K. = Fq.extension(6) phi = DrinfeldModule(FqX, [z12, 0, 1]) K_over = phi.base_ring() Fq(K_over(1)) # Exception --- src/sage/categories/drinfeld_modules.py | 306 +++++----- .../function_field/drinfeld_modules/action.py | 29 +- .../drinfeld_modules/drinfeld_module.py | 535 +++++++++--------- .../finite_drinfeld_module.py | 83 +-- .../function_field/drinfeld_modules/homset.py | 8 +- .../drinfeld_modules/morphism.py | 20 +- 6 files changed, 484 insertions(+), 497 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index dc11284e7fa..5f78eab3a3f 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -18,45 +18,44 @@ # http://www.gnu.org/licenses/ # ****************************************************************************** -from sage.categories.category_types import Category_over_base +from sage.categories.category_types import Category_over_base_ring from sage.categories.homsets import Homsets from sage.misc.functional import log from sage.misc.latex import latex from sage.rings.integer import Integer -from sage.rings.morphism import RingHomomorphism from sage.rings.polynomial.ore_polynomial_ring import OrePolynomialRing from sage.rings.polynomial.polynomial_ring import PolynomialRing_general +from sage.rings.ring_extension import RingExtension_generic -class DrinfeldModules(Category_over_base): +class DrinfeldModules(Category_over_base_ring): r""" This class represents the category of Drinfeld modules on a given base. Let `\mathbb{F}_q[X]` be a polynomial ring with coefficients in a - finite field `\mathbb{F}_q` and let `K` be a field. We fix a ring - morphism `\gamma: \mathbb{F}_q[X] \to K`, which we call the *base* - of the category (it is the base of the Drinfeld modules in the - category). Please note that the base is not a ring; in particular, - it is not the field `K`. We also call `K` an - `\mathbb{F}_q[X]`-field. - - The category is uniquely defined by its base. - - The monic polynomial that generates the kernel of the base is called - the `\mathbb{F}_q[X]`-characteristic of the `\mathbb{F}_q[X]`-field - `K`. + finite field `\mathbb{F}_q` and let `K` be a field. Fix a ring + morphism `\gamma: \mathbb{F}_q[X] \to K`. We say that the field `K` + is an `\mathbb{F}_q[X]`-field, so that the *base of the category* is + defined as the `\mathbb{F}_q[X]`-field *K*. The base uniquely + defines the category, and we also refer to it as the *base ring* or + *base field*. The *base morphism* is the morphism `\gamma: + \mathbb{F}_q[X] \to K`. .. NOTE:: - These notations will be used throughout this documentation. + Equivalently, the base of the category could be defined as the + base morphism `\gamma: \mathbb{F}_q[X] \to K`. + + The monic polynomial that generates the kernel of the base morphism + is called the `\mathbb{F}_q[X]`-characteristic of the + `\mathbb{F}_q[X]`-field `K`. It cal also be referred to as the + function-field characteristic of `K`. We say that `\mathbb{F}_q[X]` is the function ring of the category; - `K\{\tau\}` is the polynomial ring of the category. The constant - coefficient of the category is the image of `X` under the base. The - `\mathbb{F}_q[X]`-characteristic of the `\mathbb{F}_q[X]`-field `K` - can also be referred to as its function ring-characteristic. - Finally, `K` is just refered to as the codomain base. + `K\{\tau\}` is the Ore polynomial ring of the category. The constant + coefficient of the category is the image of `X` under the base + morphism. INPUT: the base ring morphism @@ -73,32 +72,37 @@ class DrinfeldModules(Category_over_base): sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat - Category of Drinfeld modules defined over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Finite Field in z of size 11^4 - Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + Category of Drinfeld modules defined over Finite Field in z of size 11^4 over its base The output tells the user that the category is only defined by its base. .. RUBRIC:: Properties of the category - The base, which is a morphism, is retrieved using the method - :meth:`morphism`:: + The base ring is retrieved using the method :meth:`base` or + :meth:`base_ring`:: sage: cat.base() + Finite Field in z of size 11^4 over its base + sage: cat.base_ring() + Finite Field in z of size 11^4 over its base + + Equivalently, one can use :meth:`base_morphism` to retrieve the base + morphism:: + + sage: cat.base_morphism() Ring morphism: From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Finite Field in z of size 11^4 + To: Finite Field in z of size 11^4 over its base Defn: X |--> z^3 + 7*z^2 + 6*z + 10 The so-called constant coefficient --- which is the same for all Drinfeld modules in the category --- is simply the image of `X` by - this morphism: + the base morphism:: sage: cat.constant_coefficient() z^3 + 7*z^2 + 6*z + 10 - sage: cat.base()(X) == cat.constant_coefficient() + sage: cat.base_morphism()(X) == cat.constant_coefficient() True Similarly, the function ring-characteristic of the category is @@ -107,25 +111,24 @@ class DrinfeldModules(Category_over_base): sage: cat.characteristic() X^2 + 7*X + 2 - sage: cat.base()(cat.characteristic()) + sage: cat.base_morphism()(cat.characteristic()) 0 - The base, function ring and Ore polynomial ring are the - same for the category and its objects:: + The base ring, base morphism, function ring and Ore polynomial ring + are the same for the category and its objects:: sage: cat.base() is phi.base() True + sage: cat.base_morphism() is phi.base_morphism() + True sage: cat.function_ring() is phi.function_ring() True sage: cat.function_ring() Univariate Polynomial Ring in X over Finite Field of size 11 - sage: cat.function_ring() is cat.base().domain() - True - sage: cat.ore_polring() is phi.ore_polring() True sage: cat.ore_polring() - Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11 + Ore Polynomial Ring in t over Finite Field in z of size 11^4 over its base twisted by Frob .. RUBRIC:: Creating Drinfeld module objects from the category @@ -134,10 +137,7 @@ class DrinfeldModules(Category_over_base): sage: psi = cat.object([p_root, 1]) sage: psi - Drinfeld module defined by X |--> t + z^3 + 7*z^2 + 6*z + 10 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Finite Field in z of size 11^4 - Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + Drinfeld module defined by X |--> t + z^3 + 7*z^2 + 6*z + 10 over base Finite Field in z of size 11^4 over its base sage: psi.category() is cat True @@ -147,7 +147,7 @@ class DrinfeldModules(Category_over_base): sage: cat.object([z, 1]) Traceback (most recent call last): ... - ValueError: constant coefficient must be the generator of the morphism that defines the category + ValueError: constant coefficient must equal that of the category It is also possible to create a random object in the category. The input is the desired rank:: @@ -170,13 +170,23 @@ class DrinfeldModules(Category_over_base): sage: cat = DrinfeldModules(base) Traceback (most recent call last): ... - ValueError: base must be a nonzero morphism + TypeError: base field must be a ring extension + + :: + + sage: cat.base().defining_morphism() == cat.base_morphism() + True + + :: sage: base = Hom(FqX, FqX)(1) sage: cat = DrinfeldModules(base) Traceback (most recent call last): ... - TypeError: base codomain must be a field + TypeError: base field must be a ring extension + + :: + sage: base = 'I hate Rostropovitch' sage: cat = DrinfeldModules(base) # known bug (blankline) @@ -185,6 +195,8 @@ class DrinfeldModules(Category_over_base): ... TypeError: input must be a ring morphism + :: + sage: ZZT. = ZZ[] sage: base = Hom(ZZT, K)(1) sage: cat = DrinfeldModules(base) # known bug (blankline) @@ -193,15 +205,17 @@ class DrinfeldModules(Category_over_base): ... TypeError: function ring base must be a finite field """ - def __init__(self, base, name='t'): + + def __init__(self, base_field, name='t'): r""" Initialize `self`. INPUT: - - ``base`` -- the base ring morphism + - ``base_ring`` -- the base field, which is a ring extension + over a base - ``name`` (default: `'t'`) -- the name of the Ore polynomial - ring generator + variable TESTS:: @@ -211,25 +225,33 @@ def __init__(self, base, name='t'): sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() - sage: ore_polring. = OrePolynomialRing(K, K.frobenius_endomorphism()) + sage: ore_polring. = OrePolynomialRing(phi.base(), phi.base().frobenius_endomorphism()) sage: cat._ore_polring is ore_polring True - sage: base = Hom(FqX, K)(p_root) - sage: cat._base == base + sage: i = phi.base().coerce_map_from(K) + sage: base_morphism = Hom(FqX, K)(p_root) + sage: cat.base() == K.over(base_morphism) + True + sage: cat._base_morphism == i * base_morphism True sage: cat._function_ring is FqX True - sage: cat._constant_coefficient == base(X) + sage: cat._constant_coefficient == base_morphism(X) True sage: cat._characteristic(cat._constant_coefficient) 0 """ - # Check input is a ring Morphism - if not isinstance(base, RingHomomorphism): - raise TypeError('input must be a Ring morphism') - self._base = base - self._function_ring = base.domain() - # Check domain of base is Fq[X] + # Check input is a ring extension + if not isinstance(base_field, RingExtension_generic): + raise TypeError('base field must be a ring extension') + base_morphism = base_field.defining_morphism() + self._base_morphism = base_morphism + # Check input is a field + if not base_field.is_field(): + raise TypeError('input must be a field') + self._base_field = base_field + self._function_ring = base_morphism.domain() + # Check domain of base morphism is Fq[X] function_ring = self._function_ring if not isinstance(function_ring, PolynomialRing_general): raise NotImplementedError('function ring must be a polynomial ' @@ -238,32 +260,26 @@ def __init__(self, base, name='t'): if not function_ring_base.is_field() \ or not function_ring_base.is_finite(): raise TypeError('function ring base must be a finite field') + # Shortcuts Fq = function_ring_base FqX = function_ring X = FqX.gen() - # Check codomain of base is a field - K = base.codomain() - if not K.is_field(): - raise TypeError('base codomain must be a field') - # Check base is a nonzero morphism - if base(X).is_zero(): - raise ValueError('base must be a nonzero morphism') + K = base_field # A ring extension # Build K{t} d = log(Fq.cardinality(), Fq.characteristic()) tau = K.frobenius_endomorphism(d) self._ore_polring = OrePolynomialRing(K, tau, names=name, polcast=False) # Create constant coefficient - self._constant_coefficient = base(X) + self._constant_coefficient = base_morphism(X) # Create characteristic self._characteristic = None if K.is_finite(): - f = base * FqX.coerce_map_from(Fq) # Fq -> K - E = K.over(f) - self._characteristic = FqX(E(base(X)).minpoly()) + #FIXME: This minpoly is over Fp, not Fq + self._characteristic = FqX(K(base_morphism(X)).minpoly()) elif FqX.is_subring(K): self._characteristic = Integer(0) - super().__init__(base=base) + super().__init__(base=base_field) def _latex_(self): r""" @@ -280,15 +296,10 @@ def _latex_(self): sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: latex(cat) - \text{Category{ }of{ }Drinfeld{ }modules{ }defined{ }over{ }base{ }\begin{array}{l} - \text{\texttt{Ring{ }morphism:}}\\ - \text{\texttt{{ }{ }From:{ }Univariate{ }Polynomial{ }Ring{ }in{ }X{ }over{ }Finite{ }Field{ }of{ }size{ }11}}\\ - \text{\texttt{{ }{ }To:{ }{ }{ }Finite{ }Field{ }in{ }z{ }of{ }size{ }11{\char`\^}4}}\\ - \text{\texttt{{ }{ }Defn:{ }X{ }|{-}{-}>{ }z{\char`\^}3{ }+{ }7*z{\char`\^}2{ }+{ }6*z{ }+{ }10}} - \end{array} + \text{Category{ }of{ }Drinfeld{ }modules{ }defined{ }over{ }\Bold{F}_{11^{4}} """ return f'\\text{{Category{{ }}of{{ }}Drinfeld{{ }}modules{{ }}' \ - f'defined{{ }}over{{ }}base{{ }}{latex(self._base)}' + f'defined{{ }}over{{ }}{latex(self._base_field)}' def _repr_(self): r""" @@ -305,12 +316,9 @@ def _repr_(self): sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat - Category of Drinfeld modules defined over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Finite Field in z of size 11^4 - Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + Category of Drinfeld modules defined over Finite Field in z of size 11^4 over its base """ - return f'Category of Drinfeld modules defined over base {self._base}' + return f'Category of Drinfeld modules defined over {self._base_field}' def Homsets(self): r""" @@ -352,6 +360,30 @@ def Endsets(self): """ return Homsets().Endsets() + def base_morphism(self): + r""" + Return the base morphism of the category + + OUTPUT: a ring morphism + + EXAMPLES:: + + sage: Fq = GF(11) + sage: FqX. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat.base_morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field of size 11 + To: Finite Field in z of size 11^4 over its base + Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + sage: cat.constant_coefficient() == cat.base_morphism()(X) + True + """ + return self._base_morphism + def characteristic(self): r""" Return the function ring-characteristic. @@ -371,15 +403,8 @@ def characteristic(self): :: - sage: L = Frac(FqX) - sage: psi = DrinfeldModule(FqX, [L.gen(), 1]) - sage: psi - Drinfeld module defined by X |--> t + X over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Fraction Field of Univariate Polynomial Ring in X over Finite Field of size 11 - Defn: X |--> X - sage: fox = psi.category() - sage: fox.characteristic() + sage: psi = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) # todo: not tested + sage: psi.category().characteristic() # todo: not tested 0 """ if self._characteristic is None: @@ -449,10 +474,7 @@ def object(self, gen): sage: cat = phi.category() sage: psi = cat.object([p_root, 0, 1]) sage: psi - Drinfeld module defined by X |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Finite Field in z of size 11^4 - Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + Drinfeld module defined by X |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over base Finite Field in z of size 11^4 over its base sage: t = phi.ore_polring().gen() sage: cat.object(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi True @@ -461,10 +483,9 @@ def object(self, gen): # If gen is not in the Ore polring, an exception is raised gen = self._ore_polring(gen) X = self._function_ring.gen() - base = self._base - if gen[0] != base(X): - raise ValueError('constant coefficient must be the generator ' - 'of the morphism that defines the category') + if gen[0] != self._base_morphism(X): + raise ValueError('constant coefficient must equal that of the ' \ + 'category') return DrinfeldModule(self._function_ring, gen) def ore_polring(self): @@ -482,7 +503,7 @@ def ore_polring(self): sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat.ore_polring() - Ore Polynomial Ring in t over Finite Field in z of size 11^4 twisted by z |--> z^11 + Ore Polynomial Ring in t over Finite Field in z of size 11^4 over its base twisted by Frob sage: cat.ore_polring() is phi.ore_polring() True """ @@ -515,7 +536,7 @@ def random_object(self, rank): if rank <= 0: raise ValueError('rank must be a positive integer') - K = self._base.codomain() + K = self._base_field coeffs = [self._constant_coefficient] for _ in range(rank-1): coeffs.append(K.random_element()) @@ -545,9 +566,9 @@ class ParentMethods: def base(self): r""" - Return the base of the Drinfeld module. + Return the base field of the Drinfeld module. - OUTPUT: a ring morphism + OUTPUT: a field, which is a ring extension over a base EXAMPLES:: @@ -557,49 +578,41 @@ def base(self): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: phi.base() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z12 of size 5^12 - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Finite Field in z12 of size 5^12 over its base - The base codomain can be infinite:: + The base can be infinite:: - sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) - sage: sigma.base() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - Defn: X |--> X - - And it can also be the base field of the function ring:: - - sage: psi = DrinfeldModule(FqX, [Fq(1), Fq.gen()]) - sage: psi.base() - Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z2 of size 5^2 - Defn: X |--> 1 - - In this case the Ore polynomial ring is isomorphic to a regular - polynomial ring:: + sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) # todo: not tested + sage: sigma.base() # todo: not tested + Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 over its base + """ + return self.category().base() - sage: psi.ore_polring() - Ore Polynomial Ring in t over Finite Field in z2 of size 5^2 twisted by Identity - sage: psi.ore_polring().twisting_morphism() - Identity endomorphism of Finite Field in z2 of size 5^2 + def base_morphism(self): + r""" + Return the base morphism of the Drinfeld module. - TESTS:: + OUTPUT: a ring morphism - sage: psi.ore_polring().twisting_morphism().is_identity() - True + EXAMPLES:: - :: + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi.base_morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + To: Finite Field in z12 of size 5^12 over its base + Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: psi.base().codomain() is psi.function_ring().base_ring() - True + The base codomain can be infinite:: + sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) # todo: not tested + sage: sigma.base_morphism() # todo: not tested """ - return self.category().base() + return self.category().base_morphism() def characteristic(self): r""" @@ -614,14 +627,16 @@ def characteristic(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.characteristic() + sage: phi.characteristic() # todo: not tested X^2 + (4*z2 + 2)*X + 2 - sage: phi.characteristic()(phi.constant_coefficient()) + sage: phi.base_morphism()(phi.characteristic()) 0 - sage: L = Frac(FqX) - sage: psi = DrinfeldModule(FqX, [L(1), 0, 0, L(1)]) - sage: psi.characteristic() + :: + + sage: L = Frac(FqX) # todo: not tested + sage: psi = DrinfeldModule(FqX, [L(1), 0, 0, L(1)]) # todo: not tested + sage: psi.characteristic() # todo: not tested 0 """ return self.category().characteristic() @@ -675,10 +690,7 @@ def constant_coefficient(self): sage: t = phi.ore_polring().gen() sage: psi = cat.object(phi.constant_coefficient() + t^3) sage: psi - Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z12 of size 5^12 - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base Reciprocally, it is impossible to create two Drinfeld modules in this category if they do not share the same constant @@ -687,7 +699,7 @@ def constant_coefficient(self): sage: rho = cat.object(phi.constant_coefficient() + 1 + t^3) Traceback (most recent call last): ... - ValueError: constant coefficient must be the generator of the morphism that defines the category + ValueError: constant coefficient must equal that of the category """ return self.category().constant_coefficient() @@ -706,7 +718,7 @@ def ore_polring(self): sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: ore_polring = phi.ore_polring() sage: ore_polring - Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 over its base twisted by Frob^2 The Ore polynomial ring can also be retrieved from the category of the Drinfeld module:: diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 096b5428059..e8319a1f308 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -51,10 +51,7 @@ class DrinfeldModuleAction(Action): sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by X |--> t^3 + z over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Finite Field in z of size 11^2 - Defn: X |--> z + Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by X |--> t^3 + z over base Finite Field in z of size 11^2 over its base The action on elements is computed as follows:: @@ -90,14 +87,14 @@ def __init__(self, drinfeld_module): sage: action = phi.action() sage: action._drinfeld_module is phi True - sage: action._field is phi.base().codomain() + sage: action._base_ring is phi.base() True """ if not isinstance(drinfeld_module, DrinfeldModule): raise TypeError('input must be a DrinfeldModule') self._drinfeld_module = drinfeld_module - self._field = drinfeld_module.base().codomain() - super().__init__(drinfeld_module.function_ring(), self._field) + self._base_ring = drinfeld_module.base() + super().__init__(drinfeld_module.function_ring(), self._base_ring) def _act_(self, pol, x): r""" @@ -128,7 +125,7 @@ def _act_(self, pol, x): """ if pol not in self._drinfeld_module.function_ring(): raise TypeError('first input must be in the function ring') - if x not in self._field: + if x not in self._base_ring: raise TypeError('second input must be in the field acted upon') return self._drinfeld_module(pol)(x) @@ -146,15 +143,10 @@ def _latex_(self): sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) sage: action = phi.action() sage: latex(action) - \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto t^{3} + z\text{{ }over{ }base{ }}\begin{array}{l} - \text{\texttt{Ring{ }morphism:}}\\ - \text{\texttt{{ }{ }From:{ }Univariate{ }Polynomial{ }Ring{ }in{ }X{ }over{ }Finite{ }Field{ }of{ }size{ }11}}\\ - \text{\texttt{{ }{ }To:{ }{ }{ }Finite{ }Field{ }in{ }z{ }of{ }size{ }11{\char`\^}2}}\\ - \text{\texttt{{ }{ }Defn:{ }X{ }|{-}{-}>{ }z}} - \end{array} + \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto t^{3} + z\text{{ }over{ }base{ }}\Bold{F}_{11^{2}} """ return f'\\text{{Action{{ }}on{{ }}}}' \ - f'{latex(self._field)}\\text{{{{ }}' \ + f'{latex(self._base_ring)}\\text{{{{ }}' \ f'induced{{ }}by{{ }}}}{latex(self._drinfeld_module)}' def _repr_(self): @@ -171,12 +163,9 @@ def _repr_(self): sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 induced by Drinfeld module defined by X |--> t^3 + z over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 - To: Finite Field in z of size 11^2 - Defn: X |--> z + Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by X |--> t^3 + z over base Finite Field in z of size 11^2 over its base """ - return f'Action on {self._field} induced by ' \ + return f'Action on {self._base_ring} induced by ' \ f'{self._drinfeld_module}' def drinfeld_module(self): diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index cbc1c8ac8f2..d82632c6db2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -25,12 +25,14 @@ # ***************************************************************************** from sage.categories.drinfeld_modules import DrinfeldModules +from sage.categories.homset import Hom from sage.matrix.constructor import Matrix from sage.misc.latex import latex from sage.modules.free_module_element import vector from sage.rings.integer import Integer from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.polynomial_ring import PolynomialRing_general +from sage.rings.ring_extension import RingExtension_generic from sage.structure.parent import Parent from sage.structure.sequence import Sequence from sage.structure.unique_representation import UniqueRepresentation @@ -41,25 +43,32 @@ class DrinfeldModule(Parent, UniqueRepresentation): This class represents a Drinfeld `\mathbb{F}_q[X]`-module. Let `\mathbb{F}_q[X]` be a polynomial ring with coefficients in a - finite field `\mathbb{F}_q` and let `K` be a field. We fix a ring - morphism `\gamma: \mathbb{F}_q[X] \to K`, which we call the base of - the Drinfeld module. + finite field `\mathbb{F}_q` and let `K` be a field. Fix a ring + morphism `\gamma: \mathbb{F}_q[X] \to K`. We say that the field `K` + is an `\mathbb{F}_q[X]`-field, so that the *base of the Drinfeld + module* is defined as the `\mathbb{F}_q[X]`-field *K*. The base + uniquely defines the category, and we also refer to it as the *base + ring* or *base field*. The *base morphism* is the morphism `\gamma: + \mathbb{F}_q[X] \to K`. .. NOTE:: - The base is not a ring. Specifically, it is not the field `K`. - We say however that `K` is an `\mathbb{F}_q[X]`-field. + Equivalently, the base of the Drinfeld module could be defined as the + base morphism `\gamma: \mathbb{F}_q[X] \to K`. - The base of the Drinfeld module is the base of the category of - the Drinfeld module. - The monic polynomial that generates the kernel of the base is called - the `\mathbb{F}_q[X]`-characteristic of the `\mathbb{F}_q[X]`-field - `K`. + .. NOTE:: + + See also :class:`sage.categories.drinfeld_modules`. + + The monic polynomial that generates the kernel of the base morphism + is called the `\mathbb{F}_q[X]`-characteristic of the + `\mathbb{F}_q[X]`-field `K`. It cal also be referred to as the + function-field characteristic of `K`. Let `K\{\tau\}` be the ring of Ore polynomials with coefficients in `K` and Frobenius variable `\tau: x \mapsto x^q`. A Drinfeld - `\mathbb{F}_q[X]`-module over the base `\gamma` is an + `\mathbb{F}_q[X]`-module over the `\mathbb{F}_q[X]`-field `K` is an `\mathbb{F}_q`-algebra morphism `\phi: \mathbb{F}_q[X] \to K\{\tau\}` such that: @@ -70,33 +79,26 @@ class DrinfeldModule(Parent, UniqueRepresentation): For `a` in the function ring, `\phi(a)` is denoted `\phi_a`. The Drinfeld `\mathbb{F}_q[X]`-module `\phi` is uniquely determined - by the image `\phi_X` of `X`. This serves as input of the class. + by the image `\phi_X` of `X` — this serves as input of the class. - A Drinfeld module is saif to be finite if the base ring codomain is - a finite field. Despite an emphasis on this case, the base codomain - can be any extension of the field `\mathbb{F}_q`:: + A Drinfeld module is said to be finite if the field `K` is. Despite + an emphasis on this case, the base field can be any extension of + `\mathbb{F}_q`:: sage: Fq = GF(25) sage: FqX. = Fq[] sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z, 4, 1]) sage: phi - Drinfeld module defined by X |--> t^2 + 4*t + z over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z of size 5^12 - Defn: X |--> z + Drinfeld module defined by X |--> t^2 + 4*t + z over base Finite Field in z of size 5^12 over its base :: sage: Fq = GF(49) sage: FqX. = Fq[] sage: K. = Frac(FqX) - sage: phi = DrinfeldModule(FqX, [z, X+1]) - sage: phi - Drinfeld module defined by X |--> (X + 1)*t + X over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 - To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 7^2 - Defn: X |--> X + sage: psi = DrinfeldModule(FqX, [z, X+1]) # todo: not tested + sage: psi # todo: not tested .. NOTE:: @@ -143,36 +145,29 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(FqX, [z, 1, 1]) sage: phi - Drinfeld module defined by X |--> t^2 + t + z over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z + Drinfeld module defined by X |--> t^2 + t + z over base Finite Field in z of size 3^12 over its base Note that the definition of the base morphism is implicit; it is - defined as the `\mathbb{F}_q`-algebra morphism `\mathbb{F}_q[X] \to - K` which maps `X` to the constant coefficient of the generator, and - where `K` is the compositum of all the parents of the coefficients:: - - sage: phi.base().codomain() is K - True - - .. NOTE:: - - Formally, `K` is defined as `Sequence(gen).universe()`, where - `gen` is the generator of the Drinfeld module. + defined as the `\mathbb{F}_q`-algebra morphism defined from + `\mathbb{F}_q[X]` to the base ring, and the base ring is a ring + extension over the base morphism whose underlying ring is the + compositum of all the parents of the coefficients. The above Drinfeld module is finite; it can also be infinite:: - sage: L = Frac(FqX) - sage: psi = DrinfeldModule(FqX, [L(X), 1, X^3 + X + 1]) - sage: psi + sage: L = Frac(FqX) # todo: not tested + sage: psi = DrinfeldModule(FqX, [L(X), 1, X^3 + X + 1]) # todo: not tested + sage: psi # todo: not tested Drinfeld module defined by X |--> (X^3 + X + 1)*t^2 + t + X over base Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 Defn: X |--> X + + :: + sage: phi.is_finite() True - sage: psi.is_finite() + sage: psi.is_finite() # todo: not tested False In those examples, we used a list of coefficients (``[z, 1, 1]``) to @@ -184,76 +179,18 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: rho_X = z + t^3 sage: rho = DrinfeldModule(FqX, rho_X) sage: rho - Drinfeld module defined by X |--> t^3 + z over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z + Drinfeld module defined by X |--> t^3 + z over base Finite Field in z of size 3^12 over its base sage: rho(X) == rho_X True - The generator must have positive degree:: - - sage: DrinfeldModule(FqX, [z]) - Traceback (most recent call last): - ... - ValueError: generator must have positive degree - - The constant coefficient must be nonzero:: - - sage: DrinfeldModule(FqX, [K(0), K(1)]) - Traceback (most recent call last): - ... - ValueError: base must be a nonzero morphism - - The coefficients of the generator must lie in an - `\mathbb{F}_q[X]`-field, where `\mathbb{F}_q[X]` is the function - ring of the Drinfeld module:: - - sage: DrinfeldModule(FqX, [z, QQ(1)]) - Traceback (most recent call last): - ... - ValueError: function ring base must coerce into base codomain - - :: - - sage: DrinfeldModule(FqX, [1, QQ(1)]) - Traceback (most recent call last): - ... - ValueError: function ring base must coerce into base codomain - - The function ring must be an univariate polynomial ring whose - base is a finite field:: - - sage: DrinfeldModule(K, [z, 1, 1]) - Traceback (most recent call last): - ... - NotImplementedError: function ring must be a polynomial ring - - :: - - sage: FqXY. = FqX[] - sage: DrinfeldModule(FqXY, [z, 1, 1]) - Traceback (most recent call last): - ... - TypeError: function ring base must be a finite field - - If you already defined a category of Drinfeld modules, and you - create a Drinfeld module through this category, you must - ensure that the constant coefficient is the generator of the algebra - morphism that defines the category:: - - sage: cat = phi.category() - sage: cat.object([1, 1, K(1)]) - Traceback (most recent call last): - ... - ValueError: constant coefficient must be the generator of the morphism that defines the category - - .. NOTE:: + Images under the Drinfeld module are computed by calling the object:: - The reader may think that it is odd to build a Drinfeld module - without explicitly specifying the base. However, the base can be - deduced from the generator, and we omit the base in the input - of the class for conciseness. + sage: phi(X) # phi_X, the generator of the Drinfeld module + t^2 + t + z + sage: phi(X^3 + X + 1) # phi_(X^3 +X + 1) + t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 + sage: phi(1) # phi_1 + 1 .. RUBRIC:: The category of Drinfeld modules @@ -261,60 +198,57 @@ class DrinfeldModule(Parent, UniqueRepresentation): :class:`sage.categories.drinfeld_modules.DrinfeldModules`):: sage: phi.category() - Category of Drinfeld modules defined over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z - sage: phi.category() is psi.category() + Category of Drinfeld modules defined over Finite Field in z of size 3^12 over its base + sage: phi.category() is psi.category() # todo: not tested False sage: phi.category() is rho.category() True - This category holds crucial information, like the - function ring-characteristic of the base:: + One can use the category to directly create new objects:: - sage: char = phi.category().characteristic() + sage: cat = phi.category() + sage: cat.object([z, 0, 0, 1]) + Drinfeld module defined by X |--> t^3 + z over base Finite Field in z of size 3^12 over its base - As the output of :meth:`category` suggests, the base uniquely - determines the category. + .. RUBRIC:: The base ring of a Drinfeld module - .. RUBRIC:: Basics + The base ring of the Drinfeld module is retrieved using + :meth:`base_ring` or :meth:`base`:: - Images under the Drinfeld module are computed by calling the object:: + sage: phi.base_ring() + Finite Field in z of size 3^12 over its base - sage: phi(X) # phi_X - t^2 + t + z - sage: phi(X^3 + X + 1) # phi_(X^3 +X + 1) - t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 - sage: phi(1) # phi_1 - 1 + The base morphism is retrieved using :meth:`base_morphism`:: - This is useful to quickly retrieve the generator of the Drinfeld - module. Furthermore, a Drinfeld `\mathbb{F}_q[X]`-module can be seen - as an Ore polynomial with positive degree and constant coefficient - `\gamma(X)`, where `\gamma` is the base. This analogy is the - motivation for the following methods:: + sage: phi.base_morphism() + Ring morphism: + From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + To: Finite Field in z of size 3^12 over its base + Defn: X |--> z - sage: phi.coefficients() - [z, 1, 1] + Note that the base ring is *not* the field `K`. Rather, it is a ring + extension (see :class:`sage.rings.ring_extension.RingExtension`) + whose underlying ring is `K` and whose base is the base morphism:: - :: + sage: phi.base_ring() is K + False - sage: phi.coefficient(1) - 1 + .. RUBRIC:: Getters One can retrieve basic properties:: - sage: phi.base() + :: + + sage: phi.base_morphism() Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 + To: Finite Field in z of size 3^12 over its base Defn: X |--> z :: sage: phi.ore_polring() # K{t} - Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) + Ore Polynomial Ring in t over Finite Field in z of size 3^12 over its base twisted by Frob^2 :: @@ -338,14 +272,14 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.morphism() # The Drinfeld module as a morphism Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Ore Polynomial Ring in t over Finite Field in z of size 3^12 twisted by z |--> z^(3^2) + To: Ore Polynomial Ring in t over Finite Field in z of size 3^12 over its base twisted by Frob^2 Defn: X |--> t^2 + t + z One can compute the rank and height:: sage: phi.rank() 2 - sage: phi.height() + sage: phi.height() # todo: not tested 1 As well as the j-invariant if the rank is two:: @@ -353,7 +287,21 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.j_invariant() # j-invariant 1 - .. RUBRIC:: Morphisms, isogenies + A Drinfeld `\mathbb{F}_q[X]`-module can be seen as an Ore + polynomial with positive degree and constant coefficient + `\gamma(X)`, where `\gamma` is the base morphism. This analogy is + the motivation for the following methods:: + + sage: phi.coefficients() + [z, 1, 1] + + :: + + sage: phi.coefficient(1) + 1 + + + .. RUBRIC:: Morphisms and isogenies A morphism of Drinfeld modules `\phi \to \psi` is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a` in @@ -432,10 +380,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z sage: psi = phi.velu(ore_pol) sage: psi - Drinfeld module defined by X |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z + Drinfeld module defined by X |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over base Finite Field in z of size 3^12 over its base sage: ore_pol in Hom(phi, psi) True sage: ore_pol * phi(X) == psi(X) * ore_pol @@ -466,10 +411,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: action = phi.action() sage: action - Action on Finite Field in z of size 3^12 induced by Drinfeld module defined by X |--> t^2 + t + z over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Finite Field in z of size 3^12 - Defn: X |--> z + Action on Finite Field in z of size 3^12 over its base induced by Drinfeld module defined by X |--> t^2 + t + z over base Finite Field in z of size 3^12 over its base The action on elements is computed by calling the action object:: @@ -485,22 +427,105 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. RUBRIC:: Inverting the Drinfeld module - Given an Ore polynomial that equals `\phi_a` for some function ring - elelement `a`, one can retrieve `a` (as a morphism, a Drinfeld - module is injective, see [Gos1998]_, cor. 4.5.2.):: + The morphism that defines a Drinfeld module is injective. Given an + Ore polynomial that equals `\phi_a` for some function ring elelement + `a`, one can retrieve `a` (as a morphism, a Drinfeld module is + injective, see [Gos1998]_, cor. 4.5.2.):: sage: a = FqX.random_element() - sage: phi.invert(phi(a)) == a + sage: phi.invert(phi(a)) == a # todo: not tested True - TESTS:: + TESTS: + + The generator must have positive degree:: + + sage: Fq = GF(2) + sage: K. = Fq.extension(2) + sage: FqX. = Fq[] + sage: DrinfeldModule(FqX, [K(1)]) + Traceback (most recent call last): + ... + ValueError: generator must have positive degree + + The constant coefficient must be nonzero:: + + sage: Fq = GF(2) + sage: K. = Fq.extension(2) + sage: FqX. = Fq[] + sage: DrinfeldModule(FqX, [K(0), K(1)]) + Traceback (most recent call last): + ... + ValueError: constant coefficient must be nonzero + + The coefficients of the generator must lie in an + `\mathbb{F}_q[X]`-field, where `\mathbb{F}_q[X]` is the function + ring of the Drinfeld module:: + + sage: Fq = GF(2) + sage: K. = Fq.extension(2) + sage: FqX. = Fq[] + sage: DrinfeldModule(FqX, [z, QQ(1)]) + Traceback (most recent call last): + ... + ValueError: function ring base must coerce into base ring + + :: + + sage: Fq = GF(2) + sage: K. = Fq.extension(2) + sage: FqX. = Fq[] + sage: DrinfeldModule(FqX, [1, QQ(1)]) + Traceback (most recent call last): + ... + ValueError: function ring base must coerce into base ring + + The function ring must be an univariate polynomial ring whose + base is a finite field:: + + sage: Fq = GF(2) + sage: K. = Fq.extension(2) + sage: FqX. = Fq[] + sage: DrinfeldModule(K, [z, 1, 1]) + Traceback (most recent call last): + ... + NotImplementedError: function ring must be a polynomial ring + + :: + + sage: Fq = GF(2) + sage: K. = Fq.extension(2) + sage: FqX. = Fq[] + sage: FqXY. = FqX[] + sage: DrinfeldModule(FqXY, [z, 1, 1]) + Traceback (most recent call last): + ... + TypeError: function ring base must be a finite field + + If you already defined a category of Drinfeld modules, and you + create a Drinfeld module through this category, you must ensure that + the constant coefficient is that of the category:: + + sage: Fq = GF(2) + sage: K. = Fq.extension(2) + sage: FqX. = Fq[] + sage: phi = DrinfeldModule(FqX, [z, 1]) + sage: phi.category().object([1, 1, K(1)]) + Traceback (most recent call last): + ... + ValueError: constant coefficient must equal that of the category + + + :: sage: Fq = K = GF(2) sage: FqX. = Fq[] sage: phi = DrinfeldModule(FqX, [1, 1]) Traceback (most recent call last): ... - ValueError: function ring base must coerce into base codomain + ValueError: function ring base must coerce into base ring + + :: sage: Fq = K = GF(2) sage: FqX. = Fq[] @@ -510,45 +535,44 @@ class DrinfeldModule(Parent, UniqueRepresentation): Test that the base morphism is correct:: - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K = Frac(Fq) - sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(1)]) - sage: phi.base().codomain() is K - True - - :: + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K = Frac(Fq) + sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(1)]) + sage: phi.base().codomain() is K # todo: not tested + True - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: k = Frac(Fq) - sage: kT. = k[] - sage: K = k.extension(T^3 + T + 1) - sage: phi = DrinfeldModule(FqX, [Fq.gen(), K.gen()]) - sage: phi.base().codomain() is K - True + :: - In particular, note that the field `K` may not be the smallest field - of definition of the coefficients:: + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: k = Frac(Fq) + sage: kT. = k[] + sage: K = k.extension(T^3 + T + 1) + sage: phi = DrinfeldModule(FqX, [Fq.gen(), K.gen()]) + sage: phi.base().codomain() is K # todo: not tested + True - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: k = Frac(Fq) - sage: kT. = k[] - sage: K = k.extension(T^3 + T + 1) - sage: phi = DrinfeldModule(FqX, [K(k.gen()), 1]) - sage: phi.base().codomain() is K - True + In particular, note that the field `K` may not be the smallest field + of definition of the coefficients:: - :: + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: k = Frac(Fq) + sage: kT. = k[] + sage: K = k.extension(T^3 + T + 1) + sage: phi = DrinfeldModule(FqX, [K(k.gen()), 1]) + sage: phi.base().codomain() is K # todo: not tested + True - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(Fq.gen())]) - sage: phi.base().codomain() is K - True + :: + sage: Fq = GF(25) + sage: FqX. = Fq[] + sage: K = Fq.extension(2) + sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(Fq.gen())]) + sage: phi.base().codomain() is K # todo: not tested + True """ @staticmethod @@ -582,8 +606,8 @@ def __classcall_private__(cls, function_ring, gen, name='t'): :: sage: K = Frac(FqX) - sage: phi = DrinfeldModule(FqX, [K(X), 1]) - sage: isinstance(psi, FiniteDrinfeldModule) + sage: phi = DrinfeldModule(FqX, [K(X), 1]) # todo: not tested + sage: isinstance(psi, FiniteDrinfeldModule) # todo: not tested False """ @@ -605,26 +629,33 @@ def __classcall_private__(cls, function_ring, gen, name='t'): # `gen` is an Ore polynomial: if isinstance(gen, OrePolynomial): ore_polring = gen.parent() - ore_polring_base = ore_polring.base_ring() + # Base ring without morphism structure: + base_ring_noext = ore_polring.base() name = ore_polring.variable_name() # `gen` is a list of coefficients (function_ring = Fq[X]): elif isinstance(gen, (list, tuple)): ore_polring = None - ore_polring_base = Sequence(gen).universe() + # Base ring without morphism structure: + base_ring_noext = Sequence(gen).universe() else: raise TypeError('generator must be list of coefficients or Ore ' 'polynomial') + # Constant coefficient must be nonzero: + if gen[0].is_zero(): + raise ValueError('constant coefficient must be nonzero') # The coefficients are in a base ring that has coercion from Fq: - if not (hasattr(ore_polring_base, 'has_coerce_map_from') - and ore_polring_base.has_coerce_map_from( - function_ring.base_ring())): - raise ValueError('function ring base must coerce into base codomain') + if not (hasattr(base_ring_noext, 'has_coerce_map_from') and + base_ring_noext.has_coerce_map_from(function_ring.base_ring())): + raise ValueError('function ring base must coerce into base ring') - # Build the morphism that defines the category - base = function_ring.hom([ore_polring_base(gen[0])]) + # Build the category + if isinstance(base_ring_noext, RingExtension_generic): + base_ring = base_ring_noext + else: + base_morphism = Hom(function_ring, base_ring_noext)(gen[0]) + base_ring = base_ring_noext.over(base_morphism) - # Other checks in the category definition - category = DrinfeldModules(base, name=name) + category = DrinfeldModules(base_ring, name=name) # Check gen as Ore polynomial ore_polring = category.ore_polring() # Sanity cast @@ -633,7 +664,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): raise ValueError('generator must have positive degree') # Instantiate the appropriate class - if ore_polring_base.is_finite(): + if base_ring.is_finite(): from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule return FiniteDrinfeldModule(gen, category) return cls.__classcall__(cls, gen, category) @@ -682,37 +713,6 @@ def __init__(self, gen, category): self._Fq = self._function_ring.base_ring() # Must be last super().__init__(base=self._base, category=category) - def base_ring(self): - r""" - Raise exception ``AttributeError``. - - The base of a Drinfeld module is a ring morphism, not a ring. - - This method is implemented in ``CategoryObject``, of which Parent - inherits. It returns the base of the category. I overloaded it - to avoid confusion. - - EXAMPLES:: - - sage: Fq = GF(25) - sage: FqX. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.base_ring() - Traceback (most recent call last): - ... - AttributeError: the base of a Drinfeld module is a morphism - """ - # FIXME - # This method depends on the sole category of the Drinfeld - # module. It should therefore be implemented in the - # `ParentMethods` bloc of `DrinfeldModule`. This is not possible - # as the method `base_ring` from `CategoryObject` --- of which - # `Parent` and so `DrinfeldModule inherit --- is called instead. - # Any better way would be welcome. - raise AttributeError('the base of a Drinfeld module is a morphism') - def __call__(self, a): r""" Return the image of input ``a`` by the morphism that defines the @@ -828,12 +828,7 @@ def _latex_(self): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: latex(phi) - \text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }base{ }}\begin{array}{l} - \text{\texttt{Ring{ }morphism:}}\\ - \text{\texttt{{ }{ }From:{ }Univariate{ }Polynomial{ }Ring{ }in{ }X{ }over{ }Finite{ }Field{ }in{ }z2{ }of{ }size{ }5{\char`\^}2}}\\ - \text{\texttt{{ }{ }To:{ }{ }{ }Finite{ }Field{ }in{ }z12{ }of{ }size{ }5{\char`\^}12}}\\ - \text{\texttt{{ }{ }Defn:{ }X{ }|{-}{-}>{ }2*z12{\char`\^}11{ }+{ }2*z12{\char`\^}10{ }+{ }z12{\char`\^}9{ }+{ }3*z12{\char`\^}8{ }+{ }z12{\char`\^}7{ }+{ }2*z12{\char`\^}5{ }+{ }2*z12{\char`\^}4{ }+{ }3*z12{\char`\^}3{ }+{ }z12{\char`\^}2{ }+{ }2*z12}} - \end{array} + \text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }base{ }}\Bold{F}_{5^{12}} """ return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ f'{latex(self._function_ring.gen())} '\ @@ -854,10 +849,7 @@ def _repr_(self): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: phi - Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z12 of size 5^12 - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base """ return f'Drinfeld module defined by {self._function_ring.gen()} ' \ f'|--> {self._gen} over base {self._base}' @@ -880,10 +872,7 @@ def action(self): sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: action = phi.action() sage: action - Action on Finite Field in z12 of size 5^12 induced by Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z12 of size 5^12 - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Action on Finite Field in z12 of size 5^12 over its base induced by Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base The action on elements is computed as follows:: @@ -1023,14 +1012,14 @@ def height(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.height() == 1 + sage: phi.height() == 1 # todo: not tested True - sage: phi.is_ordinary() + sage: phi.is_ordinary() # todo: not tested True sage: L = Frac(FqX) - sage: phi = DrinfeldModule(FqX, [L(2), L(1)]) - sage: phi.height() + sage: phi = DrinfeldModule(FqX, [L(2), L(1)]) # todo: not tested + sage: phi.height() # todo: not tested Traceback (most recent call last): ... ValueError: height is defined for prime function field characteristic @@ -1039,9 +1028,9 @@ def height(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: phi.height() + sage: phi.height() # todo: not tested 2 - sage: phi.is_supersingular() + sage: phi.is_supersingular() # todo: not tested True """ @@ -1076,22 +1065,22 @@ def invert(self, ore_pol): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: a = FqX.random_element() - sage: phi.invert(phi(a)) == a + sage: phi.invert(phi(a)) == a # todo: not tested True - sage: phi.invert(phi(X)) == X + sage: phi.invert(phi(X)) == X # todo: not tested True - sage: phi.invert(phi(Fq.gen())) == Fq.gen() + sage: phi.invert(phi(Fq.gen())) == Fq.gen() # todo: not tested True When the input is not in the image of the Drinfeld module, an exception is raised:: sage: t = phi.ore_polring().gen() - sage: phi.invert(t + 1) + sage: phi.invert(t + 1) # todo: not tested Traceback (most recent call last): ... ValueError: input must be in the image of the Drinfeld module - sage: phi.invert(t^3 + t^2 + 1) + sage: phi.invert(t^3 + t^2 + 1) # todo: not tested Traceback (most recent call last): ... ValueError: input must be in the image of the Drinfeld module @@ -1106,19 +1095,19 @@ def invert(self, ore_pol): sage: a = FqX.random_element() sage: cat = phi.category() sage: phi_r1 = cat.random_object(1) - sage: phi_r1.invert(phi_r1(a)) == a + sage: phi_r1.invert(phi_r1(a)) == a # todo: not tested True sage: phi_r2 = cat.random_object(2) - sage: phi_r2.invert(phi_r2(a)) == a + sage: phi_r2.invert(phi_r2(a)) == a # todo: not tested True sage: phi_r3 = cat.random_object(3) - sage: phi_r3.invert(phi_r3(a)) == a + sage: phi_r3.invert(phi_r3(a)) == a # todo: not tested True sage: phi_r4 = cat.random_object(4) - sage: phi_r4.invert(phi_r4(a)) == a + sage: phi_r4.invert(phi_r4(a)) == a # todo: not tested True sage: phi_r5 = cat.random_object(5) - sage: phi_r5.invert(phi_r5(a)) == a + sage: phi_r5.invert(phi_r5(a)) == a # todo: not tested True """ deg = ore_pol.degree() @@ -1145,7 +1134,8 @@ def invert(self, ore_pol): if self(pre_image) == ore_pol: return pre_image else: - return None + raise ValueError('input must be in the image of the Drinfeld ' + 'module') def is_finite(self): r""" @@ -1163,8 +1153,8 @@ def is_finite(self): sage: phi.is_finite() True sage: L = Frac(FqX) - sage: psi = DrinfeldModule(FqX, [L(2), L(1)]) - sage: psi.is_finite() + sage: psi = DrinfeldModule(FqX, [L(2), L(1)]) # todo: not tested + sage: psi.is_finite() # todo: not tested False """ from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule @@ -1229,7 +1219,7 @@ def morphism(self): sage: phi.morphism() Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 twisted by z12 |--> z12^(5^2) + To: Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 over its base twisted by Frob^2 Defn: X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: from sage.rings.morphism import RingHomomorphism sage: isinstance(phi.morphism(), RingHomomorphism) @@ -1329,10 +1319,7 @@ def velu(self, isog): sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(isog) sage: psi - Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z12 of size 5^12 - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base sage: isog in Hom(phi, psi) True @@ -1340,7 +1327,7 @@ def velu(self, isog): sage: phi.velu(phi(X)) is phi True - sage: phi.velu(t^6) is phi + sage: phi.velu(t^6) is phi # todo: not tested True The following inputs do not define isogenies, and the method diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 0991a343b35..46f6be20bd3 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -47,6 +47,11 @@ class FiniteDrinfeldModule(DrinfeldModule): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [z6, 0, 5]) + sage: phi + Drinfeld module defined by X |--> 5*t^2 + z6 over base Finite Field in z6 of size 7^6 over its base + + :: + sage: isinstance(phi, DrinfeldModule) True sage: from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule @@ -68,8 +73,8 @@ class FiniteDrinfeldModule(DrinfeldModule): First of all, it is easy to create the Frobenius endomorphism:: - sage: frobenius_endomorphism = phi.frobenius_endomorphism() - sage: frobenius_endomorphism + sage: frobenius_endomorphism = phi.frobenius_endomorphism() # todo: not tested + sage: frobenius_endomorphism # todo: not tested Drinfeld Module morphism: From (gen): 5*t^2 + z6 To (gen): 5*t^2 + z6 @@ -77,27 +82,27 @@ class FiniteDrinfeldModule(DrinfeldModule): Its characteristic polynomial can be computed:: - sage: chi = phi.frobenius_charpoly() - sage: chi + sage: chi = phi.frobenius_charpoly() # todo: not tested + sage: chi # todo: not tested T^2 + (X + 2*z3^2 + 2*z3 + 1)*T + 2*X^2 + (z3^2 + z3 + 4)*X + 2*z3 - sage: frob_pol = frobenius_endomorphism.ore_polynomial() - sage: chi(frob_pol, phi(X)) + sage: frob_pol = frobenius_endomorphism.ore_polynomial() # todo: not tested + sage: chi(frob_pol, phi(X)) # todo: not tested 0 This makes it possible to compute the Frobenius trace and norm:: - sage: phi.frobenius_trace() + sage: phi.frobenius_trace() # todo: not tested 6*X + 5*z3^2 + 5*z3 + 6 - sage: phi.frobenius_trace() == -chi[1] + sage: phi.frobenius_trace() == -chi[1] # todo: not tested True - sage: phi.frobenius_norm() + sage: phi.frobenius_norm() # todo: not tested 2*X^2 + (z3^2 + z3 + 4)*X + 2*z3 And to decide if a Drinfeld module is ordinary or supersingular:: - sage: phi.is_ordinary() + sage: phi.is_ordinary() # todo: not tested True - sage: phi.is_supersingular() + sage: phi.is_supersingular() # todo: not tested False """ @@ -155,19 +160,19 @@ def frobenius_endomorphism(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: phi.frobenius_endomorphism() + sage: phi.frobenius_endomorphism() # todo: not tested Drinfeld Module morphism: From (gen): z6*t^2 + 1 To (gen): z6*t^2 + 1 Defn: t^2 sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism - sage: isinstance(phi.frobenius_endomorphism(), DrinfeldModuleMorphism) + sage: isinstance(phi.frobenius_endomorphism(), DrinfeldModuleMorphism) # todo: not tested True """ t = self.ore_polring().gen() - L = self._base.codomain() - Fq = self._function_ring.base_ring() - deg = L.over(Fq).degree(Fq) + Fq = self._function_ring.base() + #FIXME + deg = self._base.over(Fq).degree(Fq) return self._Hom_(self, category=self.category())(t**deg) def frobenius_charpoly(self, var='T'): @@ -206,31 +211,31 @@ def frobenius_charpoly(self, var='T'): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: chi = phi.frobenius_charpoly() - sage: chi + sage: chi = phi.frobenius_charpoly() # todo: not tested + sage: chi # todo: not tested T^2 + ((3*z3^2 + z3 + 4)*X + 4*z3^2 + 6*z3 + 3)*T + (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 :: - sage: frob_pol = phi.frobenius_endomorphism().ore_polynomial() - sage: chi(frob_pol, phi(X)) + sage: frob_pol = phi.frobenius_endomorphism().ore_polynomial() # todo: not tested + sage: chi(frob_pol, phi(X)) # todo: not tested 0 :: - sage: A = phi.frobenius_trace() - sage: A + sage: A = phi.frobenius_trace() # todo: not tested + sage: A # todo: not tested (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 - sage: B = phi.frobenius_norm() - sage: B + sage: B = phi.frobenius_norm() # todo: not tested + sage: B # todo: not tested (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 :: sage: n = 2 # Degree over Fq of the base codomain - sage: A.degree() <= n/2 + sage: A.degree() <= n/2 # todo: not tested True - sage: B.degree() == n + sage: B.degree() == n # todo: not tested True ALGORITHM: @@ -272,19 +277,19 @@ def frobenius_norm(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: B = phi.frobenius_norm() - sage: B + sage: B = phi.frobenius_norm() # todo: not tested + sage: B # todo: not tested (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 :: sage: n = 2 # Degree over Fq of the base codomain - sage: B.degree() == n + sage: B.degree() == n # todo: not tested True :: - sage: B == phi.frobenius_charpoly()[0] + sage: B == phi.frobenius_charpoly()[0] # todo: not tested True ALGORITHM: @@ -339,19 +344,19 @@ def frobenius_trace(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: A = phi.frobenius_trace() - sage: A + sage: A = phi.frobenius_trace() # todo: not tested + sage: A # todo: not tested (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 :: sage: n = 2 # Degree over Fq of the base codomain - sage: A.degree() <= n/2 + sage: A.degree() <= n/2 # todo: not tested True :: - sage: A == -phi.frobenius_charpoly()[1] + sage: A == -phi.frobenius_charpoly()[1] # todo: not tested True """ self._check_rank_two() @@ -384,10 +389,10 @@ def is_ordinary(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: phi.is_ordinary() + sage: phi.is_ordinary() # todo: not tested False - sage: phi_p = phi(phi.characteristic()) - sage: phi_p # Purely inseparable + sage: phi_p = phi(phi.characteristic()) # todo: not tested + sage: phi_p # Purely inseparable # todo: not tested z6*t^2 ALGORITHM: @@ -420,9 +425,9 @@ def is_supersingular(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: phi.is_supersingular() + sage: phi.is_supersingular() # todo: not tested True - sage: phi(phi.characteristic()) # Purely inseparable + sage: phi(phi.characteristic()) # Purely inseparable # todo: not tested z6*t^2 ALGORITHM: diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 690d35653b8..71604593ab2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -64,8 +64,8 @@ class DrinfeldModuleHomset(Homset): The domain and codomain must have the same Drinfeld modules category:: - sage: rho = DrinfeldModule(FqX, [Frac(FqX)(X), 1]) - sage: Hom(phi, rho) + sage: rho = DrinfeldModule(FqX, [Frac(FqX)(X), 1]) # todo: not tested + sage: Hom(phi, rho) # todo: not tested Traceback (most recent call last): ... ValueError: Drinfeld modules must be in the same category @@ -262,8 +262,8 @@ def __contains__(self, x): sage: identity_morphism = end(1) sage: identity_morphism in hom False - sage: frobenius_endomorphism = phi.frobenius_endomorphism() - sage: frobenius_endomorphism in hom + sage: frobenius_endomorphism = phi.frobenius_endomorphism() # todo: not tested + sage: frobenius_endomorphism in hom # todo: not tested False """ try: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 9f36cdeca13..a752b3ddb1c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -64,20 +64,14 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, One can get basic data on the morphism:: sage: morphism.domain() - Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z12 of size 5^12 - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base sage: morphism.domain() is phi True :: sage: morphism.codomain() - Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 - To: Finite Field in z12 of size 5^12 - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base sage: morphism.codomain() is psi True @@ -106,7 +100,7 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: morphism.parent() == Hom(phi, psi) True - sage: phi.frobenius_endomorphism().parent() == End(phi) + sage: phi.frobenius_endomorphism().parent() == End(phi) # todo: not tested True sage: End(phi)(0).parent() == End(phi) True @@ -321,8 +315,8 @@ def is_isogeny(self): :: - sage: frobenius_endomorphism = phi.frobenius_endomorphism() - sage: frobenius_endomorphism.is_isogeny() + sage: frobenius_endomorphism = phi.frobenius_endomorphism() # todo: not tested + sage: frobenius_endomorphism.is_isogeny() # todo: not tested True """ return not self.is_zero() @@ -357,8 +351,8 @@ def is_isomorphism(self): :: - sage: frobenius_endomorphism = phi.frobenius_endomorphism() - sage: frobenius_endomorphism.is_isomorphism() + sage: frobenius_endomorphism = phi.frobenius_endomorphism() # todo: not tested + sage: frobenius_endomorphism.is_isomorphism() # todo: not tested False """ return self.is_isogeny() and self._ore_polynomial.degree() == 0 From 321ba43b09501549f459a0ec0e70349ade428c92 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 5 Dec 2022 09:41:46 +0100 Subject: [PATCH 161/392] start to cache solutions --- src/sage/combinat/bijectionist.py | 186 +++++++++++++++++++++++++----- 1 file changed, 154 insertions(+), 32 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 4b4b52089d6..fd2c804ede1 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1529,16 +1529,16 @@ def _forced_constant_blocks(self): if updated_preimages: break for i, j in itertools.combinations(copy(multiple_preimages[values]), r=2): # copy to be able to modify list - bmilp_veto = deepcopy(bmilp) # adding constraints to a simple copy adds them to the original instance, too + tmp_constraints = [] try: # veto the two blocks having the same value for z in self._possible_block_values[i]: if z in self._possible_block_values[j]: # intersection - bmilp_veto.milp.add_constraint(bmilp_veto._x[i, z] + bmilp_veto._x[j, z] <= 1) - bmilp_veto.milp.solve() + tmp_constraints.append(bmilp._x[i, z] + bmilp._x[j, z] <= 1) + bmilp.solve(tmp_constraints) # solution exists, update dictionary - solution = self._solution_by_blocks(bmilp_veto) + solution = self._solution_by_blocks(bmilp) updated_multiple_preimages = {} for values in multiple_preimages: for p in multiple_preimages[values]: @@ -1688,17 +1688,17 @@ def add_solution(solutions, solution): # iterate through blocks and generate all values for p in blocks: - veto_bmilp = deepcopy(bmilp) # adding constraints to a simple copy adds them to the original instance, too + tmp_constraints = [] for value in solutions[p]: - veto_bmilp.milp.add_constraint(veto_bmilp._x[p, value] == 0) + tmp_constraints.append(bmilp._x[p, value] == 0) while True: try: - veto_bmilp.milp.solve() # problem has a solution, so new value was found - solution = self._solution(veto_bmilp) + bmilp.solve(tmp_constraints) + solution = self._solution(bmilp) add_solution(solutions, solution) # veto new value and try again - veto_bmilp.milp.add_constraint(veto_bmilp._x[p, solution[p]] == 0) + tmp_constraints.append(bmilp._x[p, solution[p]] == 0) except MIPSolverException: # no solution, so all possible values have been found break @@ -1788,6 +1788,7 @@ def minimal_subdistributions_iterator(self, tA=None): except MIPSolverException: return s = self._solution(bmilp) + tmp_constraints = [] while True: for v in self._Z: minimal_subdistribution.add_constraint(sum(D[a] for a in self._A if s[a] == v) == V[v]) @@ -1833,10 +1834,9 @@ def _find_counter_example(self, bmilp, s0, d): if not v_in_d_count: continue - veto_bmilp = deepcopy(bmilp) # adding constraints to a simple copy adds them to the original instance, too # try to find a solution which has a different # subdistribution on d than s0 - v_in_d = sum(d[a] * veto_bmilp._x[self._P.find(a), v] + v_in_d = sum(d[a] * bmilp._x[self._P.find(a), v] for a in self._A if v in self._possible_block_values[self._P.find(a)]) @@ -1844,10 +1844,10 @@ def _find_counter_example(self, bmilp, s0, d): # a value among {a | d[a] == 1} than it does in # v_in_d_count, because, if the distributions are # different, one such v must exist - veto_bmilp.milp.add_constraint(v_in_d <= v_in_d_count - 1) + tmp_constraints = [v_in_d <= v_in_d_count - 1] try: - veto_bmilp.milp.solve() - return self._solution(veto_bmilp) + bmilp.solve(tmp_constraints) + return self._solution(bmilp) except MIPSolverException: pass return @@ -2055,10 +2055,9 @@ def _find_counter_example2(self, bmilp, P, s0, d): if not v_in_d_count: continue - veto_bmilp = deepcopy(bmilp) # adding constraints to a simple copy adds them to the original instance, too # try to find a solution which has a different # subdistribution on d than s0 - v_in_d = sum(d[p] * veto_bmilp._x[p, v] + v_in_d = sum(d[p] * bmilp._x[p, v] for p in P if v in self._possible_block_values[p]) @@ -2066,10 +2065,10 @@ def _find_counter_example2(self, bmilp, P, s0, d): # a value among {a | d[a] == 1} than it does in # v_in_d_count, because, if the distributions are # different, one such v must exist - veto_bmilp.milp.add_constraint(v_in_d <= v_in_d_count - 1) + tmp_constraints = [v_in_d <= v_in_d_count - 1] try: - veto_bmilp.milp.solve() - return self._solution_by_blocks(veto_bmilp) + bmilp.solve(tmp_constraints) + return self._solution_by_blocks(bmilp) except MIPSolverException: pass return @@ -2346,17 +2345,16 @@ def solutions_iterator(self): """ try: - bmilp = self._generate_and_solve_initial_bmilp() + self.bmilp = self._generate_and_solve_initial_bmilp() except MIPSolverException: return while True: - yield self._solution(bmilp) - bmilp.veto_current_solution() + yield self._solution(self.bmilp) if get_verbose() >= 2: print("after vetoing") - self._show_bmilp(bmilp, variables=False) + self._show_bmilp(self.bmilp, variables=False) try: - bmilp.milp.solve() + self.bmilp.solve([], force_new_solution=True) except MIPSolverException: return @@ -2369,7 +2367,7 @@ def _solution(self, bmilp): map = {} # A -> Z, a +-> s(a) for p, block in self._P.root_to_elements_dict().items(): for z in self._possible_block_values[p]: - if bmilp.milp.get_values(bmilp._x[p, z]) == 1: + if bmilp.get_value(p, z) == 1: for a in block: map[a] = z break @@ -2384,7 +2382,7 @@ def _solution_by_blocks(self, bmilp): map = {} # P -> Z, a +-> s(a) for p in _disjoint_set_roots(self._P): for z in self._possible_block_values[p]: - if bmilp.milp.get_values(bmilp._x[p, z]) == 1: + if bmilp.get_value(p, z) == 1: map[p] = z break return map @@ -2447,7 +2445,7 @@ def _generate_and_solve_initial_bmilp(self): if get_verbose() >= 2: self._show_bmilp(bmilp) assert n == bmilp.milp.number_of_variables(), "The number of variables increased." - bmilp.milp.solve() + bmilp.solve([]) return bmilp @@ -2464,6 +2462,11 @@ def __init__(self, bijectionist: Bijectionist): # _W, _Z, _A, _B, _P, _alpha, _beta, _tau, _pi_rho self.milp = MixedIntegerLinearProgram(solver=bijectionist._solver) self.milp.set_objective(None) + self._n_variables = -1 + self._solution_cache = [] + self._last_solution = {} + self._index_block_value_dict = None + self._block_value_index_dict = None# TODO: may not be needed? self._x = self.milp.new_variable(binary=True) # indexed by P x Z self._bijectionist = bijectionist @@ -2474,6 +2477,79 @@ def __init__(self, bijectionist: Bijectionist): for z in bijectionist._possible_block_values[p]) == 1, name=name[:50]) + def clear_solution_cache(self): + self._n_variables = -1 + self._solution_cache = [] + self._last_solution = {} + self._index_block_value_dict = None + self._block_value_index_dict = None # TODO: may not be needed? + + def solve(self, tmp_constraints, force_new_solution=False): + if self._n_variables < 0: + self._n_variables = self.milp.number_of_variables() + self._index_block_value_dict = {} + self._block_value_index_dict = {} + for (p, z), v in self._x.items(): + variable_index = next(iter(v.dict().keys())) + self._index_block_value_dict[variable_index] = (p,z) + self._block_value_index_dict[(p,z)] = variable_index + assert self._n_variables == self.milp.number_of_variables(), "The number of variables changed." # number of variables would change with creation of constraints with new variables + + # check if previous solution exists with constraints + previous_solution_exists = False + self.last_solution = {} + if not force_new_solution: + for solution in self._solution_cache: + fulfills_constraints = True + # loop through all constraints + for constraint in tmp_constraints: + # check equations + for linear_function, value in constraint.equations(): + solution_value = self._evaluate_linear_function(linear_function.dict(), self._index_block_value_dict, solution) + if solution_value != value.dict()[-1]: + fulfills_constraints = False + break + if not fulfills_constraints: + break + # check inequalities + for linear_function, value in constraint.inequalities(): + solution_value = self._evaluate_linear_function(linear_function.dict(), self._index_block_value_dict, solution) + if solution_value > value.dict()[-1]: + fulfills_constraints = False + break + if not fulfills_constraints: + break + if fulfills_constraints: + previous_solution_exists = True + self.last_solution = solution + break + + # if no previous solution satisfies the constraints, generate a new one + if not previous_solution_exists: + try: + n_constraints = self.milp.number_of_constraints() + for constraint in tmp_constraints: + self.milp.add_constraint(constraint) + self.milp.solve() + for _ in range(self.milp.number_of_constraints()-n_constraints): + self.milp.remove_constraint(n_constraints) + except MIPSolverException as error: + for _ in range(self.milp.number_of_constraints()-n_constraints): + self.milp.remove_constraint(n_constraints) + raise error + + self.last_solution = self.milp.get_values(self._x) + self._solution_cache.append(self.last_solution) + + self.veto_current_solution() + return self.last_solution + + def _evaluate_linear_function(self, linear_function_dict, block_index_dict, values): + return float(sum(linear_function_dict[index]*values[block_index_dict[index]] for index in linear_function_dict)) + + def get_value(self, p, v): + return self.last_solution[p,v] + def add_alpha_beta_constraints(self): r""" Add constraints enforcing that `(alpha, s)` is equidistributed @@ -2700,12 +2776,62 @@ def _non_copying_intersection(sets): """ TESTS:: + ##################### + # caching base test # + ##################### + sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] + sage: tau = lambda D: D.number_of_touch_points() + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) + sage: bmilp = bij._generate_and_solve_initial_bmilp() + +Print the generated solution:: + + sage: bmilp.milp.get_values(bmilp._x) + {([], 0): 1.0, + ([1, 0], 1): 1.0, + ([1, 0, 1, 0], 1): 0.0, + ([1, 0, 1, 0], 2): 1.0, + ([1, 1, 0, 0], 1): 1.0, + ([1, 1, 0, 0], 2): 0.0} + +Generating a new solution that also maps `1010` to `2` fails: + + sage: from sage.combinat.bijectionist import _disjoint_set_roots + sage: permutation1010root = list(_disjoint_set_roots(bij._P))[2] + sage: permutation1010root + [1, 0, 1, 0] + sage: bmilp.solve([bmilp._x[permutation1010root, 1] <= 0.5], force_new_solution=True) + Traceback (most recent call last): + ... + MIPSolverException: GLPK: Problem has no feasible solution + +However, searching for a cached solution succeeds, for inequalities and equalities:: + + sage: bmilp.solve([bmilp._x[permutation1010root, 1] <= 0.5]) + {([], 0): 1.0, + ([1, 0], 1): 1.0, + ([1, 0, 1, 0], 1): 0.0, + ([1, 0, 1, 0], 2): 1.0, + ([1, 1, 0, 0], 1): 1.0, + ([1, 1, 0, 0], 2): 0.0} + + sage: bmilp.solve([bmilp._x[permutation1010root, 1] == 0]) + {([], 0): 1.0, + ([1, 0], 1): 1.0, + ([1, 0, 1, 0], 1): 0.0, + ([1, 0, 1, 0], 2): 1.0, + ([1, 1, 0, 0], 1): 1.0, + ([1, 1, 0, 0], 2): 0.0} + + sage: As = Bs = [[], ....: [(1,i,j) for i in [-1,0,1] for j in [-1,1]], ....: [(2,i,j) for i in [-1,0,1] for j in [-1,1]], ....: [(3,i,j) for i in [-2,-1,0,1,2] for j in [-1,1]]] - # adding [(2,-2,-1), (2,2,-1), (2,-2,1), (2,2,1)] makes it take (seemingly) forever +Note that adding ``[(2,-2,-1), (2,2,-1), (2,-2,1), (2,2,1)]`` makes +it take (seemingly) forever.:: sage: c1 = lambda a, b: (a[0]+b[0], a[1]*b[1], a[2]*b[2]) sage: c2 = lambda a: (a[0], -a[1], a[2]) @@ -2745,10 +2871,6 @@ def _non_copying_intersection(sets): sage: sorted(l1, key=lambda s: tuple(s.items())) == l2 True -""" - - -""" Our benchmark example:: sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition From 306395cf92435f6abc1656c7677cbb68ef7b02b2 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 21 Dec 2022 16:09:45 +0100 Subject: [PATCH 162/392] finish implementation of cache --- src/sage/combinat/bijectionist.py | 372 +++++++++++++++++++----------- 1 file changed, 239 insertions(+), 133 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index fd2c804ede1..75e904df59e 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1,24 +1,4 @@ # -*- coding: utf-8 -*- -# pylint: disable=all - -# TODO: (high): -# -# check whether it makes sense to keep a list of solutions, and keep -# a global MILP up to date with this list - -# TODO: (medium): -# -# can we somehow tweak gurobi so that -# minimal_subdistributions_iterator considers the minimal -# subdistributions with "smallest" (e.g., in the sorting order -# defined above) elements first? - -# Priorities: - -# 1) for an all-knowing user, code should be as fast as possible -# 2) code should be easy to understand -# 3) anticipate that a user computes things in a bad order - r""" A bijectionist's toolkit @@ -499,7 +479,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), ele Check that large input sets are handled well:: sage: A = B = list(range(20000)) - sage: bij = Bijectionist(A, B) + sage: bij = Bijectionist(A, B) # long time """ # glossary of standard letters: # A, B, Z, W ... finite sets @@ -515,6 +495,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), ele assert len(A) == len(set(A)), "A must have distinct items" assert len(B) == len(set(B)), "B must have distinct items" + self.bmilp = None self._A = A self._B = B self._sorter = {} @@ -607,6 +588,7 @@ def set_constant_blocks(self, P): MIPSolverException: ... """ + self.bmilp = None self._P = DisjointSet(self._A) P = sorted(self._sorter["A"](p) for p in P) for p in P: @@ -738,6 +720,7 @@ def set_statistics(self, *alpha_beta): {[]: 2, [1]: 1, [1, 2]: 0, [2, 1]: 0} """ + self.bmilp = None self._n_statistics = len(alpha_beta) # TODO: (low) do we really want to recompute statistics every time? self._alpha = lambda p: tuple(arg[0](p) for arg in alpha_beta) @@ -1018,11 +1001,7 @@ def set_value_restrictions(self, *a_values): empty. In this example, the image of `\tau` under any legal bijection is disjoint to the specified values. - .. TODO:: - - we now have to call - :meth:`_compute_possible_block_values` for the error - message. Is this intended behaviour? + TESTS:: sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: tau = Permutation.longest_increasing_subsequence_length @@ -1033,8 +1012,6 @@ def set_value_restrictions(self, *a_values): ... ValueError: No possible values found for singleton block [[1, 2]] - TESTS:: - sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: tau = Permutation.longest_increasing_subsequence_length sage: bij = Bijectionist(A, B, tau) @@ -1058,6 +1035,7 @@ def set_value_restrictions(self, *a_values): # of _statistics_possible_values - however, we do not want to # insist that set_value_restrictions is called after # set_statistics + self.bmilp = None set_Z = set(self._Z) self._restrictions_possible_values = {a: set_Z for a in self._A} for a, values in a_values: @@ -1223,6 +1201,7 @@ def set_distributions(self, *elements_distributions): not in `A`. """ + self.bmilp = None for elements, values in elements_distributions: assert len(elements) == len(values), f"{elements} and {values} are not of the same size!" for a, z in zip(elements, values): @@ -1318,6 +1297,7 @@ def set_intertwining_relations(self, *pi_rho): [] """ + self.bmilp = None Pi_Rho = namedtuple("Pi_Rho", "numargs pi rho domain") self._pi_rho = [] @@ -1506,10 +1486,11 @@ def _forced_constant_blocks(self): {{'a', 'b'}, {'c', 'd'}} """ - bmilp = self._generate_and_solve_initial_bmilp() # may throw Exception + if self.bmilp is None: + self.bmilp = self._generate_and_solve_initial_bmilp() # may throw Exception # generate blockwise preimage to determine which blocks have the same image - solution = self._solution_by_blocks(bmilp) + solution = self._solution_by_blocks(self.bmilp) multiple_preimages = {(value,): preimages for value, preimages in _invert_dict(solution).items() if len(preimages) > 1} @@ -1534,11 +1515,11 @@ def _forced_constant_blocks(self): # veto the two blocks having the same value for z in self._possible_block_values[i]: if z in self._possible_block_values[j]: # intersection - tmp_constraints.append(bmilp._x[i, z] + bmilp._x[j, z] <= 1) - bmilp.solve(tmp_constraints) + tmp_constraints.append(self.bmilp._x[i, z] + self.bmilp._x[j, z] <= 1) + self.bmilp.solve(tmp_constraints) # solution exists, update dictionary - solution = self._solution_by_blocks(bmilp) + solution = self._solution_by_blocks(self.bmilp) updated_multiple_preimages = {} for values in multiple_preimages: for p in multiple_preimages[values]: @@ -1565,11 +1546,6 @@ def possible_values(self, p=None, optimal=False): Return for each block the values of `s` compatible with the imposed restrictions. - .. TODO:: - - should this method update and return - ``self._possible_block_values``? - INPUT: - ``p`` (optional, default: ``None``) -- a block of `P`, or @@ -1654,11 +1630,6 @@ def possible_values(self, p=None, optimal=False): sage: bij.possible_values(p=[DyckWord([]), DyckWord([1, 0]), DyckWord([1, 0, 1, 0]), DyckWord([1, 1, 0, 0])], optimal=True) {[]: {0}, [1, 0]: {1}, [1, 0, 1, 0]: {1, 2}, [1, 1, 0, 0]: {1, 2}} - .. TODO:: - - test to show that the solution for all blocks is not more - expensive than using :meth:`solutions_iterator` - """ # convert input to set of block representatives blocks = set() @@ -1681,8 +1652,9 @@ def add_solution(solutions, solution): solutions[p].add(value) # generate initial solution, solution dict and add solution - bmilp = self._generate_and_solve_initial_bmilp() - solution = self._solution(bmilp) + if self.bmilp is None: + self.bmilp = self._generate_and_solve_initial_bmilp() + solution = self._solution(self.bmilp) solutions = {} add_solution(solutions, solution) @@ -1690,25 +1662,23 @@ def add_solution(solutions, solution): for p in blocks: tmp_constraints = [] for value in solutions[p]: - tmp_constraints.append(bmilp._x[p, value] == 0) + tmp_constraints.append(self.bmilp._x[p, value] == 0) while True: try: # problem has a solution, so new value was found - bmilp.solve(tmp_constraints) - solution = self._solution(bmilp) + self.bmilp.solve(tmp_constraints) + solution = self._solution(self.bmilp) add_solution(solutions, solution) # veto new value and try again - tmp_constraints.append(bmilp._x[p, solution[p]] == 0) + tmp_constraints.append(self.bmilp._x[p, solution[p]] == 0) except MIPSolverException: # no solution, so all possible values have been found break - # TODO: update possible block values if wanted - # create dictionary to return possible_values = {} for p in blocks: - for a in self._P.root_to_elements_dict()[p]: # TODO: is this the format we want to return in or possible_values[block]? + for a in self._P.root_to_elements_dict()[p]: if optimal: possible_values[a] = solutions[p] else: @@ -1716,24 +1686,13 @@ def add_solution(solutions, solution): return possible_values - def minimal_subdistributions_iterator(self, tA=None): + def minimal_subdistributions_iterator(self): r""" - Return all minimal subsets `\tilde A` of `A` containing `tA` + Return all minimal subsets `\tilde A` of `A` together with submultisets `\tilde Z` with `s(\tilde A) = \tilde Z` as multisets. - .. TODO:: - - should this method interact with ``self._elements_distributions``? - - INPUT: - - - ``tA`` (optional, default: ``None``) -- a subset of `A` TODO: add this - - If ``tA`` is not ``None``, return an iterator of the - subdistributions containing ``tA``. - - TESTS:: + EXAMPLES:: sage: A = B = [permutation for n in range(3) for permutation in Permutations(n)] sage: bij = Bijectionist(A, B, len) @@ -1784,11 +1743,11 @@ def minimal_subdistributions_iterator(self, tA=None): minimal_subdistribution.add_constraint(sum(D[a] for a in self._A) >= 1) try: - bmilp = self._generate_and_solve_initial_bmilp() + if self.bmilp is None: + self.bmilp = self._generate_and_solve_initial_bmilp() except MIPSolverException: return - s = self._solution(bmilp) - tmp_constraints = [] + s = self._solution(self.bmilp) while True: for v in self._Z: minimal_subdistribution.add_constraint(sum(D[a] for a in self._A if s[a] == v) == V[v]) @@ -1797,7 +1756,7 @@ def minimal_subdistributions_iterator(self, tA=None): except MIPSolverException: return d = minimal_subdistribution.get_values(D) # a dict from A to {0, 1} - new_s = self._find_counter_example(bmilp, s, d) + new_s = self._find_counter_example(self.bmilp, s, d) if new_s is None: values = self._sorter["Z"](s[a] for a in self._A if d[a]) yield ([a for a in self._A if d[a]], values) @@ -1852,10 +1811,10 @@ def _find_counter_example(self, bmilp, s0, d): pass return - def minimal_subdistributions_blocks_iterator(self, p=None): + def minimal_subdistributions_blocks_iterator(self): r""" Return all representatives of minimal subsets `\tilde P` - of `P` containing `p` together with submultisets `\tilde Z` + of `P` together with submultisets `\tilde Z` with `s(\tilde P) = \tilde Z` as multisets. .. WARNING:: @@ -1868,21 +1827,6 @@ def minimal_subdistributions_blocks_iterator(self, p=None): :meth:`minimal_subdistributions_iterator`, which is, however, computationally more expensive. - .. TODO:: - - should this method interact with ``self._elements_distributions``? - - INPUT: - - - ``p`` (optional, default: ``None``) -- a subset of `P` - - If ``p`` is not ``None``, return an iterator of the - subdistributions containing ``p``. - - .. TODO:: - - the optional argument is not yet supported - EXAMPLES:: sage: A = B = [permutation for n in range(3) for permutation in Permutations(n)] @@ -2003,10 +1947,11 @@ def add_counter_example_constraint(s): if s[p] == v) == V[v]) try: - bmilp = self._generate_and_solve_initial_bmilp() + if self.bmilp is None: + self.bmilp = self._generate_and_solve_initial_bmilp() except MIPSolverException: return - s = self._solution_by_blocks(bmilp) + s = self._solution_by_blocks(self.bmilp) add_counter_example_constraint(s) while True: try: @@ -2014,7 +1959,7 @@ def add_counter_example_constraint(s): except MIPSolverException: return d = minimal_subdistribution.get_values(D) # a dict from P to multiplicities - new_s = self._find_counter_example2(bmilp, P, s, d) + new_s = self._find_counter_example2(self.bmilp, P, s, d) if new_s is None: yield ([p for p in P for _ in range(ZZ(d[p]))], self._sorter["Z"](s[p] @@ -2264,6 +2209,48 @@ def solutions_iterator(self): sage: set_verbose(2) sage: _ = list(bij.solutions_iterator()) + after vetoing + Constraints are: + block []: 1 <= x_0 <= 1 + block [1]: 1 <= x_1 <= 1 + block [1, 2]: 1 <= x_2 + x_3 <= 1 + block [2, 1]: 1 <= x_4 + x_5 <= 1 + block [1, 2, 3]: 1 <= x_6 + x_7 + x_8 <= 1 + block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 + block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 + statistics: 1 <= x_0 <= 1 + statistics: 1 <= x_1 <= 1 + statistics: 1 <= x_2 + x_4 <= 1 + statistics: 1 <= x_3 + x_5 <= 1 + statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 + statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 + statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 + veto: x_0 + x_1 + x_3 + x_4 + x_6 + x_10 + x_14 <= 6 + veto: x_0 + x_1 + x_2 + x_5 + x_6 + x_10 + x_14 <= 6 + after vetoing + Constraints are: + block []: 1 <= x_0 <= 1 + block [1]: 1 <= x_1 <= 1 + block [1, 2]: 1 <= x_2 + x_3 <= 1 + block [2, 1]: 1 <= x_4 + x_5 <= 1 + block [1, 2, 3]: 1 <= x_6 + x_7 + x_8 <= 1 + block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 + block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 + statistics: 1 <= x_0 <= 1 + statistics: 1 <= x_1 <= 1 + statistics: 1 <= x_2 + x_4 <= 1 + statistics: 1 <= x_3 + x_5 <= 1 + statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 + statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 + statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 + veto: x_0 + x_1 + x_3 + x_4 + x_6 + x_10 + x_14 <= 6 + veto: x_0 + x_1 + x_2 + x_5 + x_6 + x_10 + x_14 <= 6 + + Changing or re-setting problem parameters clears the internal cache and + prints even more information:: + + sage: bij.set_constant_blocks(P) + sage: _ = list(bij.solutions_iterator()) Constraints are: block []: 1 <= x_0 <= 1 block [1]: 1 <= x_1 <= 1 @@ -2343,18 +2330,147 @@ def solutions_iterator(self): sage: list(bij.solutions_iterator()) [] + Testing interactions between multiple instances using Fedor Petrov's example from https://mathoverflow.net/q/424187:: + + sage: A = B = ["a"+str(i) for i in range(1, 9)] + ["b"+str(i) for i in range(3, 9)] + ["d"] + sage: tau = {b: 0 if i < 10 else 1 for i, b in enumerate(B)}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_constant_blocks([["a"+str(i), "b"+str(i)] for i in range(1, 9) if "b"+str(i) in A]) + sage: d = [0]*8+[1]*4 + sage: bij.set_distributions((A[:8] + A[8+2:-1], d), (A[:8] + A[8:-3], d)) + sage: iterator1 = bij.solutions_iterator() + sage: iterator2 = bij.solutions_iterator() + + Generate a solution in iterator1, iterator2 should generate the same solution and vice versa:: + sage: next(iterator1) + {'a1': 1, + 'a2': 1, + 'a3': 0, + 'a4': 0, + 'a5': 0, + 'a6': 1, + 'a7': 0, + 'a8': 0, + 'b3': 0, + 'b4': 0, + 'b5': 0, + 'b6': 1, + 'b7': 0, + 'b8': 0, + 'd': 1} + + sage: next(iterator2) + {'a1': 1, + 'a2': 1, + 'a3': 0, + 'a4': 0, + 'a5': 0, + 'a6': 1, + 'a7': 0, + 'a8': 0, + 'b3': 0, + 'b4': 0, + 'b5': 0, + 'b6': 1, + 'b7': 0, + 'b8': 0, + 'd': 1} + + sage: next(iterator2) + {'a1': 1, + 'a2': 1, + 'a3': 0, + 'a4': 0, + 'a5': 1, + 'a6': 0, + 'a7': 0, + 'a8': 0, + 'b3': 0, + 'b4': 0, + 'b5': 1, + 'b6': 0, + 'b7': 0, + 'b8': 0, + 'd': 1} + + sage: next(iterator1) + {'a1': 1, + 'a2': 1, + 'a3': 0, + 'a4': 0, + 'a5': 1, + 'a6': 0, + 'a7': 0, + 'a8': 0, + 'b3': 0, + 'b4': 0, + 'b5': 1, + 'b6': 0, + 'b7': 0, + 'b8': 0, + 'd': 1} + + Re-setting the distribution resets the cache, so a new iterator will generate the first solutions again, + but the old iterator continues:: + + sage: bij.set_distributions((A[:8] + A[8+2:-1], d), (A[:8] + A[8:-3], d)) + sage: iterator3 = bij.solutions_iterator() + + sage: next(iterator3) + {'a1': 1, + 'a2': 1, + 'a3': 0, + 'a4': 0, + 'a5': 0, + 'a6': 1, + 'a7': 0, + 'a8': 0, + 'b3': 0, + 'b4': 0, + 'b5': 0, + 'b6': 1, + 'b7': 0, + 'b8': 0, + 'd': 1} + + sage: next(iterator1) + {'a1': 0, + 'a2': 1, + 'a3': 0, + 'a4': 1, + 'a5': 0, + 'a6': 0, + 'a7': 0, + 'a8': 1, + 'b3': 0, + 'b4': 1, + 'b5': 0, + 'b6': 0, + 'b7': 0, + 'b8': 1, + 'd': 0} """ + bmilp = None + next_solution = None try: - self.bmilp = self._generate_and_solve_initial_bmilp() + if self.bmilp is None: + self.bmilp = self._generate_and_solve_initial_bmilp() + bmilp = self.bmilp + bmilp.solve([], 0) + next_solution = self._solution(bmilp) except MIPSolverException: return + + solution_index = 1 while True: - yield self._solution(self.bmilp) + yield next_solution if get_verbose() >= 2: print("after vetoing") - self._show_bmilp(self.bmilp, variables=False) + self._show_bmilp(bmilp, variables=False) try: - self.bmilp.solve([], force_new_solution=True) + bmilp.solve([], solution_index) + next_solution = self._solution(bmilp) + solution_index += 1 except MIPSolverException: return @@ -2466,7 +2582,6 @@ def __init__(self, bijectionist: Bijectionist): self._solution_cache = [] self._last_solution = {} self._index_block_value_dict = None - self._block_value_index_dict = None# TODO: may not be needed? self._x = self.milp.new_variable(binary=True) # indexed by P x Z self._bijectionist = bijectionist @@ -2477,52 +2592,42 @@ def __init__(self, bijectionist: Bijectionist): for z in bijectionist._possible_block_values[p]) == 1, name=name[:50]) - def clear_solution_cache(self): - self._n_variables = -1 - self._solution_cache = [] - self._last_solution = {} - self._index_block_value_dict = None - self._block_value_index_dict = None # TODO: may not be needed? - - def solve(self, tmp_constraints, force_new_solution=False): + def solve(self, tmp_constraints, solution_index=0): if self._n_variables < 0: self._n_variables = self.milp.number_of_variables() self._index_block_value_dict = {} - self._block_value_index_dict = {} for (p, z), v in self._x.items(): variable_index = next(iter(v.dict().keys())) - self._index_block_value_dict[variable_index] = (p,z) - self._block_value_index_dict[(p,z)] = variable_index - assert self._n_variables == self.milp.number_of_variables(), "The number of variables changed." # number of variables would change with creation of constraints with new variables + self._index_block_value_dict[variable_index] = (p, z) + # number of variables would change with creation of constraints with new variables + assert self._n_variables == self.milp.number_of_variables(), "The number of variables changed." # check if previous solution exists with constraints previous_solution_exists = False - self.last_solution = {} - if not force_new_solution: - for solution in self._solution_cache: - fulfills_constraints = True - # loop through all constraints - for constraint in tmp_constraints: - # check equations - for linear_function, value in constraint.equations(): - solution_value = self._evaluate_linear_function(linear_function.dict(), self._index_block_value_dict, solution) - if solution_value != value.dict()[-1]: - fulfills_constraints = False - break - if not fulfills_constraints: + for solution in self._solution_cache[solution_index:]: + fulfills_constraints = True + # loop through all constraints + for constraint in tmp_constraints: + # check equations + for linear_function, value in constraint.equations(): + solution_value = self._evaluate_linear_function(linear_function.dict(), self._index_block_value_dict, solution) + if solution_value != value.dict()[-1]: + fulfills_constraints = False break - # check inequalities - for linear_function, value in constraint.inequalities(): - solution_value = self._evaluate_linear_function(linear_function.dict(), self._index_block_value_dict, solution) - if solution_value > value.dict()[-1]: - fulfills_constraints = False - break - if not fulfills_constraints: + if not fulfills_constraints: + break + # check inequalities + for linear_function, value in constraint.inequalities(): + solution_value = self._evaluate_linear_function(linear_function.dict(), self._index_block_value_dict, solution) + if solution_value > value.dict()[-1]: + fulfills_constraints = False break - if fulfills_constraints: - previous_solution_exists = True - self.last_solution = solution + if not fulfills_constraints: break + if fulfills_constraints: + previous_solution_exists = True + self.last_solution = solution + break # if no previous solution satisfies the constraints, generate a new one if not previous_solution_exists: @@ -2540,15 +2645,15 @@ def solve(self, tmp_constraints, force_new_solution=False): self.last_solution = self.milp.get_values(self._x) self._solution_cache.append(self.last_solution) - self.veto_current_solution() + return self.last_solution def _evaluate_linear_function(self, linear_function_dict, block_index_dict, values): return float(sum(linear_function_dict[index]*values[block_index_dict[index]] for index in linear_function_dict)) def get_value(self, p, v): - return self.last_solution[p,v] + return self.last_solution[p, v] def add_alpha_beta_constraints(self): r""" @@ -2702,6 +2807,7 @@ def veto_current_solution(self): :meth:`MixedIntegerLinearProgram.solve()` must return a solution different from the current one. + We require that the MILP currently has a solution. .. WARNING:: @@ -2801,7 +2907,7 @@ def _non_copying_intersection(sets): sage: permutation1010root = list(_disjoint_set_roots(bij._P))[2] sage: permutation1010root [1, 0, 1, 0] - sage: bmilp.solve([bmilp._x[permutation1010root, 1] <= 0.5], force_new_solution=True) + sage: bmilp.solve([bmilp._x[permutation1010root, 1] <= 0.5], solution_index=1) Traceback (most recent call last): ... MIPSolverException: GLPK: Problem has no feasible solution From 1ac09787fd03c114725a775cdf22b6e14f108446 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 22 Dec 2022 00:38:46 +0100 Subject: [PATCH 163/392] add some documentation and doctests, slightly simplify code --- src/sage/combinat/bijectionist.py | 268 +++++++++++++++++++----------- 1 file changed, 175 insertions(+), 93 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 75e904df59e..cb96c2ee157 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -184,9 +184,14 @@ ( [ /\/\ / \ ] [ \ / ] ) ( [ / \, / \ ], [ o o ] ) - TESTS: + The output is in a form suitable for FindStat:: - The following failed before commit c6d4d2e8804aa42afa08c72c887d50c725cc1a91:: + sage: findmap(list(bij.minimal_subdistributions_iterator())) # optional -- internet + 0: Mp00034 (quality [100]) + 1: Mp00061oMp00023 (quality [100]) + 2: Mp00018oMp00140 (quality [100]) + + TESTS:: sage: N=4; A = B = [permutation for n in range(N) for permutation in Permutations(n)] sage: theta = lambda pi: Permutation([x+1 if x != len(pi) else 1 for x in pi[-1:]+pi[:-1]]) @@ -373,11 +378,6 @@ # Z. In LaTeX, we mostly call them \tilde A, \tilde Z, etc. now. It # would be good to have a standard name in code, too. -# TODO: (medium) whenever possible, doctests of a method should only -# test this method. Currently we have very many system tests, which -# is inconvenient when modifying the design substantially. - - class Bijectionist(SageObject): r""" A toolbox to list all possible bijections between two finite sets @@ -722,7 +722,7 @@ def set_statistics(self, *alpha_beta): """ self.bmilp = None self._n_statistics = len(alpha_beta) - # TODO: (low) do we really want to recompute statistics every time? + # TODO: do we really want to recompute statistics every time? self._alpha = lambda p: tuple(arg[0](p) for arg in alpha_beta) self._beta = lambda p: tuple(arg[1](p) for arg in alpha_beta) @@ -1047,6 +1047,21 @@ def _compute_possible_block_values(self): Update the dictionary of possible values of each block. This has to be called whenever ``self._P`` was modified. + + It raises a :class:`ValueError`, if the restrictions on a + block are contradictory. + + TESTS:: + + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_value_restrictions((Permutation([1, 2]), [4, 5])) + sage: bij._compute_possible_block_values() + Traceback (most recent call last): + ... + ValueError: No possible values found for singleton block [[1, 2]] + """ self._possible_block_values = {} # P -> Power(Z) for p, block in self._P.root_to_elements_dict().items(): @@ -1769,7 +1784,6 @@ def minimal_subdistributions_iterator(self): # the current solution minimal_subdistribution.add_constraint(sum(active_vars) <= len(active_vars) - 1, name="veto") - # TODO: can we ignore that in the next step the same constraint is added again? else: s = new_s @@ -2020,14 +2034,16 @@ def _find_counter_example2(self, bmilp, P, s0, d): def _preprocess_intertwining_relations(self): r""" - - .. TODO:: - - (medium) untangle side effect and return value if possible - Make `self._P` be the finest set partition coarser than `self._P` such that composing elements preserves blocks. + OUTPUT: + + A list of triples `((\pi/\rho, p, (p_1,\dots,p_k))`, where + `p` is the block of `\rho(s(a_1),\dots, s(a_k))`, for any + `a_i\in p_i`, suitable for + :meth:`_BijectionistMILP.add_intertwining_relation_constraints`. + Suppose that `p_1`, `p_2` are blocks of `P`, and `a_1, a'_1 \in p_1` and `a_2, a'_2\in p_2`. Then, @@ -2049,6 +2065,10 @@ def _preprocess_intertwining_relations(self): create one test with one and one test with two intertwining_relations + .. TODO:: + + untangle side effect and return value if possible + """ images = {} # A^k -> A, a_1,...,a_k to pi(a_1,...,a_k), for all pi origins_by_elements = [] # (pi/rho, pi(a_1,...,a_k), a_1,...,a_k) @@ -2060,9 +2080,9 @@ def _preprocess_intertwining_relations(self): if a in self._A: if a in images: # this happens if there are several pi's of the same arity - images[a_tuple].add(a) # TODO: (low) wouldn't self._P.find(a) be more efficient here? + images[a_tuple].add(a) # TODO: wouldn't self._P.find(a) be more efficient here? else: - images[a_tuple] = set((a,)) # TODO: (low) wouldn't self._P.find(a) be more efficient here? + images[a_tuple] = set((a,)) # TODO: wouldn't self._P.find(a) be more efficient here? origins_by_elements.append((composition_index, a, a_tuple)) # merge blocks @@ -2483,7 +2503,7 @@ def _solution(self, bmilp): map = {} # A -> Z, a +-> s(a) for p, block in self._P.root_to_elements_dict().items(): for z in self._possible_block_values[p]: - if bmilp.get_value(p, z) == 1: + if bmilp.has_value(p, z): for a in block: map[a] = z break @@ -2498,7 +2518,7 @@ def _solution_by_blocks(self, bmilp): map = {} # P -> Z, a +-> s(a) for p in _disjoint_set_roots(self._P): for z in self._possible_block_values[p]: - if bmilp.get_value(p, z) == 1: + if bmilp.has_value(p, z): map[p] = z break return map @@ -2547,8 +2567,8 @@ def _show_bmilp(self, bmilp, variables=True): def _generate_and_solve_initial_bmilp(self): r""" - Generate a ``_BijectionistMILP``, add all relevant constraints - and call ``MILP.solve()``. + Generate a :class:`_BijectionistMILP`, add all relevant constraints + and call :meth:`_BijectionistMILP.solve`. """ preimage_blocks = self._preprocess_intertwining_relations() self._compute_possible_block_values() @@ -2557,7 +2577,7 @@ def _generate_and_solve_initial_bmilp(self): n = bmilp.milp.number_of_variables() bmilp.add_alpha_beta_constraints() bmilp.add_distribution_constraints() - bmilp.add_interwining_relation_constraints(preimage_blocks) + bmilp.add_intertwining_relation_constraints(preimage_blocks) if get_verbose() >= 2: self._show_bmilp(bmilp) assert n == bmilp.milp.number_of_variables(), "The number of variables increased." @@ -2565,14 +2585,28 @@ def _generate_and_solve_initial_bmilp(self): return bmilp -class _BijectionistMILP(SageObject): +class _BijectionistMILP(): r""" - Wrapper class for the MixedIntegerLinearProgram (MILP). This class is used to manage the MILP, - add constraints, solve the problem and check for uniqueness of solution values. + Wrapper class for the MixedIntegerLinearProgram (MILP). This + class is used to manage the MILP, add constraints, solve the + problem and check for uniqueness of solution values. + """ def __init__(self, bijectionist: Bijectionist): - # TODO: it would be cleaner not to pass the full bijectionist - # instance, but only those attributes we actually use: + r""" + Initialize the mixed integer linear program. + + INPUT: + + - ``bijectionist`` -- an instance of :class:`Bijectionist`. + + .. TODO:: + + it might be cleaner not to pass the full bijectionist + instance, but only those attributes we actually use + + """ + # the attributes of the bijectionist class we actually use: # _possible_block_values # _elements_distributions # _W, _Z, _A, _B, _P, _alpha, _beta, _tau, _pi_rho @@ -2592,68 +2626,91 @@ def __init__(self, bijectionist: Bijectionist): for z in bijectionist._possible_block_values[p]) == 1, name=name[:50]) - def solve(self, tmp_constraints, solution_index=0): + def solve(self, additional_constraints, solution_index=0): + r""" + Return a solution satisfying the given additional constraints. + + INPUT: + + - ``additional_constraints`` -- a list of constraints for the + underlying MILP + + - ``solution_index`` (optional, default: ``0``) -- an index + specifying how many of the solutions in the cache should be + ignored. + + """ if self._n_variables < 0: + # initialize at first call self._n_variables = self.milp.number_of_variables() self._index_block_value_dict = {} for (p, z), v in self._x.items(): variable_index = next(iter(v.dict().keys())) self._index_block_value_dict[variable_index] = (p, z) - # number of variables would change with creation of constraints with new variables + # number of variables would change with creation of + # constraints with new variables assert self._n_variables == self.milp.number_of_variables(), "The number of variables changed." - # check if previous solution exists with constraints - previous_solution_exists = False + # check if there is a solution satisfying the constraints in + # the cache for solution in self._solution_cache[solution_index:]: - fulfills_constraints = True - # loop through all constraints - for constraint in tmp_constraints: - # check equations - for linear_function, value in constraint.equations(): - solution_value = self._evaluate_linear_function(linear_function.dict(), self._index_block_value_dict, solution) - if solution_value != value.dict()[-1]: - fulfills_constraints = False - break - if not fulfills_constraints: - break - # check inequalities - for linear_function, value in constraint.inequalities(): - solution_value = self._evaluate_linear_function(linear_function.dict(), self._index_block_value_dict, solution) - if solution_value > value.dict()[-1]: - fulfills_constraints = False - break - if not fulfills_constraints: - break - if fulfills_constraints: - previous_solution_exists = True + if all(all(self._evaluate_linear_function(linear_function, + solution) == value.dict()[-1] + for linear_function, value in constraint.equations()) + and all(self._evaluate_linear_function(linear_function, + solution) <= value.dict()[-1] + for linear_function, value in constraint.inequalities()) + for constraint in additional_constraints): self.last_solution = solution - break - - # if no previous solution satisfies the constraints, generate a new one - if not previous_solution_exists: - try: - n_constraints = self.milp.number_of_constraints() - for constraint in tmp_constraints: - self.milp.add_constraint(constraint) - self.milp.solve() - for _ in range(self.milp.number_of_constraints()-n_constraints): - self.milp.remove_constraint(n_constraints) - except MIPSolverException as error: - for _ in range(self.milp.number_of_constraints()-n_constraints): - self.milp.remove_constraint(n_constraints) - raise error - - self.last_solution = self.milp.get_values(self._x) - self._solution_cache.append(self.last_solution) - self.veto_current_solution() + return self.last_solution + # otherwise generate a new one + try: + # TODO: wouldn't it be safer to copy the milp? + n_constraints = self.milp.number_of_constraints() + for constraint in additional_constraints: + self.milp.add_constraint(constraint) + self.milp.solve() + for _ in range(self.milp.number_of_constraints()-n_constraints): + self.milp.remove_constraint(n_constraints) + except MIPSolverException as error: + for _ in range(self.milp.number_of_constraints()-n_constraints): + self.milp.remove_constraint(n_constraints) + raise error + + self.last_solution = self.milp.get_values(self._x) + self._solution_cache.append(self.last_solution) + self._veto_current_solution() return self.last_solution - def _evaluate_linear_function(self, linear_function_dict, block_index_dict, values): - return float(sum(linear_function_dict[index]*values[block_index_dict[index]] for index in linear_function_dict)) + def _evaluate_linear_function(self, linear_function, values): + r""" + Evaluate the given function at the given values. + + INPUT: + + - ``linear_function``, a + :class:`sage.numerical.linear_functions.LinearFunction`. - def get_value(self, p, v): - return self.last_solution[p, v] + - ``values``, a candidate for a solution of the MILP as a + dictionary from pairs `(a, z)\in A\times Z` to `0` or `1`, + specifying whether `a` is mapped to `z`. + + """ + return float(sum(value * values[self._index_block_value_dict[index]] + for index, value in linear_function.dict().items())) + + def has_value(self, p, v): + r""" + Return whether a block is mapped to a value in the last solution + computed. + + INPUT: + + - ``p``, the representative of a block + - ``v``, a value in `Z` + """ + return self.last_solution[p, v] == 1 def add_alpha_beta_constraints(self): r""" @@ -2690,8 +2747,8 @@ def add_alpha_beta_constraints(self): z_index = Z_dict[self._bijectionist._tau[b]] B_matrix[z_index][w_index] += 1 - # TODO: (low) I am not sure that this is the best way to - # filter out empty conditions + # TODO: not sure that this is the best way to filter out + # empty conditions for w in range(len(W)): for z in range(len(Z)): c = AZ_matrix[z][w] - B_matrix[z][w] @@ -2704,7 +2761,7 @@ def add_alpha_beta_constraints(self): def add_distribution_constraints(self): r""" Add constraints so the distributions given by - :meth:`~Bijectionist.set_distributions` are fulfilled. + :meth:`set_distributions` are fulfilled. To accomplish this we add @@ -2728,8 +2785,8 @@ def add_distribution_constraints(self): for z in values: values_sum[Z_dict[z]] += 1 - # TODO: (low) I am not sure that this is the best way to - # filter out empty conditions + # TODO: not sure that this is the best way to filter out + # empty conditions for element, value in zip(elements_sum, values_sum): c = element - value if c.is_zero(): @@ -2738,13 +2795,19 @@ def add_distribution_constraints(self): raise MIPSolverException self.milp.add_constraint(c == 0, name=f"d: {element} == {value}") - def add_interwining_relation_constraints(self, origins): + def add_intertwining_relation_constraints(self, origins): r""" Add constraints corresponding to the given intertwining relations. + INPUT: + + - origins, a list of triples `((\pi/\rho, p, + (p_1,\dots,p_k))`, where `p` is the block of + `\rho(s(a_1),\dots, s(a_k))`, for any `a_i\in p_i`. + This adds the constraints imposed by - :meth:`~Bijectionist.set_intertwining_relations`. + :meth:`set_intertwining_relations`, .. MATH:: @@ -2771,15 +2834,8 @@ def add_interwining_relation_constraints(self, origins): x_{p, z}\geq 1 - k + \sum_{i=1}^k x_{p_i, z_i}. - Not that `z` must be a possible value of `p` and each `z_i` + Note that `z` must be a possible value of `p` and each `z_i` must be a possible value of `p_i`. - - INPUT: - - - origins, a list of triples `((\pi/\rho, p, - (p_1,\dots,p_k))`, where `p` is the block of - `\rho(s(a_1),\dots, s(a_k))`, for any `a_i\in p_i`. - """ for composition_index, image_block, preimage_blocks in origins: pi_rho = self._bijectionist._pi_rho[composition_index] @@ -2799,14 +2855,13 @@ def add_interwining_relation_constraints(self, origins): self.milp.add_constraint(rhs <= 0, name=f"pi/rho({composition_index})") - def veto_current_solution(self): + def _veto_current_solution(self): r""" Add a constraint vetoing the current solution. This adds a constraint such that the next call to - :meth:`MixedIntegerLinearProgram.solve()` must return a - solution different from the current one. - + :meth:`solve` must return a solution different from the + current one. We require that the MILP currently has a solution. @@ -2837,6 +2892,19 @@ def _invert_dict(d): """ Return the dictionary whose keys are the values of the input and whose values are the lists of preimages. + + INPUT: + + - ``d``, a ``dict``. + + EXAMPLES:: + + sage: from sage.combinat.bijectionist import _invert_dict + sage: _invert_dict({1: "a", 2: "a", 3:"b"}) + {'a': [1, 2], 'b': [3]} + + sage: _invert_dict({}) + {} """ preimages = {} for k, v in d.items(): @@ -2847,6 +2915,20 @@ def _invert_dict(d): def _disjoint_set_roots(d): """ Return the representatives of the blocks of the disjoint set. + + INPUT: + + - ``d``, a ``sage.sets.disjoint_set.DisjointSet_of_hashables`` + + EXAMPLES:: + + sage: from sage.combinat.bijectionist import _disjoint_set_roots + sage: d = DisjointSet('abcde') + sage: d.union("a", "b") + sage: d.union("a", "c") + sage: d.union("e", "d") + sage: _disjoint_set_roots(d) + dict_keys(['a', 'e']) """ return d.root_to_elements_dict().keys() From 47945ac2bbdbd927a6aaef8c72ead62d2922c94c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 22 Dec 2022 01:05:46 +0100 Subject: [PATCH 164/392] add missing documentation in table of contents --- src/sage/combinat/bijectionist.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index cb96c2ee157..d8a9e548442 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -15,20 +15,20 @@ :widths: 30, 70 :delim: | - :meth:`~Bijectionist.set_intertwining_relations` | Set - :meth:`~Bijectionist.set_constant_blocks` | Set - :meth:`~Bijectionist.set_statistics` | Set - :meth:`~Bijectionist.set_value_restrictions` | Set - :meth:`~Bijectionist.set_distributions` | Set - - :meth:`~Bijectionist.statistics_table` | Return - :meth:`~Bijectionist.statistics_fibers` | Return - - :meth:`~Bijectionist.constant_blocks` | Return - :meth:`~Bijectionist.solutions_iterator` | Return - :meth:`~Bijectionist.possible_values` | Return - :meth:`~Bijectionist.minimal_subdistributions_iterator` | Return - :meth:`~Bijectionist.minimal_subdistributions_blocks_iterator` | Return + :meth:`~Bijectionist.set_intertwining_relations` | Declare that the statistic intertwines with other maps. + :meth:`~Bijectionist.set_constant_blocks` | Declare that the statistic is constant on some sets. + :meth:`~Bijectionist.set_statistics` | Declare statistics that are preserved by the bijection. + :meth:`~Bijectionist.set_value_restrictions` | Restrict the values of the statistic on an element. + :meth:`~Bijectionist.set_distributions` | Restrict the distribution of values of the statistic on some elements. + + :meth:`~Bijectionist.statistics_table` | Print a table collecting information on the given statistics. + :meth:`~Bijectionist.statistics_fibers` | Collect elements with the same statistics. + + :meth:`~Bijectionist.constant_blocks` | Return the blocks on which the statistic is constant. + :meth:`~Bijectionist.solutions_iterator` | Iterate over all possible solutions. + :meth:`~Bijectionist.possible_values` | Return all possible values for a given element. + :meth:`~Bijectionist.minimal_subdistributions_iterator` | Iterate over the minimal subdistributions. + :meth:`~Bijectionist.minimal_subdistributions_blocks_iterator` | Iterate over the minimal subdistributions. A guided tour ============= From 19c3d8fdc82471993ab800923eaeb2494c7551c7 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 22 Dec 2022 11:04:09 +0100 Subject: [PATCH 165/392] mark doctests as long, slightly simplify logic --- src/sage/combinat/bijectionist.py | 200 ++++++++++++++++-------------- 1 file changed, 110 insertions(+), 90 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index d8a9e548442..7f3bda41982 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -121,7 +121,7 @@ There is no rotation invariant statistic on non crossing set partitions which is equidistributed with the Strahler number on ordered trees:: - sage: N=8; As = [[SetPartition(d.to_noncrossing_partition()) for d in DyckWords(n)] for n in range(N)] + sage: N = 8; As = [[SetPartition(d.to_noncrossing_partition()) for d in DyckWords(n)] for n in range(N)] sage: A = sum(As, []) sage: B = sum([list(OrderedTrees(n)) for n in range(1, N+1)], []) sage: theta = lambda m: SetPartition([[i % m.size() + 1 for i in b] for b in m]) @@ -186,14 +186,14 @@ The output is in a form suitable for FindStat:: - sage: findmap(list(bij.minimal_subdistributions_iterator())) # optional -- internet + sage: findmap(list(bij.minimal_subdistributions_iterator())) # optional -- internet 0: Mp00034 (quality [100]) 1: Mp00061oMp00023 (quality [100]) 2: Mp00018oMp00140 (quality [100]) TESTS:: - sage: N=4; A = B = [permutation for n in range(N) for permutation in Permutations(n)] + sage: N = 4; A = B = [permutation for n in range(N) for permutation in Permutations(n)] sage: theta = lambda pi: Permutation([x+1 if x != len(pi) else 1 for x in pi[-1:]+pi[:-1]]) sage: def tau(pi): ....: n = len(pi) @@ -479,7 +479,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), ele Check that large input sets are handled well:: sage: A = B = list(range(20000)) - sage: bij = Bijectionist(A, B) # long time + sage: bij = Bijectionist(A, B) # long time """ # glossary of standard letters: # A, B, Z, W ... finite sets @@ -1471,8 +1471,6 @@ def _forced_constant_blocks(self): sage: all(s[Permutation([2, 1])] == s[Permutation([1, 4, 2, 3])] for s in bij.solutions_iterator()) False - - sage: A = B = ["a", "b", "c", "d", "e", "f"] sage: tau = {"a": 1, "b": 1, "c": 3, "d": 4, "e": 5, "f": 6}.get sage: bij = Bijectionist(A, B, tau) @@ -1502,7 +1500,8 @@ def _forced_constant_blocks(self): """ if self.bmilp is None: - self.bmilp = self._generate_and_solve_initial_bmilp() # may throw Exception + self.bmilp = self._initialize_new_bmilp() + self.bmilp.solve([]) # generate blockwise preimage to determine which blocks have the same image solution = self._solution_by_blocks(self.bmilp) @@ -1668,7 +1667,9 @@ def add_solution(solutions, solution): # generate initial solution, solution dict and add solution if self.bmilp is None: - self.bmilp = self._generate_and_solve_initial_bmilp() + self.bmilp = self._initialize_new_bmilp() + self.bmilp.solve([]) + solution = self._solution(self.bmilp) solutions = {} add_solution(solutions, solution) @@ -1759,7 +1760,8 @@ def minimal_subdistributions_iterator(self): try: if self.bmilp is None: - self.bmilp = self._generate_and_solve_initial_bmilp() + self.bmilp = self._initialize_new_bmilp() + self.bmilp.solve([]) except MIPSolverException: return s = self._solution(self.bmilp) @@ -1960,11 +1962,12 @@ def add_counter_example_constraint(s): minimal_subdistribution.add_constraint(sum(D[p] for p in P if s[p] == v) == V[v]) - try: - if self.bmilp is None: - self.bmilp = self._generate_and_solve_initial_bmilp() - except MIPSolverException: - return + if self.bmilp is None: + try: + self.bmilp = self._initialize_new_bmilp() + self.bmilp.solve([]) + except MIPSolverException: + return s = self._solution_by_blocks(self.bmilp) add_counter_example_constraint(s) while True: @@ -2362,6 +2365,7 @@ def solutions_iterator(self): sage: iterator2 = bij.solutions_iterator() Generate a solution in iterator1, iterator2 should generate the same solution and vice versa:: + sage: next(iterator1) {'a1': 1, 'a2': 1, @@ -2470,29 +2474,24 @@ def solutions_iterator(self): 'b8': 1, 'd': 0} """ - bmilp = None next_solution = None - try: - if self.bmilp is None: - self.bmilp = self._generate_and_solve_initial_bmilp() - bmilp = self.bmilp - bmilp.solve([], 0) - next_solution = self._solution(bmilp) - except MIPSolverException: - return - - solution_index = 1 + if self.bmilp is None: + try: + self.bmilp = self._initialize_new_bmilp() + except MIPSolverException: + return + bmilp = self.bmilp + solution_index = 0 while True: - yield next_solution - if get_verbose() >= 2: - print("after vetoing") - self._show_bmilp(bmilp, variables=False) try: bmilp.solve([], solution_index) - next_solution = self._solution(bmilp) - solution_index += 1 except MIPSolverException: return + yield self._solution(bmilp) + solution_index += 1 + if get_verbose() >= 2: + print("after vetoing") + self._show_bmilp(bmilp, variables=False) def _solution(self, bmilp): """ @@ -2565,10 +2564,9 @@ def _show_bmilp(self, bmilp, variables=True): print(f" {v}: " + "".join([f"s({a}) = " for a in self._P.root_to_elements_dict()[p]]) + f"{z}") - def _generate_and_solve_initial_bmilp(self): + def _initialize_new_bmilp(self): r""" - Generate a :class:`_BijectionistMILP`, add all relevant constraints - and call :meth:`_BijectionistMILP.solve`. + Initialize a :class:`_BijectionistMILP` and add the current constraints. """ preimage_blocks = self._preprocess_intertwining_relations() self._compute_possible_block_values() @@ -2581,7 +2579,6 @@ def _generate_and_solve_initial_bmilp(self): if get_verbose() >= 2: self._show_bmilp(bmilp) assert n == bmilp.milp.number_of_variables(), "The number of variables increased." - bmilp.solve([]) return bmilp @@ -2605,6 +2602,13 @@ def __init__(self, bijectionist: Bijectionist): it might be cleaner not to pass the full bijectionist instance, but only those attributes we actually use + TESTS:: + + sage: A = B = ["a", "b", "c", "d"] + sage: bij = Bijectionist(A, B) + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: _BijectionistMILP(bij) + """ # the attributes of the bijectionist class we actually use: # _possible_block_values @@ -2639,7 +2643,72 @@ def solve(self, additional_constraints, solution_index=0): specifying how many of the solutions in the cache should be ignored. + TESTS:: + + sage: A = B = ["a", "b"] + sage: bij = Bijectionist(A, B) + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) + sage: len(bmilp._solution_cache) + 0 + + Without any constraints, we do not require that the solution is a bijection:: + + sage: bmilp.solve([bmilp._x["a", "a"] == 1, bmilp._x["b", "a"] == 1]) + {('a', 'a'): 1.0, ('a', 'b'): 0.0, ('b', 'a'): 1.0, ('b', 'b'): 0.0} + sage: len(bmilp._solution_cache) + 1 + sage: bmilp.solve([bmilp._x["a", "b"] == 1, bmilp._x["b", "b"] == 1]) + {('a', 'a'): 0.0, ('a', 'b'): 1.0, ('b', 'a'): 0.0, ('b', 'b'): 1.0} + sage: len(bmilp._solution_cache) + 2 + + A more elaborate test:: + + sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] + sage: tau = lambda D: D.number_of_touch_points() + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) + sage: bmilp = bij._initialize_new_bmilp() + + Generate a solution:: + + sage: bmilp.solve([]) + {([], 0): 1.0, + ([1, 0], 1): 1.0, + ([1, 0, 1, 0], 1): 0.0, + ([1, 0, 1, 0], 2): 1.0, + ([1, 1, 0, 0], 1): 1.0, + ([1, 1, 0, 0], 2): 0.0} + + Generating a new solution that also maps `1010` to `2` fails: + + sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] <= 0.5], solution_index=1) + Traceback (most recent call last): + ... + MIPSolverException: GLPK: Problem has no feasible solution + + However, searching for a cached solution succeeds, for inequalities and equalities:: + + sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] <= 0.5]) + {([], 0): 1.0, + ([1, 0], 1): 1.0, + ([1, 0, 1, 0], 1): 0.0, + ([1, 0, 1, 0], 2): 1.0, + ([1, 1, 0, 0], 1): 1.0, + ([1, 1, 0, 0], 2): 0.0} + + sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] == 0]) + {([], 0): 1.0, + ([1, 0], 1): 1.0, + ([1, 0, 1, 0], 1): 0.0, + ([1, 0, 1, 0], 2): 1.0, + ([1, 1, 0, 0], 1): 1.0, + ([1, 1, 0, 0], 2): 0.0} + """ + assert 0 <= solution_index <= len(self._solution_cache), "the index of the desired solution must not be larger than the number of known solutions" + if self._n_variables < 0: # initialize at first call self._n_variables = self.milp.number_of_variables() @@ -2964,55 +3033,6 @@ def _non_copying_intersection(sets): """ TESTS:: - ##################### - # caching base test # - ##################### - sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] - sage: tau = lambda D: D.number_of_touch_points() - sage: bij = Bijectionist(A, B, tau) - sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) - sage: bmilp = bij._generate_and_solve_initial_bmilp() - -Print the generated solution:: - - sage: bmilp.milp.get_values(bmilp._x) - {([], 0): 1.0, - ([1, 0], 1): 1.0, - ([1, 0, 1, 0], 1): 0.0, - ([1, 0, 1, 0], 2): 1.0, - ([1, 1, 0, 0], 1): 1.0, - ([1, 1, 0, 0], 2): 0.0} - -Generating a new solution that also maps `1010` to `2` fails: - - sage: from sage.combinat.bijectionist import _disjoint_set_roots - sage: permutation1010root = list(_disjoint_set_roots(bij._P))[2] - sage: permutation1010root - [1, 0, 1, 0] - sage: bmilp.solve([bmilp._x[permutation1010root, 1] <= 0.5], solution_index=1) - Traceback (most recent call last): - ... - MIPSolverException: GLPK: Problem has no feasible solution - -However, searching for a cached solution succeeds, for inequalities and equalities:: - - sage: bmilp.solve([bmilp._x[permutation1010root, 1] <= 0.5]) - {([], 0): 1.0, - ([1, 0], 1): 1.0, - ([1, 0, 1, 0], 1): 0.0, - ([1, 0, 1, 0], 2): 1.0, - ([1, 1, 0, 0], 1): 1.0, - ([1, 1, 0, 0], 2): 0.0} - - sage: bmilp.solve([bmilp._x[permutation1010root, 1] == 0]) - {([], 0): 1.0, - ([1, 0], 1): 1.0, - ([1, 0, 1, 0], 1): 0.0, - ([1, 0, 1, 0], 2): 1.0, - ([1, 1, 0, 0], 1): 1.0, - ([1, 1, 0, 0], 2): 0.0} - - sage: As = Bs = [[], ....: [(1,i,j) for i in [-1,0,1] for j in [-1,1]], ....: [(2,i,j) for i in [-1,0,1] for j in [-1,1]], @@ -3027,7 +3047,7 @@ def _non_copying_intersection(sets): sage: bij = Bijectionist(sum(As, []), sum(Bs, [])) sage: bij.set_statistics((lambda x: x[0], lambda x: x[0])) sage: bij.set_intertwining_relations((2, c1, c1), (1, c2, c2)) - sage: l = list(bij.solutions_iterator()); len(l) + sage: l = list(bij.solutions_iterator()); len(l) # long time 64 A brute force check would be difficult:: @@ -3055,8 +3075,8 @@ def _non_copying_intersection(sets): sage: A = sum(As, []) sage: respects_c1 = lambda s: all(c1(a1, a2) not in A or s[c1(a1, a2)] == c1(s[a1], s[a2]) for a1 in A for a2 in A) sage: respects_c2 = lambda s: all(c2(a1) not in A or s[c2(a1)] == c2(s[a1]) for a1 in A) - sage: l2 = [s for s in it if respects_c1(s) and respects_c2(s)] - sage: sorted(l1, key=lambda s: tuple(s.items())) == l2 + sage: l2 = [s for s in it if respects_c1(s) and respects_c2(s)] # long time + sage: sorted(l1, key=lambda s: tuple(s.items())) == l2 # long time True Our benchmark example:: @@ -3071,7 +3091,7 @@ def _non_copying_intersection(sets): ....: cycle = Permutation(tuple(range(1, len(p)+1))) ....: return Permutation([cycle.inverse()(p(cycle(i))) for i in range(1, len(p)+1)]) - sage: N=5 + sage: N = 5 sage: As = [list(Permutations(n)) for n in range(N+1)] sage: A = B = sum(As, []) sage: bij = Bijectionist(A, B, gamma) @@ -3103,9 +3123,9 @@ def _non_copying_intersection(sets): ([[2, 1, 5, 3, 4], [2, 5, 1, 3, 4], [3, 1, 5, 2, 4], [3, 5, 1, 2, 4]], [3, 3, 4, 4]) ([[1, 3, 2, 5, 4], [1, 3, 5, 2, 4], [1, 4, 2, 5, 3], [1, 4, 5, 2, 3], [1, 4, 5, 3, 2], [1, 5, 4, 2, 3], [1, 5, 4, 3, 2]], [2, 2, 3, 3, 3, 3, 3]) - sage: l = list(bij.solutions_iterator()); len(l) # long time + sage: l = list(bij.solutions_iterator()); len(l) # long time 504 - sage: for a, d in bij.minimal_subdistributions_iterator(): # long time + sage: for a, d in bij.minimal_subdistributions_iterator(): # long time ....: print(sorted(a), sorted(d)) """ From eca857ebb1c8f29abd4e7afb7f386f98b18584d5 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 22 Dec 2022 23:25:34 +0100 Subject: [PATCH 166/392] slightly simplify, more doctests --- src/sage/combinat/bijectionist.py | 232 ++++++++++++++---------------- 1 file changed, 108 insertions(+), 124 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 7f3bda41982..26a93072685 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -5,7 +5,7 @@ AUTHORS: - Alexander Grosz, Tobias Kietreiber, Stephan Pfannerer and Martin - Rubey (2020): Initial version + Rubey (2020-2022): Initial version Quick reference =============== @@ -1504,26 +1504,29 @@ def _forced_constant_blocks(self): self.bmilp.solve([]) # generate blockwise preimage to determine which blocks have the same image - solution = self._solution_by_blocks(self.bmilp) + solution = self._solution(self.bmilp, True) multiple_preimages = {(value,): preimages for value, preimages in _invert_dict(solution).items() if len(preimages) > 1} - # check for each pair of blocks if a solution with different values on these block exists - # if yes, use the new solution to update the multiple_preimages dictionary, restart the check + # check for each pair of blocks if a solution with different + # values on these block exists + + # if yes, use the new solution to update the + # multiple_preimages dictionary, restart the check # if no, the two blocks can be joined # _P has to be copied to not mess with the solution-process - # since we do not want to regenerate the bmilp in each step, so blocks - # have to stay consistent during the whole process + # since we do not want to regenerate the bmilp in each step, + # so blocks have to stay consistent during the whole process tmp_P = deepcopy(self._P) updated_preimages = True while updated_preimages: updated_preimages = False - for values in copy(multiple_preimages): # copy to be able to modify dict + for values in copy(multiple_preimages): if updated_preimages: break - for i, j in itertools.combinations(copy(multiple_preimages[values]), r=2): # copy to be able to modify list + for i, j in itertools.combinations(copy(multiple_preimages[values]), r=2): tmp_constraints = [] try: # veto the two blocks having the same value @@ -1533,11 +1536,11 @@ def _forced_constant_blocks(self): self.bmilp.solve(tmp_constraints) # solution exists, update dictionary - solution = self._solution_by_blocks(self.bmilp) + solution = self._solution(self.bmilp, True) updated_multiple_preimages = {} for values in multiple_preimages: for p in multiple_preimages[values]: - solution_tuple = (*values, solution[p]) # tuple so actual solutions were equal in lookup + solution_tuple = (*values, solution[p]) if solution_tuple not in updated_multiple_preimages: updated_multiple_preimages[solution_tuple] = [] updated_multiple_preimages[solution_tuple].append(p) @@ -1547,7 +1550,8 @@ def _forced_constant_blocks(self): except MIPSolverException: # no solution exists, join blocks tmp_P.union(i, j) - if i in multiple_preimages[values] and j in multiple_preimages[values]: # only one of the joined blocks should remain in the list + if i in multiple_preimages[values] and j in multiple_preimages[values]: + # only one of the joined blocks should remain in the list multiple_preimages[values].remove(j) if len(multiple_preimages[values]) == 1: del multiple_preimages[values] @@ -1773,7 +1777,7 @@ def minimal_subdistributions_iterator(self): except MIPSolverException: return d = minimal_subdistribution.get_values(D) # a dict from A to {0, 1} - new_s = self._find_counter_example(self.bmilp, s, d) + new_s = self._find_counter_example(self._A, s, d, False) if new_s is None: values = self._sorter["Z"](s[a] for a in self._A if d[a]) yield ([a for a in self._A if d[a]], values) @@ -1789,31 +1793,35 @@ def minimal_subdistributions_iterator(self): else: s = new_s - def _find_counter_example(self, bmilp, s0, d): + def _find_counter_example(self, P, s0, d, on_blocks): r""" Return a solution `s` such that ``d`` is not a subdistribution of `s0`. - TODO: better name - INPUT: - - ``bmilp``, the mixed linear integer program + - ``P``, the representatives of the blocks, or `A` if + ``on_blocks`` is ``False``. + + - ``s0``, a solution. - - ``s0``, a solution + - ``d``, a subset of `A`, in the form of a dict from `A` to `\{0, 1\}`. + + - ``on_blocks``, whether to return the counter example on + blocks or on elements. - - ``d``, a subset of `A`, in the form of a dict from `A` to `\{0, 1\}` """ + bmilp = self.bmilp for v in self._Z: - v_in_d_count = sum(d[a] for a in self._A if s0[a] == v) + v_in_d_count = sum(d[p] for p in P if s0[p] == v) if not v_in_d_count: continue # try to find a solution which has a different # subdistribution on d than s0 - v_in_d = sum(d[a] * bmilp._x[self._P.find(a), v] - for a in self._A - if v in self._possible_block_values[self._P.find(a)]) + v_in_d = sum(d[p] * bmilp._x[self._P.find(p), v] + for p in P + if v in self._possible_block_values[self._P.find(p)]) # it is sufficient to require that v occurs less often as # a value among {a | d[a] == 1} than it does in @@ -1822,11 +1830,12 @@ def _find_counter_example(self, bmilp, s0, d): tmp_constraints = [v_in_d <= v_in_d_count - 1] try: bmilp.solve(tmp_constraints) - return self._solution(bmilp) + return self._solution(bmilp, on_blocks) except MIPSolverException: pass return + def minimal_subdistributions_blocks_iterator(self): r""" Return all representatives of minimal subsets `\tilde P` @@ -1968,7 +1977,7 @@ def add_counter_example_constraint(s): self.bmilp.solve([]) except MIPSolverException: return - s = self._solution_by_blocks(self.bmilp) + s = self._solution(self.bmilp, True) add_counter_example_constraint(s) while True: try: @@ -1976,7 +1985,7 @@ def add_counter_example_constraint(s): except MIPSolverException: return d = minimal_subdistribution.get_values(D) # a dict from P to multiplicities - new_s = self._find_counter_example2(self.bmilp, P, s, d) + new_s = self._find_counter_example(P, s, d, True) if new_s is None: yield ([p for p in P for _ in range(ZZ(d[p]))], self._sorter["Z"](s[p] @@ -1991,50 +2000,6 @@ def add_counter_example_constraint(s): s = new_s add_counter_example_constraint(s) - def _find_counter_example2(self, bmilp, P, s0, d): - r""" - Return a solution `s` such that ``d`` is not a subdistribution of - `s0`. - - .. TODO:: - - find a better name - possibly not relevant if we - implement the cache of solutions - - INPUT: - - - ``bmilp``, the mixed linear integer program - - - ``P``, the representatives of the blocks - - - ``s0``, a solution - - - ``d``, a subset of `A`, in the form of a dict from `A` to `\{0, 1\}` - - """ - for v in self._Z: - v_in_d_count = sum(d[p] for p in P if s0[p] == v) - if not v_in_d_count: - continue - - # try to find a solution which has a different - # subdistribution on d than s0 - v_in_d = sum(d[p] * bmilp._x[p, v] - for p in P - if v in self._possible_block_values[p]) - - # it is sufficient to require that v occurs less often as - # a value among {a | d[a] == 1} than it does in - # v_in_d_count, because, if the distributions are - # different, one such v must exist - tmp_constraints = [v_in_d <= v_in_d_count - 1] - try: - bmilp.solve(tmp_constraints) - return self._solution_by_blocks(bmilp) - except MIPSolverException: - pass - return - def _preprocess_intertwining_relations(self): r""" Make `self._P` be the finest set partition coarser than `self._P` @@ -2493,34 +2458,30 @@ def solutions_iterator(self): print("after vetoing") self._show_bmilp(bmilp, variables=False) - def _solution(self, bmilp): - """ - Return the bmilp solution as a dictionary from `A` to - `Z`. + def _solution(self, bmilp, on_blocks=False): + r""" + Return the current solution as a dictionary from `A` (or + `P`) to `Z`. - """ - map = {} # A -> Z, a +-> s(a) - for p, block in self._P.root_to_elements_dict().items(): - for z in self._possible_block_values[p]: - if bmilp.has_value(p, z): - for a in block: - map[a] = z - break - return map + INPUT: - def _solution_by_blocks(self, bmilp): - """ - Return the bmilp solution as a dictionary from block - representatives of `P` to `Z`. + - ``bmilp``, a :class:`_BijectionistMILP`. + + - ``on_blocks``, whether to return the solution on blocks or + on all elements """ - map = {} # P -> Z, a +-> s(a) - for p in _disjoint_set_roots(self._P): + mapping = {} # A -> Z or P -> Z, a +-> s(a) + for p, block in self._P.root_to_elements_dict().items(): for z in self._possible_block_values[p]: if bmilp.has_value(p, z): - map[p] = z + if on_blocks: + mapping[p] = z + else: + for a in block: + mapping[a] = z break - return map + return mapping def _show_bmilp(self, bmilp, variables=True): """ @@ -2740,10 +2701,10 @@ def solve(self, additional_constraints, solution_index=0): for constraint in additional_constraints: self.milp.add_constraint(constraint) self.milp.solve() - for _ in range(self.milp.number_of_constraints()-n_constraints): + for _ in range(self.milp.number_of_constraints() - n_constraints): self.milp.remove_constraint(n_constraints) except MIPSolverException as error: - for _ in range(self.milp.number_of_constraints()-n_constraints): + for _ in range(self.milp.number_of_constraints() - n_constraints): self.milp.remove_constraint(n_constraints) raise error @@ -2765,10 +2726,55 @@ def _evaluate_linear_function(self, linear_function, values): dictionary from pairs `(a, z)\in A\times Z` to `0` or `1`, specifying whether `a` is mapped to `z`. + EXAMPLES:: + + sage: A = B = ["a", "b"] + sage: bij = Bijectionist(A, B) + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) + sage: _ = bmilp.solve([]) + sage: bmilp._index_block_value_dict # random + {0: ('a', 'a'), 1: ('a', 'b'), 2: ('b', 'a'), 3: ('b', 'b')} + sage: f = bmilp._x["a", "a"] + bmilp._x["b", "a"] + sage: v = {('a', 'a'): 1.0, ('a', 'b'): 0.0, ('b', 'a'): 1.0, ('b', 'b'): 0.0} + sage: bmilp._evaluate_linear_function(f, v) + 2.0 """ return float(sum(value * values[self._index_block_value_dict[index]] for index, value in linear_function.dict().items())) + def _veto_current_solution(self): + r""" + Add a constraint vetoing the current solution. + + This adds a constraint such that the next call to + :meth:`solve` must return a solution different from the + current one. + + We require that the MILP currently has a solution. + + .. WARNING:: + + The underlying MILP will be modified! + + ALGORITHM: + + We add the constraint `\sum_{x\in V} x < |V|`` where `V` is + the set of variables `x_{p, z}` with value 1, that is, the + set of variables indicating the current solution. + + """ + # get all variables with value 1 + active_vars = [self._x[p, z] + for p in _disjoint_set_roots(self._bijectionist._P) + for z in self._bijectionist._possible_block_values[p] + if self.milp.get_values(self._x[p, z])] + + # add constraint that not all of these can be 1, thus vetoing + # the current solution + self.milp.add_constraint(sum(active_vars) <= len(active_vars) - 1, + name="veto") + def has_value(self, p, v): r""" Return whether a block is mapped to a value in the last solution @@ -2778,6 +2784,16 @@ def has_value(self, p, v): - ``p``, the representative of a block - ``v``, a value in `Z` + + EXAMPLES:: + + sage: A = B = ["a", "b"] + sage: bij = Bijectionist(A, B) + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) + sage: _ = bmilp.solve([bmilp._x["a", "b"] == 1]) + sage: bmilp.has_value("a", "b") + True """ return self.last_solution[p, v] == 1 @@ -2924,38 +2940,6 @@ def add_intertwining_relation_constraints(self, origins): self.milp.add_constraint(rhs <= 0, name=f"pi/rho({composition_index})") - def _veto_current_solution(self): - r""" - Add a constraint vetoing the current solution. - - This adds a constraint such that the next call to - :meth:`solve` must return a solution different from the - current one. - - We require that the MILP currently has a solution. - - .. WARNING:: - - The underlying MILP will be modified! - - ALGORITHM: - - We add the constraint `\sum_{x\in V} x < |V|`` where `V` is - the set of variables `x_{p, z}` with value 1, that is, the - set of variables indicating the current solution. - - """ - # get all variables with value 1 - active_vars = [self._x[p, z] - for p in _disjoint_set_roots(self._bijectionist._P) - for z in self._bijectionist._possible_block_values[p] - if self.milp.get_values(self._x[p, z])] - - # add constraint that not all of these can be 1, thus vetoing - # the current solution - self.milp.add_constraint(sum(active_vars) <= len(active_vars) - 1, - name="veto") - def _invert_dict(d): """ From a04d1460832a87550ef18bb1f6767f4240d6230f Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 22 Dec 2022 23:44:38 +0100 Subject: [PATCH 167/392] doctest _find_counter_example --- src/sage/combinat/bijectionist.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 26a93072685..d8788ce68a9 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1810,6 +1810,20 @@ def _find_counter_example(self, P, s0, d, on_blocks): - ``on_blocks``, whether to return the counter example on blocks or on elements. + EXAMPLES:: + + sage: A = B = ["a", "b", "c", "d", "e"] + sage: tau = {"a": 1, "b": 1, "c": 2, "d": 2, "e": 3}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_constant_blocks([["a", "b"]]) + sage: bij.set_value_restrictions(("a", [1, 2])) + sage: next(bij.solutions_iterator()) + {'a': 1, 'b': 1, 'c': 2, 'd': 3, 'e': 2} + + sage: s0 = {'a': 1, 'b': 1, 'c': 2, 'd': 3, 'e': 2} + sage: d = {'a': 1, 'b': 0, 'c': 0, 'd': 0, 'e': 0} + sage: bij._find_counter_example(bij._A, s0, d, False) + {'a': 2, 'b': 2, 'c': 1, 'd': 3, 'e': 1} """ bmilp = self.bmilp for v in self._Z: @@ -3107,9 +3121,9 @@ def _non_copying_intersection(sets): ([[2, 1, 5, 3, 4], [2, 5, 1, 3, 4], [3, 1, 5, 2, 4], [3, 5, 1, 2, 4]], [3, 3, 4, 4]) ([[1, 3, 2, 5, 4], [1, 3, 5, 2, 4], [1, 4, 2, 5, 3], [1, 4, 5, 2, 3], [1, 4, 5, 3, 2], [1, 5, 4, 2, 3], [1, 5, 4, 3, 2]], [2, 2, 3, 3, 3, 3, 3]) - sage: l = list(bij.solutions_iterator()); len(l) # long time + sage: l = list(bij.solutions_iterator()); len(l) # not tested 504 - sage: for a, d in bij.minimal_subdistributions_iterator(): # long time + sage: for a, d in bij.minimal_subdistributions_iterator(): # not tested ....: print(sorted(a), sorted(d)) """ From 3508426a9d10c3c904f998cdfe55447c4753cf5c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 23 Dec 2022 00:07:50 +0100 Subject: [PATCH 168/392] doctest add_distribution_constraints and add_intertwing_relation_constraints --- src/sage/combinat/bijectionist.py | 46 +++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index d8788ce68a9..568ceca83cd 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2825,6 +2825,17 @@ def add_alpha_beta_constraints(self): as a matrix equation. + EXAMPLES:: + + sage: A = B = [permutation for n in range(3) for permutation in Permutations(n)] + sage: bij = Bijectionist(A, B, len) + sage: bij.set_statistics((len, len)) + sage: bij._compute_possible_block_values() + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) + sage: bmilp.add_alpha_beta_constraints() + sage: bmilp.solve([]) + {([], 0): 1.0, ([1], 1): 1.0, ([1, 2], 2): 1.0, ([2, 1], 2): 1.0} """ W = self._bijectionist._W Z = self._bijectionist._Z @@ -2871,6 +2882,25 @@ def add_distribution_constraints(self): where `p(a)` is the block containing `a`, for each given distribution as a vector equation. + EXAMPLES:: + + sage: A = B = Permutations(3) + sage: tau = Permutation.longest_increasing_subsequence_length + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_distributions(([Permutation([1, 2, 3]), Permutation([1, 3, 2])], [1, 3])) + sage: bij._compute_possible_block_values() + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) + sage: bmilp.add_distribution_constraints() + sage: _ = bmilp.solve([]) + sage: bij._solution(bmilp) + {[1, 2, 3]: 3, + [1, 3, 2]: 1, + [2, 1, 3]: 3, + [2, 3, 1]: 3, + [3, 1, 2]: 3, + [3, 2, 1]: 3} + """ Z = self._bijectionist._Z Z_dict = {z: i for i, z in enumerate(Z)} @@ -2935,6 +2965,22 @@ def add_intertwining_relation_constraints(self, origins): Note that `z` must be a possible value of `p` and each `z_i` must be a possible value of `p_i`. + + EXAMPLES:: + + sage: A = B = list('abcd') + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) + sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] + sage: rho = lambda s1, s2: (s1 + s2) % 2 + sage: bij.set_intertwining_relations((2, pi, rho)) + sage: preimage_blocks = bij._preprocess_intertwining_relations() + sage: bij._compute_possible_block_values() + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) + sage: bmilp.add_intertwining_relation_constraints(preimage_blocks) + sage: _ = bmilp.solve([]) + sage: bij._solution(bmilp) + {'a': 0, 'b': 1, 'c': 0, 'd': 1} """ for composition_index, image_block, preimage_blocks in origins: pi_rho = self._bijectionist._pi_rho[composition_index] From 0ac618c968e976d19fd2c61c2462611ce8f0cc3d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 23 Dec 2022 00:37:46 +0100 Subject: [PATCH 169/392] doctest _preprocess_intertwining_relations, _solution, _show_bmilp, _initialize_new_bmilp, _veto_current_solution --- src/sage/combinat/bijectionist.py | 146 +++++++++++++++++++++++------- 1 file changed, 112 insertions(+), 34 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 568ceca83cd..e1aaf010ea2 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -495,7 +495,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), ele assert len(A) == len(set(A)), "A must have distinct items" assert len(B) == len(set(B)), "B must have distinct items" - self.bmilp = None + self._bmilp = None self._A = A self._B = B self._sorter = {} @@ -588,7 +588,7 @@ def set_constant_blocks(self, P): MIPSolverException: ... """ - self.bmilp = None + self._bmilp = None self._P = DisjointSet(self._A) P = sorted(self._sorter["A"](p) for p in P) for p in P: @@ -720,7 +720,7 @@ def set_statistics(self, *alpha_beta): {[]: 2, [1]: 1, [1, 2]: 0, [2, 1]: 0} """ - self.bmilp = None + self._bmilp = None self._n_statistics = len(alpha_beta) # TODO: do we really want to recompute statistics every time? self._alpha = lambda p: tuple(arg[0](p) for arg in alpha_beta) @@ -1035,7 +1035,7 @@ def set_value_restrictions(self, *a_values): # of _statistics_possible_values - however, we do not want to # insist that set_value_restrictions is called after # set_statistics - self.bmilp = None + self._bmilp = None set_Z = set(self._Z) self._restrictions_possible_values = {a: set_Z for a in self._A} for a, values in a_values: @@ -1216,7 +1216,7 @@ def set_distributions(self, *elements_distributions): not in `A`. """ - self.bmilp = None + self._bmilp = None for elements, values in elements_distributions: assert len(elements) == len(values), f"{elements} and {values} are not of the same size!" for a, z in zip(elements, values): @@ -1312,7 +1312,7 @@ def set_intertwining_relations(self, *pi_rho): [] """ - self.bmilp = None + self._bmilp = None Pi_Rho = namedtuple("Pi_Rho", "numargs pi rho domain") self._pi_rho = [] @@ -1354,7 +1354,7 @@ def _forced_constant_blocks(self): sage: bij = Bijectionist(A, B, lambda x: 0) sage: bij.constant_blocks() {} - sage: bij.constant_blocks(optimal=True) + sage: bij.constant_blocks(optimal=True) # indirect doctest {{[], [1], [1, 2], [2, 1]}} In this other example we look at permutations with length 2 and 3:: @@ -1499,12 +1499,12 @@ def _forced_constant_blocks(self): {{'a', 'b'}, {'c', 'd'}} """ - if self.bmilp is None: - self.bmilp = self._initialize_new_bmilp() - self.bmilp.solve([]) + if self._bmilp is None: + self._bmilp = self._initialize_new_bmilp() + self._bmilp.solve([]) # generate blockwise preimage to determine which blocks have the same image - solution = self._solution(self.bmilp, True) + solution = self._solution(self._bmilp, True) multiple_preimages = {(value,): preimages for value, preimages in _invert_dict(solution).items() if len(preimages) > 1} @@ -1532,11 +1532,11 @@ def _forced_constant_blocks(self): # veto the two blocks having the same value for z in self._possible_block_values[i]: if z in self._possible_block_values[j]: # intersection - tmp_constraints.append(self.bmilp._x[i, z] + self.bmilp._x[j, z] <= 1) - self.bmilp.solve(tmp_constraints) + tmp_constraints.append(self._bmilp._x[i, z] + self._bmilp._x[j, z] <= 1) + self._bmilp.solve(tmp_constraints) # solution exists, update dictionary - solution = self._solution(self.bmilp, True) + solution = self._solution(self._bmilp, True) updated_multiple_preimages = {} for values in multiple_preimages: for p in multiple_preimages[values]: @@ -1670,11 +1670,11 @@ def add_solution(solutions, solution): solutions[p].add(value) # generate initial solution, solution dict and add solution - if self.bmilp is None: - self.bmilp = self._initialize_new_bmilp() - self.bmilp.solve([]) + if self._bmilp is None: + self._bmilp = self._initialize_new_bmilp() + self._bmilp.solve([]) - solution = self._solution(self.bmilp) + solution = self._solution(self._bmilp) solutions = {} add_solution(solutions, solution) @@ -1682,15 +1682,15 @@ def add_solution(solutions, solution): for p in blocks: tmp_constraints = [] for value in solutions[p]: - tmp_constraints.append(self.bmilp._x[p, value] == 0) + tmp_constraints.append(self._bmilp._x[p, value] == 0) while True: try: # problem has a solution, so new value was found - self.bmilp.solve(tmp_constraints) - solution = self._solution(self.bmilp) + self._bmilp.solve(tmp_constraints) + solution = self._solution(self._bmilp) add_solution(solutions, solution) # veto new value and try again - tmp_constraints.append(self.bmilp._x[p, solution[p]] == 0) + tmp_constraints.append(self._bmilp._x[p, solution[p]] == 0) except MIPSolverException: # no solution, so all possible values have been found break @@ -1763,12 +1763,12 @@ def minimal_subdistributions_iterator(self): minimal_subdistribution.add_constraint(sum(D[a] for a in self._A) >= 1) try: - if self.bmilp is None: - self.bmilp = self._initialize_new_bmilp() - self.bmilp.solve([]) + if self._bmilp is None: + self._bmilp = self._initialize_new_bmilp() + self._bmilp.solve([]) except MIPSolverException: return - s = self._solution(self.bmilp) + s = self._solution(self._bmilp) while True: for v in self._Z: minimal_subdistribution.add_constraint(sum(D[a] for a in self._A if s[a] == v) == V[v]) @@ -1825,7 +1825,7 @@ def _find_counter_example(self, P, s0, d, on_blocks): sage: bij._find_counter_example(bij._A, s0, d, False) {'a': 2, 'b': 2, 'c': 1, 'd': 3, 'e': 1} """ - bmilp = self.bmilp + bmilp = self._bmilp for v in self._Z: v_in_d_count = sum(d[p] for p in P if s0[p] == v) if not v_in_d_count: @@ -1985,13 +1985,13 @@ def add_counter_example_constraint(s): minimal_subdistribution.add_constraint(sum(D[p] for p in P if s[p] == v) == V[v]) - if self.bmilp is None: + if self._bmilp is None: try: - self.bmilp = self._initialize_new_bmilp() - self.bmilp.solve([]) + self._bmilp = self._initialize_new_bmilp() + self._bmilp.solve([]) except MIPSolverException: return - s = self._solution(self.bmilp, True) + s = self._solution(self._bmilp, True) add_counter_example_constraint(s) while True: try: @@ -2051,6 +2051,24 @@ def _preprocess_intertwining_relations(self): untangle side effect and return value if possible + EXAMPLES:: + + sage: A = B = list('abcd') + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) + sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] + sage: rho = lambda s1, s2: (s1 + s2) % 2 + sage: bij.set_intertwining_relations((2, pi, rho)) + sage: bij._preprocess_intertwining_relations() + {(0, 'a', ('a', 'a')), + (0, 'b', ('a', 'b')), + (0, 'b', ('b', 'a')), + (0, 'c', ('a', 'c')), + (0, 'c', ('b', 'b')), + (0, 'c', ('c', 'a')), + (0, 'd', ('a', 'd')), + (0, 'd', ('b', 'c')), + (0, 'd', ('c', 'b')), + (0, 'd', ('d', 'a'))} """ images = {} # A^k -> A, a_1,...,a_k to pi(a_1,...,a_k), for all pi origins_by_elements = [] # (pi/rho, pi(a_1,...,a_k), a_1,...,a_k) @@ -2454,12 +2472,12 @@ def solutions_iterator(self): 'd': 0} """ next_solution = None - if self.bmilp is None: + if self._bmilp is None: try: - self.bmilp = self._initialize_new_bmilp() + self._bmilp = self._initialize_new_bmilp() except MIPSolverException: return - bmilp = self.bmilp + bmilp = self._bmilp solution_index = 0 while True: try: @@ -2484,6 +2502,16 @@ def _solution(self, bmilp, on_blocks=False): - ``on_blocks``, whether to return the solution on blocks or on all elements + EXAMPLES:: + + sage: A = B = ["a", "b", "c"] + sage: bij = Bijectionist(A, B, lambda x: 0) + sage: bij.set_constant_blocks([["a", "b"]]) + sage: next(bij.solutions_iterator()) + {'a': 0, 'b': 0, 'c': 0} + sage: bij._solution(bij._bmilp, True) + {'a': 0, 'c': 0} + """ mapping = {} # A -> Z or P -> Z, a +-> s(a) for p, block in self._P.root_to_elements_dict().items(): @@ -2502,6 +2530,26 @@ def _show_bmilp(self, bmilp, variables=True): Print the constraints and variables of the current MILP together with some explanations. + + EXAMPLES:: + + sage: A = B = ["a", "b", "c"] + sage: bij = Bijectionist(A, B, lambda x: A.index(x) % 2) + sage: bij.set_constant_blocks([["a", "b"]]) + sage: next(bij.solutions_iterator()) + {'a': 0, 'b': 0, 'c': 1} + sage: bij._show_bmilp(bij._bmilp) + Constraints are: + block a: 1 <= x_0 + x_1 <= 1 + block c: 1 <= x_2 + x_3 <= 1 + statistics: 2 <= 2 x_0 + x_2 <= 2 + statistics: 1 <= 2 x_1 + x_3 <= 1 + veto: x_0 + x_3 <= 1 + Variables are: + x_0: s(a) = s(b) = 0 + x_1: s(a) = s(b) = 1 + x_2: s(c) = 0 + x_3: s(c) = 1 """ print("Constraints are:") b = bmilp.milp.get_backend() @@ -2542,6 +2590,16 @@ def _show_bmilp(self, bmilp, variables=True): def _initialize_new_bmilp(self): r""" Initialize a :class:`_BijectionistMILP` and add the current constraints. + + EXAMPLES:: + + sage: A = B = list('abcd') + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) + sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] + sage: rho = lambda s1, s2: (s1 + s2) % 2 + sage: bij.set_intertwining_relations((2, pi, rho)) + sage: bij._initialize_new_bmilp() + """ preimage_blocks = self._preprocess_intertwining_relations() self._compute_possible_block_values() @@ -2777,6 +2835,26 @@ def _veto_current_solution(self): the set of variables `x_{p, z}` with value 1, that is, the set of variables indicating the current solution. + EXAMPLES:: + + sage: A = B = ["a", "b", "c"] + sage: bij = Bijectionist(A, B, lambda x: A.index(x) % 2) + sage: bij.set_constant_blocks([["a", "b"]]) + sage: iter = bij.solutions_iterator() + sage: next(iter) # indirect doctest + {'a': 0, 'b': 0, 'c': 1} + sage: bij._show_bmilp(bij._bmilp) + Constraints are: + block a: 1 <= x_0 + x_1 <= 1 + block c: 1 <= x_2 + x_3 <= 1 + statistics: 2 <= 2 x_0 + x_2 <= 2 + statistics: 1 <= 2 x_1 + x_3 <= 1 + veto: x_0 + x_3 <= 1 + Variables are: + x_0: s(a) = s(b) = 0 + x_1: s(a) = s(b) = 1 + x_2: s(c) = 0 + x_3: s(c) = 1 """ # get all variables with value 1 active_vars = [self._x[p, z] From d50b62c2008a96849f99004a4c2ad883fab7d0f2 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 23 Dec 2022 09:59:31 +0100 Subject: [PATCH 170/392] expand docstring of main class --- src/sage/combinat/bijectionist.py | 41 ++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index e1aaf010ea2..c1e55b42852 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -329,10 +329,12 @@ {[]: 0, [1]: 1, [1, 2]: 0, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 1, [2, 1, 3]: 1, [2, 3, 1]: 0, [3, 1, 2]: 3, [3, 2, 1]: 0} Value restrictions:: + sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: tau = Permutation.longest_increasing_subsequence_length - sage: bij = Bijectionist(A, B, tau, alpha_beta=((len, len),), a_values=((Permutation([1, 2]), [1]), - ....: (Permutation([3, 2, 1]), [2, 3, 4]),)) + sage: alpha_beta = [(len, len)] + sage: value_restrictions = [(Permutation([1, 2]), [1]), (Permutation([3, 2, 1]), [2, 3, 4])] + sage: bij = Bijectionist(A, B, tau, alpha_beta=alpha_beta, value_restrictions=value_restrictions) sage: for sol in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): ....: print(sol) {[]: 0, [1]: 1, [1, 2]: 1, [2, 1]: 2, [1, 2, 3]: 1, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 3} @@ -341,7 +343,7 @@ sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: tau = Permutation.longest_increasing_subsequence_length - sage: bij = Bijectionist(A, B, tau, a_values=((Permutation([1, 2]), [4, 5]),)) + sage: bij = Bijectionist(A, B, tau, value_restrictions=((Permutation([1, 2]), [4, 5]),)) Traceback (most recent call last): ... ValueError: No possible values found for singleton block [[1, 2]] @@ -380,8 +382,8 @@ class Bijectionist(SageObject): r""" - A toolbox to list all possible bijections between two finite sets - under various constraints. + A toolbox to list all possible bijections between two finite + sets under various constraints. INPUT: @@ -391,13 +393,13 @@ class Bijectionist(SageObject): to ``Z``, in case of ``None``, the identity map ``lambda x: x`` is used - - ``alpha`` (optional) -- a statistic from ``A`` to ``W`` - - - ``beta`` (optional) -- a statistic from ``B`` to ``W`` + - ``alpha_beta`` (optional) -- a list of pairs of statistics + ``alpha`` from ``A`` to ``W`` and ``beta`` from ``B`` to ``W`` - ``P`` (optional) -- a partition of ``A`` - - ``pi_rho`` (optional) -- a triple ``(k, pi, rho)`` where + - ``pi_rho`` (optional) -- a list of triples ``(k, pi, rho)`` + where - ``pi`` is a ``k``-ary operation composing objects in ``A`` and @@ -405,6 +407,15 @@ class Bijectionist(SageObject): - ``rho`` is a ``k``-ary function composing statistic values in `Z` + - ``elements_distributions`` (optional) -- a list of pairs ``(tA, + tZ)``, specifying the distributions of ``tA`` + + - ``value_restrictions`` (optional) -- a list of pairs ``(a, + tZ)``, restricting the possible values of ``a`` + + - ``solver`` (optional) -- the backend used to solve the mixed + integer linear programs + ``W`` and ``Z`` can be arbitrary sets. As a natural example we may think of the natural numbers or tuples of integers. @@ -470,7 +481,9 @@ class Bijectionist(SageObject): specification. """ - def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), elements_distributions=tuple(), a_values=tuple(), solver=None, key=None): + def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], + pi_rho=tuple(), elements_distributions=tuple(), + value_restrictions=tuple(), solver=None, key=None): """ Initialize the bijectionist. @@ -521,7 +534,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], pi_rho=tuple(), ele # set optional inputs self.set_statistics(*alpha_beta) - self.set_value_restrictions(*a_values) + self.set_value_restrictions(*value_restrictions) self.set_distributions(*elements_distributions) self.set_intertwining_relations(*pi_rho) self.set_constant_blocks(P) @@ -940,7 +953,7 @@ def statistics_table(self, header=True): return output_alphas, output_tau_betas - def set_value_restrictions(self, *a_values): + def set_value_restrictions(self, *value_restrictions): r""" Restrict the set of possible values `s(a)` for a given element `a`. @@ -952,7 +965,7 @@ def set_value_restrictions(self, *a_values): INPUT: - - ``a_values`` -- one or more pairs `(a\in A, \tilde + - ``value_restrictions`` -- one or more pairs `(a\in A, \tilde Z\subseteq Z)` EXAMPLES: @@ -1038,7 +1051,7 @@ def set_value_restrictions(self, *a_values): self._bmilp = None set_Z = set(self._Z) self._restrictions_possible_values = {a: set_Z for a in self._A} - for a, values in a_values: + for a, values in value_restrictions: assert a in self._A, f"Element {a} was not found in A" self._restrictions_possible_values[a] = self._restrictions_possible_values[a].intersection(values) From 288e3917aaa8a2d56ec45c2137c5f4889842488e Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 23 Dec 2022 16:27:43 +0100 Subject: [PATCH 171/392] copy milp instead of adding and removing constraints --- src/sage/combinat/bijectionist.py | 42 +++++++++++-------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index c1e55b42852..2d9b74eda86 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -156,7 +156,7 @@ sage: bij = Bijectionist(A, B) sage: bij.set_intertwining_relations((2, concat_path, concat_tree)) sage: bij.set_statistics((lambda d: d.semilength(), lambda t: t.node_number())) - sage: for D in bij.minimal_subdistributions_iterator(): + sage: for D in sorted(bij.minimal_subdistributions_iterator(), key=lambda x: (len(x[0][0]), x)): ....: ascii_art(D) ( [ /\ ], [ o ] ) ( [ o ] ) @@ -376,9 +376,6 @@ from copy import copy, deepcopy from sage.misc.verbose import get_verbose -# TODO: (low) we frequently need variable names for subsets of A, B, -# Z. In LaTeX, we mostly call them \tilde A, \tilde Z, etc. now. It -# would be good to have a standard name in code, too. class Bijectionist(SageObject): r""" @@ -496,8 +493,8 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], """ # glossary of standard letters: # A, B, Z, W ... finite sets - # ???? tilde_A, tilde_Z, ..., subsets? # P ... set partition of A + # tA, tB, tZ, tP ... subsets # a in A, b in B, p in P # S: A -> B # alpha: A -> W, beta: B -> W @@ -505,7 +502,6 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], # k arity of pi and rho # pi: A^k -> A, rho: Z^k -> Z # a_tuple in A^k - assert len(A) == len(set(A)), "A must have distinct items" assert len(B) == len(set(B)), "B must have distinct items" self._bmilp = None @@ -1230,14 +1226,14 @@ def set_distributions(self, *elements_distributions): """ self._bmilp = None - for elements, values in elements_distributions: - assert len(elements) == len(values), f"{elements} and {values} are not of the same size!" - for a, z in zip(elements, values): + for tA, tZ in elements_distributions: + assert len(tA) == len(tZ), f"{elements} and {values} are not of the same size!" + for a, z in zip(tA, tZ): if a not in self._A: raise ValueError(f"Element {a} was not found in A!") if z not in self._Z: raise ValueError(f"Value {z} was not found in tau(A)!") - self._elements_distributions = elements_distributions + self._elements_distributions = tuple(elements_distributions) def set_intertwining_relations(self, *pi_rho): r""" @@ -1862,7 +1858,6 @@ def _find_counter_example(self, P, s0, d, on_blocks): pass return - def minimal_subdistributions_blocks_iterator(self): r""" Return all representatives of minimal subsets `\tilde P` @@ -2547,7 +2542,7 @@ def _show_bmilp(self, bmilp, variables=True): EXAMPLES:: sage: A = B = ["a", "b", "c"] - sage: bij = Bijectionist(A, B, lambda x: A.index(x) % 2) + sage: bij = Bijectionist(A, B, lambda x: A.index(x) % 2, solver="GLPK") sage: bij.set_constant_blocks([["a", "b"]]) sage: next(bij.solutions_iterator()) {'a': 0, 'b': 0, 'c': 1} @@ -2732,7 +2727,7 @@ def solve(self, additional_constraints, solution_index=0): sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] <= 0.5], solution_index=1) Traceback (most recent call last): ... - MIPSolverException: GLPK: Problem has no feasible solution + MIPSolverException: ... no feasible solution However, searching for a cached solution succeeds, for inequalities and equalities:: @@ -2780,20 +2775,11 @@ def solve(self, additional_constraints, solution_index=0): return self.last_solution # otherwise generate a new one - try: - # TODO: wouldn't it be safer to copy the milp? - n_constraints = self.milp.number_of_constraints() - for constraint in additional_constraints: - self.milp.add_constraint(constraint) - self.milp.solve() - for _ in range(self.milp.number_of_constraints() - n_constraints): - self.milp.remove_constraint(n_constraints) - except MIPSolverException as error: - for _ in range(self.milp.number_of_constraints() - n_constraints): - self.milp.remove_constraint(n_constraints) - raise error - - self.last_solution = self.milp.get_values(self._x) + tmp_milp = deepcopy(self.milp) + for constraint in additional_constraints: + tmp_milp.add_constraint(constraint) + tmp_milp.solve() + self.last_solution = tmp_milp.get_values(self._x.copy_for_mip(tmp_milp)) self._solution_cache.append(self.last_solution) self._veto_current_solution() return self.last_solution @@ -2873,7 +2859,7 @@ def _veto_current_solution(self): active_vars = [self._x[p, z] for p in _disjoint_set_roots(self._bijectionist._P) for z in self._bijectionist._possible_block_values[p] - if self.milp.get_values(self._x[p, z])] + if self.last_solution[(p, z)]] # add constraint that not all of these can be 1, thus vetoing # the current solution From d57c8e5295144152214c870f3a206ab0dc5a7716 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 23 Dec 2022 17:21:48 +0100 Subject: [PATCH 172/392] derandomize a test, mark example as random --- src/sage/combinat/bijectionist.py | 125 +++++------------------------- 1 file changed, 21 insertions(+), 104 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 2d9b74eda86..cd6fa9b3c94 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1950,7 +1950,7 @@ def minimal_subdistributions_blocks_iterator(self): [1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1], [1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1]] - sage: sorted(bij.minimal_subdistributions_blocks_iterator()) + sage: sorted(bij.minimal_subdistributions_blocks_iterator()) # random [(['a1', 'a2', 'a3', 'a4', 'a5', 'a5', 'a6', 'a6', 'a7', 'a7', 'a8', 'a8'], [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]), (['a3', 'a4', 'd'], [0, 0, 1]), @@ -2371,113 +2371,30 @@ def solutions_iterator(self): Generate a solution in iterator1, iterator2 should generate the same solution and vice versa:: - sage: next(iterator1) - {'a1': 1, - 'a2': 1, - 'a3': 0, - 'a4': 0, - 'a5': 0, - 'a6': 1, - 'a7': 0, - 'a8': 0, - 'b3': 0, - 'b4': 0, - 'b5': 0, - 'b6': 1, - 'b7': 0, - 'b8': 0, - 'd': 1} - - sage: next(iterator2) - {'a1': 1, - 'a2': 1, - 'a3': 0, - 'a4': 0, - 'a5': 0, - 'a6': 1, - 'a7': 0, - 'a8': 0, - 'b3': 0, - 'b4': 0, - 'b5': 0, - 'b6': 1, - 'b7': 0, - 'b8': 0, - 'd': 1} - - sage: next(iterator2) - {'a1': 1, - 'a2': 1, - 'a3': 0, - 'a4': 0, - 'a5': 1, - 'a6': 0, - 'a7': 0, - 'a8': 0, - 'b3': 0, - 'b4': 0, - 'b5': 1, - 'b6': 0, - 'b7': 0, - 'b8': 0, - 'd': 1} - - sage: next(iterator1) - {'a1': 1, - 'a2': 1, - 'a3': 0, - 'a4': 0, - 'a5': 1, - 'a6': 0, - 'a7': 0, - 'a8': 0, - 'b3': 0, - 'b4': 0, - 'b5': 1, - 'b6': 0, - 'b7': 0, - 'b8': 0, - 'd': 1} - - Re-setting the distribution resets the cache, so a new iterator will generate the first solutions again, - but the old iterator continues:: + sage: s1_1 = next(iterator1) + sage: s2_1 = next(iterator2) + sage: s1_1 == s2_1 + True + sage: s2_2 = next(iterator2) + sage: s1_2 = next(iterator1) + sage: s1_2 == s2_2 + True + + Re-setting the distribution resets the cache, so a new + iterator will generate the first solutions again, but the old + iterator continues:: sage: bij.set_distributions((A[:8] + A[8+2:-1], d), (A[:8] + A[8:-3], d)) sage: iterator3 = bij.solutions_iterator() - sage: next(iterator3) - {'a1': 1, - 'a2': 1, - 'a3': 0, - 'a4': 0, - 'a5': 0, - 'a6': 1, - 'a7': 0, - 'a8': 0, - 'b3': 0, - 'b4': 0, - 'b5': 0, - 'b6': 1, - 'b7': 0, - 'b8': 0, - 'd': 1} - - sage: next(iterator1) - {'a1': 0, - 'a2': 1, - 'a3': 0, - 'a4': 1, - 'a5': 0, - 'a6': 0, - 'a7': 0, - 'a8': 1, - 'b3': 0, - 'b4': 1, - 'b5': 0, - 'b6': 0, - 'b7': 0, - 'b8': 1, - 'd': 0} + sage: s3_1 = next(iterator3) + sage: s1_1 == s3_1 + True + + sage: s1_3 = next(iterator1) + sage: len(set([tuple(sorted(s.items())) for s in [s1_1, s1_2, s1_3]])) + 3 + """ next_solution = None if self._bmilp is None: From 7353fb523075c798dd3afe2cd57108b88909c94a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 23 Dec 2022 22:40:49 +0100 Subject: [PATCH 173/392] correct typo, remove useless assignment --- src/sage/combinat/bijectionist.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index cd6fa9b3c94..b9b1bf7e98f 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1227,7 +1227,7 @@ def set_distributions(self, *elements_distributions): """ self._bmilp = None for tA, tZ in elements_distributions: - assert len(tA) == len(tZ), f"{elements} and {values} are not of the same size!" + assert len(tA) == len(tZ), f"{tA} and {tZ} are not of the same size!" for a, z in zip(tA, tZ): if a not in self._A: raise ValueError(f"Element {a} was not found in A!") @@ -2396,7 +2396,6 @@ def solutions_iterator(self): 3 """ - next_solution = None if self._bmilp is None: try: self._bmilp = self._initialize_new_bmilp() From db850f00bdcea2011ed9194de177fbcbcc8b93d1 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 23 Dec 2022 23:35:42 +0100 Subject: [PATCH 174/392] add and remove constraints instead of copying the whole program --- src/sage/combinat/bijectionist.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index b9b1bf7e98f..26418062876 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2691,10 +2691,26 @@ def solve(self, additional_constraints, solution_index=0): return self.last_solution # otherwise generate a new one - tmp_milp = deepcopy(self.milp) + # set copy = True if the solver requires us to copy the program and throw it away again + copy = False + if copy: + tmp_milp = deepcopy(self.milp) + else: + tmp_milp = self.milp + for constraint in additional_constraints: tmp_milp.add_constraint(constraint) - tmp_milp.solve() + + if copy: + tmp_milp.solve() + else: + n = tmp_milp.number_of_constraints() - 1 + try: + tmp_milp.solve() + finally: + for i in range(len(additional_constraints)): + tmp_milp.remove_constraint(n - i) + self.last_solution = tmp_milp.get_values(self._x.copy_for_mip(tmp_milp)) self._solution_cache.append(self.last_solution) self._veto_current_solution() From ec3271c0e0d79727e2f5f80867bcb253eb6a639e Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 24 Dec 2022 00:00:37 +0100 Subject: [PATCH 175/392] fix problem with SCIP, add timings --- src/sage/combinat/bijectionist.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 26418062876..4bcd34fda8d 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2707,11 +2707,11 @@ def solve(self, additional_constraints, solution_index=0): n = tmp_milp.number_of_constraints() - 1 try: tmp_milp.solve() + self.last_solution = tmp_milp.get_values(self._x.copy_for_mip(tmp_milp)) finally: for i in range(len(additional_constraints)): tmp_milp.remove_constraint(n - i) - self.last_solution = tmp_milp.get_values(self._x.copy_for_mip(tmp_milp)) self._solution_cache.append(self.last_solution) self._veto_current_solution() return self.last_solution @@ -3100,7 +3100,7 @@ def _non_copying_intersection(sets): sage: bij = Bijectionist(sum(As, []), sum(Bs, [])) sage: bij.set_statistics((lambda x: x[0], lambda x: x[0])) sage: bij.set_intertwining_relations((2, c1, c1), (1, c2, c2)) - sage: l = list(bij.solutions_iterator()); len(l) # long time + sage: l = list(bij.solutions_iterator()); len(l) # long time -- (2.7 seconds with SCIP on AMD Ryzen 5 PRO 3500U w/ Radeon Vega Mobile Gfx) 64 A brute force check would be difficult:: @@ -3128,7 +3128,7 @@ def _non_copying_intersection(sets): sage: A = sum(As, []) sage: respects_c1 = lambda s: all(c1(a1, a2) not in A or s[c1(a1, a2)] == c1(s[a1], s[a2]) for a1 in A for a2 in A) sage: respects_c2 = lambda s: all(c2(a1) not in A or s[c2(a1)] == c2(s[a1]) for a1 in A) - sage: l2 = [s for s in it if respects_c1(s) and respects_c2(s)] # long time + sage: l2 = [s for s in it if respects_c1(s) and respects_c2(s)] # long time -- (17 seconds with SCIP on AMD Ryzen 5 PRO 3500U w/ Radeon Vega Mobile Gfx) sage: sorted(l1, key=lambda s: tuple(s.items())) == l2 # long time True @@ -3176,7 +3176,7 @@ def _non_copying_intersection(sets): ([[2, 1, 5, 3, 4], [2, 5, 1, 3, 4], [3, 1, 5, 2, 4], [3, 5, 1, 2, 4]], [3, 3, 4, 4]) ([[1, 3, 2, 5, 4], [1, 3, 5, 2, 4], [1, 4, 2, 5, 3], [1, 4, 5, 2, 3], [1, 4, 5, 3, 2], [1, 5, 4, 2, 3], [1, 5, 4, 3, 2]], [2, 2, 3, 3, 3, 3, 3]) - sage: l = list(bij.solutions_iterator()); len(l) # not tested + sage: l = list(bij.solutions_iterator()); len(l) # not tested -- (17 seconds with SCIP on AMD Ryzen 5 PRO 3500U w/ Radeon Vega Mobile Gfx) 504 sage: for a, d in bij.minimal_subdistributions_iterator(): # not tested From dfdc6bacdcdbc2198fd62b96167aedc3bf94e1de Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 24 Dec 2022 14:58:35 +0100 Subject: [PATCH 176/392] use convert for MixedIntegerLinearProgram.get_values --- src/sage/combinat/bijectionist.py | 51 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 4bcd34fda8d..e33c6602f06 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1785,7 +1785,7 @@ def minimal_subdistributions_iterator(self): minimal_subdistribution.solve() except MIPSolverException: return - d = minimal_subdistribution.get_values(D) # a dict from A to {0, 1} + d = minimal_subdistribution.get_values(D, convert=bool, tolerance=0.1) # a dict from A to {0, 1} new_s = self._find_counter_example(self._A, s, d, False) if new_s is None: values = self._sorter["Z"](s[a] for a in self._A if d[a]) @@ -1793,7 +1793,7 @@ def minimal_subdistributions_iterator(self): # get all variables with value 1 active_vars = [D[a] for a in self._A - if minimal_subdistribution.get_values(D[a])] + if minimal_subdistribution.get_values(D[a], convert=bool, tolerance=0.1)] # add constraint that not all of these can be 1, thus vetoing # the current solution @@ -2006,7 +2006,7 @@ def add_counter_example_constraint(s): minimal_subdistribution.solve() except MIPSolverException: return - d = minimal_subdistribution.get_values(D) # a dict from P to multiplicities + d = minimal_subdistribution.get_values(D, convert=ZZ, tolerance=0.1) # a dict from P to multiplicities new_s = self._find_counter_example(P, s, d, True) if new_s is None: yield ([p for p in P for _ in range(ZZ(d[p]))], @@ -2612,11 +2612,11 @@ def solve(self, additional_constraints, solution_index=0): Without any constraints, we do not require that the solution is a bijection:: sage: bmilp.solve([bmilp._x["a", "a"] == 1, bmilp._x["b", "a"] == 1]) - {('a', 'a'): 1.0, ('a', 'b'): 0.0, ('b', 'a'): 1.0, ('b', 'b'): 0.0} + {('a', 'a'): True, ('a', 'b'): False, ('b', 'a'): True, ('b', 'b'): False} sage: len(bmilp._solution_cache) 1 sage: bmilp.solve([bmilp._x["a", "b"] == 1, bmilp._x["b", "b"] == 1]) - {('a', 'a'): 0.0, ('a', 'b'): 1.0, ('b', 'a'): 0.0, ('b', 'b'): 1.0} + {('a', 'a'): False, ('a', 'b'): True, ('b', 'a'): False, ('b', 'b'): True} sage: len(bmilp._solution_cache) 2 @@ -2631,12 +2631,12 @@ def solve(self, additional_constraints, solution_index=0): Generate a solution:: sage: bmilp.solve([]) - {([], 0): 1.0, - ([1, 0], 1): 1.0, - ([1, 0, 1, 0], 1): 0.0, - ([1, 0, 1, 0], 2): 1.0, - ([1, 1, 0, 0], 1): 1.0, - ([1, 1, 0, 0], 2): 0.0} + {([], 0): True, + ([1, 0], 1): True, + ([1, 0, 1, 0], 1): False, + ([1, 0, 1, 0], 2): True, + ([1, 1, 0, 0], 1): True, + ([1, 1, 0, 0], 2): False} Generating a new solution that also maps `1010` to `2` fails: @@ -2648,20 +2648,20 @@ def solve(self, additional_constraints, solution_index=0): However, searching for a cached solution succeeds, for inequalities and equalities:: sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] <= 0.5]) - {([], 0): 1.0, - ([1, 0], 1): 1.0, - ([1, 0, 1, 0], 1): 0.0, - ([1, 0, 1, 0], 2): 1.0, - ([1, 1, 0, 0], 1): 1.0, - ([1, 1, 0, 0], 2): 0.0} + {([], 0): True, + ([1, 0], 1): True, + ([1, 0, 1, 0], 1): False, + ([1, 0, 1, 0], 2): True, + ([1, 1, 0, 0], 1): True, + ([1, 1, 0, 0], 2): False} sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] == 0]) - {([], 0): 1.0, - ([1, 0], 1): 1.0, - ([1, 0, 1, 0], 1): 0.0, - ([1, 0, 1, 0], 2): 1.0, - ([1, 1, 0, 0], 1): 1.0, - ([1, 1, 0, 0], 2): 0.0} + {([], 0): True, + ([1, 0], 1): True, + ([1, 0, 1, 0], 1): False, + ([1, 0, 1, 0], 2): True, + ([1, 1, 0, 0], 1): True, + ([1, 1, 0, 0], 2): False} """ assert 0 <= solution_index <= len(self._solution_cache), "the index of the desired solution must not be larger than the number of known solutions" @@ -2707,7 +2707,8 @@ def solve(self, additional_constraints, solution_index=0): n = tmp_milp.number_of_constraints() - 1 try: tmp_milp.solve() - self.last_solution = tmp_milp.get_values(self._x.copy_for_mip(tmp_milp)) + self.last_solution = tmp_milp.get_values(self._x.copy_for_mip(tmp_milp), + convert=bool, tolerance=0.1) finally: for i in range(len(additional_constraints)): tmp_milp.remove_constraint(n - i) @@ -2844,7 +2845,7 @@ def add_alpha_beta_constraints(self): sage: bmilp = _BijectionistMILP(bij) sage: bmilp.add_alpha_beta_constraints() sage: bmilp.solve([]) - {([], 0): 1.0, ([1], 1): 1.0, ([1, 2], 2): 1.0, ([2, 1], 2): 1.0} + {([], 0): True, ([1], 1): True, ([1, 2], 2): True, ([2, 1], 2): True} """ W = self._bijectionist._W Z = self._bijectionist._Z From d0836cb93095985a933c549ea8a8893ba4d94a04 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 25 Dec 2022 22:26:12 +0100 Subject: [PATCH 177/392] include information on MILP also in public documentation --- src/sage/combinat/bijectionist.py | 81 ++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index e33c6602f06..7c4cb15b274 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -660,6 +660,17 @@ def set_statistics(self, *alpha_beta): If the statistics `\alpha` and `\beta` are not equidistributed, an error is raised. + ALGORITHM: + + We add + + .. MATH:: + + \sum_{a\in A, z\in Z} x_{p(a), z} s^z t^{\alpha(a)} + = \sum_{b\in B} s^{\tau(b)} t(\beta(b)) + + as a matrix equation to the MILP. + EXAMPLES: We look for bijections `S` on permutations such that the @@ -1094,16 +1105,28 @@ def set_distributions(self, *elements_distributions): INPUT: - - one or more pairs of `(\tilde A\subseteq A, \tilde Z)`, - where `\tilde Z` is a list of values in `Z` of the same - size as `\tilde A` + - one or more pairs of `(\tilde A, \tilde Z)`, where `\tilde + A\subseteq A` and `\tilde Z` is a list of values in `Z` of + the same size as `\tilde A` This method specifies that `\{s(a) | a\in\tilde A\}` equals - ``\tilde Z`` as a multiset for each of the pairs. + `\tilde Z` as a multiset for each of the pairs. When specifying several distributions, the subsets of `A` do not have to be disjoint. + ALGORITHM: + + We add + + .. MATH:: + + \sum_{a\in\tilde A} x_{p(a), z}t^z = \sum_{z\in\tilde Z} t^z, + + where `p(a)` is the block containing `a`, for each given + distribution as a vector equation to the MILP. + + EXAMPLES:: sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] @@ -1248,13 +1271,45 @@ def set_intertwining_relations(self, *pi_rho): INPUT: - ``pi_rho`` -- one or more tuples `(k, \pi: A^k\to A, \rho: - Z^k\to Z, \tilde A)` where `\tilde A` (optional) is an - `k`-ary function that returns true if and only if an + Z^k\to Z, \tilde A)` where `\tilde A` (optional) is a + `k`-ary function that returns true if and only if a `k`-tuple of objects in `A` is in the domain of `\pi` + ALGORITHM: + + The relation + + .. MATH:: + + s(\pi(a_1,\dots, a_k)) = \rho(s(a_1),\dots, s(a_k)) + + for each pair `(\pi, \rho)` implies immediately that + `s(\pi(a_1,\dots, a_k))` only depends on the blocks of + `a_1,\dots, a_k`. + + The MILP formulation is as follows. Let `a_1,\dots,a_k \in + A` and let `a = \pi(a_1,\dots,a_k)`. Let `z_1,\dots,z_k \in + Z` and let `z = \rho(z_1,\dots,z_k)`. Suppose that `a_i\in + p_i` for all `i` and that `a\in p`. + + We then want to model the implication + + .. MATH:: + + x_{p_1, z_1} = 1,\dots, x_{p_k, z_k} = 1 \Rightarrow x_{p, z} = 1. + + We achieve this by requiring + + .. MATH:: + + x_{p, z}\geq 1 - k + \sum_{i=1}^k x_{p_i, z_i}. + + Note that `z` must be a possible value of `p` and each `z_i` + must be a possible value of `p_i`. + EXAMPLES: - We can concatenate two permutations, by increasing the values + We can concatenate two permutations by increasing the values of the second permutation by the length of the first permutation:: @@ -2184,10 +2239,10 @@ def solutions_iterator(self): x_{p, z} \geq 1-k + \sum_{i=1}^k x_{p_i, z_i}. * for each distribution restriction, i.e. a set of elements - `e` and a distribution of values given by integers `d_z` - representing the multiplicity of each `z \in Z`, and `r_p = - |p \cap e|` indicating the relative size of block `p` in - the set of elements of the distribution, + `\tilde A` and a distribution of values given by integers + `d_z` representing the multiplicity of each `z \in Z`, and + `r_p = |p \cap\tilde A|` indicating the relative size of + block `p` in the set of elements of the distribution, .. MATH:: @@ -2887,7 +2942,7 @@ def add_distribution_constraints(self): .. MATH:: - \sum_{a\in elements} x_{p(a), z}t^z = \sum_{z\in values} t^z, + \sum_{a\in\tilde A} x_{p(a), z}t^z = \sum_{z\in\tilde Z} t^z, where `p(a)` is the block containing `a`, for each given distribution as a vector equation. @@ -2950,7 +3005,7 @@ def add_intertwining_relation_constraints(self, origins): .. MATH:: - s(\pi(a_1,\dots, a_k)) = \rho(s(a_1),\dots, s(a_k))` + s(\pi(a_1,\dots, a_k)) = \rho(s(a_1),\dots, s(a_k)) for each pair `(\pi, \rho)`. The relation implies immediately that `s(\pi(a_1,\dots, a_k))` only depends on the From 563af988ae4e282cc2778599c73b728e8eb71f0c Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sun, 25 Dec 2022 23:25:51 +0100 Subject: [PATCH 178/392] remove unnecessary copy --- src/sage/combinat/bijectionist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 7c4cb15b274..623b81d4753 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2762,7 +2762,7 @@ def solve(self, additional_constraints, solution_index=0): n = tmp_milp.number_of_constraints() - 1 try: tmp_milp.solve() - self.last_solution = tmp_milp.get_values(self._x.copy_for_mip(tmp_milp), + self.last_solution = tmp_milp.get_values(self._x, convert=bool, tolerance=0.1) finally: for i in range(len(additional_constraints)): From 590bae24937ea07a1d5f22216b0900cce949ce55 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 26 Dec 2022 19:36:19 +0100 Subject: [PATCH 179/392] add possibility to constrain to involutions --- src/sage/combinat/bijectionist.py | 158 +++++++++++++++++++++++++----- 1 file changed, 136 insertions(+), 22 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 623b81d4753..726185d9907 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -479,7 +479,8 @@ class Bijectionist(SageObject): """ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], - pi_rho=tuple(), elements_distributions=tuple(), + pi_rho=tuple(), phi_psi=tuple(), + elements_distributions=tuple(), value_restrictions=tuple(), solver=None, key=None): """ Initialize the bijectionist. @@ -532,6 +533,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], self.set_statistics(*alpha_beta) self.set_value_restrictions(*value_restrictions) self.set_distributions(*elements_distributions) + self.set_pseudo_inverse_relation(*phi_psi) self.set_intertwining_relations(*pi_rho) self.set_constant_blocks(P) @@ -1126,7 +1128,6 @@ def set_distributions(self, *elements_distributions): where `p(a)` is the block containing `a`, for each given distribution as a vector equation to the MILP. - EXAMPLES:: sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] @@ -1389,6 +1390,61 @@ def set_intertwining_relations(self, *pi_rho): self._pi_rho.append(Pi_Rho(numargs=k, pi=pi, rho=rho, domain=domain)) + set_semi_conjugacy = set_intertwining_relations + + def set_pseudo_inverse_relation(self, *phi_psi): + r""" + Add restrictions of the form `s\circ\psi\circ s = \phi`. + + INPUT: + + - ``phi_psi`` (optional) -- a list of pairs `(\phi, \rho)` + where `\phi: A\to Z` and `\psi: Z\to A` + + ALGORITHM: + + We add + + .. MATH:: + + x_{p(a), z} = x_{p(\psi(z)), \phi(a)} + + for `a\in A` and `z\in Z` to the MILP, where `\phi:A\to Z` + and `\psi:Z\to A`. Note that, in particular, `\phi` must be + constant on blocks. + + + EXAMPLES:: + + sage: A = B = DyckWords(3) + sage: bij = Bijectionist(A, B) + sage: bij.set_statistics((lambda D: D.number_of_touch_points(), lambda D: D.number_of_initial_rises())) + sage: ascii_art(list(bij.minimal_subdistributions_iterator())) + [ ( [ /\ ] ) + [ ( [ / \ ] ) ( [ /\ /\ ] [ /\ /\/\ ] ) + [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \, / \/\ ], [ / \/\, / \ ] ), + + ( [ /\ ] ) ] + ( [ /\/\ / \ ] [ /\ ] ) ] + ( [ / \, / \ ], [ /\/\/\, /\/ \ ] ) ] + sage: bij.set_pseudo_inverse_relation((lambda D: D, lambda D: D)) + sage: ascii_art(list(bij.minimal_subdistributions_iterator())) + [ ( [ /\ ] ) + [ ( [ / \ ] ) ( [ /\ ] [ /\/\ ] ) + [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \ ], [ / \ ] ), + + + ( [ /\ ] [ /\ ] ) ( [ /\/\ ] [ /\ ] ) + ( [ / \/\ ], [ / \/\ ] ), ( [ / \ ], [ /\/ \ ] ), + + ( [ /\ ] ) ] + ( [ / \ ] ) ] + ( [ / \ ], [ /\/\/\ ] ) ] + + """ + self._bmilp = None + self._phi_psi = phi_psi + def _forced_constant_blocks(self): r""" Modify current partition into blocks to the coarsest possible @@ -1717,7 +1773,7 @@ def possible_values(self, p=None, optimal=False): blocks = set() if p in self._A: blocks.add(self._P.find(p)) - elif type(p) is list: + elif type(p) is list: # TODO: this looks very brittle for p1 in p: if p1 in self._A: blocks.add(self._P.find(p1)) @@ -1890,22 +1946,22 @@ def _find_counter_example(self, P, s0, d, on_blocks): {'a': 2, 'b': 2, 'c': 1, 'd': 3, 'e': 1} """ bmilp = self._bmilp - for v in self._Z: - v_in_d_count = sum(d[p] for p in P if s0[p] == v) - if not v_in_d_count: + for z in self._Z: + z_in_d_count = sum(d[p] for p in P if s0[p] == z) + if not z_in_d_count: continue # try to find a solution which has a different # subdistribution on d than s0 - v_in_d = sum(d[p] * bmilp._x[self._P.find(p), v] + z_in_d = sum(d[p] * bmilp._x[self._P.find(p), z] for p in P - if v in self._possible_block_values[self._P.find(p)]) + if z in self._possible_block_values[self._P.find(p)]) - # it is sufficient to require that v occurs less often as + # it is sufficient to require that z occurs less often as # a value among {a | d[a] == 1} than it does in - # v_in_d_count, because, if the distributions are - # different, one such v must exist - tmp_constraints = [v_in_d <= v_in_d_count - 1] + # z_in_d_count, because, if the distributions are + # different, one such z must exist + tmp_constraints = [z_in_d <= z_in_d_count - 1] try: bmilp.solve(tmp_constraints) return self._solution(bmilp, on_blocks) @@ -2587,6 +2643,7 @@ def _initialize_new_bmilp(self): n = bmilp.milp.number_of_variables() bmilp.add_alpha_beta_constraints() bmilp.add_distribution_constraints() + bmilp.add_pseudo_inverse_relation_constraints() bmilp.add_intertwining_relation_constraints(preimage_blocks) if get_verbose() >= 2: self._show_bmilp(bmilp) @@ -2969,25 +3026,25 @@ def add_distribution_constraints(self): """ Z = self._bijectionist._Z Z_dict = {z: i for i, z in enumerate(Z)} - for elements, values in self._bijectionist._elements_distributions: - elements_sum = [ZZ(0)]*len(Z_dict) - values_sum = [ZZ(0)]*len(Z_dict) - for a in elements: + for tA, tZ in self._bijectionist._elements_distributions: + tA_sum = [ZZ(0)]*len(Z_dict) + tZ_sum = [ZZ(0)]*len(Z_dict) + for a in tA: p = self._bijectionist._P.find(a) for z in self._bijectionist._possible_block_values[p]: - elements_sum[Z_dict[z]] += self._x[p, z] - for z in values: - values_sum[Z_dict[z]] += 1 + tA_sum[Z_dict[z]] += self._x[p, z] + for z in tZ: + tZ_sum[Z_dict[z]] += 1 # TODO: not sure that this is the best way to filter out # empty conditions - for element, value in zip(elements_sum, values_sum): - c = element - value + for a, z in zip(tA_sum, tZ_sum): + c = a - z if c.is_zero(): continue if c in ZZ: raise MIPSolverException - self.milp.add_constraint(c == 0, name=f"d: {element} == {value}") + self.milp.add_constraint(c == 0, name=f"d: {a} == {z}") def add_intertwining_relation_constraints(self, origins): r""" @@ -3065,6 +3122,63 @@ def add_intertwining_relation_constraints(self, origins): self.milp.add_constraint(rhs <= 0, name=f"pi/rho({composition_index})") + def add_pseudo_inverse_relation_constraints(self): + r""" + Add constraints enforcing that `s\circ\phi\circ s = + \psi`. + + We do this by adding + + .. MATH:: + + x_{p(a), z} = x_{p(\psi(z)), \phi(a)} + + for `a\in A` and `z\in Z`, where `\phi:A\to Z` and `\psi:Z\to + A`. Note that, in particular, `\phi` must be constant on + blocks. + + EXAMPLES:: + + sage: A = B = DyckWords(3) + sage: bij = Bijectionist(A, B) + sage: bij.set_statistics((lambda D: D.number_of_touch_points(), lambda D: D.number_of_initial_rises())) + sage: ascii_art(list(bij.minimal_subdistributions_iterator())) + [ ( [ /\ ] ) + [ ( [ / \ ] ) ( [ /\ /\ ] [ /\ /\/\ ] ) + [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \, / \/\ ], [ / \/\, / \ ] ), + + ( [ /\ ] ) ] + ( [ /\/\ / \ ] [ /\ ] ) ] + ( [ / \, / \ ], [ /\/\/\, /\/ \ ] ) ] + sage: bij.set_pseudo_inverse_relation((lambda D: D, lambda D: D)) + sage: ascii_art(list(bij.minimal_subdistributions_iterator())) + [ ( [ /\ ] ) + [ ( [ / \ ] ) ( [ /\ ] [ /\/\ ] ) + [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \ ], [ / \ ] ), + + + ( [ /\ ] [ /\ ] ) ( [ /\/\ ] [ /\ ] ) + ( [ / \/\ ], [ / \/\ ] ), ( [ / \ ], [ /\/ \ ] ), + + ( [ /\ ] ) ] + ( [ / \ ] ) ] + ( [ / \ ], [ /\/\/\ ] ) ] + + """ + P = self._bijectionist._P + for phi, psi in self._bijectionist._phi_psi: + for p, block in P.root_to_elements_dict().items(): + z0 = phi(p) + assert all(phi(a) == z0 for a in block), "phi must be constant on the block %s" % block + for z in self._bijectionist._possible_block_values[p]: + p0 = P.find(psi(z)) + if z0 in self._bijectionist._possible_block_values[p0]: + c = self._x[p, z] - self._x[p0, z0] + if c.is_zero(): + continue + self.milp.add_constraint(c == 0, name=f"i: s({p})={z}<->s(psi({z})=phi({p})") + else: + self.milp.add_constraint(self._x[p, z] == 0, name=f"i: s({p})!={z}") def _invert_dict(d): """ From da0655b5e1dcd4b1b1a35943fbbedf2698900786 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 26 Dec 2022 23:27:45 +0100 Subject: [PATCH 180/392] correct indentation --- src/sage/combinat/bijectionist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 726185d9907..97319f78d5e 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1399,7 +1399,7 @@ def set_pseudo_inverse_relation(self, *phi_psi): INPUT: - ``phi_psi`` (optional) -- a list of pairs `(\phi, \rho)` - where `\phi: A\to Z` and `\psi: Z\to A` + where `\phi: A\to Z` and `\psi: Z\to A` ALGORITHM: @@ -3150,7 +3150,7 @@ def add_pseudo_inverse_relation_constraints(self): ( [ /\ ] ) ] ( [ /\/\ / \ ] [ /\ ] ) ] ( [ / \, / \ ], [ /\/\/\, /\/ \ ] ) ] - sage: bij.set_pseudo_inverse_relation((lambda D: D, lambda D: D)) + sage: bij.set_pseudo_inverse_relation((lambda D: D, lambda D: D)) # indirect doctest sage: ascii_art(list(bij.minimal_subdistributions_iterator())) [ ( [ /\ ] ) [ ( [ / \ ] ) ( [ /\ ] [ /\/\ ] ) From 03b1fb836611da4674cb8c34ea3faad709418670 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 29 Dec 2022 13:28:14 +0100 Subject: [PATCH 181/392] better handling of empty constraints --- src/sage/combinat/bijectionist.py | 119 +++++++++++++++--------------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 97319f78d5e..980668f7c7b 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2358,9 +2358,14 @@ def solutions_iterator(self): block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 statistics: 1 <= x_0 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_1 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_2 + x_4 <= 1 statistics: 1 <= x_3 + x_5 <= 1 + statistics: 0 <= <= 0 statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 @@ -2376,9 +2381,14 @@ def solutions_iterator(self): block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 statistics: 1 <= x_0 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_1 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_2 + x_4 <= 1 statistics: 1 <= x_3 + x_5 <= 1 + statistics: 0 <= <= 0 statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 @@ -2399,9 +2409,14 @@ def solutions_iterator(self): block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 statistics: 1 <= x_0 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_1 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_2 + x_4 <= 1 statistics: 1 <= x_3 + x_5 <= 1 + statistics: 0 <= <= 0 statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 @@ -2431,9 +2446,14 @@ def solutions_iterator(self): block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 statistics: 1 <= x_0 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_1 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_2 + x_4 <= 1 statistics: 1 <= x_3 + x_5 <= 1 + statistics: 0 <= <= 0 statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 @@ -2448,9 +2468,14 @@ def solutions_iterator(self): block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 statistics: 1 <= x_0 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_1 <= 1 + statistics: 0 <= <= 0 + statistics: 0 <= <= 0 statistics: 1 <= x_2 + x_4 <= 1 statistics: 1 <= x_3 + x_5 <= 1 + statistics: 0 <= <= 0 statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 @@ -2640,14 +2665,12 @@ def _initialize_new_bmilp(self): self._compute_possible_block_values() bmilp = _BijectionistMILP(self) - n = bmilp.milp.number_of_variables() bmilp.add_alpha_beta_constraints() bmilp.add_distribution_constraints() bmilp.add_pseudo_inverse_relation_constraints() bmilp.add_intertwining_relation_constraints(preimage_blocks) if get_verbose() >= 2: self._show_bmilp(bmilp) - assert n == bmilp.milp.number_of_variables(), "The number of variables increased." return bmilp @@ -2682,22 +2705,21 @@ def __init__(self, bijectionist: Bijectionist): # the attributes of the bijectionist class we actually use: # _possible_block_values # _elements_distributions - # _W, _Z, _A, _B, _P, _alpha, _beta, _tau, _pi_rho + # _W, _Z, _A, _B, _P, _alpha, _beta, _tau, _pi_rho, _phi_psi + self._bijectionist = bijectionist self.milp = MixedIntegerLinearProgram(solver=bijectionist._solver) self.milp.set_objective(None) - self._n_variables = -1 + indices = [(p, z) + for p, tZ in bijectionist._possible_block_values.items() + for z in tZ] + self._x = self.milp.new_variable(binary=True, indices=indices) self._solution_cache = [] self._last_solution = {} - self._index_block_value_dict = None - self._x = self.milp.new_variable(binary=True) # indexed by P x Z - - self._bijectionist = bijectionist for p in _disjoint_set_roots(bijectionist._P): - name = f"block {p}" self.milp.add_constraint(sum(self._x[p, z] for z in bijectionist._possible_block_values[p]) == 1, - name=name[:50]) + name=f"block {p}"[:50]) def solve(self, additional_constraints, solution_index=0): r""" @@ -2777,20 +2799,8 @@ def solve(self, additional_constraints, solution_index=0): """ assert 0 <= solution_index <= len(self._solution_cache), "the index of the desired solution must not be larger than the number of known solutions" - - if self._n_variables < 0: - # initialize at first call - self._n_variables = self.milp.number_of_variables() - self._index_block_value_dict = {} - for (p, z), v in self._x.items(): - variable_index = next(iter(v.dict().keys())) - self._index_block_value_dict[variable_index] = (p, z) - # number of variables would change with creation of - # constraints with new variables - assert self._n_variables == self.milp.number_of_variables(), "The number of variables changed." - - # check if there is a solution satisfying the constraints in - # the cache + # check whether there is a solution in the cache satisfying + # the additional constraints for solution in self._solution_cache[solution_index:]: if all(all(self._evaluate_linear_function(linear_function, solution) == value.dict()[-1] @@ -2807,23 +2817,19 @@ def solve(self, additional_constraints, solution_index=0): copy = False if copy: tmp_milp = deepcopy(self.milp) - else: - tmp_milp = self.milp - - for constraint in additional_constraints: - tmp_milp.add_constraint(constraint) - - if copy: + for constraint in additional_constraints: + tmp_milp.add_constraint(constraint, return_indices=True) tmp_milp.solve() else: - n = tmp_milp.number_of_constraints() - 1 + new_indices = [] + for constraint in additional_constraints: + new_indices.extend(self.milp.add_constraint(constraint, return_indices=True)) try: - tmp_milp.solve() - self.last_solution = tmp_milp.get_values(self._x, - convert=bool, tolerance=0.1) + self.milp.solve() + self.last_solution = self.milp.get_values(self._x, + convert=bool, tolerance=0.1) finally: - for i in range(len(additional_constraints)): - tmp_milp.remove_constraint(n - i) + self.milp.remove_constraints(new_indices) self._solution_cache.append(self.last_solution) self._veto_current_solution() @@ -2849,13 +2855,16 @@ def _evaluate_linear_function(self, linear_function, values): sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) sage: _ = bmilp.solve([]) - sage: bmilp._index_block_value_dict # random - {0: ('a', 'a'), 1: ('a', 'b'), 2: ('b', 'a'), 3: ('b', 'b')} sage: f = bmilp._x["a", "a"] + bmilp._x["b", "a"] sage: v = {('a', 'a'): 1.0, ('a', 'b'): 0.0, ('b', 'a'): 1.0, ('b', 'b'): 0.0} sage: bmilp._evaluate_linear_function(f, v) 2.0 """ + self._index_block_value_dict = {} + for (p, z), v in self._x.items(): + variable_index = next(iter(v.dict().keys())) + self._index_block_value_dict[variable_index] = (p, z) + return float(sum(value * values[self._index_block_value_dict[index]] for index, value in linear_function.dict().items())) @@ -2961,8 +2970,9 @@ def add_alpha_beta_constraints(self): """ W = self._bijectionist._W Z = self._bijectionist._Z - AZ_matrix = [[ZZ(0)]*len(W) for _ in range(len(Z))] - B_matrix = [[ZZ(0)]*len(W) for _ in range(len(Z))] + zero = self.milp.linear_functions_parent().zero() + AZ_matrix = [[zero]*len(W) for _ in range(len(Z))] + B_matrix = [[zero]*len(W) for _ in range(len(Z))] W_dict = {w: i for i, w in enumerate(W)} Z_dict = {z: i for i, z in enumerate(Z)} @@ -2979,16 +2989,10 @@ def add_alpha_beta_constraints(self): z_index = Z_dict[self._bijectionist._tau[b]] B_matrix[z_index][w_index] += 1 - # TODO: not sure that this is the best way to filter out - # empty conditions for w in range(len(W)): for z in range(len(Z)): - c = AZ_matrix[z][w] - B_matrix[z][w] - if c.is_zero(): - continue - if c in ZZ: - raise MIPSolverException - self.milp.add_constraint(c == 0, name="statistics") + self.milp.add_constraint(AZ_matrix[z][w] == B_matrix[z][w], + name="statistics") def add_distribution_constraints(self): r""" @@ -3026,9 +3030,10 @@ def add_distribution_constraints(self): """ Z = self._bijectionist._Z Z_dict = {z: i for i, z in enumerate(Z)} + zero = self.milp.linear_functions_parent().zero() for tA, tZ in self._bijectionist._elements_distributions: - tA_sum = [ZZ(0)]*len(Z_dict) - tZ_sum = [ZZ(0)]*len(Z_dict) + tA_sum = [zero]*len(Z_dict) + tZ_sum = [zero]*len(Z_dict) for a in tA: p = self._bijectionist._P.find(a) for z in self._bijectionist._possible_block_values[p]: @@ -3036,15 +3041,8 @@ def add_distribution_constraints(self): for z in tZ: tZ_sum[Z_dict[z]] += 1 - # TODO: not sure that this is the best way to filter out - # empty conditions for a, z in zip(tA_sum, tZ_sum): - c = a - z - if c.is_zero(): - continue - if c in ZZ: - raise MIPSolverException - self.milp.add_constraint(c == 0, name=f"d: {a} == {z}") + self.milp.add_constraint(a == z, name=f"d: {a} == {z}") def add_intertwining_relation_constraints(self, origins): r""" @@ -3180,6 +3178,7 @@ def add_pseudo_inverse_relation_constraints(self): else: self.milp.add_constraint(self._x[p, z] == 0, name=f"i: s({p})!={z}") + def _invert_dict(d): """ Return the dictionary whose keys are the values of the input and @@ -3298,7 +3297,7 @@ def _non_copying_intersection(sets): sage: A = sum(As, []) sage: respects_c1 = lambda s: all(c1(a1, a2) not in A or s[c1(a1, a2)] == c1(s[a1], s[a2]) for a1 in A for a2 in A) sage: respects_c2 = lambda s: all(c2(a1) not in A or s[c2(a1)] == c2(s[a1]) for a1 in A) - sage: l2 = [s for s in it if respects_c1(s) and respects_c2(s)] # long time -- (17 seconds with SCIP on AMD Ryzen 5 PRO 3500U w/ Radeon Vega Mobile Gfx) + sage: l2 = [s for s in it if respects_c1(s) and respects_c2(s)] # long time -- (17 seconds on AMD Ryzen 5 PRO 3500U w/ Radeon Vega Mobile Gfx) sage: sorted(l1, key=lambda s: tuple(s.items())) == l2 # long time True From a3364dec31f6eee2ca1c2f2777a39df5a57a716a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Thu, 29 Dec 2022 22:47:50 +0100 Subject: [PATCH 182/392] move _show to the _BijectionistMILP class --- src/sage/combinat/bijectionist.py | 131 +++++++++++++++--------------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 980668f7c7b..93c4867ed0b 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2548,7 +2548,7 @@ def solutions_iterator(self): solution_index += 1 if get_verbose() >= 2: print("after vetoing") - self._show_bmilp(bmilp, variables=False) + bmilp.show(variables=False) def _solution(self, bmilp, on_blocks=False): r""" @@ -2585,68 +2585,6 @@ def _solution(self, bmilp, on_blocks=False): break return mapping - def _show_bmilp(self, bmilp, variables=True): - """ - Print the constraints and variables of the current MILP - together with some explanations. - - - EXAMPLES:: - - sage: A = B = ["a", "b", "c"] - sage: bij = Bijectionist(A, B, lambda x: A.index(x) % 2, solver="GLPK") - sage: bij.set_constant_blocks([["a", "b"]]) - sage: next(bij.solutions_iterator()) - {'a': 0, 'b': 0, 'c': 1} - sage: bij._show_bmilp(bij._bmilp) - Constraints are: - block a: 1 <= x_0 + x_1 <= 1 - block c: 1 <= x_2 + x_3 <= 1 - statistics: 2 <= 2 x_0 + x_2 <= 2 - statistics: 1 <= 2 x_1 + x_3 <= 1 - veto: x_0 + x_3 <= 1 - Variables are: - x_0: s(a) = s(b) = 0 - x_1: s(a) = s(b) = 1 - x_2: s(c) = 0 - x_3: s(c) = 1 - """ - print("Constraints are:") - b = bmilp.milp.get_backend() - varid_name = {} - for i in range(b.ncols()): - s = b.col_name(i) - default_name = str(bmilp.milp.linear_functions_parent()({i: 1})) - if s and s != default_name: - varid_name[i] = s - else: - varid_name[i] = default_name - for i, (lb, (indices, values), ub) in enumerate(bmilp.milp.constraints()): - if b.row_name(i): - print(" "+b.row_name(i)+":", end=" ") - if lb is not None: - print(str(ZZ(lb))+" <=", end=" ") - first = True - for j, c in sorted(zip(indices, values)): - c = ZZ(c) - if c == 0: - continue - print((("+ " if (not first and c > 0) else "") + - ("" if c == 1 else - ("- " if c == -1 else - (str(c) + " " if first and c < 0 else - ("- " + str(abs(c)) + " " if c < 0 else str(c) + " ")))) - + varid_name[j]), end=" ") - first = False - # Upper bound - print("<= "+str(ZZ(ub)) if ub is not None else "") - - if variables: - print("Variables are:") - for (p, z), v in bmilp._x.items(): - print(f" {v}: " + "".join([f"s({a}) = " - for a in self._P.root_to_elements_dict()[p]]) + f"{z}") - def _initialize_new_bmilp(self): r""" Initialize a :class:`_BijectionistMILP` and add the current constraints. @@ -2670,7 +2608,7 @@ def _initialize_new_bmilp(self): bmilp.add_pseudo_inverse_relation_constraints() bmilp.add_intertwining_relation_constraints(preimage_blocks) if get_verbose() >= 2: - self._show_bmilp(bmilp) + bmilp.show() return bmilp @@ -2721,6 +2659,69 @@ def __init__(self, bijectionist: Bijectionist): for z in bijectionist._possible_block_values[p]) == 1, name=f"block {p}"[:50]) + def show(self, variables=True): + r""" + Print the constraints and variables of the MILP together + with some explanations. + + EXAMPLES:: + + sage: A = B = ["a", "b", "c"] + sage: bij = Bijectionist(A, B, lambda x: A.index(x) % 2, solver="GLPK") + sage: bij.set_constant_blocks([["a", "b"]]) + sage: next(bij.solutions_iterator()) + {'a': 0, 'b': 0, 'c': 1} + sage: bij._bmilp.show() + Constraints are: + block a: 1 <= x_0 + x_1 <= 1 + block c: 1 <= x_2 + x_3 <= 1 + statistics: 2 <= 2 x_0 + x_2 <= 2 + statistics: 1 <= 2 x_1 + x_3 <= 1 + veto: x_0 + x_3 <= 1 + Variables are: + x_0: s(a) = s(b) = 0 + x_1: s(a) = s(b) = 1 + x_2: s(c) = 0 + x_3: s(c) = 1 + + """ + print("Constraints are:") + b = self.milp.get_backend() + varid_name = {} + for i in range(b.ncols()): + s = b.col_name(i) + default_name = str(self.milp.linear_functions_parent()({i: 1})) + if s and s != default_name: + varid_name[i] = s + else: + varid_name[i] = default_name + for i, (lb, (indices, values), ub) in enumerate(self.milp.constraints()): + if b.row_name(i): + print(" "+b.row_name(i)+":", end=" ") + if lb is not None: + print(str(ZZ(lb))+" <=", end=" ") + first = True + for j, c in sorted(zip(indices, values)): + c = ZZ(c) + if c == 0: + continue + print((("+ " if (not first and c > 0) else "") + + ("" if c == 1 else + ("- " if c == -1 else + (str(c) + " " if first and c < 0 else + ("- " + str(abs(c)) + " " if c < 0 else str(c) + " ")))) + + varid_name[j]), end=" ") + first = False + # Upper bound + print("<= "+str(ZZ(ub)) if ub is not None else "") + + if variables: + print("Variables are:") + P = self._bijectionist._P.root_to_elements_dict() + for (p, z), v in self._x.items(): + print(f" {v}: " + "".join([f"s({a}) = " + for a in P[p]]) + f"{z}") + def solve(self, additional_constraints, solution_index=0): r""" Return a solution satisfying the given additional constraints. @@ -2896,7 +2897,7 @@ def _veto_current_solution(self): sage: iter = bij.solutions_iterator() sage: next(iter) # indirect doctest {'a': 0, 'b': 0, 'c': 1} - sage: bij._show_bmilp(bij._bmilp) + sage: bij._bmilp.show() Constraints are: block a: 1 <= x_0 + x_1 <= 1 block c: 1 <= x_2 + x_3 <= 1 From 9d83a6c50c0da26065e7fda350b7a7980e98f52e Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 30 Dec 2022 14:43:30 +0100 Subject: [PATCH 183/392] remove unused code for backends that do not support removing constraints --- src/sage/combinat/bijectionist.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 93c4867ed0b..64ec91f1d17 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2814,23 +2814,15 @@ def solve(self, additional_constraints, solution_index=0): return self.last_solution # otherwise generate a new one - # set copy = True if the solver requires us to copy the program and throw it away again - copy = False - if copy: - tmp_milp = deepcopy(self.milp) - for constraint in additional_constraints: - tmp_milp.add_constraint(constraint, return_indices=True) - tmp_milp.solve() - else: - new_indices = [] - for constraint in additional_constraints: - new_indices.extend(self.milp.add_constraint(constraint, return_indices=True)) - try: - self.milp.solve() - self.last_solution = self.milp.get_values(self._x, - convert=bool, tolerance=0.1) - finally: - self.milp.remove_constraints(new_indices) + new_indices = [] + for constraint in additional_constraints: + new_indices.extend(self.milp.add_constraint(constraint, return_indices=True)) + try: + self.milp.solve() + self.last_solution = self.milp.get_values(self._x, + convert=bool, tolerance=0.1) + finally: + self.milp.remove_constraints(new_indices) self._solution_cache.append(self.last_solution) self._veto_current_solution() From 864029dc1800ac3af007a58f50f9f19c2ccb98ac Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 30 Dec 2022 14:46:46 +0100 Subject: [PATCH 184/392] make _solution a public method of _BijectionistMILP, simplify solve --- src/sage/combinat/bijectionist.py | 232 +++++++++++------------------- 1 file changed, 86 insertions(+), 146 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 64ec91f1d17..01c57830a16 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1624,10 +1624,10 @@ def _forced_constant_blocks(self): self._bmilp.solve([]) # generate blockwise preimage to determine which blocks have the same image - solution = self._solution(self._bmilp, True) - multiple_preimages = {(value,): preimages - for value, preimages in _invert_dict(solution).items() - if len(preimages) > 1} + solution = self._bmilp.solution(True) + multiple_preimages = {(z,): tP + for z, tP in _invert_dict(solution).items() + if len(tP) > 1} # check for each pair of blocks if a solution with different # values on these block exists @@ -1636,27 +1636,36 @@ def _forced_constant_blocks(self): # multiple_preimages dictionary, restart the check # if no, the two blocks can be joined - # _P has to be copied to not mess with the solution-process + # _P has to be copied to not mess with the solution process # since we do not want to regenerate the bmilp in each step, # so blocks have to stay consistent during the whole process tmp_P = deepcopy(self._P) updated_preimages = True while updated_preimages: updated_preimages = False - for values in copy(multiple_preimages): + for tZ in copy(multiple_preimages): if updated_preimages: break - for i, j in itertools.combinations(copy(multiple_preimages[values]), r=2): + for i, j in itertools.combinations(copy(multiple_preimages[tZ]), r=2): + # veto two blocks having the same value tmp_constraints = [] + for z in self._possible_block_values[i]: + if z in self._possible_block_values[j]: # intersection + tmp_constraints.append(self._bmilp._x[i, z] + self._bmilp._x[j, z] <= 1) try: - # veto the two blocks having the same value - for z in self._possible_block_values[i]: - if z in self._possible_block_values[j]: # intersection - tmp_constraints.append(self._bmilp._x[i, z] + self._bmilp._x[j, z] <= 1) self._bmilp.solve(tmp_constraints) - + except MIPSolverException: + # no solution exists, join blocks + tmp_P.union(i, j) + if i in multiple_preimages[tZ] and j in multiple_preimages[tZ]: + # only one of the joined blocks should remain in the list + multiple_preimages[tZ].remove(j) + if len(multiple_preimages[tZ]) == 1: + del multiple_preimages[tZ] + break + else: # solution exists, update dictionary - solution = self._solution(self._bmilp, True) + solution = self._bmilp.solution(True) updated_multiple_preimages = {} for values in multiple_preimages: for p in multiple_preimages[values]: @@ -1667,15 +1676,6 @@ def _forced_constant_blocks(self): updated_preimages = True multiple_preimages = updated_multiple_preimages break - except MIPSolverException: - # no solution exists, join blocks - tmp_P.union(i, j) - if i in multiple_preimages[values] and j in multiple_preimages[values]: - # only one of the joined blocks should remain in the list - multiple_preimages[values].remove(j) - if len(multiple_preimages[values]) == 1: - del multiple_preimages[values] - break self.set_constant_blocks(tmp_P) @@ -1794,7 +1794,7 @@ def add_solution(solutions, solution): self._bmilp = self._initialize_new_bmilp() self._bmilp.solve([]) - solution = self._solution(self._bmilp) + solution = self._bmilp.solution(False) solutions = {} add_solution(solutions, solution) @@ -1807,7 +1807,7 @@ def add_solution(solutions, solution): try: # problem has a solution, so new value was found self._bmilp.solve(tmp_constraints) - solution = self._solution(self._bmilp) + solution = self._bmilp.solution(False) add_solution(solutions, solution) # veto new value and try again tmp_constraints.append(self._bmilp._x[p, solution[p]] == 0) @@ -1888,7 +1888,7 @@ def minimal_subdistributions_iterator(self): self._bmilp.solve([]) except MIPSolverException: return - s = self._solution(self._bmilp) + s = self._bmilp.solution(False) while True: for v in self._Z: minimal_subdistribution.add_constraint(sum(D[a] for a in self._A if s[a] == v) == V[v]) @@ -1964,7 +1964,7 @@ def _find_counter_example(self, P, s0, d, on_blocks): tmp_constraints = [z_in_d <= z_in_d_count - 1] try: bmilp.solve(tmp_constraints) - return self._solution(bmilp, on_blocks) + return bmilp.solution(on_blocks) except MIPSolverException: pass return @@ -2110,7 +2110,7 @@ def add_counter_example_constraint(s): self._bmilp.solve([]) except MIPSolverException: return - s = self._solution(self._bmilp, True) + s = self._bmilp.solution(True) add_counter_example_constraint(s) while True: try: @@ -2544,47 +2544,12 @@ def solutions_iterator(self): bmilp.solve([], solution_index) except MIPSolverException: return - yield self._solution(bmilp) + yield bmilp.solution(False) solution_index += 1 if get_verbose() >= 2: print("after vetoing") bmilp.show(variables=False) - def _solution(self, bmilp, on_blocks=False): - r""" - Return the current solution as a dictionary from `A` (or - `P`) to `Z`. - - INPUT: - - - ``bmilp``, a :class:`_BijectionistMILP`. - - - ``on_blocks``, whether to return the solution on blocks or - on all elements - - EXAMPLES:: - - sage: A = B = ["a", "b", "c"] - sage: bij = Bijectionist(A, B, lambda x: 0) - sage: bij.set_constant_blocks([["a", "b"]]) - sage: next(bij.solutions_iterator()) - {'a': 0, 'b': 0, 'c': 0} - sage: bij._solution(bij._bmilp, True) - {'a': 0, 'c': 0} - - """ - mapping = {} # A -> Z or P -> Z, a +-> s(a) - for p, block in self._P.root_to_elements_dict().items(): - for z in self._possible_block_values[p]: - if bmilp.has_value(p, z): - if on_blocks: - mapping[p] = z - else: - for a in block: - mapping[a] = z - break - return mapping - def _initialize_new_bmilp(self): r""" Initialize a :class:`_BijectionistMILP` and add the current constraints. @@ -2652,7 +2617,6 @@ def __init__(self, bijectionist: Bijectionist): for z in tZ] self._x = self.milp.new_variable(binary=True, indices=indices) self._solution_cache = [] - self._last_solution = {} for p in _disjoint_set_roots(bijectionist._P): self.milp.add_constraint(sum(self._x[p, z] @@ -2803,12 +2767,7 @@ def solve(self, additional_constraints, solution_index=0): # check whether there is a solution in the cache satisfying # the additional constraints for solution in self._solution_cache[solution_index:]: - if all(all(self._evaluate_linear_function(linear_function, - solution) == value.dict()[-1] - for linear_function, value in constraint.equations()) - and all(self._evaluate_linear_function(linear_function, - solution) <= value.dict()[-1] - for linear_function, value in constraint.inequalities()) + if all(self._is_solution(constraint, solution) for constraint in additional_constraints): self.last_solution = solution return self.last_solution @@ -2824,18 +2783,26 @@ def solve(self, additional_constraints, solution_index=0): finally: self.milp.remove_constraints(new_indices) + # veto the solution, by requiring that not all variables with + # value 1 have value 1 in the new MILP + active_vars = [self._x[p, z] + for p in _disjoint_set_roots(self._bijectionist._P) + for z in self._bijectionist._possible_block_values[p] + if self.last_solution[(p, z)]] + self.milp.add_constraint(sum(active_vars) <= len(active_vars) - 1, + name="veto") + self._solution_cache.append(self.last_solution) - self._veto_current_solution() return self.last_solution - def _evaluate_linear_function(self, linear_function, values): + def _is_solution(self, constraint, values): r""" Evaluate the given function at the given values. INPUT: - - ``linear_function``, a - :class:`sage.numerical.linear_functions.LinearFunction`. + - ``constraint``, a + :class:`sage.numerical.linear_functions.LinearConstraint`. - ``values``, a candidate for a solution of the MILP as a dictionary from pairs `(a, z)\in A\times Z` to `0` or `1`, @@ -2848,92 +2815,65 @@ def _evaluate_linear_function(self, linear_function, values): sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) sage: _ = bmilp.solve([]) - sage: f = bmilp._x["a", "a"] + bmilp._x["b", "a"] - sage: v = {('a', 'a'): 1.0, ('a', 'b'): 0.0, ('b', 'a'): 1.0, ('b', 'b'): 0.0} - sage: bmilp._evaluate_linear_function(f, v) - 2.0 + sage: f = bmilp._x["a", "a"] + bmilp._x["b", "a"] >= bmilp._x["b", "b"] + 1 + sage: v = {('a', 'a'): 1, ('a', 'b'): 0, ('b', 'a'): 1, ('b', 'b'): 1} + sage: bmilp._is_solution(f, v) + True + sage: v = {('a', 'a'): 0, ('a', 'b'): 0, ('b', 'a'): 1, ('b', 'b'): 1} + sage: bmilp._is_solution(f, v) + False """ - self._index_block_value_dict = {} + index_block_value_dict = {} for (p, z), v in self._x.items(): variable_index = next(iter(v.dict().keys())) - self._index_block_value_dict[variable_index] = (p, z) - - return float(sum(value * values[self._index_block_value_dict[index]] - for index, value in linear_function.dict().items())) + index_block_value_dict[variable_index] = (p, z) - def _veto_current_solution(self): - r""" - Add a constraint vetoing the current solution. - - This adds a constraint such that the next call to - :meth:`solve` must return a solution different from the - current one. + evaluate = lambda f: sum(coeff if index == -1 else + coeff * values[index_block_value_dict[index]] + for index, coeff in f.dict().items()) - We require that the MILP currently has a solution. + for lhs, rhs in constraint.equations(): + if evaluate(lhs - rhs): + return False + for lhs, rhs in constraint.inequalities(): + if evaluate(lhs - rhs) > 0: + return False + return True - .. WARNING:: + def solution(self, on_blocks): + r""" + Return the current solution as a dictionary from `A` (or + `P`) to `Z`. - The underlying MILP will be modified! + INPUT: - ALGORITHM: + - ``bmilp``, a :class:`_BijectionistMILP`. - We add the constraint `\sum_{x\in V} x < |V|`` where `V` is - the set of variables `x_{p, z}` with value 1, that is, the - set of variables indicating the current solution. + - ``on_blocks``, whether to return the solution on blocks or + on all elements EXAMPLES:: sage: A = B = ["a", "b", "c"] - sage: bij = Bijectionist(A, B, lambda x: A.index(x) % 2) + sage: bij = Bijectionist(A, B, lambda x: 0) sage: bij.set_constant_blocks([["a", "b"]]) - sage: iter = bij.solutions_iterator() - sage: next(iter) # indirect doctest - {'a': 0, 'b': 0, 'c': 1} - sage: bij._bmilp.show() - Constraints are: - block a: 1 <= x_0 + x_1 <= 1 - block c: 1 <= x_2 + x_3 <= 1 - statistics: 2 <= 2 x_0 + x_2 <= 2 - statistics: 1 <= 2 x_1 + x_3 <= 1 - veto: x_0 + x_3 <= 1 - Variables are: - x_0: s(a) = s(b) = 0 - x_1: s(a) = s(b) = 1 - x_2: s(c) = 0 - x_3: s(c) = 1 - """ - # get all variables with value 1 - active_vars = [self._x[p, z] - for p in _disjoint_set_roots(self._bijectionist._P) - for z in self._bijectionist._possible_block_values[p] - if self.last_solution[(p, z)]] - - # add constraint that not all of these can be 1, thus vetoing - # the current solution - self.milp.add_constraint(sum(active_vars) <= len(active_vars) - 1, - name="veto") - - def has_value(self, p, v): - r""" - Return whether a block is mapped to a value in the last solution - computed. - - INPUT: - - - ``p``, the representative of a block - - ``v``, a value in `Z` - - EXAMPLES:: + sage: next(bij.solutions_iterator()) + {'a': 0, 'b': 0, 'c': 0} + sage: bij._bmilp.solution(True) + {'a': 0, 'c': 0} - sage: A = B = ["a", "b"] - sage: bij = Bijectionist(A, B) - sage: from sage.combinat.bijectionist import _BijectionistMILP - sage: bmilp = _BijectionistMILP(bij) - sage: _ = bmilp.solve([bmilp._x["a", "b"] == 1]) - sage: bmilp.has_value("a", "b") - True """ - return self.last_solution[p, v] == 1 + mapping = {} # A -> Z or P -> Z, a +-> s(a) + for p, block in self._bijectionist._P.root_to_elements_dict().items(): + for z in self._bijectionist._possible_block_values[p]: + if self.last_solution[p, z] == 1: + if on_blocks: + mapping[p] = z + else: + for a in block: + mapping[a] = z + break + return mapping def add_alpha_beta_constraints(self): r""" @@ -3012,7 +2952,7 @@ def add_distribution_constraints(self): sage: bmilp = _BijectionistMILP(bij) sage: bmilp.add_distribution_constraints() sage: _ = bmilp.solve([]) - sage: bij._solution(bmilp) + sage: bmilp.solution(False) {[1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 3, @@ -3092,7 +3032,7 @@ def add_intertwining_relation_constraints(self, origins): sage: bmilp = _BijectionistMILP(bij) sage: bmilp.add_intertwining_relation_constraints(preimage_blocks) sage: _ = bmilp.solve([]) - sage: bij._solution(bmilp) + sage: bmilp.solution(False) {'a': 0, 'b': 1, 'c': 0, 'd': 1} """ for composition_index, image_block, preimage_blocks in origins: From 64101a8ffe9fabffa5a33faca001b4292ba6e28d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 30 Dec 2022 15:11:40 +0100 Subject: [PATCH 185/392] eliminate _initialize_new_bmilp --- src/sage/combinat/bijectionist.py | 95 +++++++++---------------------- 1 file changed, 27 insertions(+), 68 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 01c57830a16..4da0be9fd9f 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1620,7 +1620,7 @@ def _forced_constant_blocks(self): """ if self._bmilp is None: - self._bmilp = self._initialize_new_bmilp() + self._bmilp = _BijectionistMILP(self) self._bmilp.solve([]) # generate blockwise preimage to determine which blocks have the same image @@ -1791,7 +1791,7 @@ def add_solution(solutions, solution): # generate initial solution, solution dict and add solution if self._bmilp is None: - self._bmilp = self._initialize_new_bmilp() + self._bmilp = _BijectionistMILP(self) self._bmilp.solve([]) solution = self._bmilp.solution(False) @@ -1884,7 +1884,7 @@ def minimal_subdistributions_iterator(self): try: if self._bmilp is None: - self._bmilp = self._initialize_new_bmilp() + self._bmilp = _BijectionistMILP(self) self._bmilp.solve([]) except MIPSolverException: return @@ -2106,7 +2106,7 @@ def add_counter_example_constraint(s): if self._bmilp is None: try: - self._bmilp = self._initialize_new_bmilp() + self._bmilp = _BijectionistMILP(self) self._bmilp.solve([]) except MIPSolverException: return @@ -2534,7 +2534,7 @@ def solutions_iterator(self): """ if self._bmilp is None: try: - self._bmilp = self._initialize_new_bmilp() + self._bmilp = _BijectionistMILP(self) except MIPSolverException: return bmilp = self._bmilp @@ -2550,32 +2550,6 @@ def solutions_iterator(self): print("after vetoing") bmilp.show(variables=False) - def _initialize_new_bmilp(self): - r""" - Initialize a :class:`_BijectionistMILP` and add the current constraints. - - EXAMPLES:: - - sage: A = B = list('abcd') - sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) - sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] - sage: rho = lambda s1, s2: (s1 + s2) % 2 - sage: bij.set_intertwining_relations((2, pi, rho)) - sage: bij._initialize_new_bmilp() - - """ - preimage_blocks = self._preprocess_intertwining_relations() - self._compute_possible_block_values() - - bmilp = _BijectionistMILP(self) - bmilp.add_alpha_beta_constraints() - bmilp.add_distribution_constraints() - bmilp.add_pseudo_inverse_relation_constraints() - bmilp.add_intertwining_relation_constraints(preimage_blocks) - if get_verbose() >= 2: - bmilp.show() - return bmilp - class _BijectionistMILP(): r""" @@ -2610,18 +2584,26 @@ def __init__(self, bijectionist: Bijectionist): # _elements_distributions # _W, _Z, _A, _B, _P, _alpha, _beta, _tau, _pi_rho, _phi_psi self._bijectionist = bijectionist + preimage_blocks = bijectionist._preprocess_intertwining_relations() + bijectionist._compute_possible_block_values() self.milp = MixedIntegerLinearProgram(solver=bijectionist._solver) self.milp.set_objective(None) + self._solution_cache = [] indices = [(p, z) for p, tZ in bijectionist._possible_block_values.items() for z in tZ] self._x = self.milp.new_variable(binary=True, indices=indices) - self._solution_cache = [] for p in _disjoint_set_roots(bijectionist._P): self.milp.add_constraint(sum(self._x[p, z] for z in bijectionist._possible_block_values[p]) == 1, name=f"block {p}"[:50]) + self.add_alpha_beta_constraints() + self.add_distribution_constraints() + self.add_pseudo_inverse_relation_constraints() + self.add_intertwining_relation_constraints(preimage_blocks) + if get_verbose() >= 2: + self.show() def show(self, variables=True): r""" @@ -2701,31 +2683,12 @@ def solve(self, additional_constraints, solution_index=0): TESTS:: - sage: A = B = ["a", "b"] - sage: bij = Bijectionist(A, B) sage: from sage.combinat.bijectionist import _BijectionistMILP - sage: bmilp = _BijectionistMILP(bij) - sage: len(bmilp._solution_cache) - 0 - - Without any constraints, we do not require that the solution is a bijection:: - - sage: bmilp.solve([bmilp._x["a", "a"] == 1, bmilp._x["b", "a"] == 1]) - {('a', 'a'): True, ('a', 'b'): False, ('b', 'a'): True, ('b', 'b'): False} - sage: len(bmilp._solution_cache) - 1 - sage: bmilp.solve([bmilp._x["a", "b"] == 1, bmilp._x["b", "b"] == 1]) - {('a', 'a'): False, ('a', 'b'): True, ('b', 'a'): False, ('b', 'b'): True} - sage: len(bmilp._solution_cache) - 2 - - A more elaborate test:: - sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] sage: tau = lambda D: D.number_of_touch_points() sage: bij = Bijectionist(A, B, tau) sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) - sage: bmilp = bij._initialize_new_bmilp() + sage: bmilp = _BijectionistMILP(bij) Generate a solution:: @@ -2739,14 +2702,14 @@ def solve(self, additional_constraints, solution_index=0): Generating a new solution that also maps `1010` to `2` fails: - sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] <= 0.5], solution_index=1) + sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 2] == 1], solution_index=1) Traceback (most recent call last): ... MIPSolverException: ... no feasible solution However, searching for a cached solution succeeds, for inequalities and equalities:: - sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] <= 0.5]) + sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 2] >= 1]) {([], 0): True, ([1, 0], 1): True, ([1, 0, 1, 0], 1): False, @@ -2762,6 +2725,9 @@ def solve(self, additional_constraints, solution_index=0): ([1, 1, 0, 0], 1): True, ([1, 1, 0, 0], 2): False} + sage: len(bmilp._solution_cache) + 1 + """ assert 0 <= solution_index <= len(self._solution_cache), "the index of the desired solution must not be larger than the number of known solutions" # check whether there is a solution in the cache satisfying @@ -2894,10 +2860,8 @@ def add_alpha_beta_constraints(self): sage: A = B = [permutation for n in range(3) for permutation in Permutations(n)] sage: bij = Bijectionist(A, B, len) sage: bij.set_statistics((len, len)) - sage: bij._compute_possible_block_values() sage: from sage.combinat.bijectionist import _BijectionistMILP - sage: bmilp = _BijectionistMILP(bij) - sage: bmilp.add_alpha_beta_constraints() + sage: bmilp = _BijectionistMILP(bij) # indirect doctest sage: bmilp.solve([]) {([], 0): True, ([1], 1): True, ([1, 2], 2): True, ([2, 1], 2): True} """ @@ -2947,18 +2911,16 @@ def add_distribution_constraints(self): sage: tau = Permutation.longest_increasing_subsequence_length sage: bij = Bijectionist(A, B, tau) sage: bij.set_distributions(([Permutation([1, 2, 3]), Permutation([1, 3, 2])], [1, 3])) - sage: bij._compute_possible_block_values() sage: from sage.combinat.bijectionist import _BijectionistMILP - sage: bmilp = _BijectionistMILP(bij) - sage: bmilp.add_distribution_constraints() + sage: bmilp = _BijectionistMILP(bij) # indirect doctest sage: _ = bmilp.solve([]) sage: bmilp.solution(False) {[1, 2, 3]: 3, [1, 3, 2]: 1, - [2, 1, 3]: 3, - [2, 3, 1]: 3, - [3, 1, 2]: 3, - [3, 2, 1]: 3} + [2, 1, 3]: 2, + [2, 3, 1]: 2, + [3, 1, 2]: 2, + [3, 2, 1]: 2} """ Z = self._bijectionist._Z @@ -3026,11 +2988,8 @@ def add_intertwining_relation_constraints(self, origins): sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] sage: rho = lambda s1, s2: (s1 + s2) % 2 sage: bij.set_intertwining_relations((2, pi, rho)) - sage: preimage_blocks = bij._preprocess_intertwining_relations() - sage: bij._compute_possible_block_values() sage: from sage.combinat.bijectionist import _BijectionistMILP - sage: bmilp = _BijectionistMILP(bij) - sage: bmilp.add_intertwining_relation_constraints(preimage_blocks) + sage: bmilp = _BijectionistMILP(bij) # indirect doctest sage: _ = bmilp.solve([]) sage: bmilp.solution(False) {'a': 0, 'b': 1, 'c': 0, 'd': 1} From 2d349114a367d5c7196ad50f1e847dfe466857c7 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 31 Dec 2022 00:11:50 +0100 Subject: [PATCH 186/392] slightly simplify logic of _forced_constant_blocks, use defaultdict --- src/sage/combinat/bijectionist.py | 175 ++++++++++++++---------------- 1 file changed, 81 insertions(+), 94 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 4da0be9fd9f..b6a80db5acb 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -367,13 +367,13 @@ # https://www.gnu.org/licenses/ # *************************************************************************** import itertools -from collections import namedtuple +from collections import namedtuple, defaultdict from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException from sage.rings.integer_ring import ZZ from sage.combinat.set_partition import SetPartition from sage.sets.disjoint_set import DisjointSet from sage.structure.sage_object import SageObject -from copy import copy, deepcopy +from copy import deepcopy from sage.misc.verbose import get_verbose @@ -1419,7 +1419,7 @@ def set_pseudo_inverse_relation(self, *phi_psi): sage: A = B = DyckWords(3) sage: bij = Bijectionist(A, B) sage: bij.set_statistics((lambda D: D.number_of_touch_points(), lambda D: D.number_of_initial_rises())) - sage: ascii_art(list(bij.minimal_subdistributions_iterator())) + sage: ascii_art(sorted(bij.minimal_subdistributions_iterator())) [ ( [ /\ ] ) [ ( [ / \ ] ) ( [ /\ /\ ] [ /\ /\/\ ] ) [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \, / \/\ ], [ / \/\, / \ ] ), @@ -1428,7 +1428,7 @@ def set_pseudo_inverse_relation(self, *phi_psi): ( [ /\/\ / \ ] [ /\ ] ) ] ( [ / \, / \ ], [ /\/\/\, /\/ \ ] ) ] sage: bij.set_pseudo_inverse_relation((lambda D: D, lambda D: D)) - sage: ascii_art(list(bij.minimal_subdistributions_iterator())) + sage: ascii_art(sorted(bij.minimal_subdistributions_iterator())) [ ( [ /\ ] ) [ ( [ / \ ] ) ( [ /\ ] [ /\/\ ] ) [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \ ], [ / \ ] ), @@ -1623,61 +1623,59 @@ def _forced_constant_blocks(self): self._bmilp = _BijectionistMILP(self) self._bmilp.solve([]) - # generate blockwise preimage to determine which blocks have the same image solution = self._bmilp.solution(True) + # multiple_preimages[tZ] are the blocks p which have the same + # value tZ[i] in the i-th known solution multiple_preimages = {(z,): tP for z, tP in _invert_dict(solution).items() if len(tP) > 1} - # check for each pair of blocks if a solution with different - # values on these block exists - - # if yes, use the new solution to update the - # multiple_preimages dictionary, restart the check - # if no, the two blocks can be joined - # _P has to be copied to not mess with the solution process # since we do not want to regenerate the bmilp in each step, # so blocks have to stay consistent during the whole process tmp_P = deepcopy(self._P) - updated_preimages = True - while updated_preimages: - updated_preimages = False - for tZ in copy(multiple_preimages): - if updated_preimages: - break - for i, j in itertools.combinations(copy(multiple_preimages[tZ]), r=2): - # veto two blocks having the same value - tmp_constraints = [] - for z in self._possible_block_values[i]: - if z in self._possible_block_values[j]: # intersection - tmp_constraints.append(self._bmilp._x[i, z] + self._bmilp._x[j, z] <= 1) - try: - self._bmilp.solve(tmp_constraints) - except MIPSolverException: - # no solution exists, join blocks - tmp_P.union(i, j) - if i in multiple_preimages[tZ] and j in multiple_preimages[tZ]: - # only one of the joined blocks should remain in the list - multiple_preimages[tZ].remove(j) - if len(multiple_preimages[tZ]) == 1: - del multiple_preimages[tZ] - break - else: - # solution exists, update dictionary - solution = self._bmilp.solution(True) - updated_multiple_preimages = {} - for values in multiple_preimages: - for p in multiple_preimages[values]: - solution_tuple = (*values, solution[p]) - if solution_tuple not in updated_multiple_preimages: - updated_multiple_preimages[solution_tuple] = [] - updated_multiple_preimages[solution_tuple].append(p) - updated_preimages = True - multiple_preimages = updated_multiple_preimages - break - self.set_constant_blocks(tmp_P) + # check whether blocks p1 and p2 can have different values, + # if so return such a solution + def different_values(p1, p2): + tmp_constraints = [self._bmilp._x[p1, z] + self._bmilp._x[p2, z] <= 1 + for z in self._possible_block_values[p1] + if z in self._possible_block_values[p2]] + try: + self._bmilp.solve(tmp_constraints) + return self._bmilp.solution(True) + except MIPSolverException: + pass + + # try to find a pair of blocks having the same value on all + # known solutions, and a solution such that the values are + # different on this solution + def merge_until_split(): + for tZ in list(multiple_preimages): + tP = multiple_preimages[tZ] + for i2 in range(len(tP)-1, -1, -1): + for i1 in range(i2): + solution = different_values(tP[i1], tP[i2]) + if solution is None: + tmp_P.union(tP[i1], tP[i2]) + if len(multiple_preimages[tZ]) == 2: + del multiple_preimages[tZ] + else: + tP.remove(tP[i2]) + break # skip all pairs (i, j) containing i2 + return solution + + while True: + solution = merge_until_split() + if solution is None: + self.set_constant_blocks(tmp_P) + return + + updated_multiple_preimages = defaultdict(list) + for tZ, tP in multiple_preimages.items(): + for p in tP: + updated_multiple_preimages[tZ + (solution[p],)].append(p) + multiple_preimages = updated_multiple_preimages def possible_values(self, p=None, optimal=False): r""" @@ -1782,38 +1780,34 @@ def possible_values(self, p=None, optimal=False): blocks.add(self._P.find(p2)) if optimal: - # function adding a solution to dict of solutions - def add_solution(solutions, solution): - for p, value in solution.items(): - if p not in solutions: - solutions[p] = set() - solutions[p].add(value) - # generate initial solution, solution dict and add solution if self._bmilp is None: self._bmilp = _BijectionistMILP(self) self._bmilp.solve([]) + solutions = defaultdict(set) solution = self._bmilp.solution(False) - solutions = {} - add_solution(solutions, solution) + for p, z in solution.items(): + solutions[p].add(z) # iterate through blocks and generate all values for p in blocks: tmp_constraints = [] - for value in solutions[p]: - tmp_constraints.append(self._bmilp._x[p, value] == 0) + for z in solutions[p]: + tmp_constraints.append(self._bmilp._x[p, z] == 0) while True: try: # problem has a solution, so new value was found self._bmilp.solve(tmp_constraints) - solution = self._bmilp.solution(False) - add_solution(solutions, solution) - # veto new value and try again - tmp_constraints.append(self._bmilp._x[p, solution[p]] == 0) except MIPSolverException: # no solution, so all possible values have been found break + solution = self._bmilp.solution(False) + for p, z in solution.items(): + solutions[p].add(z) + # veto new value and try again + tmp_constraints.append(self._bmilp._x[p, solution[p]] == 0) + # create dictionary to return possible_values = {} @@ -2210,13 +2204,10 @@ def _preprocess_intertwining_relations(self): something_changed = False # collect (preimage, image) pairs by (representatives) of # the blocks of the elements of the preimage - updated_images = {} # (p_1,...,p_k) to {a_1,....} + updated_images = defaultdict(set) # (p_1,...,p_k) to {a_1,....} for a_tuple, image_set in images.items(): representatives = tuple(self._P.find(a) for a in a_tuple) - if representatives in updated_images: - updated_images[representatives].update(image_set) - else: - updated_images[representatives] = image_set + updated_images[representatives].update(image_set) # merge blocks for a_tuple, image_set in updated_images.items(): @@ -2670,7 +2661,9 @@ def show(self, variables=True): def solve(self, additional_constraints, solution_index=0): r""" - Return a solution satisfying the given additional constraints. + Find a solution satisfying the given additional constraints. + + The solution can then be retrieved using :meth:`solution`. INPUT: @@ -2693,12 +2686,8 @@ def solve(self, additional_constraints, solution_index=0): Generate a solution:: sage: bmilp.solve([]) - {([], 0): True, - ([1, 0], 1): True, - ([1, 0, 1, 0], 1): False, - ([1, 0, 1, 0], 2): True, - ([1, 1, 0, 0], 1): True, - ([1, 1, 0, 0], 2): False} + sage: bmilp.solution(False) + {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} Generating a new solution that also maps `1010` to `2` fails: @@ -2710,20 +2699,12 @@ def solve(self, additional_constraints, solution_index=0): However, searching for a cached solution succeeds, for inequalities and equalities:: sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 2] >= 1]) - {([], 0): True, - ([1, 0], 1): True, - ([1, 0, 1, 0], 1): False, - ([1, 0, 1, 0], 2): True, - ([1, 1, 0, 0], 1): True, - ([1, 1, 0, 0], 2): False} + sage: bmilp.solution(False) + {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] == 0]) - {([], 0): True, - ([1, 0], 1): True, - ([1, 0, 1, 0], 1): False, - ([1, 0, 1, 0], 2): True, - ([1, 1, 0, 0], 1): True, - ([1, 1, 0, 0], 2): False} + sage: bmilp.solution(False) + {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} sage: len(bmilp._solution_cache) 1 @@ -2736,17 +2717,23 @@ def solve(self, additional_constraints, solution_index=0): if all(self._is_solution(constraint, solution) for constraint in additional_constraints): self.last_solution = solution - return self.last_solution + return # otherwise generate a new one new_indices = [] for constraint in additional_constraints: - new_indices.extend(self.milp.add_constraint(constraint, return_indices=True)) + new_indices.extend(self.milp.add_constraint(constraint, + return_indices=True)) try: self.milp.solve() self.last_solution = self.milp.get_values(self._x, convert=bool, tolerance=0.1) finally: + b = self.milp.get_backend() + if hasattr(b, "_get_model"): + m = b._get_model() + if m.getStatus() != 'unknown': + m.freeTransform() self.milp.remove_constraints(new_indices) # veto the solution, by requiring that not all variables with @@ -2759,7 +2746,6 @@ def solve(self, additional_constraints, solution_index=0): name="veto") self._solution_cache.append(self.last_solution) - return self.last_solution def _is_solution(self, constraint, values): r""" @@ -2863,7 +2849,8 @@ def add_alpha_beta_constraints(self): sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) # indirect doctest sage: bmilp.solve([]) - {([], 0): True, ([1], 1): True, ([1, 2], 2): True, ([2, 1], 2): True} + sage: bmilp.solution(False) + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 2} """ W = self._bijectionist._W Z = self._bijectionist._Z @@ -3032,7 +3019,7 @@ def add_pseudo_inverse_relation_constraints(self): sage: A = B = DyckWords(3) sage: bij = Bijectionist(A, B) sage: bij.set_statistics((lambda D: D.number_of_touch_points(), lambda D: D.number_of_initial_rises())) - sage: ascii_art(list(bij.minimal_subdistributions_iterator())) + sage: ascii_art(sorted(bij.minimal_subdistributions_iterator())) [ ( [ /\ ] ) [ ( [ / \ ] ) ( [ /\ /\ ] [ /\ /\/\ ] ) [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \, / \/\ ], [ / \/\, / \ ] ), @@ -3041,7 +3028,7 @@ def add_pseudo_inverse_relation_constraints(self): ( [ /\/\ / \ ] [ /\ ] ) ] ( [ / \, / \ ], [ /\/\/\, /\/ \ ] ) ] sage: bij.set_pseudo_inverse_relation((lambda D: D, lambda D: D)) # indirect doctest - sage: ascii_art(list(bij.minimal_subdistributions_iterator())) + sage: ascii_art(sorted(bij.minimal_subdistributions_iterator())) [ ( [ /\ ] ) [ ( [ / \ ] ) ( [ /\ ] [ /\/\ ] ) [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \ ], [ / \ ] ), From c2f0062e977fe6ff595862ce72e061d87aaf2d32 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 31 Dec 2022 00:23:49 +0100 Subject: [PATCH 187/392] slight simplification --- src/sage/combinat/bijectionist.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index b6a80db5acb..b69c620f669 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1643,9 +1643,9 @@ def different_values(p1, p2): if z in self._possible_block_values[p2]] try: self._bmilp.solve(tmp_constraints) - return self._bmilp.solution(True) except MIPSolverException: - pass + return + return self._bmilp.solution(True) # try to find a pair of blocks having the same value on all # known solutions, and a solution such that the values are @@ -1792,9 +1792,8 @@ def possible_values(self, p=None, optimal=False): # iterate through blocks and generate all values for p in blocks: - tmp_constraints = [] - for z in solutions[p]: - tmp_constraints.append(self._bmilp._x[p, z] == 0) + tmp_constraints = [self._bmilp._x[p, z] == 0 + for z in solutions[p]] while True: try: # problem has a solution, so new value was found @@ -1803,12 +1802,11 @@ def possible_values(self, p=None, optimal=False): # no solution, so all possible values have been found break solution = self._bmilp.solution(False) - for p, z in solution.items(): - solutions[p].add(z) + for p0, z in solution.items(): + solutions[p0].add(z) # veto new value and try again tmp_constraints.append(self._bmilp._x[p, solution[p]] == 0) - # create dictionary to return possible_values = {} for p in blocks: From 78968aebf38b7ca80295a67c296ef0e8df213b7a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 31 Dec 2022 14:13:04 +0100 Subject: [PATCH 188/392] copy (instead of deepcopy) should be correct --- src/sage/combinat/bijectionist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index b69c620f669..0e03f3ab6e3 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -373,7 +373,7 @@ from sage.combinat.set_partition import SetPartition from sage.sets.disjoint_set import DisjointSet from sage.structure.sage_object import SageObject -from copy import deepcopy +from copy import copy from sage.misc.verbose import get_verbose @@ -1633,7 +1633,7 @@ def _forced_constant_blocks(self): # _P has to be copied to not mess with the solution process # since we do not want to regenerate the bmilp in each step, # so blocks have to stay consistent during the whole process - tmp_P = deepcopy(self._P) + tmp_P = copy(self._P) # check whether blocks p1 and p2 can have different values, # if so return such a solution From 6ddaeae19c78bb860744fba4bf2b00dc3ef7cd74 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 31 Dec 2022 20:44:46 +0100 Subject: [PATCH 189/392] slightly simplify _preprocess_intertwining_relations --- src/sage/combinat/bijectionist.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 0e03f3ab6e3..45c73487f09 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2181,7 +2181,7 @@ def _preprocess_intertwining_relations(self): (0, 'd', ('c', 'b')), (0, 'd', ('d', 'a'))} """ - images = {} # A^k -> A, a_1,...,a_k to pi(a_1,...,a_k), for all pi + images = defaultdict(set) # A^k -> A, a_1,...,a_k +-> {pi(a_1,...,a_k) for all pi} origins_by_elements = [] # (pi/rho, pi(a_1,...,a_k), a_1,...,a_k) for composition_index, pi_rho in enumerate(self._pi_rho): for a_tuple in itertools.product(*([self._A]*pi_rho.numargs)): @@ -2189,11 +2189,7 @@ def _preprocess_intertwining_relations(self): continue a = pi_rho.pi(*a_tuple) if a in self._A: - if a in images: - # this happens if there are several pi's of the same arity - images[a_tuple].add(a) # TODO: wouldn't self._P.find(a) be more efficient here? - else: - images[a_tuple] = set((a,)) # TODO: wouldn't self._P.find(a) be more efficient here? + images[a_tuple].add(a) origins_by_elements.append((composition_index, a, a_tuple)) # merge blocks From 60286e60eafe0206430176074e0b158a6d6f9d0a Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 31 Dec 2022 20:46:21 +0100 Subject: [PATCH 190/392] slightly improve non_copying_intersection --- src/sage/combinat/bijectionist.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 45c73487f09..ddfe65e454e 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -3111,20 +3111,21 @@ def _non_copying_intersection(sets): sage: _non_copying_intersection([A, B]) is A True + sage: A = set([1,2]); B = set([2,3]) + sage: _non_copying_intersection([A, B]) + {2} + """ sets = sorted(sets, key=len) result = set.intersection(*sets) n = len(result) - if n < len(sets[0]): - return result for s in sets: N = len(s) - if N > n: + if n < N: return result if s == result: return s - """ TESTS:: From c38f5b94d92171a2d18e884f9bdb095ff34ffc65 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 31 Dec 2022 21:15:50 +0100 Subject: [PATCH 191/392] add some internal documentation --- src/sage/combinat/bijectionist.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index ddfe65e454e..5fe36548a6f 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2569,8 +2569,13 @@ def __init__(self, bijectionist: Bijectionist): # _elements_distributions # _W, _Z, _A, _B, _P, _alpha, _beta, _tau, _pi_rho, _phi_psi self._bijectionist = bijectionist + # the variables of the MILP are indexed by pairs (p, z), for + # p in _P and z an element of _posible_block_values[p]. + # Thus, _P and _posible_block_values have to be fixed before + # creating the MILP. preimage_blocks = bijectionist._preprocess_intertwining_relations() bijectionist._compute_possible_block_values() + self.milp = MixedIntegerLinearProgram(solver=bijectionist._solver) self.milp.set_objective(None) self._solution_cache = [] From 6fb06f0b17b08528f53e7299d610b67ecf216b11 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 2 Jan 2023 12:50:50 +0100 Subject: [PATCH 192/392] untangle _preprocess_intertwining_relations --- src/sage/combinat/bijectionist.py | 121 +++++++++++++++--------------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 5fe36548a6f..564b2b3d287 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -479,7 +479,7 @@ class Bijectionist(SageObject): """ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], - pi_rho=tuple(), phi_psi=tuple(), + pi_rho=tuple(), phi_psi=tuple(), Q=None, elements_distributions=tuple(), value_restrictions=tuple(), solver=None, key=None): """ @@ -503,11 +503,11 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], # k arity of pi and rho # pi: A^k -> A, rho: Z^k -> Z # a_tuple in A^k - assert len(A) == len(set(A)), "A must have distinct items" - assert len(B) == len(set(B)), "B must have distinct items" + self._A = list(A) + self._B = list(B) + assert len(self._A) == len(set(self._A)), "A must have distinct items" + assert len(self._B) == len(set(self._B)), "B must have distinct items" self._bmilp = None - self._A = A - self._B = B self._sorter = {} self._sorter["A"] = lambda x: sorted(x, key=self._A.index) self._sorter["B"] = lambda x: sorted(x, key=self._B.index) @@ -2127,15 +2127,9 @@ def add_counter_example_constraint(s): def _preprocess_intertwining_relations(self): r""" - Make `self._P` be the finest set partition coarser than `self._P` - such that composing elements preserves blocks. - - OUTPUT: - - A list of triples `((\pi/\rho, p, (p_1,\dots,p_k))`, where - `p` is the block of `\rho(s(a_1),\dots, s(a_k))`, for any - `a_i\in p_i`, suitable for - :meth:`_BijectionistMILP.add_intertwining_relation_constraints`. + Make ``self._P`` be the finest set partition coarser + than ``self._P`` such that composing elements preserves + blocks. Suppose that `p_1`, `p_2` are blocks of `P`, and `a_1, a'_1 \in p_1` and `a_2, a'_2\in p_2`. Then, @@ -2153,14 +2147,17 @@ def _preprocess_intertwining_relations(self): In other words, `s(\pi(a_1,\dots,a_k))` only depends on the blocks of `a_1,\dots,a_k`. + In particular, if `P` consists only if singletons, this + method has no effect. + .. TODO:: create one test with one and one test with two - intertwining_relations + intertwining relations .. TODO:: - untangle side effect and return value if possible + it is not clear, whether this method makes sense EXAMPLES:: @@ -2170,27 +2167,29 @@ def _preprocess_intertwining_relations(self): sage: rho = lambda s1, s2: (s1 + s2) % 2 sage: bij.set_intertwining_relations((2, pi, rho)) sage: bij._preprocess_intertwining_relations() - {(0, 'a', ('a', 'a')), - (0, 'b', ('a', 'b')), - (0, 'b', ('b', 'a')), - (0, 'c', ('a', 'c')), - (0, 'c', ('b', 'b')), - (0, 'c', ('c', 'a')), - (0, 'd', ('a', 'd')), - (0, 'd', ('b', 'c')), - (0, 'd', ('c', 'b')), - (0, 'd', ('d', 'a'))} + sage: bij._P + {{'a'}, {'b'}, {'c'}, {'d'}} + + Let a group act on permutations:: + + sage: A = B = Permutations(3) + sage: bij = Bijectionist(A, B, lambda x: x[0]) + sage: bij.set_intertwining_relations((1, lambda pi: pi.reverse(), lambda z: z)) + sage: bij._preprocess_intertwining_relations() + sage: bij._P + {{[1, 2, 3]}, {[1, 3, 2]}, {[2, 1, 3]}, {[2, 3, 1]}, {[3, 1, 2]}, {[3, 2, 1]}} + """ + A = self._A + P = self._P images = defaultdict(set) # A^k -> A, a_1,...,a_k +-> {pi(a_1,...,a_k) for all pi} - origins_by_elements = [] # (pi/rho, pi(a_1,...,a_k), a_1,...,a_k) for composition_index, pi_rho in enumerate(self._pi_rho): - for a_tuple in itertools.product(*([self._A]*pi_rho.numargs)): + for a_tuple in itertools.product(*([A]*pi_rho.numargs)): if pi_rho.domain is not None and not pi_rho.domain(*a_tuple): continue a = pi_rho.pi(*a_tuple) - if a in self._A: + if a in A: images[a_tuple].add(a) - origins_by_elements.append((composition_index, a, a_tuple)) # merge blocks something_changed = True @@ -2200,27 +2199,20 @@ def _preprocess_intertwining_relations(self): # the blocks of the elements of the preimage updated_images = defaultdict(set) # (p_1,...,p_k) to {a_1,....} for a_tuple, image_set in images.items(): - representatives = tuple(self._P.find(a) for a in a_tuple) + representatives = tuple(P.find(a) for a in a_tuple) updated_images[representatives].update(image_set) # merge blocks for a_tuple, image_set in updated_images.items(): image = image_set.pop() while image_set: - self._P.union(image, image_set.pop()) + P.union(image, image_set.pop()) something_changed = True # we keep a representative image_set.add(image) images = updated_images - origins = set() - for composition_index, image, preimage in origins_by_elements: - origins.add((composition_index, - self._P.find(image), - tuple(self._P.find(a) for a in preimage))) - return origins - def solutions_iterator(self): r""" An iterator over all solutions of the problem. @@ -2573,7 +2565,7 @@ def __init__(self, bijectionist: Bijectionist): # p in _P and z an element of _posible_block_values[p]. # Thus, _P and _posible_block_values have to be fixed before # creating the MILP. - preimage_blocks = bijectionist._preprocess_intertwining_relations() + bijectionist._preprocess_intertwining_relations() bijectionist._compute_possible_block_values() self.milp = MixedIntegerLinearProgram(solver=bijectionist._solver) @@ -2591,7 +2583,7 @@ def __init__(self, bijectionist: Bijectionist): self.add_alpha_beta_constraints() self.add_distribution_constraints() self.add_pseudo_inverse_relation_constraints() - self.add_intertwining_relation_constraints(preimage_blocks) + self.add_intertwining_relation_constraints() if get_verbose() >= 2: self.show() @@ -2925,7 +2917,7 @@ def add_distribution_constraints(self): for a, z in zip(tA_sum, tZ_sum): self.milp.add_constraint(a == z, name=f"d: {a} == {z}") - def add_intertwining_relation_constraints(self, origins): + def add_intertwining_relation_constraints(self): r""" Add constraints corresponding to the given intertwining relations. @@ -2980,23 +2972,34 @@ def add_intertwining_relation_constraints(self, origins): sage: bmilp.solution(False) {'a': 0, 'b': 1, 'c': 0, 'd': 1} """ - for composition_index, image_block, preimage_blocks in origins: - pi_rho = self._bijectionist._pi_rho[composition_index] - # iterate over all possible value combinations of the origin blocks - for z_tuple in itertools.product(*[self._bijectionist._possible_block_values[p] - for p in preimage_blocks]): - rhs = 1 - pi_rho.numargs + sum(self._x[p_i, z_i] - for p_i, z_i in zip(preimage_blocks, z_tuple)) - z = pi_rho.rho(*z_tuple) - if z in self._bijectionist._possible_block_values[image_block]: - c = self._x[image_block, z] - rhs - if c.is_zero(): - continue - self.milp.add_constraint(c >= 0, - name=f"pi/rho({composition_index})") - else: - self.milp.add_constraint(rhs <= 0, - name=f"pi/rho({composition_index})") + A = self._bijectionist._A + tZ = self._bijectionist._possible_block_values + P = self._bijectionist._P + for composition_index, pi_rho in enumerate(self._bijectionist._pi_rho): + pi_blocks = set() + for a_tuple in itertools.product(*([A]*pi_rho.numargs)): + if pi_rho.domain is not None and not pi_rho.domain(*a_tuple): + continue + a = pi_rho.pi(*a_tuple) + if a in A: + p_tuple = tuple(P.find(a) for a in a_tuple) + p = P.find(a) + if (p_tuple, p) not in pi_blocks: + pi_blocks.add((p_tuple, p)) + for z_tuple in itertools.product(*[tZ[p] for p in p_tuple]): + rhs = (1 - pi_rho.numargs + + sum(self._x[p_i, z_i] + for p_i, z_i in zip(p_tuple, z_tuple))) + z = pi_rho.rho(*z_tuple) + if z in tZ[p]: + c = self._x[p, z] - rhs + if c.is_zero(): + continue + self.milp.add_constraint(c >= 0, + name=f"pi/rho({composition_index})") + else: + self.milp.add_constraint(rhs <= 0, + name=f"pi/rho({composition_index})") def add_pseudo_inverse_relation_constraints(self): r""" From 706056e5dbdaf049f56963d2cf67300267899142 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 2 Jan 2023 13:25:27 +0100 Subject: [PATCH 193/392] add possibility to require a homomesy --- src/sage/combinat/bijectionist.py | 64 ++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 564b2b3d287..106d81c4a49 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -370,7 +370,7 @@ from collections import namedtuple, defaultdict from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException from sage.rings.integer_ring import ZZ -from sage.combinat.set_partition import SetPartition +from sage.combinat.set_partition import SetPartition, SetPartitions from sage.sets.disjoint_set import DisjointSet from sage.structure.sage_object import SageObject from copy import copy @@ -534,6 +534,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], self.set_value_restrictions(*value_restrictions) self.set_distributions(*elements_distributions) self.set_pseudo_inverse_relation(*phi_psi) + self.set_homomesic(Q) self.set_intertwining_relations(*pi_rho) self.set_constant_blocks(P) @@ -1445,6 +1446,31 @@ def set_pseudo_inverse_relation(self, *phi_psi): self._bmilp = None self._phi_psi = phi_psi + def set_homomesic(self, Q): + """ + Assert that the average of `s` on each block of `Q` is + constant. + + INPUT: + + - ``Q``, a set partition of ``A``. + + EXAMPLES:: + + sage: A = B = [1,2,3] + sage: bij = Bijectionist(A, B, lambda b: b % 3) + sage: bij.set_homomesic([[1,2], [3]]) + sage: list(bij.solutions_iterator()) + [{1: 2, 2: 0, 3: 1}, {1: 0, 2: 2, 3: 1}] + + """ + self._bmilp = None + if Q is None: + self._Q = None + else: + self._Q = SetPartition(Q) + assert self._Q in SetPartitions(self._A), f"{Q} must be a set partition of A" + def _forced_constant_blocks(self): r""" Modify current partition into blocks to the coarsest possible @@ -2583,6 +2609,7 @@ def __init__(self, bijectionist: Bijectionist): self.add_alpha_beta_constraints() self.add_distribution_constraints() self.add_pseudo_inverse_relation_constraints() + self.add_homomesic_constraints() self.add_intertwining_relation_constraints() if get_verbose() >= 2: self.show() @@ -3059,6 +3086,41 @@ def add_pseudo_inverse_relation_constraints(self): else: self.milp.add_constraint(self._x[p, z] == 0, name=f"i: s({p})!={z}") + def add_homomesic_constraints(self): + r""" + Add constraints enforcing that `s` has constant average + on the blocks of `Q`. + + We do this by adding + + .. MATH:: + + \frac{1}{|q|}\sum_{a\in q} \sum_z z x_{p(a), z} = + \frac{1}{|q_0|}\sum_{a\in q_0} \sum_z z x_{p(a), z}, + + for `q\in Q`, where `q_0` is some fixed block of `Q`. + + EXAMPLES:: + + sage: A = B = [1,2,3] + sage: bij = Bijectionist(A, B, lambda b: b % 3) + sage: bij.set_homomesic([[1,2], [3]]) # indirect doctest + sage: list(bij.solutions_iterator()) + [{1: 2, 2: 0, 3: 1}, {1: 0, 2: 2, 3: 1}] + """ + Q = self._bijectionist._Q + if Q is None: + return + P = self._bijectionist._P + tZ = self._bijectionist._possible_block_values + + def sum_q(q): + return sum(sum(z*self._x[P.find(a), z] for z in tZ[P.find(a)]) + for a in q) + q0 = Q[0] + v0 = sum_q(q0) + for q in Q[1:]: + self.milp.add_constraint(len(q0)*sum_q(q) == len(q)*v0, name=f"h: ({q})~({q0})") def _invert_dict(d): """ From 65b0c4adfff850b54c98695d8c027dc20a510ff6 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 3 Jan 2023 00:58:42 +0100 Subject: [PATCH 194/392] preserve the cache of solutions after computing the optimal constant blocks --- src/sage/combinat/bijectionist.py | 70 +++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 106d81c4a49..1b5adffbf06 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -15,11 +15,14 @@ :widths: 30, 70 :delim: | - :meth:`~Bijectionist.set_intertwining_relations` | Declare that the statistic intertwines with other maps. - :meth:`~Bijectionist.set_constant_blocks` | Declare that the statistic is constant on some sets. :meth:`~Bijectionist.set_statistics` | Declare statistics that are preserved by the bijection. :meth:`~Bijectionist.set_value_restrictions` | Restrict the values of the statistic on an element. + :meth:`~Bijectionist.set_constant_blocks` | Declare that the statistic is constant on some sets. :meth:`~Bijectionist.set_distributions` | Restrict the distribution of values of the statistic on some elements. + :meth:`~Bijectionist.set_intertwining_relations` | Declare that the statistic intertwines with other maps. + :meth:`~Bijectionist.set_pseudo_inverse_relation` | Declare that the statistic satisfies a certain relation. + :meth:`~Bijectionist.set_homomesic` | Declare that the statistic is homomesic with respect to a given set partition. + :meth:`~Bijectionist.statistics_table` | Print a table collecting information on the given statistics. :meth:`~Bijectionist.statistics_fibers` | Collect elements with the same statistics. @@ -1168,7 +1171,7 @@ def set_distributions(self, *elements_distributions): [([[]], [0]), ([[1]], [1]), ([[1, 2, 3]], [3]), - ([[2, 1, 3]], [2]), + ([[2, 3, 1]], [2]), ([[1, 2], [2, 1]], [1, 2])] TESTS: @@ -1694,7 +1697,10 @@ def merge_until_split(): while True: solution = merge_until_split() if solution is None: - self.set_constant_blocks(tmp_P) + self._P = tmp_P + # recreate the MILP + self._bmilp = _BijectionistMILP(self, + self._bmilp._solution_cache) return updated_multiple_preimages = defaultdict(list) @@ -2040,7 +2046,7 @@ def minimal_subdistributions_blocks_iterator(self): sage: bij.constant_blocks(optimal=True) {{'a', 'b'}} sage: list(bij.minimal_subdistributions_blocks_iterator()) - [(['a', 'a', 'c', 'd', 'e'], [1, 1, 2, 2, 3])] + [(['b', 'b', 'c', 'd', 'e'], [1, 1, 2, 2, 3])] An example with overlapping minimal subdistributions:: @@ -2561,7 +2567,7 @@ class is used to manage the MILP, add constraints, solve the problem and check for uniqueness of solution values. """ - def __init__(self, bijectionist: Bijectionist): + def __init__(self, bijectionist: Bijectionist, solutions=None): r""" Initialize the mixed integer linear program. @@ -2569,6 +2575,11 @@ def __init__(self, bijectionist: Bijectionist): - ``bijectionist`` -- an instance of :class:`Bijectionist`. + - ``solutions`` (optional, default: ``None``) -- a list of + solutions of the problem, each provided as a dictionary + mapping `(a, z)` to a Boolean, such that at least one + element from each block of `P` appears as `a`. + .. TODO:: it might be cleaner not to pass the full bijectionist @@ -2581,6 +2592,7 @@ def __init__(self, bijectionist: Bijectionist): sage: from sage.combinat.bijectionist import _BijectionistMILP sage: _BijectionistMILP(bij) + """ # the attributes of the bijectionist class we actually use: # _possible_block_values @@ -2596,15 +2608,15 @@ def __init__(self, bijectionist: Bijectionist): self.milp = MixedIntegerLinearProgram(solver=bijectionist._solver) self.milp.set_objective(None) - self._solution_cache = [] indices = [(p, z) for p, tZ in bijectionist._possible_block_values.items() for z in tZ] self._x = self.milp.new_variable(binary=True, indices=indices) - for p in _disjoint_set_roots(bijectionist._P): - self.milp.add_constraint(sum(self._x[p, z] - for z in bijectionist._possible_block_values[p]) == 1, + tZ = bijectionist._possible_block_values + P = bijectionist._P + for p in _disjoint_set_roots(P): + self.milp.add_constraint(sum(self._x[p, z] for z in tZ[p]) == 1, name=f"block {p}"[:50]) self.add_alpha_beta_constraints() self.add_distribution_constraints() @@ -2614,6 +2626,12 @@ def __init__(self, bijectionist: Bijectionist): if get_verbose() >= 2: self.show() + self._solution_cache = [] + if solutions is not None: + for solution in solutions: + self._add_solution({(P.find(a), z): value + for (a, z), value in solution.items()}) + def show(self, variables=True): r""" Print the constraints and variables of the MILP together @@ -2744,8 +2762,9 @@ def solve(self, additional_constraints, solution_index=0): return_indices=True)) try: self.milp.solve() - self.last_solution = self.milp.get_values(self._x, - convert=bool, tolerance=0.1) + # moving this out of the try...finally block breaks SCIP + solution = self.milp.get_values(self._x, + convert=bool, tolerance=0.1) finally: b = self.milp.get_backend() if hasattr(b, "_get_model"): @@ -2754,16 +2773,23 @@ def solve(self, additional_constraints, solution_index=0): m.freeTransform() self.milp.remove_constraints(new_indices) - # veto the solution, by requiring that not all variables with - # value 1 have value 1 in the new MILP + self._add_solution(solution) + + def _add_solution(self, solution): + r""" + Add the ``last_solution`` to the cache and an + appropriate constraint to the MILP. + + """ active_vars = [self._x[p, z] for p in _disjoint_set_roots(self._bijectionist._P) for z in self._bijectionist._possible_block_values[p] - if self.last_solution[(p, z)]] + if solution[(p, z)]] self.milp.add_constraint(sum(active_vars) <= len(active_vars) - 1, name="veto") + self._solution_cache.append(solution) + self.last_solution = solution - self._solution_cache.append(self.last_solution) def _is_solution(self, constraint, values): r""" @@ -2833,9 +2859,11 @@ def solution(self, on_blocks): {'a': 0, 'c': 0} """ + P = self._bijectionist._P + tZ = self._bijectionist._possible_block_values mapping = {} # A -> Z or P -> Z, a +-> s(a) - for p, block in self._bijectionist._P.root_to_elements_dict().items(): - for z in self._bijectionist._possible_block_values[p]: + for p, block in P.root_to_elements_dict().items(): + for z in tZ[p]: if self.last_solution[p, z] == 1: if on_blocks: mapping[p] = z @@ -3285,9 +3313,9 @@ def _non_copying_intersection(sets): ([[3, 1, 2]], [3]) ([[4, 1, 2, 3]], [4]) ([[5, 1, 2, 3, 4]], [5]) - ([[2, 1, 4, 5, 3], [2, 3, 5, 1, 4], [2, 4, 1, 5, 3], [2, 4, 5, 1, 3]], [2, 3, 3, 3]) - ([[2, 1, 5, 3, 4], [2, 5, 1, 3, 4], [3, 1, 5, 2, 4], [3, 5, 1, 2, 4]], [3, 3, 4, 4]) - ([[1, 3, 2, 5, 4], [1, 3, 5, 2, 4], [1, 4, 2, 5, 3], [1, 4, 5, 2, 3], [1, 4, 5, 3, 2], [1, 5, 4, 2, 3], [1, 5, 4, 3, 2]], [2, 2, 3, 3, 3, 3, 3]) + ([[2, 3, 1, 5, 4], [2, 4, 5, 3, 1], [2, 5, 4, 1, 3], [3, 4, 1, 5, 2]], [2, 3, 3, 3]) + ([[3, 1, 2, 5, 4], [4, 1, 2, 5, 3], [3, 5, 2, 1, 4], [4, 1, 5, 2, 3]], [3, 3, 4, 4]) + ([[2, 1, 3, 5, 4], [2, 4, 1, 3, 5], [2, 5, 3, 1, 4], [3, 4, 1, 2, 5], [3, 1, 5, 4, 2], [2, 5, 1, 4, 3], [2, 1, 5, 4, 3]], [2, 2, 3, 3, 3, 3, 3]) sage: l = list(bij.solutions_iterator()); len(l) # not tested -- (17 seconds with SCIP on AMD Ryzen 5 PRO 3500U w/ Radeon Vega Mobile Gfx) 504 From 21cb54f6e116d3b89db4a4d3789c904cc1ce5177 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 3 Jan 2023 13:44:06 +0100 Subject: [PATCH 195/392] make _BijectionistMILP.solution the only entrypoint --- src/sage/combinat/bijectionist.py | 236 +++++++++++++----------------- 1 file changed, 103 insertions(+), 133 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 1b5adffbf06..1544adf786a 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -600,7 +600,7 @@ def set_constant_blocks(self, P): sage: bij.constant_blocks(optimal=True) Traceback (most recent call last): ... - MIPSolverException: ... + sage.numerical.mip.MIPSolverException... """ self._bmilp = None @@ -1650,9 +1650,10 @@ def _forced_constant_blocks(self): """ if self._bmilp is None: self._bmilp = _BijectionistMILP(self) - self._bmilp.solve([]) - solution = self._bmilp.solution(True) + solution = self._bmilp.solution(True, []) + if solution is None: + raise MIPSolverException # multiple_preimages[tZ] are the blocks p which have the same # value tZ[i] in the i-th known solution multiple_preimages = {(z,): tP @@ -1670,11 +1671,7 @@ def different_values(p1, p2): tmp_constraints = [self._bmilp._x[p1, z] + self._bmilp._x[p2, z] <= 1 for z in self._possible_block_values[p1] if z in self._possible_block_values[p2]] - try: - self._bmilp.solve(tmp_constraints) - except MIPSolverException: - return - return self._bmilp.solution(True) + return self._bmilp.solution(True, tmp_constraints) # try to find a pair of blocks having the same value on all # known solutions, and a solution such that the values are @@ -1720,14 +1717,35 @@ def possible_values(self, p=None, optimal=False): an element of a block of `P`, or a list of these - ``optimal`` (optional, default: ``False``) -- whether or - not to compute the minimal possible set of statistic values, - throws a MIPSolverException if no solution is found. + not to compute the minimal possible set of statistic values. .. NOTE:: computing the minimal possible set of statistic values may be computationally expensive. + .. TODO:: + + currently, calling this method with ``optimal=True`` does + not update the internal dictionary, because this would + interfere with the variables of the MILP. + + EXAMPLES:: + + sage: A = B = ["a", "b", "c", "d"] + sage: tau = {"a": 1, "b": 1, "c": 1, "d": 2}.get + sage: bij = Bijectionist(A, B, tau) + sage: bij.set_constant_blocks([["a", "b"]]) + sage: bij.possible_values(A) + {'a': {1, 2}, 'b': {1, 2}, 'c': {1, 2}, 'd': {1, 2}} + sage: bij.possible_values(A, optimal=True) + {'a': {1}, 'b': {1}, 'c': {1, 2}, 'd': {1, 2}} + + The internal dictionary is not updated:: + + sage: bij.possible_values(A) + {'a': {1, 2}, 'b': {1, 2}, 'c': {1, 2}, 'd': {1, 2}} + TESTS:: sage: A = B = ["a", "b", "c", "d"] @@ -1746,17 +1764,7 @@ def possible_values(self, p=None, optimal=False): sage: bij.possible_values(p=[["a", "b"], ["c"]]) {'a': {1, 2}, 'b': {1, 2}, 'c': {1, 2}} - Test optimal:: - - sage: bij.possible_values(p=["a", "c"], optimal=True) - {'a': {1, 2}, 'b': {1, 2}, 'c': {1, 2}} - - Verify by listing all solutions:: - - sage: sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))) - [{'a': 1, 'b': 1, 'c': 2, 'd': 2}, {'a': 2, 'b': 2, 'c': 1, 'd': 1}] - - Test if MIPSolverException is thrown:: + Test an unfeasible problem:: sage: A = B = list('ab') sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) @@ -1764,40 +1772,7 @@ def possible_values(self, p=None, optimal=False): sage: bij.possible_values(p="a") {'a': {0, 1}, 'b': {0, 1}} sage: bij.possible_values(p="a", optimal=True) - Traceback (most recent call last): - ... - sage.numerical.mip.MIPSolverException: ... - - Another example:: - - sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] - sage: tau = Permutation.longest_increasing_subsequence_length - sage: bij = Bijectionist(A, B, tau) - sage: alpha = lambda p: p(1) if len(p) > 0 else 0 - sage: beta = lambda p: p(1) if len(p) > 0 else 0 - sage: bij.set_statistics((alpha, beta), (len, len)) - sage: for sol in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): - ....: print(sol) - {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} - {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 2, [1, 3, 2]: 3, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} - {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 1, [3, 2, 1]: 2} - {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 1, [1, 2, 3]: 3, [1, 3, 2]: 2, [2, 1, 3]: 2, [2, 3, 1]: 2, [3, 1, 2]: 2, [3, 2, 1]: 1} - sage: bij.possible_values(p=[Permutation([1]), Permutation([1, 2, 3]), Permutation([3, 1, 2])], optimal=True) - {[1]: {1}, [1, 2, 3]: {2, 3}, [3, 1, 2]: {1, 2}} - - Another example:: - - sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] - sage: tau = lambda D: D.number_of_touch_points() - sage: bij = Bijectionist(A, B, tau) - sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) - sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): - ....: print(solution) - {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 1, [1, 1, 0, 0]: 2} - {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} - sage: bij.possible_values(p=[DyckWord([]), DyckWord([1, 0]), DyckWord([1, 0, 1, 0]), DyckWord([1, 1, 0, 0])], optimal=True) - {[]: {0}, [1, 0]: {1}, [1, 0, 1, 0]: {1, 2}, [1, 1, 0, 0]: {1, 2}} - + {'a': set(), 'b': set()} """ # convert input to set of block representatives blocks = set() @@ -1812,32 +1787,25 @@ def possible_values(self, p=None, optimal=False): blocks.add(self._P.find(p2)) if optimal: - # generate initial solution, solution dict and add solution if self._bmilp is None: self._bmilp = _BijectionistMILP(self) - self._bmilp.solve([]) solutions = defaultdict(set) - solution = self._bmilp.solution(False) - for p, z in solution.items(): - solutions[p].add(z) - - # iterate through blocks and generate all values - for p in blocks: - tmp_constraints = [self._bmilp._x[p, z] == 0 - for z in solutions[p]] - while True: - try: - # problem has a solution, so new value was found - self._bmilp.solve(tmp_constraints) - except MIPSolverException: - # no solution, so all possible values have been found - break - solution = self._bmilp.solution(False) - for p0, z in solution.items(): - solutions[p0].add(z) - # veto new value and try again - tmp_constraints.append(self._bmilp._x[p, solution[p]] == 0) + solution = self._bmilp.solution(True, []) + if solution is not None: + for p, z in solution.items(): + solutions[p].add(z) + for p in blocks: + tmp_constraints = [self._bmilp._x[p, z] == 0 + for z in solutions[p]] + while True: + solution = self._bmilp.solution(True, tmp_constraints) + if solution is None: + break + for p0, z in solution.items(): + solutions[p0].add(z) + # veto new value and try again + tmp_constraints.append(self._bmilp._x[p, solution[p]] == 0) # create dictionary to return possible_values = {} @@ -1906,13 +1874,9 @@ def minimal_subdistributions_iterator(self): minimal_subdistribution.set_objective(sum(D[a] for a in self._A)) minimal_subdistribution.add_constraint(sum(D[a] for a in self._A) >= 1) - try: - if self._bmilp is None: - self._bmilp = _BijectionistMILP(self) - self._bmilp.solve([]) - except MIPSolverException: - return - s = self._bmilp.solution(False) + if self._bmilp is None: + self._bmilp = _BijectionistMILP(self) + s = self._bmilp.solution(False, []) while True: for v in self._Z: minimal_subdistribution.add_constraint(sum(D[a] for a in self._A if s[a] == v) == V[v]) @@ -1986,12 +1950,9 @@ def _find_counter_example(self, P, s0, d, on_blocks): # z_in_d_count, because, if the distributions are # different, one such z must exist tmp_constraints = [z_in_d <= z_in_d_count - 1] - try: - bmilp.solve(tmp_constraints) - return bmilp.solution(on_blocks) - except MIPSolverException: - pass - return + solution = bmilp.solution(on_blocks, tmp_constraints) + if solution is not None: + return solution def minimal_subdistributions_blocks_iterator(self): r""" @@ -2129,12 +2090,9 @@ def add_counter_example_constraint(s): if s[p] == v) == V[v]) if self._bmilp is None: - try: - self._bmilp = _BijectionistMILP(self) - self._bmilp.solve([]) - except MIPSolverException: - return - s = self._bmilp.solution(True) + self._bmilp = _BijectionistMILP(self) + + s = self._bmilp.solution(True, []) add_counter_example_constraint(s) while True: try: @@ -2542,19 +2500,15 @@ def solutions_iterator(self): """ if self._bmilp is None: - try: - self._bmilp = _BijectionistMILP(self) - except MIPSolverException: - return + self._bmilp = _BijectionistMILP(self) bmilp = self._bmilp - solution_index = 0 + index = 0 while True: - try: - bmilp.solve([], solution_index) - except MIPSolverException: + solution = bmilp.solution(False, [], index) + if solution is None: return - yield bmilp.solution(False) - solution_index += 1 + index += 1 + yield solution if get_verbose() >= 2: print("after vetoing") bmilp.show(variables=False) @@ -2695,7 +2649,7 @@ def show(self, variables=True): print(f" {v}: " + "".join([f"s({a}) = " for a in P[p]]) + f"{z}") - def solve(self, additional_constraints, solution_index=0): + def _solve(self, additional_constraints, solution_index=0): r""" Find a solution satisfying the given additional constraints. @@ -2721,25 +2675,19 @@ def solve(self, additional_constraints, solution_index=0): Generate a solution:: - sage: bmilp.solve([]) - sage: bmilp.solution(False) + sage: bmilp.solution(False, []) # indirect doctest {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} Generating a new solution that also maps `1010` to `2` fails: - sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 2] == 1], solution_index=1) - Traceback (most recent call last): - ... - MIPSolverException: ... no feasible solution + sage: bmilp.solution(False, [bmilp._x[DyckWord([1,0,1,0]), 2] == 1], index=1) However, searching for a cached solution succeeds, for inequalities and equalities:: - sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 2] >= 1]) - sage: bmilp.solution(False) + sage: bmilp.solution(False, [bmilp._x[DyckWord([1,0,1,0]), 2] >= 1]) {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} - sage: bmilp.solve([bmilp._x[DyckWord([1,0,1,0]), 1] == 0]) - sage: bmilp.solution(False) + sage: bmilp.solution(False, [bmilp._x[DyckWord([1,0,1,0]), 1] == 0]) {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} sage: len(bmilp._solution_cache) @@ -2778,8 +2726,32 @@ def solve(self, additional_constraints, solution_index=0): def _add_solution(self, solution): r""" Add the ``last_solution`` to the cache and an - appropriate constraint to the MILP. + appropriate veto constraint to the MILP. + + INPUT: + - ``solution``, a dictionary from the indices of the MILP to + Boolean. + + EXAMPLES:: + + sage: A = B = ["a", "b"] + sage: bij = Bijectionist(A, B) + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) + sage: bmilp._add_solution({(a, b): a == b for a in A for b in B}) + sage: bmilp.show() # random + Constraints are: + block a: 1 <= x_0 + x_1 <= 1 + block b: 1 <= x_2 + x_3 <= 1 + statistics: 1 <= x_1 + x_3 <= 1 + statistics: 1 <= x_0 + x_2 <= 1 + veto: x_1 + x_2 <= 1 + Variables are: + x_0: s(a) = b + x_1: s(a) = a + x_2: s(b) = b + x_3: s(b) = a """ active_vars = [self._x[p, z] for p in _disjoint_set_roots(self._bijectionist._P) @@ -2790,7 +2762,6 @@ def _add_solution(self, solution): self._solution_cache.append(solution) self.last_solution = solution - def _is_solution(self, constraint, values): r""" Evaluate the given function at the given values. @@ -2810,7 +2781,7 @@ def _is_solution(self, constraint, values): sage: bij = Bijectionist(A, B) sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) - sage: _ = bmilp.solve([]) + sage: _ = bmilp._solve([]) sage: f = bmilp._x["a", "a"] + bmilp._x["b", "a"] >= bmilp._x["b", "b"] + 1 sage: v = {('a', 'a'): 1, ('a', 'b'): 0, ('b', 'a'): 1, ('b', 'b'): 1} sage: bmilp._is_solution(f, v) @@ -2836,15 +2807,13 @@ def _is_solution(self, constraint, values): return False return True - def solution(self, on_blocks): + def solution(self, on_blocks, constraints, index=0): r""" - Return the current solution as a dictionary from `A` (or - `P`) to `Z`. + Return a solution as a dictionary from `A` (or `P`) to + `Z`, or ``None`` INPUT: - - ``bmilp``, a :class:`_BijectionistMILP`. - - ``on_blocks``, whether to return the solution on blocks or on all elements @@ -2855,10 +2824,14 @@ def solution(self, on_blocks): sage: bij.set_constant_blocks([["a", "b"]]) sage: next(bij.solutions_iterator()) {'a': 0, 'b': 0, 'c': 0} - sage: bij._bmilp.solution(True) + sage: bij._bmilp.solution(True, []) {'a': 0, 'c': 0} - """ + try: + self._solve(constraints, index) + except MIPSolverException: + return + P = self._bijectionist._P tZ = self._bijectionist._possible_block_values mapping = {} # A -> Z or P -> Z, a +-> s(a) @@ -2894,8 +2867,7 @@ def add_alpha_beta_constraints(self): sage: bij.set_statistics((len, len)) sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) # indirect doctest - sage: bmilp.solve([]) - sage: bmilp.solution(False) + sage: bmilp.solution(False, []) {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 2} """ W = self._bijectionist._W @@ -2946,8 +2918,7 @@ def add_distribution_constraints(self): sage: bij.set_distributions(([Permutation([1, 2, 3]), Permutation([1, 3, 2])], [1, 3])) sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) # indirect doctest - sage: _ = bmilp.solve([]) - sage: bmilp.solution(False) + sage: bmilp.solution(False, []) {[1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 2, @@ -3023,8 +2994,7 @@ def add_intertwining_relation_constraints(self): sage: bij.set_intertwining_relations((2, pi, rho)) sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) # indirect doctest - sage: _ = bmilp.solve([]) - sage: bmilp.solution(False) + sage: bmilp.solution(False, []) {'a': 0, 'b': 1, 'c': 0, 'd': 1} """ A = self._bijectionist._A From a573bb4cdb914a689e20eaaa51c43860f1e48a7d Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 3 Jan 2023 20:50:18 +0100 Subject: [PATCH 196/392] remove unnecessary calls to list in doctests --- src/sage/combinat/bijectionist.py | 33 ++++++++++++++----------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 1544adf786a..1ebb54ab0dd 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -42,8 +42,7 @@ `(s, wex, fix) \sim (llis, des, adj)`:: sage: N = 3 - sage: As = [list(Permutations(n)) for n in range(N+1)] - sage: A = B = sum(As, []) + sage: A = B = [pi for n in range(N+1) for pi in Permutations(n)] sage: alpha1 = lambda p: len(p.weak_excedences()) sage: alpha2 = lambda p: len(p.fixed_points()) sage: beta1 = lambda p: len(p.descents(final_descent=True)) if p else 0 @@ -106,7 +105,7 @@ +-----------+---+--------+--------+--------+ sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition - sage: bij.set_constant_blocks(sum([orbit_decomposition(A, rotate_permutation) for A in As], [])) + sage: bij.set_constant_blocks(orbit_decomposition(A, rotate_permutation)) sage: bij.constant_blocks() {{[1, 3, 2], [2, 1, 3], [3, 2, 1]}} sage: next(bij.solutions_iterator()) @@ -124,9 +123,9 @@ There is no rotation invariant statistic on non crossing set partitions which is equidistributed with the Strahler number on ordered trees:: - sage: N = 8; As = [[SetPartition(d.to_noncrossing_partition()) for d in DyckWords(n)] for n in range(N)] - sage: A = sum(As, []) - sage: B = sum([list(OrderedTrees(n)) for n in range(1, N+1)], []) + sage: N = 8; + sage: A = [SetPartition(d.to_noncrossing_partition()) for n in range(N) for d in DyckWords(n)] + sage: B = [t for n in range(1, N+1) for t in OrderedTrees(n)] sage: theta = lambda m: SetPartition([[i % m.size() + 1 for i in b] for b in m]) The following code is equivalent to ``tau = findstat(397)``:: @@ -144,7 +143,7 @@ sage: bij = Bijectionist(A, B, tau) sage: bij.set_statistics((lambda a: a.size(), lambda b: b.node_number()-1)) sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition - sage: bij.set_constant_blocks(sum([orbit_decomposition(A_n, theta) for A_n in As], [])) + sage: bij.set_constant_blocks(orbit_decomposition(A, theta)) sage: list(bij.solutions_iterator()) [] @@ -279,7 +278,7 @@ Constant blocks:: - sage: A = B = list('abcd') + sage: A = B = 'abcd' sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] sage: rho = lambda s1, s2: (s1 + s2) % 2 sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2, P=[['a', 'c']], pi_rho=((2, pi, rho),)) @@ -492,7 +491,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], Check that large input sets are handled well:: - sage: A = B = list(range(20000)) + sage: A = B = range(20000) sage: bij = Bijectionist(A, B) # long time """ # glossary of standard letters: @@ -569,7 +568,7 @@ def set_constant_blocks(self, P): current partition can be reviewed using :meth:`constant_blocks`:: - sage: A = B = list('abcd') + sage: A = B = 'abcd' sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) sage: bij.constant_blocks() {} @@ -1766,7 +1765,7 @@ def possible_values(self, p=None, optimal=False): Test an unfeasible problem:: - sage: A = B = list('ab') + sage: A = B = 'ab' sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) sage: bij.set_constant_blocks([['a', 'b']]) sage: bij.possible_values(p="a") @@ -2147,11 +2146,11 @@ def _preprocess_intertwining_relations(self): .. TODO:: - it is not clear, whether this method makes sense + it is not clear whether this method makes sense EXAMPLES:: - sage: A = B = list('abcd') + sage: A = B = 'abcd' sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] sage: rho = lambda s1, s2: (s1 + s2) % 2 @@ -2273,7 +2272,7 @@ def solutions_iterator(self): EXAMPLES:: - sage: A = B = list('abc') + sage: A = B = 'abc' sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2, solver="GLPK") sage: next(bij.solutions_iterator()) {'a': 0, 'b': 1, 'c': 0} @@ -2362,8 +2361,7 @@ def solutions_iterator(self): veto: x_0 + x_1 + x_3 + x_4 + x_6 + x_10 + x_14 <= 6 veto: x_0 + x_1 + x_2 + x_5 + x_6 + x_10 + x_14 <= 6 - Changing or re-setting problem parameters clears the internal cache and - prints even more information:: + Changing or re-setting problem parameters clears the internal cache:: sage: bij.set_constant_blocks(P) sage: _ = list(bij.solutions_iterator()) @@ -2519,7 +2517,6 @@ class _BijectionistMILP(): Wrapper class for the MixedIntegerLinearProgram (MILP). This class is used to manage the MILP, add constraints, solve the problem and check for uniqueness of solution values. - """ def __init__(self, bijectionist: Bijectionist, solutions=None): r""" @@ -2987,7 +2984,7 @@ def add_intertwining_relation_constraints(self): EXAMPLES:: - sage: A = B = list('abcd') + sage: A = B = 'abcd' sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] sage: rho = lambda s1, s2: (s1 + s2) % 2 From 686826127927622f10e97b8f0189ed46d3e55f89 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Tue, 3 Jan 2023 20:50:49 +0100 Subject: [PATCH 197/392] move iterator over all solutions to _BijectionistMILP --- src/sage/combinat/bijectionist.py | 38 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 1ebb54ab0dd..6f4833014af 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2499,17 +2499,7 @@ def solutions_iterator(self): """ if self._bmilp is None: self._bmilp = _BijectionistMILP(self) - bmilp = self._bmilp - index = 0 - while True: - solution = bmilp.solution(False, [], index) - if solution is None: - return - index += 1 - yield solution - if get_verbose() >= 2: - print("after vetoing") - bmilp.show(variables=False) + yield from self._bmilp class _BijectionistMILP(): @@ -2843,6 +2833,32 @@ def solution(self, on_blocks, constraints, index=0): break return mapping + def __iter__(self): + r""" + Iterate over all solutions of the MILP. + + EXAMPLES:: + + sage: A = B = 'abc' + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2, solver="GLPK") + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: list(_BijectionistMILP(bij)) + [{'a': 0, 'b': 1, 'c': 0}, + {'a': 1, 'b': 0, 'c': 0}, + {'a': 0, 'b': 0, 'c': 1}] + + """ + index = 0 + while True: + solution = self.solution(False, [], index) + if solution is None: + return + index += 1 + yield solution + if get_verbose() >= 2: + print("after vetoing") + self.show(variables=False) + def add_alpha_beta_constraints(self): r""" Add constraints enforcing that `(alpha, s)` is equidistributed From 61e97bc64d9742026ad8486784b25ea4c730b4f0 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 4 Jan 2023 01:17:43 +0100 Subject: [PATCH 198/392] merge _solve, solution and __iter__ --- src/sage/combinat/bijectionist.py | 321 +++++++++++------------------- 1 file changed, 120 insertions(+), 201 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 6f4833014af..279ce091c3a 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -599,7 +599,7 @@ def set_constant_blocks(self, P): sage: bij.constant_blocks(optimal=True) Traceback (most recent call last): ... - sage.numerical.mip.MIPSolverException... + StopIteration """ self._bmilp = None @@ -1650,9 +1650,7 @@ def _forced_constant_blocks(self): if self._bmilp is None: self._bmilp = _BijectionistMILP(self) - solution = self._bmilp.solution(True, []) - if solution is None: - raise MIPSolverException + solution = next(self._bmilp.solutions_iterator(True, [])) # multiple_preimages[tZ] are the blocks p which have the same # value tZ[i] in the i-th known solution multiple_preimages = {(z,): tP @@ -1670,7 +1668,7 @@ def different_values(p1, p2): tmp_constraints = [self._bmilp._x[p1, z] + self._bmilp._x[p2, z] <= 1 for z in self._possible_block_values[p1] if z in self._possible_block_values[p2]] - return self._bmilp.solution(True, tmp_constraints) + return next(self._bmilp.solutions_iterator(True, tmp_constraints)) # try to find a pair of blocks having the same value on all # known solutions, and a solution such that the values are @@ -1680,8 +1678,9 @@ def merge_until_split(): tP = multiple_preimages[tZ] for i2 in range(len(tP)-1, -1, -1): for i1 in range(i2): - solution = different_values(tP[i1], tP[i2]) - if solution is None: + try: + solution = different_values(tP[i1], tP[i2]) + except StopIteration: tmp_P.union(tP[i1], tP[i2]) if len(multiple_preimages[tZ]) == 2: del multiple_preimages[tZ] @@ -1788,23 +1787,26 @@ def possible_values(self, p=None, optimal=False): if optimal: if self._bmilp is None: self._bmilp = _BijectionistMILP(self) - + bmilp = self._bmilp solutions = defaultdict(set) - solution = self._bmilp.solution(True, []) - if solution is not None: + try: + solution = next(bmilp.solutions_iterator(True, [])) + except StopIteration: + pass + else: for p, z in solution.items(): solutions[p].add(z) for p in blocks: - tmp_constraints = [self._bmilp._x[p, z] == 0 - for z in solutions[p]] + tmp_constraints = [bmilp._x[p, z] == 0 for z in solutions[p]] while True: - solution = self._bmilp.solution(True, tmp_constraints) - if solution is None: + try: + solution = next(bmilp.solutions_iterator(True, tmp_constraints)) + except StopIteration: break for p0, z in solution.items(): solutions[p0].add(z) # veto new value and try again - tmp_constraints.append(self._bmilp._x[p, solution[p]] == 0) + tmp_constraints.append(bmilp._x[p, solution[p]] == 0) # create dictionary to return possible_values = {} @@ -1875,7 +1877,7 @@ def minimal_subdistributions_iterator(self): if self._bmilp is None: self._bmilp = _BijectionistMILP(self) - s = self._bmilp.solution(False, []) + s = next(self._bmilp.solutions_iterator(False, [])) while True: for v in self._Z: minimal_subdistribution.add_constraint(sum(D[a] for a in self._A if s[a] == v) == V[v]) @@ -1949,9 +1951,11 @@ def _find_counter_example(self, P, s0, d, on_blocks): # z_in_d_count, because, if the distributions are # different, one such z must exist tmp_constraints = [z_in_d <= z_in_d_count - 1] - solution = bmilp.solution(on_blocks, tmp_constraints) - if solution is not None: + try: + solution = next(bmilp.solutions_iterator(on_blocks, tmp_constraints)) return solution + except StopIteration: + pass def minimal_subdistributions_blocks_iterator(self): r""" @@ -2091,7 +2095,7 @@ def add_counter_example_constraint(s): if self._bmilp is None: self._bmilp = _BijectionistMILP(self) - s = self._bmilp.solution(True, []) + s = next(self._bmilp.solutions_iterator(True, [])) add_counter_example_constraint(s) while True: try: @@ -2310,59 +2314,10 @@ def solutions_iterator(self): {[]: 0, [1]: 0, [1, 2]: 1, [2, 1]: 0, [1, 2, 3]: 0, [1, 3, 2]: 1, [2, 1, 3]: 1, [3, 2, 1]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2} {[]: 0, [1]: 0, [1, 2]: 0, [2, 1]: 1, [1, 2, 3]: 0, [1, 3, 2]: 1, [2, 1, 3]: 1, [3, 2, 1]: 1, [2, 3, 1]: 2, [3, 1, 2]: 2} - Setting the verbosity prints the MILP which is solved:: + Changing or re-setting problem parameters clears the internal + cache. Setting the verbosity prints the MILP which is solved.:: sage: set_verbose(2) - sage: _ = list(bij.solutions_iterator()) - after vetoing - Constraints are: - block []: 1 <= x_0 <= 1 - block [1]: 1 <= x_1 <= 1 - block [1, 2]: 1 <= x_2 + x_3 <= 1 - block [2, 1]: 1 <= x_4 + x_5 <= 1 - block [1, 2, 3]: 1 <= x_6 + x_7 + x_8 <= 1 - block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 - block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 - statistics: 1 <= x_0 <= 1 - statistics: 0 <= <= 0 - statistics: 0 <= <= 0 - statistics: 1 <= x_1 <= 1 - statistics: 0 <= <= 0 - statistics: 0 <= <= 0 - statistics: 1 <= x_2 + x_4 <= 1 - statistics: 1 <= x_3 + x_5 <= 1 - statistics: 0 <= <= 0 - statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 - statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 - statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 - veto: x_0 + x_1 + x_3 + x_4 + x_6 + x_10 + x_14 <= 6 - veto: x_0 + x_1 + x_2 + x_5 + x_6 + x_10 + x_14 <= 6 - after vetoing - Constraints are: - block []: 1 <= x_0 <= 1 - block [1]: 1 <= x_1 <= 1 - block [1, 2]: 1 <= x_2 + x_3 <= 1 - block [2, 1]: 1 <= x_4 + x_5 <= 1 - block [1, 2, 3]: 1 <= x_6 + x_7 + x_8 <= 1 - block [1, 3, 2]: 1 <= x_9 + x_10 + x_11 <= 1 - block [2, 3, 1]: 1 <= x_12 + x_13 + x_14 <= 1 - statistics: 1 <= x_0 <= 1 - statistics: 0 <= <= 0 - statistics: 0 <= <= 0 - statistics: 1 <= x_1 <= 1 - statistics: 0 <= <= 0 - statistics: 0 <= <= 0 - statistics: 1 <= x_2 + x_4 <= 1 - statistics: 1 <= x_3 + x_5 <= 1 - statistics: 0 <= <= 0 - statistics: 1 <= x_6 + 3 x_9 + 2 x_12 <= 1 - statistics: 3 <= x_7 + 3 x_10 + 2 x_13 <= 3 - statistics: 2 <= x_8 + 3 x_11 + 2 x_14 <= 2 - veto: x_0 + x_1 + x_3 + x_4 + x_6 + x_10 + x_14 <= 6 - veto: x_0 + x_1 + x_2 + x_5 + x_6 + x_10 + x_14 <= 6 - - Changing or re-setting problem parameters clears the internal cache:: - sage: bij.set_constant_blocks(P) sage: _ = list(bij.solutions_iterator()) Constraints are: @@ -2499,8 +2454,7 @@ def solutions_iterator(self): """ if self._bmilp is None: self._bmilp = _BijectionistMILP(self) - yield from self._bmilp - + yield from self._bmilp.solutions_iterator(False, []) class _BijectionistMILP(): r""" @@ -2636,84 +2590,115 @@ def show(self, variables=True): print(f" {v}: " + "".join([f"s({a}) = " for a in P[p]]) + f"{z}") - def _solve(self, additional_constraints, solution_index=0): - r""" - Find a solution satisfying the given additional constraints. - The solution can then be retrieved using :meth:`solution`. + def _prepare_solution(self, on_blocks, solution): + r""" + Return the solution as a dictionary from `A` (or `P`) to + `Z`. INPUT: - - ``additional_constraints`` -- a list of constraints for the - underlying MILP - - - ``solution_index`` (optional, default: ``0``) -- an index - specifying how many of the solutions in the cache should be - ignored. + - ``on_blocks``, whether to return the solution on blocks or + on all elements TESTS:: - sage: from sage.combinat.bijectionist import _BijectionistMILP - sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] - sage: tau = lambda D: D.number_of_touch_points() - sage: bij = Bijectionist(A, B, tau) - sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) - sage: bmilp = _BijectionistMILP(bij) - - Generate a solution:: + sage: A = B = ["a", "b", "c"] + sage: bij = Bijectionist(A, B, lambda x: 0) + sage: bij.set_constant_blocks([["a", "b"]]) + sage: next(bij.solutions_iterator()) + {'a': 0, 'b': 0, 'c': 0} + sage: bmilp = bij._bmilp + sage: bmilp._prepare_solution(True, bmilp._solution_cache[0]) + {'a': 0, 'c': 0} - sage: bmilp.solution(False, []) # indirect doctest - {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} + """ + P = self._bijectionist._P + tZ = self._bijectionist._possible_block_values + mapping = {} # A -> Z or P -> Z, a +-> s(a) + for p, block in P.root_to_elements_dict().items(): + for z in tZ[p]: + if solution[p, z] == 1: + if on_blocks: + mapping[p] = z + else: + for a in block: + mapping[a] = z + break + return mapping - Generating a new solution that also maps `1010` to `2` fails: + def solutions_iterator(self, on_blocks, additional_constraints): + r""" + Iterate over all solutions satisfying the additional constraints. - sage: bmilp.solution(False, [bmilp._x[DyckWord([1,0,1,0]), 2] == 1], index=1) + INPUT: - However, searching for a cached solution succeeds, for inequalities and equalities:: + - ``additional_constraints`` -- a list of constraints for the + underlying MILP. - sage: bmilp.solution(False, [bmilp._x[DyckWord([1,0,1,0]), 2] >= 1]) - {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} + - ``on_blocks``, whether to return the solution on blocks or + on all elements. - sage: bmilp.solution(False, [bmilp._x[DyckWord([1,0,1,0]), 1] == 0]) - {[]: 0, [1, 0]: 1, [1, 0, 1, 0]: 2, [1, 1, 0, 0]: 1} + TESTS:: - sage: len(bmilp._solution_cache) - 1 + sage: A = B = 'abc' + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2, solver="GLPK") + sage: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) + sage: it = bmilp.solutions_iterator(False, []) + sage: it2 = bmilp.solutions_iterator(False, [bmilp._x[('c', 1)] == 1]) + sage: next(it) + {'a': 0, 'b': 1, 'c': 0} + sage: next(it2) + {'a': 0, 'b': 0, 'c': 1} + sage: next(it) + {'a': 0, 'b': 0, 'c': 1} + sage: next(it) + {'a': 1, 'b': 0, 'c': 0} """ - assert 0 <= solution_index <= len(self._solution_cache), "the index of the desired solution must not be larger than the number of known solutions" - # check whether there is a solution in the cache satisfying - # the additional constraints - for solution in self._solution_cache[solution_index:]: - if all(self._is_solution(constraint, solution) - for constraint in additional_constraints): - self.last_solution = solution - return - - # otherwise generate a new one - new_indices = [] - for constraint in additional_constraints: - new_indices.extend(self.milp.add_constraint(constraint, - return_indices=True)) - try: - self.milp.solve() - # moving this out of the try...finally block breaks SCIP - solution = self.milp.get_values(self._x, - convert=bool, tolerance=0.1) - finally: - b = self.milp.get_backend() - if hasattr(b, "_get_model"): - m = b._get_model() - if m.getStatus() != 'unknown': - m.freeTransform() - self.milp.remove_constraints(new_indices) - - self._add_solution(solution) + i = 0 # the first unconsidered element of _solution_cache + while True: + # skip solutions which do not satisfy additional_constraints + while i < len(self._solution_cache): + solution = self._solution_cache[i] + i += 1 + if all(self._is_solution(constraint, solution) + for constraint in additional_constraints): + yield self._prepare_solution(on_blocks, solution) + break + else: + new_indices = [] + for constraint in additional_constraints: + new_indices.extend(self.milp.add_constraint(constraint, + return_indices=True)) + try: + self.milp.solve() + # moving this out of the try...finally block breaks SCIP + solution = self.milp.get_values(self._x, + convert=bool, tolerance=0.1) + except MIPSolverException: + return + finally: + b = self.milp.get_backend() + if hasattr(b, "_get_model"): + m = b._get_model() + if m.getStatus() != 'unknown': + m.freeTransform() + self.milp.remove_constraints(new_indices) + + self._add_solution(solution) + i += 1 + assert i == len(self._solution_cache) + yield self._prepare_solution(on_blocks, solution) + if get_verbose() >= 2: + print("after vetoing") + self.show(variables=False) def _add_solution(self, solution): r""" - Add the ``last_solution`` to the cache and an - appropriate veto constraint to the MILP. + Add the ``solution`` to the cache and an appropriate + veto constraint to the MILP. INPUT: @@ -2739,6 +2724,7 @@ def _add_solution(self, solution): x_1: s(a) = a x_2: s(b) = b x_3: s(b) = a + """ active_vars = [self._x[p, z] for p in _disjoint_set_roots(self._bijectionist._P) @@ -2747,7 +2733,6 @@ def _add_solution(self, solution): self.milp.add_constraint(sum(active_vars) <= len(active_vars) - 1, name="veto") self._solution_cache.append(solution) - self.last_solution = solution def _is_solution(self, constraint, values): r""" @@ -2768,7 +2753,6 @@ def _is_solution(self, constraint, values): sage: bij = Bijectionist(A, B) sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) - sage: _ = bmilp._solve([]) sage: f = bmilp._x["a", "a"] + bmilp._x["b", "a"] >= bmilp._x["b", "b"] + 1 sage: v = {('a', 'a'): 1, ('a', 'b'): 0, ('b', 'a'): 1, ('b', 'b'): 1} sage: bmilp._is_solution(f, v) @@ -2794,71 +2778,6 @@ def _is_solution(self, constraint, values): return False return True - def solution(self, on_blocks, constraints, index=0): - r""" - Return a solution as a dictionary from `A` (or `P`) to - `Z`, or ``None`` - - INPUT: - - - ``on_blocks``, whether to return the solution on blocks or - on all elements - - EXAMPLES:: - - sage: A = B = ["a", "b", "c"] - sage: bij = Bijectionist(A, B, lambda x: 0) - sage: bij.set_constant_blocks([["a", "b"]]) - sage: next(bij.solutions_iterator()) - {'a': 0, 'b': 0, 'c': 0} - sage: bij._bmilp.solution(True, []) - {'a': 0, 'c': 0} - """ - try: - self._solve(constraints, index) - except MIPSolverException: - return - - P = self._bijectionist._P - tZ = self._bijectionist._possible_block_values - mapping = {} # A -> Z or P -> Z, a +-> s(a) - for p, block in P.root_to_elements_dict().items(): - for z in tZ[p]: - if self.last_solution[p, z] == 1: - if on_blocks: - mapping[p] = z - else: - for a in block: - mapping[a] = z - break - return mapping - - def __iter__(self): - r""" - Iterate over all solutions of the MILP. - - EXAMPLES:: - - sage: A = B = 'abc' - sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2, solver="GLPK") - sage: from sage.combinat.bijectionist import _BijectionistMILP - sage: list(_BijectionistMILP(bij)) - [{'a': 0, 'b': 1, 'c': 0}, - {'a': 1, 'b': 0, 'c': 0}, - {'a': 0, 'b': 0, 'c': 1}] - - """ - index = 0 - while True: - solution = self.solution(False, [], index) - if solution is None: - return - index += 1 - yield solution - if get_verbose() >= 2: - print("after vetoing") - self.show(variables=False) - def add_alpha_beta_constraints(self): r""" Add constraints enforcing that `(alpha, s)` is equidistributed @@ -2880,7 +2799,7 @@ def add_alpha_beta_constraints(self): sage: bij.set_statistics((len, len)) sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) # indirect doctest - sage: bmilp.solution(False, []) + sage: next(bmilp.solutions_iterator(False, [])) {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 2} """ W = self._bijectionist._W @@ -2931,7 +2850,7 @@ def add_distribution_constraints(self): sage: bij.set_distributions(([Permutation([1, 2, 3]), Permutation([1, 3, 2])], [1, 3])) sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) # indirect doctest - sage: bmilp.solution(False, []) + sage: next(bmilp.solutions_iterator(False, [])) {[1, 2, 3]: 3, [1, 3, 2]: 1, [2, 1, 3]: 2, @@ -3007,7 +2926,7 @@ def add_intertwining_relation_constraints(self): sage: bij.set_intertwining_relations((2, pi, rho)) sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) # indirect doctest - sage: bmilp.solution(False, []) + sage: next(bmilp.solutions_iterator(False, [])) {'a': 0, 'b': 1, 'c': 0, 'd': 1} """ A = self._bijectionist._A From db8947bfe5f116492b6fc864b36e6d9054bc34cb Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Wed, 4 Jan 2023 21:17:30 +0100 Subject: [PATCH 199/392] pycodestyle stuff --- src/sage/combinat/bijectionist.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 279ce091c3a..b80deb74e0c 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -2456,6 +2456,7 @@ def solutions_iterator(self): self._bmilp = _BijectionistMILP(self) yield from self._bmilp.solutions_iterator(False, []) + class _BijectionistMILP(): r""" Wrapper class for the MixedIntegerLinearProgram (MILP). This @@ -2590,7 +2591,6 @@ def show(self, variables=True): print(f" {v}: " + "".join([f"s({a}) = " for a in P[p]]) + f"{z}") - def _prepare_solution(self, on_blocks, solution): r""" Return the solution as a dictionary from `A` (or `P`) to @@ -2766,9 +2766,10 @@ def _is_solution(self, constraint, values): variable_index = next(iter(v.dict().keys())) index_block_value_dict[variable_index] = (p, z) - evaluate = lambda f: sum(coeff if index == -1 else - coeff * values[index_block_value_dict[index]] - for index, coeff in f.dict().items()) + def evaluate(f): + return sum(coeff if index == -1 else + coeff * values[index_block_value_dict[index]] + for index, coeff in f.dict().items()) for lhs, rhs in constraint.equations(): if evaluate(lhs - rhs): @@ -3052,6 +3053,7 @@ def sum_q(q): for q in Q[1:]: self.milp.add_constraint(len(q0)*sum_q(q) == len(q)*v0, name=f"h: ({q})~({q0})") + def _invert_dict(d): """ Return the dictionary whose keys are the values of the input and @@ -3126,6 +3128,7 @@ def _non_copying_intersection(sets): if s == result: return s + """ TESTS:: From 86fc48deddc00b508b5c75582b876315771eae49 Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Mon, 16 Jan 2023 23:43:40 +0530 Subject: [PATCH 200/392] added is_supergreedy() function --- src/sage/combinat/posets/linear_extensions.py | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 5d9aae755d8..754c4d4fae8 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -40,7 +40,7 @@ class LinearExtensionOfPoset(ClonableArray, - metaclass=InheritComparisonClasscallMetaclass): + metaclass=InheritComparisonClasscallMetaclass): r""" A linear extension of a finite poset `P` of size `n` is a total ordering `\pi := \pi_0 \pi_1 \ldots \pi_{n-1}` of its elements @@ -252,6 +252,45 @@ def is_greedy(self): return False return True + def is_supergreedy(self): + r"""" + Return ``True`` if the linear extension is supergreedy. + + A linear extension `[e_1, e_2, \ldots, e_n]` is *supergreedy* if + for every i, either `e_i+1` cover `e_i` otherwise on backtracking + from `e_i` to `e_1`, either `e_i+1` should be in the upper cover of + the first element having a non-empty upper cover or should include + only the next element. + + EXAMPLES:: + + sage: Q = posets.PentagonPoset() + sage: for l in Q.linear_extensions(): + ....: if not l.is_supergreedy(): + ....: print(l) + [0, 1, 2, 3, 4] + [0, 2, 3, 1, 4] + [0, 2, 1, 3, 4] + + TESTS:: + + sage: T = Poset() + sage: T.linear_extensions()[0].is_supergreedy() + True + """ + P = self.poset() + for i in range(len(self) - 1): + if not P.covers(self[i], self[i + 1]): + for u in range(i, -1, -1): + if len(P.upper_covers(self[u])) != 0: + if (self[i+1] in P.upper_covers(self[u])): + break + elif (self[u+1] in P.upper_covers(self[u])): + continue + else: + return False + return True + def tau(self, i): r""" Return the operator `\tau_i` on linear extensions ``self`` of a poset. @@ -855,6 +894,7 @@ class LinearExtensionsOfPosetWithHooks(LinearExtensionsOfPoset): Linear extensions such that the poset has well-defined hook lengths (i.e., d-complete). """ + def cardinality(self): r""" Count the number of linear extensions using a hook-length formula. @@ -879,6 +919,7 @@ class LinearExtensionsOfForest(LinearExtensionsOfPoset): r""" Linear extensions such that the poset is a forest. """ + def cardinality(self): r""" Use Atkinson's algorithm to compute the number of linear extensions. @@ -902,6 +943,7 @@ class LinearExtensionsOfMobile(LinearExtensionsOfPoset): r""" Linear extensions for a mobile poset. """ + def cardinality(self): r""" Return the number of linear extensions by using the determinant From cef5e45ab22d95b6e313cf3e40dcd7cf3dab44b9 Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Wed, 18 Jan 2023 23:03:06 +0530 Subject: [PATCH 201/392] implemented diff algo, for borderline cases --- src/sage/combinat/posets/linear_extensions.py | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 754c4d4fae8..e4807c44ce1 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -258,9 +258,9 @@ def is_supergreedy(self): A linear extension `[e_1, e_2, \ldots, e_n]` is *supergreedy* if for every i, either `e_i+1` cover `e_i` otherwise on backtracking - from `e_i` to `e_1`, either `e_i+1` should be in the upper cover of - the first element having a non-empty upper cover or should include - only the next element. + through the list of elements previosly choosen, either `e_i+1` + should be in the upper cover of the first element having a non-empty + upper cover or should include only the prviosly choosen elements. EXAMPLES:: @@ -279,16 +279,38 @@ def is_supergreedy(self): True """ P = self.poset() + Q = set() for i in range(len(self) - 1): + Q.add(self[i]) if not P.covers(self[i], self[i + 1]): - for u in range(i, -1, -1): - if len(P.upper_covers(self[u])) != 0: - if (self[i+1] in P.upper_covers(self[u])): - break - elif (self[u+1] in P.upper_covers(self[u])): - continue + if len(P.upper_covers(self[i])) != 0: + return False + R = True + S = [self[i]] + while(R): + for t in S: + for u in P.lower_covers(t): + if len(P.upper_covers(u)) == 0: + S = P.lower_covers(t) + break + else : + for v in P.upper_covers(u): + if v != self[i+1] and v not in Q: + return False + elif v == self[i+1] : + R=False + break + else : + continue + else : + if P.lower_covers(t) == 0 : + return False + S = P.lower_covers(t) + continue + break else: - return False + continue + break return True def tau(self, i): From 53506aac25706957bfc01aa3fdc1b58a3ddcdf06 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Fri, 20 Jan 2023 11:53:48 +0100 Subject: [PATCH 202/392] rename pseudo_inverse to quadratic --- src/sage/combinat/bijectionist.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index b80deb74e0c..8a9989176d6 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -20,7 +20,7 @@ :meth:`~Bijectionist.set_constant_blocks` | Declare that the statistic is constant on some sets. :meth:`~Bijectionist.set_distributions` | Restrict the distribution of values of the statistic on some elements. :meth:`~Bijectionist.set_intertwining_relations` | Declare that the statistic intertwines with other maps. - :meth:`~Bijectionist.set_pseudo_inverse_relation` | Declare that the statistic satisfies a certain relation. + :meth:`~Bijectionist.set_quadratic_relation` | Declare that the statistic satisfies a certain relation. :meth:`~Bijectionist.set_homomesic` | Declare that the statistic is homomesic with respect to a given set partition. @@ -535,7 +535,7 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], self.set_statistics(*alpha_beta) self.set_value_restrictions(*value_restrictions) self.set_distributions(*elements_distributions) - self.set_pseudo_inverse_relation(*phi_psi) + self.set_quadratic_relation(*phi_psi) self.set_homomesic(Q) self.set_intertwining_relations(*pi_rho) self.set_constant_blocks(P) @@ -1395,7 +1395,7 @@ def set_intertwining_relations(self, *pi_rho): set_semi_conjugacy = set_intertwining_relations - def set_pseudo_inverse_relation(self, *phi_psi): + def set_quadratic_relation(self, *phi_psi): r""" Add restrictions of the form `s\circ\psi\circ s = \phi`. @@ -1430,7 +1430,7 @@ def set_pseudo_inverse_relation(self, *phi_psi): ( [ /\ ] ) ] ( [ /\/\ / \ ] [ /\ ] ) ] ( [ / \, / \ ], [ /\/\/\, /\/ \ ] ) ] - sage: bij.set_pseudo_inverse_relation((lambda D: D, lambda D: D)) + sage: bij.set_quadratic_relation((lambda D: D, lambda D: D)) sage: ascii_art(sorted(bij.minimal_subdistributions_iterator())) [ ( [ /\ ] ) [ ( [ / \ ] ) ( [ /\ ] [ /\/\ ] ) @@ -2516,7 +2516,7 @@ def __init__(self, bijectionist: Bijectionist, solutions=None): name=f"block {p}"[:50]) self.add_alpha_beta_constraints() self.add_distribution_constraints() - self.add_pseudo_inverse_relation_constraints() + self.add_quadratic_relation_constraints() self.add_homomesic_constraints() self.add_intertwining_relation_constraints() if get_verbose() >= 2: @@ -2959,7 +2959,7 @@ def add_intertwining_relation_constraints(self): self.milp.add_constraint(rhs <= 0, name=f"pi/rho({composition_index})") - def add_pseudo_inverse_relation_constraints(self): + def add_quadratic_relation_constraints(self): r""" Add constraints enforcing that `s\circ\phi\circ s = \psi`. @@ -2987,7 +2987,7 @@ def add_pseudo_inverse_relation_constraints(self): ( [ /\ ] ) ] ( [ /\/\ / \ ] [ /\ ] ) ] ( [ / \, / \ ], [ /\/\/\, /\/ \ ] ) ] - sage: bij.set_pseudo_inverse_relation((lambda D: D, lambda D: D)) # indirect doctest + sage: bij.set_quadratic_relation((lambda D: D, lambda D: D)) # indirect doctest sage: ascii_art(sorted(bij.minimal_subdistributions_iterator())) [ ( [ /\ ] ) [ ( [ / \ ] ) ( [ /\ ] [ /\/\ ] ) From 200441dae09c8693f851178d6c28df6d80b3cc68 Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Sun, 22 Jan 2023 23:33:48 +0530 Subject: [PATCH 203/392] correcting function for disjoint set of points --- src/sage/combinat/posets/linear_extensions.py | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index e4807c44ce1..4c953c55ef5 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -279,14 +279,51 @@ def is_supergreedy(self): True """ P = self.poset() + Q = [] + N = [] + S = [] + T = [] + if not self: + return True + N.append(self[0]) + for i in range(len(self)-1): + if P.compare_elements(self[0],self[i+1]) is not None : + N.append(self[i+1]) + else: + S.append(self[i+1]) + Q.append(N.copy()) + N.clear() + while(S): + N.append(S[0]) + for i in range(len(S)-1): + if P.compare_elements(S[0],S[i+1]) is not None : + N.append(S[i+1]) + else: + T.append(S[i+1]) + Q.append(N.copy()) + S.clear() + S = T.copy() + T.clear() + N.clear() + for l in Q: + if not self.complete_supergreedy(P, l): + return False + else: + return True + + def complete_supergreedy(self, P, L): + r""" + Return ``True`` if the linear extension is supergreedy and any one + element is comparable to all the elements + """ Q = set() - for i in range(len(self) - 1): - Q.add(self[i]) - if not P.covers(self[i], self[i + 1]): - if len(P.upper_covers(self[i])) != 0: + for i in range(len(L) - 1): + Q.add(L[i]) + if not P.covers(L[i], L[i + 1]): + if len(P.upper_covers(L[i])) != 0: return False R = True - S = [self[i]] + S = [L[i]] while(R): for t in S: for u in P.lower_covers(t): @@ -295,9 +332,9 @@ def is_supergreedy(self): break else : for v in P.upper_covers(u): - if v != self[i+1] and v not in Q: + if v != L[i+1] and v not in Q: return False - elif v == self[i+1] : + elif v == L[i+1] : R=False break else : @@ -312,7 +349,7 @@ def is_supergreedy(self): continue break return True - + def tau(self, i): r""" Return the operator `\tau_i` on linear extensions ``self`` of a poset. From eb3a270968597fcec607a9f1c5d8d2ca42725e88 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 5 Dec 2022 22:14:24 -0800 Subject: [PATCH 204/392] sage.structure.element: Add ABCs Polynomial, MPolynomial, use for isinstance testing --- src/sage/combinat/kazhdan_lusztig.py | 4 ++-- src/sage/combinat/sf/sfa.py | 4 ++-- src/sage/crypto/boolean_function.pyx | 4 ++-- src/sage/crypto/sbox.pyx | 4 ++-- src/sage/crypto/stream.py | 4 ++-- src/sage/groups/affine_gps/group_element.py | 4 ++-- .../groups/perm_gps/permgroup_element.pyx | 4 ++-- .../characteristic_cohomology_class.py | 6 +++--- src/sage/quadratic_forms/constructions.py | 4 ++-- .../rings/finite_rings/element_ntl_gf2e.pyx | 2 +- .../rings/finite_rings/finite_field_base.pyx | 4 ++-- .../finite_rings/finite_field_constructor.py | 4 ++-- .../rings/finite_rings/finite_field_givaro.py | 4 ++-- .../finite_rings/finite_field_ntl_gf2e.py | 4 ++-- src/sage/rings/finite_rings/residue_field.pyx | 4 ++-- .../rings/function_field/function_field.py | 4 ++-- src/sage/rings/laurent_series_ring.py | 2 +- src/sage/rings/number_field/number_field.py | 6 +++--- src/sage/rings/padics/factory.py | 8 ++++---- .../rings/padics/padic_template_element.pxi | 4 ++-- .../rings/polynomial/multi_polynomial.pxd | 4 ++-- .../rings/polynomial/multi_polynomial.pyx | 2 +- .../polynomial_padic_capped_relative_dense.py | 2 +- .../rings/polynomial/polynomial_element.pxd | 5 +++-- .../rings/polynomial/polynomial_element.pyx | 12 +++++------ .../polynomial_integer_dense_ntl.pyx | 4 +--- src/sage/rings/qqbar.py | 4 ++-- .../schemes/berkovich/berkovich_cp_element.py | 6 +++--- src/sage/schemes/cyclic_covers/constructor.py | 4 ++-- .../elliptic_curves/ell_curve_isogeny.py | 4 ++-- .../hyperelliptic_curves/constructor.py | 4 ++-- .../hyperelliptic_curves/jacobian_homset.py | 8 ++++---- .../hyperelliptic_curves/monsky_washnitzer.py | 6 +++--- src/sage/structure/element.pxd | 6 ++++++ src/sage/structure/element.pyx | 20 +++++++++++++++++++ 35 files changed, 100 insertions(+), 75 deletions(-) diff --git a/src/sage/combinat/kazhdan_lusztig.py b/src/sage/combinat/kazhdan_lusztig.py index a8561253b28..75995aa34e4 100644 --- a/src/sage/combinat/kazhdan_lusztig.py +++ b/src/sage/combinat/kazhdan_lusztig.py @@ -20,7 +20,7 @@ #***************************************************************************** -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.misc.cachefunc import cached_method from sage.rings.polynomial.laurent_polynomial import LaurentPolynomial from sage.structure.sage_object import SageObject @@ -75,7 +75,7 @@ def __init__(self, W, q, trace=False): self._trace = trace self._one = W.one() self._base_ring = q.parent() - if is_Polynomial(q): + if isinstance(q, Polynomial): self._base_ring_type = "polynomial" elif isinstance(q, LaurentPolynomial): self._base_ring_type = "laurent" diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 040dd78f4d3..8144233329e 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -217,7 +217,7 @@ from sage.rings.integer import Integer from sage.rings.infinity import infinity from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.rings.polynomial.multi_polynomial import is_MPolynomial from sage.combinat.partition import _Partitions, Partitions, Partitions_n, Partition from sage.categories.hopf_algebras import HopfAlgebras @@ -6404,7 +6404,7 @@ def _nonnegative_coefficients(x): sage: _nonnegative_coefficients(x^2-4) False """ - if is_Polynomial(x) or is_MPolynomial(x): + if isinstance(x, Polynomial) or is_MPolynomial(x): return all(c >= 0 for c in x.coefficients(sparse=False)) else: return x >= 0 diff --git a/src/sage/crypto/boolean_function.pyx b/src/sage/crypto/boolean_function.pyx index a9ea665475c..1358ae1b958 100644 --- a/src/sage/crypto/boolean_function.pyx +++ b/src/sage/crypto/boolean_function.pyx @@ -40,7 +40,7 @@ from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.polynomial.pbori.pbori import BooleanPolynomial from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.finite_rings.finite_field_givaro import FiniteField_givaro -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.misc.superseded import deprecated_function_alias @@ -327,7 +327,7 @@ cdef class BooleanFunction(SageObject): bitset_init(self._truth_table, (1<1 or not is_Polynomial(polynomial): + from sage.structure.element import Polynomial + if polynomial.parent().ngens()>1 or not isinstance(polynomial, Polynomial): raise TypeError("polynomial must be univariate a polynomial") if names is None: names = (polynomial.variable_name(), ) diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index 9cad038840c..d3660288b4c 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -466,7 +466,7 @@ def _element_constructor_(self, x, n=0, prec=infinity): x^-3 """ from sage.rings.fraction_field_element import is_FractionFieldElement - from sage.rings.polynomial.polynomial_element import is_Polynomial + from sage.structure.element import Polynomial from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial from sage.structure.element import parent from sage.libs.pari.all import pari_gen diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index fbdd43dfabb..31572ab1c2e 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -114,7 +114,7 @@ import sage.interfaces.gap import sage.rings.complex_mpfr -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial import sage.rings.real_mpfr import sage.rings.real_mpfi import sage.rings.complex_double @@ -647,7 +647,7 @@ def create_key_and_extra_args(self, polynomial, name, check, embedding, latex_na raise TypeError("You must specify the name of the generator.") name = normalize_names(1, name) - if not is_Polynomial(polynomial): + if not isinstance(polynomial, Polynomial): try: polynomial = polynomial.polynomial(QQ) except (AttributeError, TypeError): @@ -868,7 +868,7 @@ def NumberFieldTower(polynomials, names, check=True, embeddings=None, latex_name f = polynomials[0] name = names[0] w = NumberFieldTower(polynomials[1:], names=names[1:], check=check, embeddings=embeddings[1:], latex_names=latex_names[1:], assume_disc_small=assume_disc_small, maximize_at_primes=maximize_at_primes, structures=structures[1:]) - var = f.variable_name() if is_Polynomial(f) else 'x' + var = f.variable_name() if isinstance(f, Polynomial) else 'x' R = w[var] # polynomial ring return w.extension(R(f), name, check=check, embedding=embeddings[0], structure=structures[0], latex_name=latex_names[0]) # currently, extension does not accept assume_disc_small, or maximize_at_primes diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index 044dcae0bed..72ee42a0e5b 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -35,7 +35,7 @@ from sage.rings.infinity import Infinity from sage.structure.factorization import Factorization from sage.rings.integer_ring import ZZ -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.structure.element import is_Element from .padic_base_leaves import (pAdicRingCappedRelative, pAdicRingCappedAbsolute, @@ -2535,7 +2535,7 @@ def Zq(q, prec = None, type = 'capped-rel', modulus = None, names=None, if isinstance(names, (list, tuple)): names = names[0] from sage.structure.element import Expression - if not (modulus is None or is_Polynomial(modulus) or isinstance(modulus, Expression)): + if not (modulus is None or isinstance(modulus, Polynomial) or isinstance(modulus, Expression)): raise TypeError("modulus must be a polynomial") if names is not None and not isinstance(names, str): names = str(names) @@ -3278,7 +3278,7 @@ def create_key_and_extra_args(self, base, modulus, prec = None, print_mode = Non raise ValueError("symbolic expression must be in only one variable") exact_modulus = modulus.polynomial(base.exact_field()) approx_modulus = modulus.polynomial(base) - elif is_Polynomial(modulus): + elif isinstance(modulus, Polynomial): if modulus.parent().ngens() != 1: raise ValueError("must use univariate polynomial") exact_modulus = modulus.change_ring(base.exact_field()) @@ -3381,7 +3381,7 @@ def create_object(self, version, key, approx_modulus=None, shift_seed=None): from sage.structure.element import Expression if isinstance(premodulus, Expression): exact_modulus = premodulus.polynomial(base.exact_field()) - elif is_Polynomial(premodulus): + elif isinstance(premodulus, Polynomial): exact_modulus = premodulus.change_ring(base.exact_field()) show_prec = None else: diff --git a/src/sage/rings/padics/padic_template_element.pxi b/src/sage/rings/padics/padic_template_element.pxi index fed8ac89f81..f3aa97e879b 100644 --- a/src/sage/rings/padics/padic_template_element.pxi +++ b/src/sage/rings/padics/padic_template_element.pxi @@ -36,7 +36,7 @@ from sage.rings.infinity import infinity from sage.rings.rational import Rational from sage.rings.padics.precision_error import PrecisionError from sage.rings.padics.misc import trim_zeros -from sage.structure.element import canonical_coercion +from sage.structure.element import canonical_coercion, Polynomial import itertools cdef long maxordp = (1L << (sizeof(long) * 8 - 2)) - 1 @@ -156,7 +156,7 @@ cdef class pAdicTemplateElement(pAdicGenericElement): x = x + [k.prime_subfield().zero()] * (k.degree() - len(x)) elif isinstance(x, (Integer, Rational, list, tuple)): pass - elif sage.rings.polynomial.polynomial_element.is_Polynomial(x) and x.variable_name() == self.parent().variable_name(): + elif isinstance(x, Polynomial) and x.variable_name() == self.parent().variable_name(): x = x.list() else: x = Rational(x) diff --git a/src/sage/rings/polynomial/multi_polynomial.pxd b/src/sage/rings/polynomial/multi_polynomial.pxd index 44fdf3edfc4..fd947a33bb2 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pxd +++ b/src/sage/rings/polynomial/multi_polynomial.pxd @@ -1,6 +1,6 @@ -from sage.structure.element cimport CommutativeRingElement +from sage.structure.element cimport MPolynomial as MPolynomial_base -cdef class MPolynomial(CommutativeRingElement): +cdef class MPolynomial(MPolynomial_base): cdef long _hash_c(self) except -1 cpdef _mod_(self, right) cpdef dict _mpoly_dict_recursive(self, tuple vars=*, base_ring=*) diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 49e4c52d365..57d95361ddd 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -30,7 +30,7 @@ from sage.rings.real_mpfr import RealField_class,RealField from sage.rings.polynomial.polydict cimport ETuple from sage.rings.polynomial.polynomial_element cimport Polynomial -cdef class MPolynomial(CommutativeRingElement): +cdef class MPolynomial(MPolynomial_base): #################### # Some standard conversions diff --git a/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py b/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py index 64e1f6368b6..35fe90dd8f8 100644 --- a/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py +++ b/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py @@ -661,7 +661,7 @@ def rshift_coeffs(self, shift, no_list=False): return Polynomial_padic_capped_relative_dense(self.parent(), (self._poly // fdiv, 0, [0 if a <= shift else a - shift for a in self._relprecs], False, None, None), construct=True) # def __floordiv__(self, right): - # if is_Polynomial(right) and right.is_constant() and right[0] in self.base_ring(): + # if isinstance(right, Polynomial) and right.is_constant() and right[0] in self.base_ring(): # d = self.base_ring()(right[0]) # elif (right in self.base_ring()): # d = self.base_ring()(right) diff --git a/src/sage/rings/polynomial/polynomial_element.pxd b/src/sage/rings/polynomial/polynomial_element.pxd index 1ba103329c3..cafc17eab3f 100644 --- a/src/sage/rings/polynomial/polynomial_element.pxd +++ b/src/sage/rings/polynomial/polynomial_element.pxd @@ -1,11 +1,12 @@ -from sage.structure.element import Element, CommutativeAlgebraElement +from sage.structure.element import Element from sage.structure.element cimport Element, CommutativeAlgebraElement, ModuleElement +from sage.structure.element cimport Polynomial as Polynomial_base from sage.structure.parent cimport Parent from sage.rings.integer cimport Integer from .polynomial_compiled cimport CompiledPolynomialFunction -cdef class Polynomial(CommutativeAlgebraElement): +cdef class Polynomial(Polynomial_base): cdef Polynomial _new_generic(self, list coeffs) cdef char _is_gen cdef CompiledPolynomialFunction _compiled diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index d2649163d48..a0c5fd2e84f 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -149,14 +149,14 @@ cpdef is_Polynomial(f): EXAMPLES:: - sage: from sage.rings.polynomial.polynomial_element import is_Polynomial + sage: from sage.structure.element import Polynomial sage: R. = ZZ[] - sage: is_Polynomial(x^3 + x + 1) + sage: isinstance(x^3 + x + 1, Polynomial) True sage: S. = R[] sage: f = y^3 + x*y -3*x; f y^3 + x*y - 3*x - sage: is_Polynomial(f) + sage: isinstance(f, Polynomial) True However this function does not return True for genuine multivariate @@ -166,13 +166,13 @@ cpdef is_Polynomial(f): sage: R. = QQ[] sage: f = y^3 + x*y -3*x; f y^3 + x*y - 3*x - sage: is_Polynomial(f) + sage: isinstance(f, Polynomial) False sage: var('x,y') (x, y) sage: f = y^3 + x*y -3*x; f y^3 + x*y - 3*x - sage: is_Polynomial(f) + sage: isinstance(f, Polynomial) False """ return isinstance(f, Polynomial) @@ -182,7 +182,7 @@ from .polynomial_compiled cimport CompiledPolynomialFunction from sage.rings.polynomial.polydict cimport ETuple -cdef class Polynomial(CommutativeAlgebraElement): +cdef class Polynomial(Polynomial_base): """ A polynomial. diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx index 41ddaa92e51..7a84cb86f05 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx @@ -51,8 +51,6 @@ from sage.rings.integer_ring import IntegerRing from sage.rings.integer_ring cimport IntegerRing_class ZZ_sage = IntegerRing() -from sage.rings.polynomial.polynomial_element import is_Polynomial - from sage.libs.ntl.ntl_ZZX cimport ntl_ZZX from sage.rings.integer_ring import ZZ @@ -757,7 +755,7 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): sage: g // f x - 6 """ - if is_Polynomial(right) and right.is_constant() and right[0] in ZZ: + if isinstance(right, Polynomial) and right.is_constant() and right[0] in ZZ: d = ZZ(right[0]) return self.parent()([c // d for c in self.list()], construct=True) elif (right in self.parent().base_ring()): diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index e0aeed19815..90b8645ab82 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -573,7 +573,7 @@ from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.complex_interval import is_ComplexIntervalFieldElement from sage.rings.polynomial.all import PolynomialRing -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.number_field.number_field import NumberField, GaussianField, CyclotomicField @@ -6676,7 +6676,7 @@ def __init__(self, poly): sage: type(P) # indirect doctest """ - if not is_Polynomial(poly): + if not isinstance(poly, Polynomial): raise ValueError("Trying to create AlgebraicPolynomialTracker on non-Polynomial") B = poly.base_ring() diff --git a/src/sage/schemes/berkovich/berkovich_cp_element.py b/src/sage/schemes/berkovich/berkovich_cp_element.py index b8cc1887957..57968823ec9 100644 --- a/src/sage/schemes/berkovich/berkovich_cp_element.py +++ b/src/sage/schemes/berkovich/berkovich_cp_element.py @@ -83,7 +83,7 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= Type I point centered at 4 + O(5^20) """ from sage.rings.function_field.element import is_FunctionFieldElement - from sage.rings.polynomial.polynomial_element import is_Polynomial + from sage.structure.element import Polynomial from sage.rings.fraction_field_element import FractionFieldElement_1poly_field self._type = None @@ -117,9 +117,9 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= raise TypeError('center was %s, a multivariable polynomial' % center) # check if the radius and the center are functions - center_func_check = is_FunctionFieldElement(center) or is_Polynomial(center) or\ + center_func_check = is_FunctionFieldElement(center) or isinstance(center, Polynomial) or\ isinstance(center, FractionFieldElement_1poly_field) or isinstance(center, Expression) - radius_func_check = is_FunctionFieldElement(radius) or is_Polynomial(radius) or\ + radius_func_check = is_FunctionFieldElement(radius) or isinstance(radius, Polynomial) or\ isinstance(radius, FractionFieldElement_1poly_field) or isinstance(radius, Expression) if center_func_check: diff --git a/src/sage/schemes/cyclic_covers/constructor.py b/src/sage/schemes/cyclic_covers/constructor.py index 0966b0793d5..a67a17a2f17 100644 --- a/src/sage/schemes/cyclic_covers/constructor.py +++ b/src/sage/schemes/cyclic_covers/constructor.py @@ -8,7 +8,7 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.schemes.affine.affine_space import AffineSpace from .cycliccover_generic import CyclicCover_generic @@ -100,7 +100,7 @@ def CyclicCover(r, f, names=None, check_smooth=True): """ - if not is_Polynomial(f): + if not isinstance(f, Polynomial): raise TypeError("Arguments f (= %s) must be a polynomial" % (f,)) P = f.parent() f = P(f) diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index fe80879bc15..4bc0825a35a 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -86,7 +86,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.integer import Integer from sage.rings.laurent_series_ring import LaurentSeriesRing -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.rings.fraction_field import FractionField from sage.schemes.elliptic_curves.all import EllipticCurve @@ -159,7 +159,7 @@ def _isogeny_determine_algorithm(E, kernel): kernel = [kernel] kernel_is_list = True - if is_Polynomial(kernel) or (kernel_is_list and kernel[0] in E.base_ring()): + if isinstance(kernel, Polynomial) or (kernel_is_list and kernel[0] in E.base_ring()): return "kohel" if kernel_is_list and kernel[0] in E: diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index 54556e08755..49ff0254cd1 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -25,7 +25,7 @@ import sage.rings.abc from sage.rings.rational_field import is_RationalField from sage.rings.finite_rings.finite_field_constructor import is_FiniteField -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.structure.dynamic_class import dynamic_class @@ -197,7 +197,7 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): # F is the discriminant; use this for the type check # rather than f and h, one of which might be constant. F = h**2 + 4*f - if not is_Polynomial(F): + if not isinstance(F, Polynomial): raise TypeError("Arguments f (= %s) and h (= %s) must be polynomials" % (f, h)) P = F.parent() f = P(f) diff --git a/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py b/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py index f7b0a1f67f0..9fc24367211 100644 --- a/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py +++ b/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py @@ -49,7 +49,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.integer_ring import ZZ from sage.rings.integer import is_Integer, Integer -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.schemes.generic.homset import SchemeHomset_points from sage.schemes.generic.morphism import is_SchemeMorphism @@ -138,15 +138,15 @@ def __call__(self, P): P1 = R(P1) P2 = R(P2) return JacobianMorphism_divisor_class_field(self, (P1, P2)) - if is_Integer(P1) and is_Polynomial(P2): + if is_Integer(P1) and isinstance(P2, Polynomial): R = PolynomialRing(self.value_ring(), 'x') P1 = R(P1) return JacobianMorphism_divisor_class_field(self, (P1, P2)) - if is_Integer(P2) and is_Polynomial(P1): + if is_Integer(P2) and isinstance(P1, Polynomial): R = PolynomialRing(self.value_ring(), 'x') P2 = R(P2) return JacobianMorphism_divisor_class_field(self, (P1, P2)) - if is_Polynomial(P1) and is_Polynomial(P2): + if isinstance(P1, Polynomial) and isinstance(P2, Polynomial): return JacobianMorphism_divisor_class_field(self, tuple(P)) if is_SchemeMorphism(P1) and is_SchemeMorphism(P2): return self(P1) - self(P2) diff --git a/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py b/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py index b1f10eb1344..40bb62aae0d 100644 --- a/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py +++ b/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py @@ -64,7 +64,7 @@ from sage.rings.infinity import Infinity from sage.rings.laurent_series_ring import is_LaurentSeriesRing from sage.rings.padics.all import pAdicField -from sage.rings.polynomial.polynomial_element import is_Polynomial +from sage.structure.element import Polynomial from sage.rings.ring import CommutativeAlgebra from sage.schemes.elliptic_curves.constructor import EllipticCurve from sage.schemes.elliptic_curves.ell_generic import is_EllipticCurve @@ -489,7 +489,7 @@ def __init__(self, Q, laurent_series=False): ... ArithmeticError: 2 and 3 must be invertible in the coefficient ring (=Ring of integers modulo 10) of Q """ - if not is_Polynomial(Q): + if not isinstance(Q, Polynomial): raise TypeError("Q (=%s) must be a polynomial" % Q) if Q.degree() != 3 or not Q[2].is_zero(): @@ -2389,7 +2389,7 @@ def __init__(self, Q, R=None, invert_y=True): Q = C.hyperelliptic_polynomials()[0].change_ring(R) self._curve = C - if is_Polynomial(Q): + if isinstance(Q, Polynomial): self._Q = Q.change_ring(R) self._coeffs = self._Q.coefficients(sparse=False) if self._coeffs.pop() != 1: diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index 5c6e295a4b8..d6de9b9d2d3 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -239,6 +239,12 @@ cdef class CommutativeAlgebraElement(CommutativeRingElement): cdef class Expression(CommutativeRingElement): pass +cdef class Polynomial(CommutativeAlgebraElement): + pass + +cdef class MPolynomial(CommutativeAlgebraElement): + pass + cdef class InfinityElement(RingElement): pass diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index e9430f0b086..eb694681249 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -4291,6 +4291,26 @@ def is_CommutativeAlgebraElement(x): cdef class CommutativeAlgebraElement(CommutativeRingElement): pass + ############################################## + +cdef class Polynomial(CommutativeAlgebraElement): + r""" + Abstract base class for :class:`~sage.rings.polynomial.polynomial_element.Polynomial` + """ + + pass + + ############################################## + +cdef class MPolynomial(CommutativeAlgebraElement): + r""" + Abstract base class for :class:`~sage.rings.polynomial.multi_polynomial.MPolynomial` + """ + + pass + + ############################################## + def is_InfinityElement(x): """ Return ``True`` if x is of type InfinityElement. From 6696f5c470b690f528a3b869bd9ae482e91b1e37 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 5 Dec 2022 23:27:41 -0800 Subject: [PATCH 205/392] Use ABC MPolynomial for isinstance testing --- src/sage/combinat/schubert_polynomial.py | 6 +++--- src/sage/combinat/sf/sfa.py | 4 ++-- src/sage/crypto/mq/rijndael_gf.py | 4 ++-- src/sage/groups/affine_gps/group_element.py | 4 ++-- src/sage/groups/perm_gps/permgroup_element.pyx | 4 ++-- src/sage/libs/symmetrica/sb.pxi | 2 +- src/sage/modular/modsym/ambient.py | 4 ++-- src/sage/quadratic_forms/binary_qf.py | 4 ++-- src/sage/rings/finite_rings/element_ntl_gf2e.pyx | 2 +- src/sage/rings/laurent_series_ring.py | 5 ++--- src/sage/rings/polynomial/infinite_polynomial_element.py | 4 ++-- src/sage/rings/polynomial/multi_polynomial_element.py | 5 +---- src/sage/schemes/berkovich/berkovich_cp_element.py | 4 ++-- src/sage/schemes/curves/constructor.py | 4 ++-- src/sage/schemes/elliptic_curves/constructor.py | 4 ++-- src/sage/schemes/elliptic_curves/jacobian.py | 4 ++-- src/sage/schemes/generic/hypersurface.py | 6 +++--- src/sage/schemes/plane_conics/constructor.py | 4 ++-- src/sage/schemes/plane_quartics/quartic_constructor.py | 4 ++-- 19 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/sage/combinat/schubert_polynomial.py b/src/sage/combinat/schubert_polynomial.py index 07d679ac65d..070e1b1e5bf 100644 --- a/src/sage/combinat/schubert_polynomial.py +++ b/src/sage/combinat/schubert_polynomial.py @@ -78,7 +78,7 @@ from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.polynomial.multi_polynomial import is_MPolynomial +from sage.structure.element import MPolynomial from sage.combinat.permutation import Permutations, Permutation import sage.libs.symmetrica.all as symmetrica from sage.misc.cachefunc import cached_method @@ -147,7 +147,7 @@ def expand(self): x0 """ p = symmetrica.t_SCHUBERT_POLYNOM(self) - if not is_MPolynomial(p): + if not isinstance(p, MPolynomial): R = PolynomialRing(self.parent().base_ring(), 1, 'x0') p = R(p) return p @@ -439,7 +439,7 @@ def _element_constructor_(self, x): elif isinstance(x, Permutation): perm = x.remove_extra_fixed_points() return self._from_dict({perm: self.base_ring().one()}) - elif is_MPolynomial(x): + elif isinstance(x, MPolynomial): return symmetrica.t_POLYNOM_SCHUBERT(x) else: raise TypeError diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index 8144233329e..cefc2d7f473 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -218,7 +218,7 @@ from sage.rings.infinity import infinity from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.structure.element import Polynomial -from sage.rings.polynomial.multi_polynomial import is_MPolynomial +from sage.structure.element import MPolynomial from sage.combinat.partition import _Partitions, Partitions, Partitions_n, Partition from sage.categories.hopf_algebras import HopfAlgebras from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis @@ -6404,7 +6404,7 @@ def _nonnegative_coefficients(x): sage: _nonnegative_coefficients(x^2-4) False """ - if isinstance(x, Polynomial) or is_MPolynomial(x): + if isinstance(x, Polynomial) or isinstance(x, MPolynomial): return all(c >= 0 for c in x.coefficients(sparse=False)) else: return x >= 0 diff --git a/src/sage/crypto/mq/rijndael_gf.py b/src/sage/crypto/mq/rijndael_gf.py index 154c84d146c..30160378077 100644 --- a/src/sage/crypto/mq/rijndael_gf.py +++ b/src/sage/crypto/mq/rijndael_gf.py @@ -1549,9 +1549,9 @@ def compose(self, f, g, algorithm='encrypt', f_attr=None, g_attr=None): if not isinstance(f, RijndaelGF.Round_Component_Poly_Constr): msg = "keyword 'f' must be a Round_Component_Poly_Constr" raise TypeError(msg) - from sage.rings.polynomial.multi_polynomial import is_MPolynomial + from sage.structure.element import MPolynomial if not isinstance(g, RijndaelGF.Round_Component_Poly_Constr) and \ - not is_MPolynomial(g): + not isinstance(g, MPolynomial): msg = ("keyword 'g' must be a Round_Component_Poly_Constr or a " "polynomial over {0}") raise TypeError(msg.format(self._F)) diff --git a/src/sage/groups/affine_gps/group_element.py b/src/sage/groups/affine_gps/group_element.py index fe691151cf3..3ad22c1d96b 100644 --- a/src/sage/groups/affine_gps/group_element.py +++ b/src/sage/groups/affine_gps/group_element.py @@ -419,8 +419,8 @@ def __call__(self, v): ring = v.parent() return ring([self._A[0,0], self._b[0]]) - from sage.rings.polynomial.multi_polynomial import is_MPolynomial - if is_MPolynomial(v) and parent.degree() == v.parent().ngens(): + from sage.structure.element import MPolynomial + if isinstance(v, MPolynomial) and parent.degree() == v.parent().ngens(): ring = v.parent() from sage.modules.free_module_element import vector image_coords = self._A * vector(ring, ring.gens()) + self._b diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index 90ed0e7d6b3..b06873b7f3d 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -115,7 +115,7 @@ from cypari2.gen cimport Gen from sage.ext.stdsage cimport HAS_DICTIONARY from sage.rings.all import ZZ, Integer from sage.structure.element import Polynomial -from sage.rings.polynomial.multi_polynomial import is_MPolynomial +from sage.structure.element import MPolynomial from sage.structure.element import is_Matrix from sage.matrix.all import MatrixSpace from sage.sets.finite_enumerated_set import FiniteEnumeratedSet @@ -1230,7 +1230,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): raise ValueError("%s does not act on %s" % (self, left.parent())) return left - elif is_MPolynomial(left): + elif isinstance(left, MPolynomial): R = left.parent() vars = R.gens() try: diff --git a/src/sage/libs/symmetrica/sb.pxi b/src/sage/libs/symmetrica/sb.pxi index e61fc08fb98..54ba66ecb06 100644 --- a/src/sage/libs/symmetrica/sb.pxi +++ b/src/sage/libs/symmetrica/sb.pxi @@ -111,7 +111,7 @@ def t_POLYNOM_SCHUBERT_symmetrica(a): cdef OP ca = callocobject(), cres = callocobject() - if not is_MPolynomial(a): + if not isinstance(a, MPolynomial): freeall(ca) freeall(cres) raise TypeError("a (= %s) must be a multivariate polynomial") diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index 627f1c567e7..53393317183 100644 --- a/src/sage/modular/modsym/ambient.py +++ b/src/sage/modular/modsym/ambient.py @@ -95,7 +95,7 @@ class ``ModularSymbolsAmbient``, derived from from sage.modules.free_module_element import FreeModuleElement from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ -from sage.rings.polynomial.multi_polynomial import is_MPolynomial +from sage.structure.element import MPolynomial from sage.rings.rational_field import QQ from sage.rings.ring import Ring from sage.structure.factorization import Factorization @@ -474,7 +474,7 @@ def _element_constructor_(self, x, computed_with_hecke=False): return sum([c*self(y) for c, y in x], self(0)) elif isinstance(x, list): - if len(x) == 3 and is_MPolynomial(x[0]): + if len(x) == 3 and isinstance(x[0], MPolynomial): return self.modular_symbol_sum(x) else: return self.modular_symbol(x) diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py index cfa3ada73ef..727077a7197 100755 --- a/src/sage/quadratic_forms/binary_qf.py +++ b/src/sage/quadratic_forms/binary_qf.py @@ -130,14 +130,14 @@ def __init__(self, a, b=None, c=None): sage: BinaryQF(0) 0 """ - from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial + from sage.structure.element import MPolynomial if b is None and c is None: if (isinstance(a, (list, tuple)) and len(a) == 3): a, b, c = a elif a == 0: a = b = c = 0 - elif (is_MPolynomial(a) and a.is_homogeneous() and a.base_ring() == ZZ + elif (isinstance(a, MPolynomial) and a.is_homogeneous() and a.base_ring() == ZZ and a.degree() == 2 and a.parent().ngens() == 2): x, y = a.parent().gens() a, b, c = [a.monomial_coefficient(mon) for mon in [x**2, x*y, y**2]] diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index fee9990e573..38b1aaf2ce7 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -1005,7 +1005,7 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): a^15 + a^13 + a^11 + a^10 + a^9 + a^8 + a^7 + a^6 + a^4 + a + 1 sage: from sage.structure.element import Polynomial - sage: is_Polynomial(e.polynomial()) + sage: isinstance(e.polynomial(), Polynomial) True sage: e.polynomial('x') diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index d3660288b4c..37b0455928d 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -466,8 +466,7 @@ def _element_constructor_(self, x, n=0, prec=infinity): x^-3 """ from sage.rings.fraction_field_element import is_FractionFieldElement - from sage.structure.element import Polynomial - from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial + from sage.structure.element import Polynomial, MPolynomial from sage.structure.element import parent from sage.libs.pari.all import pari_gen @@ -500,7 +499,7 @@ def _element_constructor_(self, x, n=0, prec=infinity): return (self(self.polynomial_ring()(x)) << n).add_bigoh(prec) elif (is_FractionFieldElement(x) and (x.base_ring() is self.base_ring() or x.base_ring() == self.base_ring()) - and (is_Polynomial(x.numerator()) or is_MPolynomial(x.numerator()))): + and isinstance(x.numerator(), (Polynomial, MPolynomial))): x = self(x.numerator()) / self(x.denominator()) return (x << n).add_bigoh(prec) return self.element_class(self, x, n).add_bigoh(prec) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index ac21b71553f..aacddf7a040 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -94,7 +94,7 @@ from sage.structure.element import RingElement from sage.structure.richcmp import richcmp from sage.misc.cachefunc import cached_method -from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.structure.element import MPolynomial import copy @@ -249,7 +249,7 @@ def __init__(self, A, p): # the wrong ring and we get here without going through # _element_constructor_. See trac 22514 for examples. # So a little extra checking is done here. - if not is_MPolynomial(p) or p.base_ring() is not A.base_ring(): + if not isinstance(p, MPolynomial) or p.base_ring() is not A.base_ring(): # coerce to a convenient multivariate polynomial ring p = A._minP(p) diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 702bf9af7eb..d96e9bfb9cc 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -61,7 +61,7 @@ from sage.structure.factorization import Factorization from sage.rings.polynomial.polynomial_singular_interface import Polynomial_singular_repr from sage.structure.sequence import Sequence -from .multi_polynomial import MPolynomial +from .multi_polynomial import MPolynomial, is_MPolynomial from sage.categories.morphism import Morphism from sage.misc.lazy_attribute import lazy_attribute @@ -71,9 +71,6 @@ from sage.categories.number_fields import NumberFields from sage.rings.real_mpfr import RealField -def is_MPolynomial(x): - return isinstance(x, MPolynomial) - class MPolynomial_element(MPolynomial): def __init__(self, parent, x): diff --git a/src/sage/schemes/berkovich/berkovich_cp_element.py b/src/sage/schemes/berkovich/berkovich_cp_element.py index 57968823ec9..d496d0543b6 100644 --- a/src/sage/schemes/berkovich/berkovich_cp_element.py +++ b/src/sage/schemes/berkovich/berkovich_cp_element.py @@ -109,8 +109,8 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= # is_FunctionFieldElement calls .parent elif hasattr(center, "parent") and hasattr(radius, 'parent'): - from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial - if is_MPolynomial(center): + from sage.structure.element import MPolynomial + if isinstance(center, MPolynomial): try: center = center.univariate_polynomial() except AttributeError: diff --git a/src/sage/schemes/curves/constructor.py b/src/sage/schemes/curves/constructor.py index fc3d174de59..a388e660fef 100644 --- a/src/sage/schemes/curves/constructor.py +++ b/src/sage/schemes/curves/constructor.py @@ -36,7 +36,7 @@ from sage.categories.fields import Fields -from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.structure.element import MPolynomial from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.finite_rings.finite_field_constructor import is_FiniteField @@ -239,7 +239,7 @@ def Curve(F, A=None): else: A = ProjectiveSpace(P.ngens()-1, P.base_ring(), names=P.variable_names()) A._coordinate_ring = P - elif is_MPolynomial(F): # define a plane curve + elif isinstance(F, MPolynomial): # define a plane curve P = F.parent() k = F.base_ring() diff --git a/src/sage/schemes/elliptic_curves/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index 1ecdc082cd5..ff59b42bfe9 100644 --- a/src/sage/schemes/elliptic_curves/constructor.py +++ b/src/sage/schemes/elliptic_curves/constructor.py @@ -29,7 +29,7 @@ from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.number_field.number_field import is_NumberField -from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.structure.element import MPolynomial from sage.rings.ring import is_Ring from sage.categories.fields import Fields @@ -420,7 +420,7 @@ def create_key_and_extra_args(self, x=None, y=None, j=None, minimal_twist=True, if isinstance(parent(x), sage.rings.abc.SymbolicRing): x = x._polynomial_(rings.QQ['x', 'y']) - if is_MPolynomial(x): + if isinstance(x, MPolynomial): if y is None: x = coefficients_from_Weierstrass_polynomial(x) else: diff --git a/src/sage/schemes/elliptic_curves/jacobian.py b/src/sage/schemes/elliptic_curves/jacobian.py index b6983ab75f7..dc09a18096e 100644 --- a/src/sage/schemes/elliptic_curves/jacobian.py +++ b/src/sage/schemes/elliptic_curves/jacobian.py @@ -104,8 +104,8 @@ def Jacobian(X, **kwds): pass morphism = kwds.pop('morphism', False) - from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial - if is_MPolynomial(X): + from sage.structure.element import MPolynomial + if isinstance(X, MPolynomial): if morphism: from sage.schemes.curves.constructor import Curve return Jacobian_of_equation(X, curve=Curve(X), **kwds) diff --git a/src/sage/schemes/generic/hypersurface.py b/src/sage/schemes/generic/hypersurface.py index 12138d596af..58f8ed517fd 100644 --- a/src/sage/schemes/generic/hypersurface.py +++ b/src/sage/schemes/generic/hypersurface.py @@ -18,7 +18,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.structure.element import MPolynomial from sage.schemes.affine.affine_subscheme import AlgebraicScheme_subscheme_affine from sage.schemes.projective.projective_subscheme import AlgebraicScheme_subscheme_projective @@ -92,7 +92,7 @@ def __init__(self, poly, ambient=None): sage: H == loads(dumps(H)) True """ - if not is_MPolynomial(poly): + if not isinstance(poly, MPolynomial): raise TypeError("Defining polynomial (=%s) must be a multivariate polynomial."%poly) if not poly.is_homogeneous(): raise TypeError("Defining polynomial (=%s) must be homogeneous."%poly) @@ -177,7 +177,7 @@ def __init__(self, poly, ambient=None): sage: H == loads(dumps(H)) True """ - if not is_MPolynomial(poly): + if not isinstance(poly, MPolynomial): raise TypeError("Defining polynomial (= %s) must be a multivariate polynomial"%poly) if ambient is None: R = poly.parent() diff --git a/src/sage/schemes/plane_conics/constructor.py b/src/sage/schemes/plane_conics/constructor.py index a2a14190087..c1c24049dfe 100644 --- a/src/sage/schemes/plane_conics/constructor.py +++ b/src/sage/schemes/plane_conics/constructor.py @@ -32,7 +32,7 @@ from sage.rings.ring import IntegralDomain from sage.rings.rational_field import is_RationalField from sage.rings.finite_rings.finite_field_constructor import is_FiniteField -from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.structure.element import MPolynomial from sage.rings.polynomial.polynomial_ring import is_PolynomialRing from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.fraction_field import is_FractionField @@ -204,7 +204,7 @@ def Conic(base_field, F=None, names=None, unique=True): temp_ring = PolynomialRing(F.base_ring(), 3, names) F = vector(temp_ring.gens()) * F * vector(temp_ring.gens()) - if not is_MPolynomial(F): + if not isinstance(F, MPolynomial): raise TypeError("F (=%s) must be a three-variable polynomial or " "a sequence of points or coefficients" % F) diff --git a/src/sage/schemes/plane_quartics/quartic_constructor.py b/src/sage/schemes/plane_quartics/quartic_constructor.py index a0b5ce3c93a..53f1494abf0 100644 --- a/src/sage/schemes/plane_quartics/quartic_constructor.py +++ b/src/sage/schemes/plane_quartics/quartic_constructor.py @@ -9,7 +9,7 @@ #***************************************************************************** from sage.schemes.projective.projective_space import is_ProjectiveSpace, ProjectiveSpace -from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.structure.element import MPolynomial from .quartic_generic import QuarticCurve_generic @@ -50,7 +50,7 @@ def QuarticCurve(F, PP=None, check=False): ValueError: Argument F (=x^4 + y^4) must be a polynomial in 3 variables """ - if not is_MPolynomial(F): + if not isinstance(F, MPolynomial): raise ValueError(f"Argument F (={F}) must be a multivariate polynomial") P = F.parent() if not P.ngens() == 3: From 79f7e62da9a4d9170c383da70a727302a4340e83 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 6 Dec 2022 00:13:37 -0800 Subject: [PATCH 206/392] Fixups --- src/sage/libs/symmetrica/symmetrica.pxi | 4 ++-- src/sage/rings/polynomial/multi_polynomial.pyx | 3 +++ .../rings/polynomial/polynomial_element.pyx | 17 ++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/sage/libs/symmetrica/symmetrica.pxi b/src/sage/libs/symmetrica/symmetrica.pxi index 7244b7e9655..06330bffbd4 100644 --- a/src/sage/libs/symmetrica/symmetrica.pxi +++ b/src/sage/libs/symmetrica/symmetrica.pxi @@ -401,7 +401,7 @@ cdef void late_import(): SymmetricFunctions, \ sqrt, \ builtinlist, \ - MPolynomialRing_base, is_MPolynomial,\ + MPolynomialRing_base, MPolynomial,\ SchubertPolynomialRing, SchubertPolynomial_class,\ two, fifteen, thirty, zero, sage_maxint @@ -454,7 +454,7 @@ cdef void late_import(): import sage.rings.polynomial.multi_polynomial_ring MPolynomialRing_base = sage.rings.polynomial.multi_polynomial_ring.MPolynomialRing_base import sage.rings.polynomial.multi_polynomial_element - is_MPolynomial = sage.rings.polynomial.multi_polynomial_element.is_MPolynomial + MPolynomial = sage.structure.element.MPolynomial import sage.combinat.schubert_polynomial SchubertPolynomialRing = sage.combinat.schubert_polynomial.SchubertPolynomialRing diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 57d95361ddd..f798a67aa41 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -18,6 +18,9 @@ from sage.misc.derivative import multi_derivative from sage.misc.misc_c import prod def is_MPolynomial(x): + from sage.misc.superseded import deprecation + deprecation(32709, "the function is_MPolynomial is deprecated; use isinstance(x, sage.structure.element.MPolynomial) instead") + return isinstance(x, MPolynomial) from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index a0c5fd2e84f..5cff2e446a4 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -143,20 +143,24 @@ cpdef is_Polynomial(f): """ Return True if f is of type univariate polynomial. + This function is deprecated. + INPUT: - ``f`` -- an object EXAMPLES:: - sage: from sage.structure.element import Polynomial + sage: from sage.rings.polynomial.polynomial_element import is_Polynomial sage: R. = ZZ[] - sage: isinstance(x^3 + x + 1, Polynomial) + sage: is_Polynomial(x^3 + x + 1) + doctest:...: DeprecationWarning: the function is_Polynomial is deprecated; use isinstance(x, sage.structure.element.Polynomial) instead + See https://trac.sagemath.org/32709 for details. True sage: S. = R[] sage: f = y^3 + x*y -3*x; f y^3 + x*y - 3*x - sage: isinstance(f, Polynomial) + sage: is_Polynomial(f) True However this function does not return True for genuine multivariate @@ -166,15 +170,18 @@ cpdef is_Polynomial(f): sage: R. = QQ[] sage: f = y^3 + x*y -3*x; f y^3 + x*y - 3*x - sage: isinstance(f, Polynomial) + sage: is_Polynomial(f) False sage: var('x,y') (x, y) sage: f = y^3 + x*y -3*x; f y^3 + x*y - 3*x - sage: isinstance(f, Polynomial) + sage: is_Polynomial(f) False """ + from sage.misc.superseded import deprecation + deprecation(32709, "the function is_Polynomial is deprecated; use isinstance(x, sage.structure.element.Polynomial) instead") + return isinstance(f, Polynomial) from .polynomial_compiled cimport CompiledPolynomialFunction From ed050bbe039dc694cab318b9b3e6e4cf0c7e0e0f Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 6 Dec 2022 00:28:48 -0800 Subject: [PATCH 207/392] sage.rings.polynomial: Make it a namespace package --- src/sage/rings/polynomial/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/sage/rings/polynomial/__init__.py diff --git a/src/sage/rings/polynomial/__init__.py b/src/sage/rings/polynomial/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 From 0613280e8b65291b76fd7d440ba16db3f055b5c4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 6 Jan 2023 16:47:29 -0800 Subject: [PATCH 208/392] sage.structure.element: Introduce common base class CommutativePolynomial for ABCs Polynomial, MPolynomial --- src/sage/structure/element.pxd | 7 +++++-- src/sage/structure/element.pyx | 13 +++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index d6de9b9d2d3..d0a574a95a1 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -239,10 +239,13 @@ cdef class CommutativeAlgebraElement(CommutativeRingElement): cdef class Expression(CommutativeRingElement): pass -cdef class Polynomial(CommutativeAlgebraElement): +cdef class CommutativePolynomial(CommutativeAlgebraElement): pass -cdef class MPolynomial(CommutativeAlgebraElement): +cdef class Polynomial(CommutativePolynomial): + pass + +cdef class MPolynomial(CommutativePolynomial): pass cdef class InfinityElement(RingElement): diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index eb694681249..3c80f65040d 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -4293,7 +4293,16 @@ cdef class CommutativeAlgebraElement(CommutativeRingElement): ############################################## -cdef class Polynomial(CommutativeAlgebraElement): +cdef class CommutativePolynomial(CommutativeAlgebraElement): + r""" + Abstract base class, common base for :class:`Polynomial` and :class:`MPolynomial` + """ + + pass + + ############################################## + +cdef class Polynomial(CommutativePolynomial): r""" Abstract base class for :class:`~sage.rings.polynomial.polynomial_element.Polynomial` """ @@ -4302,7 +4311,7 @@ cdef class Polynomial(CommutativeAlgebraElement): ############################################## -cdef class MPolynomial(CommutativeAlgebraElement): +cdef class MPolynomial(CommutativePolynomial): r""" Abstract base class for :class:`~sage.rings.polynomial.multi_polynomial.MPolynomial` """ From 27e888972fb6df6db5fd28ea8a1a5c276417796c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 6 Jan 2023 17:02:09 -0800 Subject: [PATCH 209/392] sage.structure.element: Introduce ABC InfinitePolynomial --- .../polynomial/infinite_polynomial_element.py | 5 ++--- src/sage/structure/element.pyx | 14 +++++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index aacddf7a040..38c97fd3cf3 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -91,10 +91,9 @@ from sage.rings.integer_ring import ZZ from sage.rings.integer import Integer -from sage.structure.element import RingElement from sage.structure.richcmp import richcmp from sage.misc.cachefunc import cached_method -from sage.structure.element import MPolynomial +from sage.structure.element import RingElement, MPolynomial, InfinitePolynomial as InfinitePolynomial_base import copy @@ -206,7 +205,7 @@ def InfinitePolynomial(A, p): return InfinitePolynomial_sparse(A, p) -class InfinitePolynomial_sparse(RingElement): +class InfinitePolynomial_sparse(InfinitePolynomial_base): """ Element of a sparse Polynomial Ring with a Countably Infinite Number of Variables. diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 3c80f65040d..f0e9a802bf5 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -4295,7 +4295,10 @@ cdef class CommutativeAlgebraElement(CommutativeRingElement): cdef class CommutativePolynomial(CommutativeAlgebraElement): r""" - Abstract base class, common base for :class:`Polynomial` and :class:`MPolynomial` + Abstract base class for commutative polynomials in any number of variables + + It is a common base for :class:`Polynomial`, :class:`MPolynomial`, and + :class:`InfinitePolynomial`. """ pass @@ -4320,6 +4323,15 @@ cdef class MPolynomial(CommutativePolynomial): ############################################## +cdef class InfinitePolynomial(CommutativePolynomial): + r""" + Abstract base class for :class:`~sage.rings.polynomial.infinite_polynomial_element.InfinitePolynomial_sparse` + """ + + pass + + ############################################## + def is_InfinityElement(x): """ Return ``True`` if x is of type InfinityElement. From 57228f59679e4b9d90d3b3071618cd6bdadc744d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 6 Jan 2023 18:05:38 -0800 Subject: [PATCH 210/392] src/sage/structure/element.pyx: Update hierarchy in documentation --- src/sage/structure/element.pyx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index f0e9a802bf5..bcecea4f589 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -47,6 +47,10 @@ abstract base classes. EuclideanDomainElement FieldElement CommutativeAlgebraElement + CommutativePolynomial + Polynomial + MPolynomial + InfinitePolynomial Expression AlgebraElement Matrix From 95e0adc1e4fae31a9e0fc2243bc599ea26f9f024 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 6 Jan 2023 19:18:46 -0800 Subject: [PATCH 211/392] src/sage/structure/element.pyx: Add unique-direct-subclass tests --- src/sage/structure/element.pyx | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index bcecea4f589..9099451b0d6 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -4312,6 +4312,23 @@ cdef class CommutativePolynomial(CommutativeAlgebraElement): cdef class Polynomial(CommutativePolynomial): r""" Abstract base class for :class:`~sage.rings.polynomial.polynomial_element.Polynomial` + + This class is defined for the purpose of :func:`isinstance` tests. It should not be + instantiated. + + EXAMPLES:: + + sage: R1. = QQ[] + sage: isinstance(x, sage.structure.element.Polynomial) + True + sage: R2. = QQ[] + sage: isinstance(y, sage.structure.element.Polynomial) + False + + By design, there is a unique direct subclass:: + + sage: len(sage.structure.element.Polynomial.__subclasses__()) <= 1 + True """ pass @@ -4321,6 +4338,23 @@ cdef class Polynomial(CommutativePolynomial): cdef class MPolynomial(CommutativePolynomial): r""" Abstract base class for :class:`~sage.rings.polynomial.multi_polynomial.MPolynomial` + + This class is defined for the purpose of :func:`isinstance` tests. It should not be + instantiated. + + EXAMPLES:: + + sage: R1. = QQ[] + sage: isinstance(x, sage.structure.element.MPolynomial) + False + sage: R2. = QQ[] + sage: isinstance(y, sage.structure.element.MPolynomial) + True + + By design, there is a unique direct subclass:: + + sage: len(sage.structure.element.MPolynomial.__subclasses__()) <= 1 + True """ pass From 1fd625cddc8bb0e5cdafd8f08612ce886aaa5b7e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 6 Jan 2023 19:47:04 -0800 Subject: [PATCH 212/392] src/sage/structure/element.pyx: Add unique-direct-subclass test for Expression --- src/sage/structure/element.pyx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 9099451b0d6..bad783a11b4 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -3357,6 +3357,19 @@ cdef class Expression(CommutativeRingElement): r""" Abstract base class for :class:`~sage.symbolic.expression.Expression`. + + This class is defined for the purpose of :func:`isinstance` tests. It should not be + instantiated. + + EXAMPLES:: + + sage: isinstance(SR.var('y'), sage.structure.element.Expression) # optional - sage.symbolic + True + + By design, there is a unique direct subclass:: + + sage: len(sage.structure.element.Expression.__subclasses__()) <= 1 + True """ pass From 960506b6bf0939bbefa17702d794f91ccafb4282 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 17 Jan 2023 18:58:14 -0800 Subject: [PATCH 213/392] InfinitePolynomial: Change from constructor function to base class with __classcall__ --- .../polynomial/infinite_polynomial_element.py | 819 +++++++++--------- src/sage/structure/element.pyx | 16 +- 2 files changed, 426 insertions(+), 409 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 38c97fd3cf3..dd87d6e9c05 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -93,11 +93,12 @@ from sage.rings.integer import Integer from sage.structure.richcmp import richcmp from sage.misc.cachefunc import cached_method +from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass from sage.structure.element import RingElement, MPolynomial, InfinitePolynomial as InfinitePolynomial_base import copy -def InfinitePolynomial(A, p): +class InfinitePolynomial(InfinitePolynomial_base, metaclass=InheritComparisonClasscallMetaclass): """ Create an element of a Polynomial Ring with a Countably Infinite Number of Variables. @@ -163,73 +164,50 @@ def InfinitePolynomial(A, p): alpha_2^2 + alpha_1^2 """ - from sage.structure.element import parent - if hasattr(A, '_P'): - if parent(p) is A._P or (A._P.base_ring().has_coerce_map_from(parent(p))): - return InfinitePolynomial_dense(A, p) - # MPolynomialRing_polydict is crab. So, in that case, use sage_eval - from sage.rings.polynomial.multi_polynomial_ring import MPolynomialRing_polydict - if isinstance(A._P, MPolynomialRing_polydict): - from sage.rings.polynomial.infinite_polynomial_ring import GenDictWithBasering - from sage.misc.sage_eval import sage_eval - p = sage_eval(repr(p), GenDictWithBasering(A._P, A._P.gens_dict())) - return InfinitePolynomial_dense(A, p) - else: - # Now there remains to fight the oddities and bugs of libsingular. - PP = p.parent() - if A._P.has_coerce_map_from(PP): - if A._P.ngens() == PP.ngens(): # coercion is sometimes by position! - f = PP.hom(PP.variable_names(), A._P) - try: - return InfinitePolynomial_dense(A, f(p)) - except (ValueError, TypeError): - # last desperate attempt: String conversion - from sage.misc.sage_eval import sage_eval - from sage.rings.polynomial.infinite_polynomial_ring import GenDictWithBasering - # the base ring may be a function field, therefore - # we need GenDictWithBasering - return InfinitePolynomial_dense(A, sage_eval(repr(p), GenDictWithBasering(A._P, A._P.gens_dict()))) - return InfinitePolynomial_dense(A, A._P(p)) - # there is no coercion, so, we set up a name-preserving map. - SV = set(repr(x) for x in p.variables()) - f = PP.hom([x if x in SV else 0 for x in PP.variable_names()], A._P) - try: - return InfinitePolynomial_dense(A, f(p)) - except (ValueError, TypeError): - # last desperate attempt: String conversion - from sage.misc.sage_eval import sage_eval - from sage.rings.polynomial.infinite_polynomial_ring import GenDictWithBasering - # the base ring may be a function field, therefore - # we need GenDictWithBasering - return InfinitePolynomial_dense(A, sage_eval(repr(p), GenDictWithBasering(A._P, A._P.gens_dict()))) - return InfinitePolynomial_sparse(A, p) - - -class InfinitePolynomial_sparse(InfinitePolynomial_base): - """ - Element of a sparse Polynomial Ring with a Countably Infinite Number of Variables. - - INPUT: - - - ``A`` -- an Infinite Polynomial Ring in sparse implementation - - ``p`` -- a *classical* polynomial that can be interpreted in ``A``. - - Of course, one should not directly invoke this class, but rather - construct elements of ``A`` in the usual way. - EXAMPLES:: - - sage: A. = QQ[] - sage: B. = InfinitePolynomialRing(A,implementation='sparse') - sage: p = a*b[100] + 1/2*c[4] - sage: p - a*b_100 + 1/2*c_4 - sage: p.parent() - Infinite polynomial ring in b, c over Univariate Polynomial Ring in a over Rational Field - sage: p.polynomial().parent() - Multivariate Polynomial Ring in b_100, b_0, c_4, c_0 over Univariate Polynomial Ring in a over Rational Field + @staticmethod + def __classcall_private__(cls, A, p): + from sage.structure.element import parent + if hasattr(A, '_P'): + if parent(p) is A._P or (A._P.base_ring().has_coerce_map_from(parent(p))): + return InfinitePolynomial_dense(A, p) + # MPolynomialRing_polydict is crab. So, in that case, use sage_eval + from sage.rings.polynomial.multi_polynomial_ring import MPolynomialRing_polydict + if isinstance(A._P, MPolynomialRing_polydict): + from sage.rings.polynomial.infinite_polynomial_ring import GenDictWithBasering + from sage.misc.sage_eval import sage_eval + p = sage_eval(repr(p), GenDictWithBasering(A._P, A._P.gens_dict())) + return InfinitePolynomial_dense(A, p) + else: + # Now there remains to fight the oddities and bugs of libsingular. + PP = p.parent() + if A._P.has_coerce_map_from(PP): + if A._P.ngens() == PP.ngens(): # coercion is sometimes by position! + f = PP.hom(PP.variable_names(), A._P) + try: + return InfinitePolynomial_dense(A, f(p)) + except (ValueError, TypeError): + # last desperate attempt: String conversion + from sage.misc.sage_eval import sage_eval + from sage.rings.polynomial.infinite_polynomial_ring import GenDictWithBasering + # the base ring may be a function field, therefore + # we need GenDictWithBasering + return InfinitePolynomial_dense(A, sage_eval(repr(p), GenDictWithBasering(A._P, A._P.gens_dict()))) + return InfinitePolynomial_dense(A, A._P(p)) + # there is no coercion, so, we set up a name-preserving map. + SV = set(repr(x) for x in p.variables()) + f = PP.hom([x if x in SV else 0 for x in PP.variable_names()], A._P) + try: + return InfinitePolynomial_dense(A, f(p)) + except (ValueError, TypeError): + # last desperate attempt: String conversion + from sage.misc.sage_eval import sage_eval + from sage.rings.polynomial.infinite_polynomial_ring import GenDictWithBasering + # the base ring may be a function field, therefore + # we need GenDictWithBasering + return InfinitePolynomial_dense(A, sage_eval(repr(p), GenDictWithBasering(A._P, A._P.gens_dict()))) + return InfinitePolynomial_sparse(A, p) - """ # Construction and other basic methods # We assume that p is good input. Type checking etc. is now done # in the _element_constructor_ of the parent. @@ -299,56 +277,6 @@ def polynomial(self): """ return self._p - def __call__(self, *args, **kwargs): - """ - EXAMPLES:: - - sage: X. = InfinitePolynomialRing(QQ,implementation='sparse') - sage: a = x[0] + x[1] - sage: a(x_0=2,x_1=x[1]) - x_1 + 2 - sage: _.parent() - Infinite polynomial ring in x over Rational Field - sage: a(x_1=3) - x_0 + 3 - sage: _.parent() - Infinite polynomial ring in x over Rational Field - sage: a(x_1=x[100]) - x_100 + x_0 - - """ - # Replace any InfinitePolynomials by their underlying polynomials - if hasattr(self._p, 'variables'): - V = [str(x) for x in self._p.variables()] - else: - V = [] - for kw in kwargs: - value = kwargs[kw] - if isinstance(value, InfinitePolynomial_sparse): - kwargs[kw] = value._p - V.append(kw) - if hasattr(value._p, 'variables'): - V.extend([str(x) for x in value._p.variables()]) - args = list(args) - for i, arg in enumerate(args): - if isinstance(arg, InfinitePolynomial_sparse): - args[i] = arg._p - if hasattr(arg._p, 'variables'): - V.extend([str(x) for x in arg._p.variables()]) - V = list(set(V)) - V.sort(key=self.parent().varname_key, reverse=True) - if V: - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(self._p.base_ring(), V, order=self.parent()._order) - else: - return self - res = R(self._p)(*args, **kwargs) - try: - from sage.misc.sage_eval import sage_eval - return sage_eval(repr(res), self.parent().gens_dict()) - except Exception: - return res - def _getAttributeNames(self): """ This method implements tab completion, see :trac:`6854`. @@ -550,101 +478,6 @@ def max_index(self): """ return max([Integer(str(X).split('_')[1]) for X in self.variables()]+[-1]) - # Basic arithmetics - def _add_(self, x): - """ - EXAMPLES:: - - sage: X. = InfinitePolynomialRing(QQ) - sage: x[1] + x[2] # indirect doctest - x_2 + x_1 - - Check adding from a different parent:: - - sage: Y. = PolynomialRing(QQ) - sage: x[0] - x_0 - 0 - """ - # One may need a new parent for self._p and x._p - try: - return InfinitePolynomial_sparse(self.parent(), self._p + x._p) - except Exception: - pass - # We can now assume that self._p and x._p actually are polynomials, - # hence, their parent is not simply the underlying ring. - VarList = list(set(self._p.parent().variable_names()).union(set(x._p.parent().variable_names()))) - VarList.sort(key=self.parent().varname_key, reverse=True) - if VarList: - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) - else: - R = self._p.base_ring() - return InfinitePolynomial_sparse(self.parent(), R(self._p) + R(x._p)) - - def _mul_(self, x): - """ - EXAMPLES:: - - sage: X. = InfinitePolynomialRing(ZZ) - sage: x[2]*x[1] # indirect doctest - x_2*x_1 - - """ - try: - return InfinitePolynomial_sparse(self.parent(), self._p * x._p) - except Exception: - pass - # We can now assume that self._p and x._p actually are polynomials, - # hence, their parent is not just the underlying ring. - VarList = list(set(self._p.parent().variable_names()).union(set(x._p.parent().variable_names()))) - VarList.sort(key=self.parent().varname_key, reverse=True) - if VarList: - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) - else: - R = self._p.base_ring() - return InfinitePolynomial_sparse(self.parent(), R(self._p) * R(x._p)) - - def gcd(self, x): - """ - computes the greatest common divisor - - EXAMPLES:: - - sage: R.=InfinitePolynomialRing(QQ) - sage: p1=x[0]+x[1]**2 - sage: gcd(p1,p1+3) - 1 - sage: gcd(p1,p1)==p1 - True - """ - P = self.parent() - self._p = P._P(self._p) - x._p = P._P(x._p) - return InfinitePolynomial_sparse(self.parent(), self._p.gcd(x._p)) - - def _rmul_(self, left): - """ - TESTS:: - - sage: R. = InfinitePolynomialRing(QQ, implementation='sparse') - sage: R.from_base_ring(4) # indirect doctest - 4 - - """ - return InfinitePolynomial_sparse(self.parent(), left * self._p) - - def _lmul_(self, right): - """ - TESTS:: - - sage: R. = InfinitePolynomialRing(QQ, implementation='sparse') - sage: alpha[3]*4 # indirect doctest - 4*alpha_3 - - """ - return InfinitePolynomial_sparse(self.parent(), self._p * right) - def _div_(self, x): r""" Division of Infinite Polynomials. @@ -719,213 +552,52 @@ def _floordiv_(self, x): R = self._p.base_ring() return InfinitePolynomial_sparse(self.parent(), R(self._p) // R(x._p)) - def _sub_(self, x): + @cached_method + def lm(self): """ + The leading monomial of ``self``. + EXAMPLES:: - sage: X. = InfinitePolynomialRing(QQ) - sage: x[2] - x[1] # indirect doctest - x_2 - x_1 + sage: X. = InfinitePolynomialRing(QQ) + sage: p = 2*x[10]*y[30]+x[10]*y[1]^3*x[1]^2 + sage: p.lm() + x_10*x_1^2*y_1^3 """ - try: - return InfinitePolynomial_sparse(self.parent(), self._p - x._p) - except Exception: - pass - # We can now assume that self._p and x._p actually are polynomials, - # hence, their parent is not just the underlying ring. - VarList = list(set(self._p.parent().variable_names()).union(x._p.parent().variable_names())) - VarList.sort(key=self.parent().varname_key, reverse=True) - if VarList: - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) - else: - R = self._p.base_ring() - return InfinitePolynomial_sparse(self.parent(), R(self._p) - R(x._p)) + if hasattr(self._p, 'lm'): + return InfinitePolynomial(self.parent(), self._p.lm()) + if self._p == 0: + return self + if hasattr(self._p, 'variable_name'): # if it is univariate + return InfinitePolynomial(self.parent(), + self._p.parent().gen() ** max(self._p.exponents())) + return self # if it is scalar - def __pow__(self, n): + @cached_method + def lc(self): """ - Exponentiation by an integer, or action by a callable object - - NOTE: - - The callable object must accept non-negative integers as input - and return non-negative integers. Typical use case is a - permutation, that will result in the corresponding permutation - of variables. + The coefficient of the leading term of ``self``. EXAMPLES:: - sage: X. = InfinitePolynomialRing(QQ,implementation='sparse') - sage: p = x[10]*y[2]+2*x[1]*y[3] - sage: P = Permutation(((1,2),(3,4,5))) - sage: p^P # indirect doctest - x_10*y_1 + 2*x_2*y_4 + sage: X. = InfinitePolynomialRing(QQ) + sage: p = 2*x[10]*y[30]+3*x[10]*y[1]^3*x[1]^2 + sage: p.lc() + 3 """ - P = self.parent() - if callable(n): - if (self._p.parent() == self._p.base_ring()): - return self - if not (hasattr(self._p, 'variables') and self._p.variables()): - return self - if hasattr(n, 'to_cycles') and hasattr(n, '__len__'): # duck typing Permutation - # auxiliary function, necessary since n(m) raises an error if m>len(n) - l = len(n) + if hasattr(self._p, 'lc'): + return self._p.lc() + if hasattr(self._p, 'variable_name'): # univariate case + return self._p.leading_coefficient() + # scalar case + return self._p - def p(m): - return n(m) if 0 < m <= l else m - else: # Permutation group element - p = n - - def q(s): return s[0]+'_'+str(p(ZZ(s[1]))) - newVars = [q(X.split('_')) for X in self._p.parent().variable_names()] - if not newVars: - return self - copyVars = copy.copy(newVars) - newVars = list(set(list(self._p.parent().variable_names())+newVars)) - newVars.sort(key=self.parent().varname_key, reverse=True) - if newVars == list(self._p.parent().variable_names()): - newR = self._p.parent() - else: - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - newR = PolynomialRing(self._p.base_ring(), newVars, order=P._order) - mapR = self._p.parent().hom(copyVars, newR) - return InfinitePolynomial_sparse(self.parent(), mapR(self._p)) - return InfinitePolynomial_sparse(self.parent(), self._p**n) - - # Basic tools for Buchberger algorithm: - # order, leading term/monomial, symmetric cancellation order - - def _richcmp_(self, x, op): - r""" - Comparison of Infinite Polynomials. - - NOTE: - - Let x and y be generators of the parent of self. We only consider - monomial orderings in which x[m] > y[n] iff x appears earlier in the - list of generators than y, or x==y and m>n - - Under this restriction, the monomial ordering can be 'lex' (default), - 'degrevlex' or 'deglex'. - - EXAMPLES:: - - sage: X. = InfinitePolynomialRing(QQ, implementation='sparse') - sage: a = x[10]^3 - sage: b = x[1] + x[2] - sage: c = x[1] + x[2] - sage: d = y[1] + x[2] - sage: a == a # indirect doctest - True - sage: b == c # indirect doctest - True - sage: a == b # indirect doctest - False - sage: c > d # indirect doctest - True - - TESTS: - - A classical and an infinite sparse polynomial ring. Note that - the Sage coercion system allows comparison only if a common - parent for the two rings can be constructed. This is why we - have to have the order 'degrevlex':: - - sage: X. = InfinitePolynomialRing(ZZ,order='degrevlex', implementation='sparse') - sage: Y. = QQ[] - sage: x[3] == x_3 # indirect doctest - True - - Two infinite polynomial rings in different implementation and - order:: - - sage: Y = InfinitePolynomialRing(QQ,['x','y'],order='deglex',implementation='dense') - sage: x[2] == Y(x[2]) # indirect doctest - True - - An example in which a previous version had failed:: - - sage: X. = InfinitePolynomialRing(GF(3), order='degrevlex', implementation='sparse') - sage: p = Y('x_3*x_0^2 + x_0*y_3*y_0') - sage: q = Y('x_1*x_0^2 + x_0*y_1*y_0') - sage: p < q # indirect doctest - False - - """ - # We can assume that self.parent() is x.parent(), - # but of course the underlying polynomial rings - # may be widely different, and the sage coercion - # system can't guess what order we want. - from sage.structure.element import parent - R1 = parent(self._p) - R2 = parent(x._p) - if (hasattr(R1, 'has_coerce_map_from') and R1.has_coerce_map_from(R2)) or (hasattr(R2, 'has_coerce_map_from') and R2.has_coerce_map_from(R1)): - return richcmp(self._p, x._p, op) - VarList = list(set(self._p.parent().variable_names()).union(x._p.parent().variable_names())) - VarList.sort(key=self.parent().varname_key, reverse=True) - if VarList: - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) - else: - R = self._p.base_ring() - if (self._p.parent() is self._p.base_ring()) or not self._p.parent().gens(): - fself = self._p.base_ring() - else: - fself = self._p.parent().hom(self._p.parent().variable_names(), R) - if (x._p.parent() is x._p.base_ring()) or not x._p.parent().gens(): - fx = x._p.base_ring() - else: - fx = x._p.parent().hom(x._p.parent().variable_names(), R) - return richcmp(fself(self._p), fx(x._p), op) - - @cached_method - def lm(self): - """ - The leading monomial of ``self``. - - EXAMPLES:: - - sage: X. = InfinitePolynomialRing(QQ) - sage: p = 2*x[10]*y[30]+x[10]*y[1]^3*x[1]^2 - sage: p.lm() - x_10*x_1^2*y_1^3 - - """ - if hasattr(self._p, 'lm'): - return InfinitePolynomial(self.parent(), self._p.lm()) - if self._p == 0: - return self - if hasattr(self._p, 'variable_name'): # if it is univariate - return InfinitePolynomial(self.parent(), - self._p.parent().gen() ** max(self._p.exponents())) - return self # if it is scalar - - @cached_method - def lc(self): - """ - The coefficient of the leading term of ``self``. - - EXAMPLES:: - - sage: X. = InfinitePolynomialRing(QQ) - sage: p = 2*x[10]*y[30]+3*x[10]*y[1]^3*x[1]^2 - sage: p.lc() - 3 - - """ - if hasattr(self._p, 'lc'): - return self._p.lc() - if hasattr(self._p, 'variable_name'): # univariate case - return self._p.leading_coefficient() - # scalar case - return self._p - - @cached_method - def lt(self): - """ - The leading term (= product of coefficient and monomial) of ``self``. + @cached_method + def lt(self): + """ + The leading term (= product of coefficient and monomial) of ``self``. EXAMPLES:: @@ -1377,8 +1049,341 @@ def __iter__(self): self.__class__(self.parent(), monomial)) for coefficient, monomial in self._p) + def gcd(self, x): + """ + computes the greatest common divisor + + EXAMPLES:: + + sage: R.=InfinitePolynomialRing(QQ) + sage: p1=x[0]+x[1]**2 + sage: gcd(p1,p1+3) + 1 + sage: gcd(p1,p1)==p1 + True + """ + P = self.parent() + self._p = P._P(self._p) + x._p = P._P(x._p) + return self.__class__.__base__(self.parent(), self._p.gcd(x._p)) + + +class InfinitePolynomial_sparse(InfinitePolynomial): + """ + Element of a sparse Polynomial Ring with a Countably Infinite Number of Variables. + + INPUT: + + - ``A`` -- an Infinite Polynomial Ring in sparse implementation + - ``p`` -- a *classical* polynomial that can be interpreted in ``A``. + + Of course, one should not directly invoke this class, but rather + construct elements of ``A`` in the usual way. + + EXAMPLES:: + + sage: A. = QQ[] + sage: B. = InfinitePolynomialRing(A,implementation='sparse') + sage: p = a*b[100] + 1/2*c[4] + sage: p + a*b_100 + 1/2*c_4 + sage: p.parent() + Infinite polynomial ring in b, c over Univariate Polynomial Ring in a over Rational Field + sage: p.polynomial().parent() + Multivariate Polynomial Ring in b_100, b_0, c_4, c_0 over Univariate Polynomial Ring in a over Rational Field -class InfinitePolynomial_dense(InfinitePolynomial_sparse): + """ + + def __call__(self, *args, **kwargs): + """ + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(QQ,implementation='sparse') + sage: a = x[0] + x[1] + sage: a(x_0=2,x_1=x[1]) + x_1 + 2 + sage: _.parent() + Infinite polynomial ring in x over Rational Field + sage: a(x_1=3) + x_0 + 3 + sage: _.parent() + Infinite polynomial ring in x over Rational Field + sage: a(x_1=x[100]) + x_100 + x_0 + + """ + # Replace any InfinitePolynomials by their underlying polynomials + if hasattr(self._p, 'variables'): + V = [str(x) for x in self._p.variables()] + else: + V = [] + for kw in kwargs: + value = kwargs[kw] + if isinstance(value, InfinitePolynomial_sparse): + kwargs[kw] = value._p + V.append(kw) + if hasattr(value._p, 'variables'): + V.extend([str(x) for x in value._p.variables()]) + args = list(args) + for i, arg in enumerate(args): + if isinstance(arg, InfinitePolynomial_sparse): + args[i] = arg._p + if hasattr(arg._p, 'variables'): + V.extend([str(x) for x in arg._p.variables()]) + V = list(set(V)) + V.sort(key=self.parent().varname_key, reverse=True) + if V: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(self._p.base_ring(), V, order=self.parent()._order) + else: + return self + res = R(self._p)(*args, **kwargs) + try: + from sage.misc.sage_eval import sage_eval + return sage_eval(repr(res), self.parent().gens_dict()) + except Exception: + return res + + # Basic arithmetics + def _add_(self, x): + """ + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(QQ) + sage: x[1] + x[2] # indirect doctest + x_2 + x_1 + + Check adding from a different parent:: + + sage: Y. = PolynomialRing(QQ) + sage: x[0] - x_0 + 0 + """ + # One may need a new parent for self._p and x._p + try: + return InfinitePolynomial_sparse(self.parent(), self._p + x._p) + except Exception: + pass + # We can now assume that self._p and x._p actually are polynomials, + # hence, their parent is not simply the underlying ring. + VarList = list(set(self._p.parent().variable_names()).union(set(x._p.parent().variable_names()))) + VarList.sort(key=self.parent().varname_key, reverse=True) + if VarList: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) + else: + R = self._p.base_ring() + return InfinitePolynomial_sparse(self.parent(), R(self._p) + R(x._p)) + + def _mul_(self, x): + """ + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(ZZ) + sage: x[2]*x[1] # indirect doctest + x_2*x_1 + + """ + try: + return InfinitePolynomial_sparse(self.parent(), self._p * x._p) + except Exception: + pass + # We can now assume that self._p and x._p actually are polynomials, + # hence, their parent is not just the underlying ring. + VarList = list(set(self._p.parent().variable_names()).union(set(x._p.parent().variable_names()))) + VarList.sort(key=self.parent().varname_key, reverse=True) + if VarList: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) + else: + R = self._p.base_ring() + return InfinitePolynomial_sparse(self.parent(), R(self._p) * R(x._p)) + + def _rmul_(self, left): + """ + TESTS:: + + sage: R. = InfinitePolynomialRing(QQ, implementation='sparse') + sage: R.from_base_ring(4) # indirect doctest + 4 + + """ + return InfinitePolynomial_sparse(self.parent(), left * self._p) + + def _lmul_(self, right): + """ + TESTS:: + + sage: R. = InfinitePolynomialRing(QQ, implementation='sparse') + sage: alpha[3]*4 # indirect doctest + 4*alpha_3 + + """ + return InfinitePolynomial_sparse(self.parent(), self._p * right) + + def _sub_(self, x): + """ + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(QQ) + sage: x[2] - x[1] # indirect doctest + x_2 - x_1 + + """ + try: + return InfinitePolynomial_sparse(self.parent(), self._p - x._p) + except Exception: + pass + # We can now assume that self._p and x._p actually are polynomials, + # hence, their parent is not just the underlying ring. + VarList = list(set(self._p.parent().variable_names()).union(x._p.parent().variable_names())) + VarList.sort(key=self.parent().varname_key, reverse=True) + if VarList: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) + else: + R = self._p.base_ring() + return InfinitePolynomial_sparse(self.parent(), R(self._p) - R(x._p)) + + def __pow__(self, n): + """ + Exponentiation by an integer, or action by a callable object + + NOTE: + + The callable object must accept non-negative integers as input + and return non-negative integers. Typical use case is a + permutation, that will result in the corresponding permutation + of variables. + + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(QQ,implementation='sparse') + sage: p = x[10]*y[2]+2*x[1]*y[3] + sage: P = Permutation(((1,2),(3,4,5))) + sage: p^P # indirect doctest + x_10*y_1 + 2*x_2*y_4 + + """ + P = self.parent() + if callable(n): + if (self._p.parent() == self._p.base_ring()): + return self + if not (hasattr(self._p, 'variables') and self._p.variables()): + return self + if hasattr(n, 'to_cycles') and hasattr(n, '__len__'): # duck typing Permutation + # auxiliary function, necessary since n(m) raises an error if m>len(n) + l = len(n) + + def p(m): + return n(m) if 0 < m <= l else m + else: # Permutation group element + p = n + + def q(s): return s[0]+'_'+str(p(ZZ(s[1]))) + newVars = [q(X.split('_')) for X in self._p.parent().variable_names()] + if not newVars: + return self + copyVars = copy.copy(newVars) + newVars = list(set(list(self._p.parent().variable_names())+newVars)) + newVars.sort(key=self.parent().varname_key, reverse=True) + if newVars == list(self._p.parent().variable_names()): + newR = self._p.parent() + else: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + newR = PolynomialRing(self._p.base_ring(), newVars, order=P._order) + mapR = self._p.parent().hom(copyVars, newR) + return InfinitePolynomial_sparse(self.parent(), mapR(self._p)) + return InfinitePolynomial_sparse(self.parent(), self._p**n) + + # Basic tools for Buchberger algorithm: + # order, leading term/monomial, symmetric cancellation order + + def _richcmp_(self, x, op): + r""" + Comparison of Infinite Polynomials. + + NOTE: + + Let x and y be generators of the parent of self. We only consider + monomial orderings in which x[m] > y[n] iff x appears earlier in the + list of generators than y, or x==y and m>n + + Under this restriction, the monomial ordering can be 'lex' (default), + 'degrevlex' or 'deglex'. + + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(QQ, implementation='sparse') + sage: a = x[10]^3 + sage: b = x[1] + x[2] + sage: c = x[1] + x[2] + sage: d = y[1] + x[2] + sage: a == a # indirect doctest + True + sage: b == c # indirect doctest + True + sage: a == b # indirect doctest + False + sage: c > d # indirect doctest + True + + TESTS: + + A classical and an infinite sparse polynomial ring. Note that + the Sage coercion system allows comparison only if a common + parent for the two rings can be constructed. This is why we + have to have the order 'degrevlex':: + + sage: X. = InfinitePolynomialRing(ZZ,order='degrevlex', implementation='sparse') + sage: Y. = QQ[] + sage: x[3] == x_3 # indirect doctest + True + + Two infinite polynomial rings in different implementation and + order:: + + sage: Y = InfinitePolynomialRing(QQ,['x','y'],order='deglex',implementation='dense') + sage: x[2] == Y(x[2]) # indirect doctest + True + + An example in which a previous version had failed:: + + sage: X. = InfinitePolynomialRing(GF(3), order='degrevlex', implementation='sparse') + sage: p = Y('x_3*x_0^2 + x_0*y_3*y_0') + sage: q = Y('x_1*x_0^2 + x_0*y_1*y_0') + sage: p < q # indirect doctest + False + + """ + # We can assume that self.parent() is x.parent(), + # but of course the underlying polynomial rings + # may be widely different, and the sage coercion + # system can't guess what order we want. + from sage.structure.element import parent + R1 = parent(self._p) + R2 = parent(x._p) + if (hasattr(R1, 'has_coerce_map_from') and R1.has_coerce_map_from(R2)) or (hasattr(R2, 'has_coerce_map_from') and R2.has_coerce_map_from(R1)): + return richcmp(self._p, x._p, op) + VarList = list(set(self._p.parent().variable_names()).union(x._p.parent().variable_names())) + VarList.sort(key=self.parent().varname_key, reverse=True) + if VarList: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(self._p.base_ring(), VarList, order=self.parent()._order) + else: + R = self._p.base_ring() + if (self._p.parent() is self._p.base_ring()) or not self._p.parent().gens(): + fself = self._p.base_ring() + else: + fself = self._p.parent().hom(self._p.parent().variable_names(), R) + if (x._p.parent() is x._p.base_ring()) or not x._p.parent().gens(): + fx = x._p.base_ring() + else: + fx = x._p.parent().hom(x._p.parent().variable_names(), R) + return richcmp(fself(self._p), fx(x._p), op) + + +class InfinitePolynomial_dense(InfinitePolynomial): """ Element of a dense Polynomial Ring with a Countably Infinite Number of Variables. @@ -1394,8 +1399,6 @@ class InfinitePolynomial_dense(InfinitePolynomial_sparse): :class:`~sage.rings.polynomial.infinite_polynomial_element.InfinitePolynomial_sparse`. See there for a description of the methods. """ - # Construction and other basic methods -## def __init__(self, A, p): # is inherited from the dense implementation def __call__(self, *args, **kwargs): """ diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index bad783a11b4..6c4bff702e4 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -4376,7 +4376,21 @@ cdef class MPolynomial(CommutativePolynomial): cdef class InfinitePolynomial(CommutativePolynomial): r""" - Abstract base class for :class:`~sage.rings.polynomial.infinite_polynomial_element.InfinitePolynomial_sparse` + Abstract base class for :class:`~sage.rings.polynomial.infinite_polynomial_element.InfinitePolynomial` + + This class is defined for the purpose of :func:`isinstance` tests. It should not be + instantiated. + + EXAMPLES:: + + sage: X. = InfinitePolynomialRing(QQ) + sage: isinstance(x[0], sage.structure.element.InfinitePolynomial) + True + + By design, there is a unique direct subclass:: + + sage: len(sage.structure.element.InfinitePolynomial.__subclasses__()) <= 1 + True """ pass From fd54c82b627c8d2f2bdd2cf0f73583bc7bdf8156 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 17 Jan 2023 23:40:11 -0800 Subject: [PATCH 214/392] Remove ABCs sage.structure.element.*Polynomial; introduce ABCs in sage.rings.polynomial --- src/sage/combinat/kazhdan_lusztig.py | 2 +- src/sage/combinat/schubert_polynomial.py | 2 +- src/sage/combinat/sf/sfa.py | 4 +- src/sage/crypto/boolean_function.pyx | 2 +- src/sage/crypto/mq/rijndael_gf.py | 2 +- src/sage/crypto/sbox.pyx | 2 +- src/sage/crypto/stream.py | 2 +- src/sage/groups/affine_gps/group_element.py | 4 +- .../groups/perm_gps/permgroup_element.pyx | 4 +- src/sage/libs/symmetrica/symmetrica.pxi | 4 +- .../characteristic_cohomology_class.py | 2 +- src/sage/modular/modsym/ambient.py | 2 +- src/sage/quadratic_forms/binary_qf.py | 2 +- src/sage/quadratic_forms/constructions.py | 2 +- .../rings/finite_rings/element_ntl_gf2e.pyx | 2 +- .../rings/finite_rings/finite_field_base.pyx | 2 +- .../finite_rings/finite_field_constructor.py | 2 +- .../rings/finite_rings/finite_field_givaro.py | 2 +- .../finite_rings/finite_field_ntl_gf2e.py | 2 +- src/sage/rings/finite_rings/residue_field.pyx | 2 +- .../rings/function_field/function_field.py | 2 +- src/sage/rings/laurent_series_ring.py | 3 +- src/sage/rings/number_field/number_field.py | 2 +- src/sage/rings/padics/factory.py | 2 +- .../rings/padics/padic_template_element.pxi | 3 +- .../polynomial/commutative_polynomial.pxd | 5 + .../polynomial/commutative_polynomial.pyx | 10 ++ .../polynomial/infinite_polynomial_element.py | 6 +- .../rings/polynomial/multi_polynomial.pxd | 8 +- .../rings/polynomial/multi_polynomial.pyx | 25 ++++- .../multi_polynomial_libsingular.pxd | 4 +- .../multi_polynomial_libsingular.pyx | 2 +- .../rings/polynomial/polynomial_element.pxd | 4 +- .../rings/polynomial/polynomial_element.pyx | 2 +- src/sage/rings/qqbar.py | 2 +- .../schemes/berkovich/berkovich_cp_element.py | 4 +- src/sage/schemes/curves/constructor.py | 2 +- src/sage/schemes/cyclic_covers/constructor.py | 2 +- .../schemes/elliptic_curves/constructor.py | 2 +- .../elliptic_curves/ell_curve_isogeny.py | 2 +- src/sage/schemes/elliptic_curves/jacobian.py | 2 +- src/sage/schemes/generic/hypersurface.py | 2 +- .../hyperelliptic_curves/constructor.py | 2 +- .../hyperelliptic_curves/jacobian_homset.py | 2 +- .../hyperelliptic_curves/monsky_washnitzer.py | 2 +- src/sage/schemes/plane_conics/constructor.py | 2 +- .../plane_quartics/quartic_constructor.py | 2 +- src/sage/structure/element.pxd | 9 -- src/sage/structure/element.pyx | 91 ------------------- 49 files changed, 100 insertions(+), 154 deletions(-) create mode 100644 src/sage/rings/polynomial/commutative_polynomial.pxd create mode 100644 src/sage/rings/polynomial/commutative_polynomial.pyx diff --git a/src/sage/combinat/kazhdan_lusztig.py b/src/sage/combinat/kazhdan_lusztig.py index 75995aa34e4..c630e185c43 100644 --- a/src/sage/combinat/kazhdan_lusztig.py +++ b/src/sage/combinat/kazhdan_lusztig.py @@ -20,7 +20,7 @@ #***************************************************************************** -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.misc.cachefunc import cached_method from sage.rings.polynomial.laurent_polynomial import LaurentPolynomial from sage.structure.sage_object import SageObject diff --git a/src/sage/combinat/schubert_polynomial.py b/src/sage/combinat/schubert_polynomial.py index 070e1b1e5bf..fdc81d19abc 100644 --- a/src/sage/combinat/schubert_polynomial.py +++ b/src/sage/combinat/schubert_polynomial.py @@ -78,7 +78,7 @@ from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.structure.element import MPolynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.combinat.permutation import Permutations, Permutation import sage.libs.symmetrica.all as symmetrica from sage.misc.cachefunc import cached_method diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index cefc2d7f473..91091a48c1f 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -217,8 +217,8 @@ from sage.rings.integer import Integer from sage.rings.infinity import infinity from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.structure.element import Polynomial -from sage.structure.element import MPolynomial +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.combinat.partition import _Partitions, Partitions, Partitions_n, Partition from sage.categories.hopf_algebras import HopfAlgebras from sage.categories.hopf_algebras_with_basis import HopfAlgebrasWithBasis diff --git a/src/sage/crypto/boolean_function.pyx b/src/sage/crypto/boolean_function.pyx index 1358ae1b958..cb251a4f03f 100644 --- a/src/sage/crypto/boolean_function.pyx +++ b/src/sage/crypto/boolean_function.pyx @@ -40,7 +40,7 @@ from sage.rings.finite_rings.finite_field_constructor import GF from sage.rings.polynomial.pbori.pbori import BooleanPolynomial from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.finite_rings.finite_field_givaro import FiniteField_givaro -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.misc.superseded import deprecated_function_alias diff --git a/src/sage/crypto/mq/rijndael_gf.py b/src/sage/crypto/mq/rijndael_gf.py index 30160378077..fc3473d1b06 100644 --- a/src/sage/crypto/mq/rijndael_gf.py +++ b/src/sage/crypto/mq/rijndael_gf.py @@ -1549,7 +1549,7 @@ def compose(self, f, g, algorithm='encrypt', f_attr=None, g_attr=None): if not isinstance(f, RijndaelGF.Round_Component_Poly_Constr): msg = "keyword 'f' must be a Round_Component_Poly_Constr" raise TypeError(msg) - from sage.structure.element import MPolynomial + from sage.rings.polynomial.multi_polynomial import MPolynomial if not isinstance(g, RijndaelGF.Round_Component_Poly_Constr) and \ not isinstance(g, MPolynomial): msg = ("keyword 'g' must be a Round_Component_Poly_Constr or a " diff --git a/src/sage/crypto/sbox.pyx b/src/sage/crypto/sbox.pyx index 47d2f6c5e69..5c63e79bbac 100644 --- a/src/sage/crypto/sbox.pyx +++ b/src/sage/crypto/sbox.pyx @@ -173,7 +173,7 @@ cdef class SBox(SageObject): sage: S.output_size() 3 """ - from sage.structure.element import Polynomial + from sage.rings.polynomial.polynomial_element import Polynomial if "S" in kwargs: args = kwargs["S"] diff --git a/src/sage/crypto/stream.py b/src/sage/crypto/stream.py index ab7c86b16a3..90e4a6f5133 100644 --- a/src/sage/crypto/stream.py +++ b/src/sage/crypto/stream.py @@ -20,7 +20,7 @@ from sage.arith.all import gcd, power_mod from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.rings.finite_rings.integer_mod_ring import IntegerModFactory -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial IntegerModRing = IntegerModFactory("IntegerModRing") diff --git a/src/sage/groups/affine_gps/group_element.py b/src/sage/groups/affine_gps/group_element.py index 3ad22c1d96b..127c1d33cb5 100644 --- a/src/sage/groups/affine_gps/group_element.py +++ b/src/sage/groups/affine_gps/group_element.py @@ -414,12 +414,12 @@ def __call__(self, v): if v in parent.vector_space(): return self._A*v + self._b - from sage.structure.element import Polynomial + from sage.rings.polynomial.polynomial_element import Polynomial if isinstance(v, Polynomial) and parent.degree() == 1: ring = v.parent() return ring([self._A[0,0], self._b[0]]) - from sage.structure.element import MPolynomial + from sage.rings.polynomial.multi_polynomial import MPolynomial if isinstance(v, MPolynomial) and parent.degree() == v.parent().ngens(): ring = v.parent() from sage.modules.free_module_element import vector diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index b06873b7f3d..84907ab0c61 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -114,8 +114,8 @@ from cypari2.gen cimport Gen from sage.ext.stdsage cimport HAS_DICTIONARY from sage.rings.all import ZZ, Integer -from sage.structure.element import Polynomial -from sage.structure.element import MPolynomial +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.structure.element import is_Matrix from sage.matrix.all import MatrixSpace from sage.sets.finite_enumerated_set import FiniteEnumeratedSet diff --git a/src/sage/libs/symmetrica/symmetrica.pxi b/src/sage/libs/symmetrica/symmetrica.pxi index 06330bffbd4..95f9e52fbda 100644 --- a/src/sage/libs/symmetrica/symmetrica.pxi +++ b/src/sage/libs/symmetrica/symmetrica.pxi @@ -453,8 +453,8 @@ cdef void late_import(): import sage.rings.polynomial.multi_polynomial_ring MPolynomialRing_base = sage.rings.polynomial.multi_polynomial_ring.MPolynomialRing_base - import sage.rings.polynomial.multi_polynomial_element - MPolynomial = sage.structure.element.MPolynomial + import sage.rings.polynomial.multi_polynomial + MPolynomial = sage.rings.polynomial.multi_polynomial.MPolynomial import sage.combinat.schubert_polynomial SchubertPolynomialRing = sage.combinat.schubert_polynomial.SchubertPolynomialRing diff --git a/src/sage/manifolds/differentiable/characteristic_cohomology_class.py b/src/sage/manifolds/differentiable/characteristic_cohomology_class.py index 144edfcd8f3..1af1c0c6d74 100644 --- a/src/sage/manifolds/differentiable/characteristic_cohomology_class.py +++ b/src/sage/manifolds/differentiable/characteristic_cohomology_class.py @@ -290,7 +290,7 @@ from .bundle_connection import BundleConnection from .levi_civita_connection import LeviCivitaConnection from sage.symbolic.expression import Expression -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index 53393317183..e7ca7c669e2 100644 --- a/src/sage/modular/modsym/ambient.py +++ b/src/sage/modular/modsym/ambient.py @@ -95,7 +95,7 @@ class ``ModularSymbolsAmbient``, derived from from sage.modules.free_module_element import FreeModuleElement from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ -from sage.structure.element import MPolynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.rings.rational_field import QQ from sage.rings.ring import Ring from sage.structure.factorization import Factorization diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py index 727077a7197..cf0ac737f14 100755 --- a/src/sage/quadratic_forms/binary_qf.py +++ b/src/sage/quadratic_forms/binary_qf.py @@ -130,7 +130,7 @@ def __init__(self, a, b=None, c=None): sage: BinaryQF(0) 0 """ - from sage.structure.element import MPolynomial + from sage.rings.polynomial.multi_polynomial import MPolynomial if b is None and c is None: if (isinstance(a, (list, tuple)) and len(a) == 3): diff --git a/src/sage/quadratic_forms/constructions.py b/src/sage/quadratic_forms/constructions.py index 7d03d5e003d..43d3876f435 100644 --- a/src/sage/quadratic_forms/constructions.py +++ b/src/sage/quadratic_forms/constructions.py @@ -6,7 +6,7 @@ ## from sage.rings.integer_ring import ZZ -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.quadratic_forms.quadratic_form import QuadraticForm diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index 38b1aaf2ce7..16065f47966 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -1004,7 +1004,7 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): sage: e.polynomial() a^15 + a^13 + a^11 + a^10 + a^9 + a^8 + a^7 + a^6 + a^4 + a + 1 - sage: from sage.structure.element import Polynomial + sage: from sage.rings.polynomial.polynomial_element import Polynomial sage: isinstance(e.polynomial(), Polynomial) True diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index 125fbf6770c..9927c9e534d 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -1521,7 +1521,7 @@ cdef class FiniteField(Field): True """ from .finite_field_constructor import GF - from sage.structure.element import Polynomial + from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.integer import Integer if name is None and names is not None: name = names diff --git a/src/sage/rings/finite_rings/finite_field_constructor.py b/src/sage/rings/finite_rings/finite_field_constructor.py index 2f6a75b5abb..8b51fd37b2e 100644 --- a/src/sage/rings/finite_rings/finite_field_constructor.py +++ b/src/sage/rings/finite_rings/finite_field_constructor.py @@ -173,7 +173,7 @@ from collections import defaultdict from sage.structure.category_object import normalize_names -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.integer import Integer # the import below is just a redirection diff --git a/src/sage/rings/finite_rings/finite_field_givaro.py b/src/sage/rings/finite_rings/finite_field_givaro.py index acf64553f50..087db824de6 100644 --- a/src/sage/rings/finite_rings/finite_field_givaro.py +++ b/src/sage/rings/finite_rings/finite_field_givaro.py @@ -137,7 +137,7 @@ def __init__(self, q, name="a", modulus=None, repr="poly", cache=False): from .finite_field_constructor import GF FiniteField.__init__(self, GF(p), name, normalize=False) - from sage.structure.element import Polynomial + from sage.rings.polynomial.polynomial_element import Polynomial if not isinstance(modulus, Polynomial): raise TypeError("modulus must be a polynomial") diff --git a/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py b/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py index 1227d71ce67..e750f2fef5e 100644 --- a/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py +++ b/src/sage/rings/finite_rings/finite_field_ntl_gf2e.py @@ -138,7 +138,7 @@ def __init__(self, q, names="a", modulus=None, repr="poly"): raise ValueError("q must be a 2-power") FiniteField.__init__(self, GF2, names, normalize=True) - from sage.structure.element import Polynomial + from sage.rings.polynomial.polynomial_element import Polynomial if not isinstance(modulus, Polynomial): raise TypeError("modulus must be a polynomial") diff --git a/src/sage/rings/finite_rings/residue_field.pyx b/src/sage/rings/finite_rings/residue_field.pyx index 83db0a58dca..225fafef0d3 100644 --- a/src/sage/rings/finite_rings/residue_field.pyx +++ b/src/sage/rings/finite_rings/residue_field.pyx @@ -170,7 +170,7 @@ from sage.rings.fraction_field import is_FractionField from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.polynomial_ring import is_PolynomialRing -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.structure.factory import UniqueFactory from sage.structure.element cimport parent diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index e2f5aea7b83..06eb19905d8 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1307,7 +1307,7 @@ def __init__(self, polynomial, names, category=None): TypeError: unable to evaluate 'x' in Fraction Field of Univariate Polynomial Ring in t over Rational Field """ - from sage.structure.element import Polynomial + from sage.rings.polynomial.polynomial_element import Polynomial if polynomial.parent().ngens()>1 or not isinstance(polynomial, Polynomial): raise TypeError("polynomial must be univariate a polynomial") if names is None: diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index 37b0455928d..be921f22154 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -466,7 +466,8 @@ def _element_constructor_(self, x, n=0, prec=infinity): x^-3 """ from sage.rings.fraction_field_element import is_FractionFieldElement - from sage.structure.element import Polynomial, MPolynomial + from sage.rings.polynomial.polynomial_element import Polynomial + from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.structure.element import parent from sage.libs.pari.all import pari_gen diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 31572ab1c2e..1d3584ba3d1 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -114,7 +114,7 @@ import sage.interfaces.gap import sage.rings.complex_mpfr -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial import sage.rings.real_mpfr import sage.rings.real_mpfi import sage.rings.complex_double diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index 72ee42a0e5b..feb778b6a74 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -35,7 +35,7 @@ from sage.rings.infinity import Infinity from sage.structure.factorization import Factorization from sage.rings.integer_ring import ZZ -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.structure.element import is_Element from .padic_base_leaves import (pAdicRingCappedRelative, pAdicRingCappedAbsolute, diff --git a/src/sage/rings/padics/padic_template_element.pxi b/src/sage/rings/padics/padic_template_element.pxi index f3aa97e879b..44b3e5ebf9e 100644 --- a/src/sage/rings/padics/padic_template_element.pxi +++ b/src/sage/rings/padics/padic_template_element.pxi @@ -36,7 +36,8 @@ from sage.rings.infinity import infinity from sage.rings.rational import Rational from sage.rings.padics.precision_error import PrecisionError from sage.rings.padics.misc import trim_zeros -from sage.structure.element import canonical_coercion, Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.structure.element import canonical_coercion import itertools cdef long maxordp = (1L << (sizeof(long) * 8 - 2)) - 1 diff --git a/src/sage/rings/polynomial/commutative_polynomial.pxd b/src/sage/rings/polynomial/commutative_polynomial.pxd new file mode 100644 index 00000000000..c4a8956daa5 --- /dev/null +++ b/src/sage/rings/polynomial/commutative_polynomial.pxd @@ -0,0 +1,5 @@ +from sage.structure.element cimport CommutativeAlgebraElement + + +cdef class CommutativePolynomial(CommutativeAlgebraElement): + pass diff --git a/src/sage/rings/polynomial/commutative_polynomial.pyx b/src/sage/rings/polynomial/commutative_polynomial.pyx new file mode 100644 index 00000000000..9784f1586c9 --- /dev/null +++ b/src/sage/rings/polynomial/commutative_polynomial.pyx @@ -0,0 +1,10 @@ +cdef class CommutativePolynomial(CommutativeAlgebraElement): + r""" + Abstract base class for commutative polynomials in any number of variables + + It is a common base for :class:`~sage.rings.polynomial.polynomial_element.Polynomial`, + :class:`~sage.rings.polynomial.multi_polynomial.MPolynomial`, and + :class:`~sage.rings.polynomial.infinite_polynomial_element.InfinitePolynomial`. + """ + + pass diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index dd87d6e9c05..aa0d9da9884 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -94,11 +94,13 @@ from sage.structure.richcmp import richcmp from sage.misc.cachefunc import cached_method from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass -from sage.structure.element import RingElement, MPolynomial, InfinitePolynomial as InfinitePolynomial_base +from sage.structure.element import RingElement +from .commutative_polynomial import CommutativePolynomial +from .multi_polynomial import MPolynomial import copy -class InfinitePolynomial(InfinitePolynomial_base, metaclass=InheritComparisonClasscallMetaclass): +class InfinitePolynomial(CommutativePolynomial, metaclass=InheritComparisonClasscallMetaclass): """ Create an element of a Polynomial Ring with a Countably Infinite Number of Variables. diff --git a/src/sage/rings/polynomial/multi_polynomial.pxd b/src/sage/rings/polynomial/multi_polynomial.pxd index fd947a33bb2..5dc75e6bd3f 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pxd +++ b/src/sage/rings/polynomial/multi_polynomial.pxd @@ -1,7 +1,11 @@ -from sage.structure.element cimport MPolynomial as MPolynomial_base +from .commutative_polynomial cimport CommutativePolynomial -cdef class MPolynomial(MPolynomial_base): + +cdef class MPolynomial(CommutativePolynomial): cdef long _hash_c(self) except -1 cpdef _mod_(self, right) cpdef dict _mpoly_dict_recursive(self, tuple vars=*, base_ring=*) + +cdef class MPolynomial_libsingular(MPolynomial): + pass diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index f798a67aa41..65af5f4a1d6 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -33,7 +33,7 @@ from sage.rings.real_mpfr import RealField_class,RealField from sage.rings.polynomial.polydict cimport ETuple from sage.rings.polynomial.polynomial_element cimport Polynomial -cdef class MPolynomial(MPolynomial_base): +cdef class MPolynomial(CommutativePolynomial): #################### # Some standard conversions @@ -2623,3 +2623,26 @@ cdef remove_from_tuple(e, int ind): return w[0] else: return tuple(w) + + +cdef class MPolynomial_libsingular(MPolynomial): + r""" + Abstract base class for :class:`~sage.rings.polynomial.multi_polynomial_libsingular.MPolynomial_libsingular` + + This class is defined for the purpose of :func:`isinstance` tests. It should not be + instantiated. + + EXAMPLES:: + + sage: R1. = QQ[] + sage: isinstance(x, sage.rings.polynomial.multi_polynomial.MPolynomial_libsingular) + False + sage: R2. = QQ[] + sage: isinstance(y, sage.rings.polynomial.multi_polynomial.MPolynomial_libsingular) + True + + By design, there is a unique direct subclass:: + + sage: len(sage.rings.polynomial.multi_polynomial.MPolynomial_libsingular.__subclasses__()) <= 1 + True + """ diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pxd b/src/sage/rings/polynomial/multi_polynomial_libsingular.pxd index f0518c93f9c..c9cec10e2bc 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pxd +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pxd @@ -1,11 +1,11 @@ from sage.libs.singular.decl cimport poly, ring -from sage.rings.polynomial.multi_polynomial cimport MPolynomial +from sage.rings.polynomial.multi_polynomial cimport MPolynomial_libsingular as MPolynomial_libsingular_base from sage.rings.polynomial.multi_polynomial_ring_base cimport MPolynomialRing_base cdef class MPolynomialRing_libsingular -cdef class MPolynomial_libsingular(MPolynomial): +cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): cdef poly *_poly cdef ring *_parent_ring cpdef _add_(self, other) diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index 6249cac4199..ca02ffbd791 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -1896,7 +1896,7 @@ def unpickle_MPolynomialRing_libsingular(base_ring, names, term_order): return _multi_variate(base_ring, tuple(names), None, term_order, None) -cdef class MPolynomial_libsingular(MPolynomial): +cdef class MPolynomial_libsingular(MPolynomial_libsingular_base): """ A multivariate polynomial implemented using libSINGULAR. """ diff --git a/src/sage/rings/polynomial/polynomial_element.pxd b/src/sage/rings/polynomial/polynomial_element.pxd index cafc17eab3f..083f506b222 100644 --- a/src/sage/rings/polynomial/polynomial_element.pxd +++ b/src/sage/rings/polynomial/polynomial_element.pxd @@ -1,12 +1,12 @@ from sage.structure.element import Element from sage.structure.element cimport Element, CommutativeAlgebraElement, ModuleElement -from sage.structure.element cimport Polynomial as Polynomial_base from sage.structure.parent cimport Parent from sage.rings.integer cimport Integer +from .commutative_polynomial cimport CommutativePolynomial from .polynomial_compiled cimport CompiledPolynomialFunction -cdef class Polynomial(Polynomial_base): +cdef class Polynomial(CommutativePolynomial): cdef Polynomial _new_generic(self, list coeffs) cdef char _is_gen cdef CompiledPolynomialFunction _compiled diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 5cff2e446a4..cba9c0ee009 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -189,7 +189,7 @@ from .polynomial_compiled cimport CompiledPolynomialFunction from sage.rings.polynomial.polydict cimport ETuple -cdef class Polynomial(Polynomial_base): +cdef class Polynomial(CommutativePolynomial): """ A polynomial. diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 90b8645ab82..d9c72b0b05d 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -573,7 +573,7 @@ from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.complex_interval import is_ComplexIntervalFieldElement from sage.rings.polynomial.all import PolynomialRing -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.number_field.number_field import NumberField, GaussianField, CyclotomicField diff --git a/src/sage/schemes/berkovich/berkovich_cp_element.py b/src/sage/schemes/berkovich/berkovich_cp_element.py index d496d0543b6..7c922fefb90 100644 --- a/src/sage/schemes/berkovich/berkovich_cp_element.py +++ b/src/sage/schemes/berkovich/berkovich_cp_element.py @@ -83,7 +83,7 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= Type I point centered at 4 + O(5^20) """ from sage.rings.function_field.element import is_FunctionFieldElement - from sage.structure.element import Polynomial + from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.fraction_field_element import FractionFieldElement_1poly_field self._type = None @@ -109,7 +109,7 @@ def __init__(self, parent, center, radius=None, power=None, prec=20, space_type= # is_FunctionFieldElement calls .parent elif hasattr(center, "parent") and hasattr(radius, 'parent'): - from sage.structure.element import MPolynomial + from sage.rings.polynomial.multi_polynomial import MPolynomial if isinstance(center, MPolynomial): try: center = center.univariate_polynomial() diff --git a/src/sage/schemes/curves/constructor.py b/src/sage/schemes/curves/constructor.py index a388e660fef..c3ff0fa9a42 100644 --- a/src/sage/schemes/curves/constructor.py +++ b/src/sage/schemes/curves/constructor.py @@ -36,7 +36,7 @@ from sage.categories.fields import Fields -from sage.structure.element import MPolynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.finite_rings.finite_field_constructor import is_FiniteField diff --git a/src/sage/schemes/cyclic_covers/constructor.py b/src/sage/schemes/cyclic_covers/constructor.py index a67a17a2f17..32bdf239b99 100644 --- a/src/sage/schemes/cyclic_covers/constructor.py +++ b/src/sage/schemes/cyclic_covers/constructor.py @@ -8,7 +8,7 @@ # https://www.gnu.org/licenses/ # ***************************************************************************** -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.schemes.affine.affine_space import AffineSpace from .cycliccover_generic import CyclicCover_generic diff --git a/src/sage/schemes/elliptic_curves/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index ff59b42bfe9..d924bd8f45c 100644 --- a/src/sage/schemes/elliptic_curves/constructor.py +++ b/src/sage/schemes/elliptic_curves/constructor.py @@ -29,7 +29,7 @@ from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.finite_rings.finite_field_constructor import is_FiniteField from sage.rings.number_field.number_field import is_NumberField -from sage.structure.element import MPolynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.rings.ring import is_Ring from sage.categories.fields import Fields diff --git a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index 4bc0825a35a..dd6d59f55b2 100644 --- a/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py +++ b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py @@ -86,7 +86,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.integer import Integer from sage.rings.laurent_series_ring import LaurentSeriesRing -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.fraction_field import FractionField from sage.schemes.elliptic_curves.all import EllipticCurve diff --git a/src/sage/schemes/elliptic_curves/jacobian.py b/src/sage/schemes/elliptic_curves/jacobian.py index dc09a18096e..6cf48d0760a 100644 --- a/src/sage/schemes/elliptic_curves/jacobian.py +++ b/src/sage/schemes/elliptic_curves/jacobian.py @@ -104,7 +104,7 @@ def Jacobian(X, **kwds): pass morphism = kwds.pop('morphism', False) - from sage.structure.element import MPolynomial + from sage.rings.polynomial.multi_polynomial import MPolynomial if isinstance(X, MPolynomial): if morphism: from sage.schemes.curves.constructor import Curve diff --git a/src/sage/schemes/generic/hypersurface.py b/src/sage/schemes/generic/hypersurface.py index 58f8ed517fd..090ffacf59a 100644 --- a/src/sage/schemes/generic/hypersurface.py +++ b/src/sage/schemes/generic/hypersurface.py @@ -18,7 +18,7 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.structure.element import MPolynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.schemes.affine.affine_subscheme import AlgebraicScheme_subscheme_affine from sage.schemes.projective.projective_subscheme import AlgebraicScheme_subscheme_projective diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index 49ff0254cd1..e73efa85558 100644 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -25,7 +25,7 @@ import sage.rings.abc from sage.rings.rational_field import is_RationalField from sage.rings.finite_rings.finite_field_constructor import is_FiniteField -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.structure.dynamic_class import dynamic_class diff --git a/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py b/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py index 9fc24367211..74cdccbaa49 100644 --- a/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py +++ b/src/sage/schemes/hyperelliptic_curves/jacobian_homset.py @@ -49,7 +49,7 @@ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.integer_ring import ZZ from sage.rings.integer import is_Integer, Integer -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.schemes.generic.homset import SchemeHomset_points from sage.schemes.generic.morphism import is_SchemeMorphism diff --git a/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py b/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py index 40bb62aae0d..3a60c34bf7d 100644 --- a/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py +++ b/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py @@ -64,7 +64,7 @@ from sage.rings.infinity import Infinity from sage.rings.laurent_series_ring import is_LaurentSeriesRing from sage.rings.padics.all import pAdicField -from sage.structure.element import Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.ring import CommutativeAlgebra from sage.schemes.elliptic_curves.constructor import EllipticCurve from sage.schemes.elliptic_curves.ell_generic import is_EllipticCurve diff --git a/src/sage/schemes/plane_conics/constructor.py b/src/sage/schemes/plane_conics/constructor.py index c1c24049dfe..abff2935854 100644 --- a/src/sage/schemes/plane_conics/constructor.py +++ b/src/sage/schemes/plane_conics/constructor.py @@ -32,7 +32,7 @@ from sage.rings.ring import IntegralDomain from sage.rings.rational_field import is_RationalField from sage.rings.finite_rings.finite_field_constructor import is_FiniteField -from sage.structure.element import MPolynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.rings.polynomial.polynomial_ring import is_PolynomialRing from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.fraction_field import is_FractionField diff --git a/src/sage/schemes/plane_quartics/quartic_constructor.py b/src/sage/schemes/plane_quartics/quartic_constructor.py index 53f1494abf0..274eaf049b6 100644 --- a/src/sage/schemes/plane_quartics/quartic_constructor.py +++ b/src/sage/schemes/plane_quartics/quartic_constructor.py @@ -9,7 +9,7 @@ #***************************************************************************** from sage.schemes.projective.projective_space import is_ProjectiveSpace, ProjectiveSpace -from sage.structure.element import MPolynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from .quartic_generic import QuarticCurve_generic diff --git a/src/sage/structure/element.pxd b/src/sage/structure/element.pxd index d0a574a95a1..5c6e295a4b8 100644 --- a/src/sage/structure/element.pxd +++ b/src/sage/structure/element.pxd @@ -239,15 +239,6 @@ cdef class CommutativeAlgebraElement(CommutativeRingElement): cdef class Expression(CommutativeRingElement): pass -cdef class CommutativePolynomial(CommutativeAlgebraElement): - pass - -cdef class Polynomial(CommutativePolynomial): - pass - -cdef class MPolynomial(CommutativePolynomial): - pass - cdef class InfinityElement(RingElement): pass diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 6c4bff702e4..de3783053dd 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -47,10 +47,6 @@ abstract base classes. EuclideanDomainElement FieldElement CommutativeAlgebraElement - CommutativePolynomial - Polynomial - MPolynomial - InfinitePolynomial Expression AlgebraElement Matrix @@ -4310,93 +4306,6 @@ cdef class CommutativeAlgebraElement(CommutativeRingElement): ############################################## -cdef class CommutativePolynomial(CommutativeAlgebraElement): - r""" - Abstract base class for commutative polynomials in any number of variables - - It is a common base for :class:`Polynomial`, :class:`MPolynomial`, and - :class:`InfinitePolynomial`. - """ - - pass - - ############################################## - -cdef class Polynomial(CommutativePolynomial): - r""" - Abstract base class for :class:`~sage.rings.polynomial.polynomial_element.Polynomial` - - This class is defined for the purpose of :func:`isinstance` tests. It should not be - instantiated. - - EXAMPLES:: - - sage: R1. = QQ[] - sage: isinstance(x, sage.structure.element.Polynomial) - True - sage: R2. = QQ[] - sage: isinstance(y, sage.structure.element.Polynomial) - False - - By design, there is a unique direct subclass:: - - sage: len(sage.structure.element.Polynomial.__subclasses__()) <= 1 - True - """ - - pass - - ############################################## - -cdef class MPolynomial(CommutativePolynomial): - r""" - Abstract base class for :class:`~sage.rings.polynomial.multi_polynomial.MPolynomial` - - This class is defined for the purpose of :func:`isinstance` tests. It should not be - instantiated. - - EXAMPLES:: - - sage: R1. = QQ[] - sage: isinstance(x, sage.structure.element.MPolynomial) - False - sage: R2. = QQ[] - sage: isinstance(y, sage.structure.element.MPolynomial) - True - - By design, there is a unique direct subclass:: - - sage: len(sage.structure.element.MPolynomial.__subclasses__()) <= 1 - True - """ - - pass - - ############################################## - -cdef class InfinitePolynomial(CommutativePolynomial): - r""" - Abstract base class for :class:`~sage.rings.polynomial.infinite_polynomial_element.InfinitePolynomial` - - This class is defined for the purpose of :func:`isinstance` tests. It should not be - instantiated. - - EXAMPLES:: - - sage: X. = InfinitePolynomialRing(QQ) - sage: isinstance(x[0], sage.structure.element.InfinitePolynomial) - True - - By design, there is a unique direct subclass:: - - sage: len(sage.structure.element.InfinitePolynomial.__subclasses__()) <= 1 - True - """ - - pass - - ############################################## - def is_InfinityElement(x): """ Return ``True`` if x is of type InfinityElement. From 538c2a7a6de1f5063993612926dc63268d38adc5 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 22 Jan 2023 17:15:56 -0800 Subject: [PATCH 215/392] Replace use of implementation class from multi_polynomial_libsingular in isinstance tests --- .../algebras/fusion_rings/shm_managers.pyx | 2 +- .../polynomial/infinite_polynomial_element.py | 2 +- .../polynomial/infinite_polynomial_ring.py | 99 ++++++++++--------- .../rings/polynomial/multi_polynomial_ring.py | 2 +- 4 files changed, 54 insertions(+), 51 deletions(-) diff --git a/src/sage/algebras/fusion_rings/shm_managers.pyx b/src/sage/algebras/fusion_rings/shm_managers.pyx index 91aba7ba59f..84c3bb22be4 100644 --- a/src/sage/algebras/fusion_rings/shm_managers.pyx +++ b/src/sage/algebras/fusion_rings/shm_managers.pyx @@ -22,7 +22,7 @@ from multiprocessing import shared_memory from sage.algebras.fusion_rings.poly_tup_engine cimport poly_to_tup, tup_fixes_sq, _flatten_coeffs from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational -from sage.rings.polynomial.multi_polynomial_libsingular cimport MPolynomial_libsingular +from sage.rings.polynomial.multi_polynomial cimport MPolynomial_libsingular from sage.rings.polynomial.polydict cimport ETuple import numpy as np diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index aa0d9da9884..6437a28bb2f 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -695,7 +695,7 @@ def footprint(self): l = len(self.parent()._names) # get the pairs (shift,exponent) of the leading monomial, indexed by the variable names Vars = self._p.parent().variable_names() - from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomial_libsingular + from sage.rings.polynomial.multi_polynomial import MPolynomial_libsingular if isinstance(self._p, MPolynomial_libsingular): L = [(Vars[i].split('_'), e) for i, e in enumerate(self._p.lm().exponents(as_ETuples=False)[0]) if e] elif hasattr(self._p, 'lm'): diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index 1bc126c1e00..5dc44ec2c10 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_ring.py +++ b/src/sage/rings/polynomial/infinite_polynomial_ring.py @@ -936,7 +936,7 @@ def _element_constructor_(self, x): raise ValueError("cannot convert %s into an element of %s" % (x, self)) # direct conversion will only be used if the underlying polynomials are libsingular. - from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomial_libsingular, MPolynomialRing_libsingular + from sage.rings.polynomial.multi_polynomial import MPolynomial_libsingular # try interpretation in self._P, if we have a dense implementation if hasattr(self, '_P'): if x.parent() is self._P: @@ -945,36 +945,38 @@ def _element_constructor_(self, x): # that MPolynomialRing_polydict does not work in complicated settings. # So, if self._P is libsingular (and this will be the case in many # applications!), we do it "nicely". Otherwise, we have to use sage_eval. - if isinstance(x, MPolynomial_libsingular) and isinstance(self._P, MPolynomialRing_libsingular): - if xmaxind == -1: # Otherwise, x has been an InfinitePolynomial - # We infer the correct variable shift. - # Note: Since we are in the "libsingular" case, there are - # no further "variables" hidden in the base ring of x.parent() + if isinstance(x, MPolynomial_libsingular): + from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomialRing_libsingular + if isinstance(self._P, MPolynomialRing_libsingular): + if xmaxind == -1: # Otherwise, x has been an InfinitePolynomial + # We infer the correct variable shift. + # Note: Since we are in the "libsingular" case, there are + # no further "variables" hidden in the base ring of x.parent() + try: + VarList = [repr(v) for v in x.variables()] + # since interpretation in base ring + # was impossible, it *must* have + # variables + # This tests admissibility on the fly: + VarList.sort(key=self.varname_key, reverse=True) + except ValueError: + raise ValueError("cannot convert %s into an element of %s - variables are not admissible" % (x, self)) + xmaxind = max([int(v.split('_')[1]) for v in VarList]) try: - VarList = [repr(v) for v in x.variables()] - # since interpretation in base ring - # was impossible, it *must* have - # variables - # This tests admissibility on the fly: - VarList.sort(key=self.varname_key, reverse=True) - except ValueError: - raise ValueError("cannot convert %s into an element of %s - variables are not admissible" % (x, self)) - xmaxind = max([int(v.split('_')[1]) for v in VarList]) - try: - # Apparently, in libsingular, the polynomial conversion is not done by - # name but by position, if the number of variables in the parents coincide. - # So, we shift self._P to achieve xmaxind, and if the number of variables is - # the same then we shift further. We then *must* be - # able to convert x into self._P, or conversion to self is - # impossible (and will be done in InfinitePolynomial(...) - if self._max < xmaxind: - self.gen()[xmaxind] - if self._P.ngens() == x.parent().ngens(): - self.gen()[self._max + 1] - # conversion to self._P will be done in InfinitePolynomial.__init__ - return InfinitePolynomial(self, x) - except (ValueError, TypeError, NameError): - raise ValueError("cannot convert %s (from %s, but variables %s) into an element of %s - no conversion into underlying polynomial ring %s" % (x, x.parent(), x.variables(), self, self._P)) + # Apparently, in libsingular, the polynomial conversion is not done by + # name but by position, if the number of variables in the parents coincide. + # So, we shift self._P to achieve xmaxind, and if the number of variables is + # the same then we shift further. We then *must* be + # able to convert x into self._P, or conversion to self is + # impossible (and will be done in InfinitePolynomial(...) + if self._max < xmaxind: + self.gen()[xmaxind] + if self._P.ngens() == x.parent().ngens(): + self.gen()[self._max + 1] + # conversion to self._P will be done in InfinitePolynomial.__init__ + return InfinitePolynomial(self, x) + except (ValueError, TypeError, NameError): + raise ValueError("cannot convert %s (from %s, but variables %s) into an element of %s - no conversion into underlying polynomial ring %s" % (x, x.parent(), x.variables(), self, self._P)) # By now, x or self._P are not libsingular. Since MPolynomialRing_polydict # is too buggy, we use string evaluation try: @@ -1014,25 +1016,26 @@ def _element_constructor_(self, x): from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing R = PolynomialRing(self._base, VarList, order=self._order) - if isinstance(R, MPolynomialRing_libsingular) and isinstance(x, MPolynomial_libsingular): # everything else is so buggy that it's even not worth to try. - try: - # Problem: If there is only a partial overlap in the variables - # of x.parent() and R, then R(x) raises an error (which, I think, - # is a bug, since we talk here about conversion, not coercion). - # Hence, for being on the safe side, we coerce into a pushout ring: - x = R(1) * x - return InfinitePolynomial(self, x) - except Exception: - # OK, last resort, to be on the safe side + if isinstance(x, MPolynomial_libsingular): # everything else is so buggy that it's even not worth to try. + from sage.rings.polynomial.multi_polynomial_libsingular import MPolynomialRing_libsingular + if isinstance(R, MPolynomialRing_libsingular): try: - return sage_eval(repr(x), self.gens_dict()) - except (ValueError, TypeError, NameError): - raise ValueError("cannot convert %s into an element of %s; conversion of the underlying polynomial failed" % (x, self)) - else: - try: - return sage_eval(repr(x), self.gens_dict()) - except (ValueError, TypeError, NameError): - raise ValueError("cannot convert %s into an element of %s" % (x, self)) + # Problem: If there is only a partial overlap in the variables + # of x.parent() and R, then R(x) raises an error (which, I think, + # is a bug, since we talk here about conversion, not coercion). + # Hence, for being on the safe side, we coerce into a pushout ring: + x = R(1) * x + return InfinitePolynomial(self, x) + except Exception: + # OK, last resort, to be on the safe side + try: + return sage_eval(repr(x), self.gens_dict()) + except (ValueError, TypeError, NameError): + raise ValueError("cannot convert %s into an element of %s; conversion of the underlying polynomial failed" % (x, self)) + try: + return sage_eval(repr(x), self.gens_dict()) + except (ValueError, TypeError, NameError): + raise ValueError("cannot convert %s into an element of %s" % (x, self)) def tensor_with_ring(self, R): """ diff --git a/src/sage/rings/polynomial/multi_polynomial_ring.py b/src/sage/rings/polynomial/multi_polynomial_ring.py index f0381318b30..64a49e97ca0 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring.py +++ b/src/sage/rings/polynomial/multi_polynomial_ring.py @@ -420,7 +420,7 @@ def __call__(self, x=0, check=True): except TypeError: pass - from .multi_polynomial_libsingular import MPolynomial_libsingular + from .multi_polynomial import MPolynomial_libsingular if isinstance(x, MPolynomial_polydict): P = x.parent() From 0fcb6fbbe723ba9c591f10766843c53918f3430c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 22 Jan 2023 17:22:25 -0800 Subject: [PATCH 216/392] src/sage/rings/polynomial/commutative_polynomial.pyx: Add doctests --- .../rings/polynomial/commutative_polynomial.pyx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/sage/rings/polynomial/commutative_polynomial.pyx b/src/sage/rings/polynomial/commutative_polynomial.pyx index 9784f1586c9..dc9f2cab8b7 100644 --- a/src/sage/rings/polynomial/commutative_polynomial.pyx +++ b/src/sage/rings/polynomial/commutative_polynomial.pyx @@ -5,6 +5,19 @@ cdef class CommutativePolynomial(CommutativeAlgebraElement): It is a common base for :class:`~sage.rings.polynomial.polynomial_element.Polynomial`, :class:`~sage.rings.polynomial.multi_polynomial.MPolynomial`, and :class:`~sage.rings.polynomial.infinite_polynomial_element.InfinitePolynomial`. + + EXAMPLES:: + + sage: from sage.rings.polynomial.commutative_polynomial import CommutativePolynomial + sage: K. = PolynomialRing(QQ) + sage: isinstance(x, CommutativePolynomial) + True + sage: K. = PolynomialRing(QQ) + sage: isinstance(x, CommutativePolynomial) + True + sage: X. = InfinitePolynomialRing(ZZ, implementation='sparse') + sage: isinstance(x[2], CommutativePolynomial) + True """ pass From 5cd17f1a3c787cf4739667343b025dce369332d3 Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Tue, 24 Jan 2023 12:26:14 +0530 Subject: [PATCH 217/392] correcting function --- src/sage/combinat/posets/linear_extensions.py | 102 ++++++------------ 1 file changed, 33 insertions(+), 69 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 4c953c55ef5..37b277138da 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -257,12 +257,21 @@ def is_supergreedy(self): Return ``True`` if the linear extension is supergreedy. A linear extension `[e_1, e_2, \ldots, e_n]` is *supergreedy* if - for every i, either `e_i+1` cover `e_i` otherwise on backtracking - through the list of elements previosly choosen, either `e_i+1` - should be in the upper cover of the first element having a non-empty - upper cover or should include only the prviosly choosen elements. + for every i, either if there is a minimal element `e_i+1` in + `[e_i+1, \ldots, e_n]` which is in the upper cover of `e_j` in + `[e_1, \ldots, e_i]` for which j is maximum or if no such element + exist `e_i+1` is the any of the minimal element in `[e_i+1,\dots, e_n]`. EXAMPLES:: + sage: X = [0,1,2,3,4,5,6] + sage: Y = [[0,5],[1,4],[1,5],[3,6],[4,3],[5,6],[6,2]] + sage: P = Poset((X,Y), cover_relations = True, facade=False) + sage: for l in P.linear_extensions(): + ....: if l.is_supergreedy(): + ....: print(l) + [1, 4, 3, 0, 5, 6, 2] + [0, 1, 4, 3, 5, 6, 2] + [0, 1, 5, 4, 3, 6, 2] sage: Q = posets.PentagonPoset() sage: for l in Q.linear_extensions(): @@ -279,77 +288,32 @@ def is_supergreedy(self): True """ P = self.poset() - Q = [] - N = [] - S = [] - T = [] + H = P.hasse_diagram() + + def next_elements(H, linext): + k = len(linext) + S = [] + while not S: + if not k: + S = [x for x in H.sources() if x not in linext] + else: + S = [x for x in H.neighbor_out_iterator(linext[k-1]) if x not in linext and all(low in linext for low in H.neighbor_in_iterator(x))] + k -= 1 + return S + if not self: return True - N.append(self[0]) - for i in range(len(self)-1): - if P.compare_elements(self[0],self[i+1]) is not None : - N.append(self[i+1]) + if self[0] not in H.sources(): + return False + for i in range(len(self)-2): + X = next_elements(H,self[:i+1]) + if self[i+1] in X: + continue else: - S.append(self[i+1]) - Q.append(N.copy()) - N.clear() - while(S): - N.append(S[0]) - for i in range(len(S)-1): - if P.compare_elements(S[0],S[i+1]) is not None : - N.append(S[i+1]) - else: - T.append(S[i+1]) - Q.append(N.copy()) - S.clear() - S = T.copy() - T.clear() - N.clear() - for l in Q: - if not self.complete_supergreedy(P, l): return False - else: - return True - - def complete_supergreedy(self, P, L): - r""" - Return ``True`` if the linear extension is supergreedy and any one - element is comparable to all the elements - """ - Q = set() - for i in range(len(L) - 1): - Q.add(L[i]) - if not P.covers(L[i], L[i + 1]): - if len(P.upper_covers(L[i])) != 0: - return False - R = True - S = [L[i]] - while(R): - for t in S: - for u in P.lower_covers(t): - if len(P.upper_covers(u)) == 0: - S = P.lower_covers(t) - break - else : - for v in P.upper_covers(u): - if v != L[i+1] and v not in Q: - return False - elif v == L[i+1] : - R=False - break - else : - continue - else : - if P.lower_covers(t) == 0 : - return False - S = P.lower_covers(t) - continue - break - else: - continue - break return True + def tau(self, i): r""" Return the operator `\tau_i` on linear extensions ``self`` of a poset. From 1406cdc3a3691819a3081bf737275845c7c6cc4b Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Tue, 24 Jan 2023 18:02:18 +0530 Subject: [PATCH 218/392] documentation changes --- src/sage/combinat/posets/linear_extensions.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 37b277138da..4fb2f4e5a44 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -257,12 +257,18 @@ def is_supergreedy(self): Return ``True`` if the linear extension is supergreedy. A linear extension `[e_1, e_2, \ldots, e_n]` is *supergreedy* if - for every i, either if there is a minimal element `e_i+1` in - `[e_i+1, \ldots, e_n]` which is in the upper cover of `e_j` in + for every `i`, either if there is a minimal element `e_{i+1}` in + `[e_{i+1}, \ldots, e_n]` which is in the upper cover of `e_j` in `[e_1, \ldots, e_i]` for which j is maximum or if no such element - exist `e_i+1` is the any of the minimal element in `[e_i+1,\dots, e_n]`. + exist `e_{i+1}` is the any of the minimal element in + `[e_i+1,\dots, e_n]`. + + Informally said a linear extension is supergreedy if it "always + goes up and receedes the least" loosely speaking, supergreedy + linear extensions are depth-first linear extensions. EXAMPLES:: + sage: X = [0,1,2,3,4,5,6] sage: Y = [[0,5],[1,4],[1,5],[3,6],[4,3],[5,6],[6,2]] sage: P = Poset((X,Y), cover_relations = True, facade=False) From 6e2adaf291edc2c0d34ef390e08aa99cee32e6d0 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 24 Jan 2023 18:49:59 -0800 Subject: [PATCH 219/392] InfinitePolynomial: Move _lmul_, _rmul_ here from subclasses --- .../polynomial/infinite_polynomial_element.py | 66 +++++++------------ 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 6437a28bb2f..ab35e5f31d2 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -480,6 +480,28 @@ def max_index(self): """ return max([Integer(str(X).split('_')[1]) for X in self.variables()]+[-1]) + def _rmul_(self, left): + """ + TESTS:: + + sage: R. = InfinitePolynomialRing(QQ, implementation='sparse') + sage: R.from_base_ring(4) # indirect doctest + 4 + + """ + return type(self)(self.parent(), left * self._p) + + def _lmul_(self, right): + """ + TESTS:: + + sage: R. = InfinitePolynomialRing(QQ, implementation='sparse') + sage: alpha[3]*4 # indirect doctest + 4*alpha_3 + + """ + return type(self)(self.parent(), self._p * right) + def _div_(self, x): r""" Division of Infinite Polynomials. @@ -1201,28 +1223,6 @@ def _mul_(self, x): R = self._p.base_ring() return InfinitePolynomial_sparse(self.parent(), R(self._p) * R(x._p)) - def _rmul_(self, left): - """ - TESTS:: - - sage: R. = InfinitePolynomialRing(QQ, implementation='sparse') - sage: R.from_base_ring(4) # indirect doctest - 4 - - """ - return InfinitePolynomial_sparse(self.parent(), left * self._p) - - def _lmul_(self, right): - """ - TESTS:: - - sage: R. = InfinitePolynomialRing(QQ, implementation='sparse') - sage: alpha[3]*4 # indirect doctest - 4*alpha_3 - - """ - return InfinitePolynomial_sparse(self.parent(), self._p * right) - def _sub_(self, x): """ EXAMPLES:: @@ -1508,28 +1508,6 @@ def _mul_(self, x): x._p = P._P(x._p) return InfinitePolynomial_dense(self.parent(), self._p * x._p) - def _rmul_(self, left): - """ - TESTS:: - - sage: R. = InfinitePolynomialRing(QQ) - sage: R.from_base_ring(4) # indirect doctest - 4 - - """ - return InfinitePolynomial_dense(self.parent(), left*self._p) - - def _lmul_(self, right): - """ - TESTS:: - - sage: R. = InfinitePolynomialRing(QQ) - sage: alpha[3]*4 # indirect doctest - 4*alpha_3 - - """ - return InfinitePolynomial_dense(self.parent(), self._p*right) - def _sub_(self, x): """ EXAMPLES:: From cdab0dfc716d77a0a0b2bef7f1779bc29a715385 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 24 Jan 2023 19:08:35 -0800 Subject: [PATCH 220/392] InfinitePolynomial.__classcall_private__: Add doctest --- .../polynomial/infinite_polynomial_element.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index ab35e5f31d2..859f9c407d5 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -169,6 +169,28 @@ class InfinitePolynomial(CommutativePolynomial, metaclass=InheritComparisonClass @staticmethod def __classcall_private__(cls, A, p): + r""" + TESTS:: + + sage: from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial + sage: X. = InfinitePolynomialRing(ZZ, implementation='sparse') + sage: xy = (x[0] + y[0]).polynomial() + sage: xy.parent() + Multivariate Polynomial Ring in x_1, x_0, y_1, y_0 over Integer Ring + sage: sparse_xy = InfinitePolynomial(X, xy); sparse_xy + x_0 + y_0 + sage: isinstance(sparse_xy, InfinitePolynomial) + True + sage: type(sparse_xy) + + sage: X. = InfinitePolynomialRing(ZZ, implementation='dense') + sage: dense_xy = InfinitePolynomial(X, xy); dense_xy + x_0 + y_0 + sage: isinstance(dense_xy, InfinitePolynomial) + True + sage: type(dense_xy) + + """ from sage.structure.element import parent if hasattr(A, '_P'): if parent(p) is A._P or (A._P.base_ring().has_coerce_map_from(parent(p))): From 1ef141292c39c14b1f6c4e15f119b14241e39ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 6 Feb 2023 11:01:17 +0100 Subject: [PATCH 221/392] Remove `# todo: not tested` comments --- .../drinfeld_modules/drinfeld_module.py | 68 +++++++++--------- .../finite_drinfeld_module.py | 72 +++++++++---------- .../function_field/drinfeld_modules/homset.py | 8 +-- .../drinfeld_modules/morphism.py | 10 +-- 4 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index d82632c6db2..22d0f3c399f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -97,8 +97,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: Fq = GF(49) sage: FqX. = Fq[] sage: K. = Frac(FqX) - sage: psi = DrinfeldModule(FqX, [z, X+1]) # todo: not tested - sage: psi # todo: not tested + sage: psi = DrinfeldModule(FqX, [z, X+1]) + sage: psi .. NOTE:: @@ -155,9 +155,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): The above Drinfeld module is finite; it can also be infinite:: - sage: L = Frac(FqX) # todo: not tested - sage: psi = DrinfeldModule(FqX, [L(X), 1, X^3 + X + 1]) # todo: not tested - sage: psi # todo: not tested + sage: L = Frac(FqX) + sage: psi = DrinfeldModule(FqX, [L(X), 1, X^3 + X + 1]) + sage: psi Drinfeld module defined by X |--> (X^3 + X + 1)*t^2 + t + X over base Ring morphism: From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 @@ -167,7 +167,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.is_finite() True - sage: psi.is_finite() # todo: not tested + sage: psi.is_finite() False In those examples, we used a list of coefficients (``[z, 1, 1]``) to @@ -199,7 +199,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.category() Category of Drinfeld modules defined over Finite Field in z of size 3^12 over its base - sage: phi.category() is psi.category() # todo: not tested + sage: phi.category() is psi.category() False sage: phi.category() is rho.category() True @@ -279,7 +279,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.rank() 2 - sage: phi.height() # todo: not tested + sage: phi.height() 1 As well as the j-invariant if the rank is two:: @@ -433,7 +433,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): injective, see [Gos1998]_, cor. 4.5.2.):: sage: a = FqX.random_element() - sage: phi.invert(phi(a)) == a # todo: not tested + sage: phi.invert(phi(a)) == a True TESTS: @@ -539,7 +539,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: FqX. = Fq[] sage: K = Frac(Fq) sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(1)]) - sage: phi.base().codomain() is K # todo: not tested + sage: phi.base().codomain() is K True :: @@ -550,7 +550,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: kT. = k[] sage: K = k.extension(T^3 + T + 1) sage: phi = DrinfeldModule(FqX, [Fq.gen(), K.gen()]) - sage: phi.base().codomain() is K # todo: not tested + sage: phi.base().codomain() is K True In particular, note that the field `K` may not be the smallest field @@ -562,7 +562,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: kT. = k[] sage: K = k.extension(T^3 + T + 1) sage: phi = DrinfeldModule(FqX, [K(k.gen()), 1]) - sage: phi.base().codomain() is K # todo: not tested + sage: phi.base().codomain() is K True :: @@ -571,7 +571,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: FqX. = Fq[] sage: K = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(Fq.gen())]) - sage: phi.base().codomain() is K # todo: not tested + sage: phi.base().codomain() is K True """ @@ -606,8 +606,8 @@ def __classcall_private__(cls, function_ring, gen, name='t'): :: sage: K = Frac(FqX) - sage: phi = DrinfeldModule(FqX, [K(X), 1]) # todo: not tested - sage: isinstance(psi, FiniteDrinfeldModule) # todo: not tested + sage: phi = DrinfeldModule(FqX, [K(X), 1]) + sage: isinstance(psi, FiniteDrinfeldModule) False """ @@ -1012,14 +1012,14 @@ def height(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.height() == 1 # todo: not tested + sage: phi.height() == 1 True - sage: phi.is_ordinary() # todo: not tested + sage: phi.is_ordinary() True sage: L = Frac(FqX) - sage: phi = DrinfeldModule(FqX, [L(2), L(1)]) # todo: not tested - sage: phi.height() # todo: not tested + sage: phi = DrinfeldModule(FqX, [L(2), L(1)]) + sage: phi.height() Traceback (most recent call last): ... ValueError: height is defined for prime function field characteristic @@ -1028,9 +1028,9 @@ def height(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: phi.height() # todo: not tested + sage: phi.height() 2 - sage: phi.is_supersingular() # todo: not tested + sage: phi.is_supersingular() True """ @@ -1065,22 +1065,22 @@ def invert(self, ore_pol): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) sage: a = FqX.random_element() - sage: phi.invert(phi(a)) == a # todo: not tested + sage: phi.invert(phi(a)) == a True - sage: phi.invert(phi(X)) == X # todo: not tested + sage: phi.invert(phi(X)) == X True - sage: phi.invert(phi(Fq.gen())) == Fq.gen() # todo: not tested + sage: phi.invert(phi(Fq.gen())) == Fq.gen() True When the input is not in the image of the Drinfeld module, an exception is raised:: sage: t = phi.ore_polring().gen() - sage: phi.invert(t + 1) # todo: not tested + sage: phi.invert(t + 1) Traceback (most recent call last): ... ValueError: input must be in the image of the Drinfeld module - sage: phi.invert(t^3 + t^2 + 1) # todo: not tested + sage: phi.invert(t^3 + t^2 + 1) Traceback (most recent call last): ... ValueError: input must be in the image of the Drinfeld module @@ -1095,19 +1095,19 @@ def invert(self, ore_pol): sage: a = FqX.random_element() sage: cat = phi.category() sage: phi_r1 = cat.random_object(1) - sage: phi_r1.invert(phi_r1(a)) == a # todo: not tested + sage: phi_r1.invert(phi_r1(a)) == a True sage: phi_r2 = cat.random_object(2) - sage: phi_r2.invert(phi_r2(a)) == a # todo: not tested + sage: phi_r2.invert(phi_r2(a)) == a True sage: phi_r3 = cat.random_object(3) - sage: phi_r3.invert(phi_r3(a)) == a # todo: not tested + sage: phi_r3.invert(phi_r3(a)) == a True sage: phi_r4 = cat.random_object(4) - sage: phi_r4.invert(phi_r4(a)) == a # todo: not tested + sage: phi_r4.invert(phi_r4(a)) == a True sage: phi_r5 = cat.random_object(5) - sage: phi_r5.invert(phi_r5(a)) == a # todo: not tested + sage: phi_r5.invert(phi_r5(a)) == a True """ deg = ore_pol.degree() @@ -1153,8 +1153,8 @@ def is_finite(self): sage: phi.is_finite() True sage: L = Frac(FqX) - sage: psi = DrinfeldModule(FqX, [L(2), L(1)]) # todo: not tested - sage: psi.is_finite() # todo: not tested + sage: psi = DrinfeldModule(FqX, [L(2), L(1)]) + sage: psi.is_finite() False """ from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule @@ -1327,7 +1327,7 @@ def velu(self, isog): sage: phi.velu(phi(X)) is phi True - sage: phi.velu(t^6) is phi # todo: not tested + sage: phi.velu(t^6) is phi True The following inputs do not define isogenies, and the method diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 46f6be20bd3..89fc6744209 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -73,8 +73,8 @@ class FiniteDrinfeldModule(DrinfeldModule): First of all, it is easy to create the Frobenius endomorphism:: - sage: frobenius_endomorphism = phi.frobenius_endomorphism() # todo: not tested - sage: frobenius_endomorphism # todo: not tested + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism Drinfeld Module morphism: From (gen): 5*t^2 + z6 To (gen): 5*t^2 + z6 @@ -82,27 +82,27 @@ class FiniteDrinfeldModule(DrinfeldModule): Its characteristic polynomial can be computed:: - sage: chi = phi.frobenius_charpoly() # todo: not tested - sage: chi # todo: not tested + sage: chi = phi.frobenius_charpoly() + sage: chi T^2 + (X + 2*z3^2 + 2*z3 + 1)*T + 2*X^2 + (z3^2 + z3 + 4)*X + 2*z3 - sage: frob_pol = frobenius_endomorphism.ore_polynomial() # todo: not tested - sage: chi(frob_pol, phi(X)) # todo: not tested + sage: frob_pol = frobenius_endomorphism.ore_polynomial() + sage: chi(frob_pol, phi(X)) 0 This makes it possible to compute the Frobenius trace and norm:: - sage: phi.frobenius_trace() # todo: not tested + sage: phi.frobenius_trace() 6*X + 5*z3^2 + 5*z3 + 6 - sage: phi.frobenius_trace() == -chi[1] # todo: not tested + sage: phi.frobenius_trace() == -chi[1] True - sage: phi.frobenius_norm() # todo: not tested + sage: phi.frobenius_norm() 2*X^2 + (z3^2 + z3 + 4)*X + 2*z3 And to decide if a Drinfeld module is ordinary or supersingular:: - sage: phi.is_ordinary() # todo: not tested + sage: phi.is_ordinary() True - sage: phi.is_supersingular() # todo: not tested + sage: phi.is_supersingular() False """ @@ -160,13 +160,13 @@ def frobenius_endomorphism(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: phi.frobenius_endomorphism() # todo: not tested + sage: phi.frobenius_endomorphism() Drinfeld Module morphism: From (gen): z6*t^2 + 1 To (gen): z6*t^2 + 1 Defn: t^2 sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism - sage: isinstance(phi.frobenius_endomorphism(), DrinfeldModuleMorphism) # todo: not tested + sage: isinstance(phi.frobenius_endomorphism(), DrinfeldModuleMorphism) True """ t = self.ore_polring().gen() @@ -211,31 +211,31 @@ def frobenius_charpoly(self, var='T'): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: chi = phi.frobenius_charpoly() # todo: not tested - sage: chi # todo: not tested + sage: chi = phi.frobenius_charpoly() + sage: chi T^2 + ((3*z3^2 + z3 + 4)*X + 4*z3^2 + 6*z3 + 3)*T + (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 :: - sage: frob_pol = phi.frobenius_endomorphism().ore_polynomial() # todo: not tested - sage: chi(frob_pol, phi(X)) # todo: not tested + sage: frob_pol = phi.frobenius_endomorphism().ore_polynomial() + sage: chi(frob_pol, phi(X)) 0 :: - sage: A = phi.frobenius_trace() # todo: not tested - sage: A # todo: not tested + sage: A = phi.frobenius_trace() + sage: A (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 - sage: B = phi.frobenius_norm() # todo: not tested - sage: B # todo: not tested + sage: B = phi.frobenius_norm() + sage: B (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 :: sage: n = 2 # Degree over Fq of the base codomain - sage: A.degree() <= n/2 # todo: not tested + sage: A.degree() <= n/2 True - sage: B.degree() == n # todo: not tested + sage: B.degree() == n True ALGORITHM: @@ -277,19 +277,19 @@ def frobenius_norm(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: B = phi.frobenius_norm() # todo: not tested - sage: B # todo: not tested + sage: B = phi.frobenius_norm() + sage: B (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 :: sage: n = 2 # Degree over Fq of the base codomain - sage: B.degree() == n # todo: not tested + sage: B.degree() == n True :: - sage: B == phi.frobenius_charpoly()[0] # todo: not tested + sage: B == phi.frobenius_charpoly()[0] True ALGORITHM: @@ -344,19 +344,19 @@ def frobenius_trace(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: A = phi.frobenius_trace() # todo: not tested - sage: A # todo: not tested + sage: A = phi.frobenius_trace() + sage: A (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 :: sage: n = 2 # Degree over Fq of the base codomain - sage: A.degree() <= n/2 # todo: not tested + sage: A.degree() <= n/2 True :: - sage: A == -phi.frobenius_charpoly()[1] # todo: not tested + sage: A == -phi.frobenius_charpoly()[1] True """ self._check_rank_two() @@ -389,10 +389,10 @@ def is_ordinary(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: phi.is_ordinary() # todo: not tested + sage: phi.is_ordinary() False - sage: phi_p = phi(phi.characteristic()) # todo: not tested - sage: phi_p # Purely inseparable # todo: not tested + sage: phi_p = phi(phi.characteristic()) + sage: phi_p # Purely inseparable z6*t^2 ALGORITHM: @@ -425,9 +425,9 @@ def is_supersingular(self): sage: FqX. = Fq[] sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(FqX, [1, 0, z6]) - sage: phi.is_supersingular() # todo: not tested + sage: phi.is_supersingular() True - sage: phi(phi.characteristic()) # Purely inseparable # todo: not tested + sage: phi(phi.characteristic()) # Purely inseparable z6*t^2 ALGORITHM: diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 71604593ab2..690d35653b8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -64,8 +64,8 @@ class DrinfeldModuleHomset(Homset): The domain and codomain must have the same Drinfeld modules category:: - sage: rho = DrinfeldModule(FqX, [Frac(FqX)(X), 1]) # todo: not tested - sage: Hom(phi, rho) # todo: not tested + sage: rho = DrinfeldModule(FqX, [Frac(FqX)(X), 1]) + sage: Hom(phi, rho) Traceback (most recent call last): ... ValueError: Drinfeld modules must be in the same category @@ -262,8 +262,8 @@ def __contains__(self, x): sage: identity_morphism = end(1) sage: identity_morphism in hom False - sage: frobenius_endomorphism = phi.frobenius_endomorphism() # todo: not tested - sage: frobenius_endomorphism in hom # todo: not tested + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism in hom False """ try: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index a752b3ddb1c..6383a9b2aad 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -100,7 +100,7 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: morphism.parent() == Hom(phi, psi) True - sage: phi.frobenius_endomorphism().parent() == End(phi) # todo: not tested + sage: phi.frobenius_endomorphism().parent() == End(phi) True sage: End(phi)(0).parent() == End(phi) True @@ -315,8 +315,8 @@ def is_isogeny(self): :: - sage: frobenius_endomorphism = phi.frobenius_endomorphism() # todo: not tested - sage: frobenius_endomorphism.is_isogeny() # todo: not tested + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism.is_isogeny() True """ return not self.is_zero() @@ -351,8 +351,8 @@ def is_isomorphism(self): :: - sage: frobenius_endomorphism = phi.frobenius_endomorphism() # todo: not tested - sage: frobenius_endomorphism.is_isomorphism() # todo: not tested + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism.is_isomorphism() False """ return self.is_isogeny() and self._ore_polynomial.degree() == 0 From 7cd5a2aa0d808df79aad8baa97de4a7222d97b6b Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Mon, 6 Feb 2023 17:41:10 +0530 Subject: [PATCH 222/392] minor chagnges --- src/sage/combinat/posets/linear_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 4fb2f4e5a44..47c315a230a 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -40,7 +40,7 @@ class LinearExtensionOfPoset(ClonableArray, - metaclass=InheritComparisonClasscallMetaclass): + metaclass=InheritComparisonClasscallMetaclass): r""" A linear extension of a finite poset `P` of size `n` is a total ordering `\pi := \pi_0 \pi_1 \ldots \pi_{n-1}` of its elements From 050c2f65c74a85dc74f361d755de8066699b51de Mon Sep 17 00:00:00 2001 From: Kryzar Date: Mon, 6 Feb 2023 14:57:36 +0100 Subject: [PATCH 223/392] (minor) Typo --- src/sage/categories/drinfeld_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 5f78eab3a3f..0d364cf5b51 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -49,7 +49,7 @@ class DrinfeldModules(Category_over_base_ring): The monic polynomial that generates the kernel of the base morphism is called the `\mathbb{F}_q[X]`-characteristic of the - `\mathbb{F}_q[X]`-field `K`. It cal also be referred to as the + `\mathbb{F}_q[X]`-field `K`. It can also be referred to as the function-field characteristic of `K`. We say that `\mathbb{F}_q[X]` is the function ring of the category; From e24c36681ae0d4630b5dbebe395752175d4fb24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Leudi=C3=A8re?= Date: Mon, 6 Feb 2023 15:27:46 +0100 Subject: [PATCH 224/392] Change X to T --- src/sage/categories/drinfeld_modules.py | 194 +++++----- .../function_field/drinfeld_modules/action.py | 40 +- .../drinfeld_modules/drinfeld_module.py | 352 +++++++++--------- .../finite_drinfeld_module.py | 98 ++--- .../function_field/drinfeld_modules/homset.py | 40 +- .../drinfeld_modules/morphism.py | 62 +-- 6 files changed, 393 insertions(+), 393 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 0d364cf5b51..142300e0971 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -33,28 +33,28 @@ class DrinfeldModules(Category_over_base_ring): This class represents the category of Drinfeld modules on a given base. - Let `\mathbb{F}_q[X]` be a polynomial ring with coefficients in a + Let `\mathbb{F}_q[T]` be a polynomial ring with coefficients in a finite field `\mathbb{F}_q` and let `K` be a field. Fix a ring - morphism `\gamma: \mathbb{F}_q[X] \to K`. We say that the field `K` - is an `\mathbb{F}_q[X]`-field, so that the *base of the category* is - defined as the `\mathbb{F}_q[X]`-field *K*. The base uniquely + morphism `\gamma: \mathbb{F}_q[T] \to K`. We say that the field `K` + is an `\mathbb{F}_q[T]`-field, so that the *base of the category* is + defined as the `\mathbb{F}_q[T]`-field *K*. The base uniquely defines the category, and we also refer to it as the *base ring* or *base field*. The *base morphism* is the morphism `\gamma: - \mathbb{F}_q[X] \to K`. + \mathbb{F}_q[T] \to K`. .. NOTE:: Equivalently, the base of the category could be defined as the - base morphism `\gamma: \mathbb{F}_q[X] \to K`. + base morphism `\gamma: \mathbb{F}_q[T] \to K`. The monic polynomial that generates the kernel of the base morphism - is called the `\mathbb{F}_q[X]`-characteristic of the - `\mathbb{F}_q[X]`-field `K`. It can also be referred to as the + is called the `\mathbb{F}_q[T]`-characteristic of the + `\mathbb{F}_q[T]`-field `K`. It can also be referred to as the function-field characteristic of `K`. - We say that `\mathbb{F}_q[X]` is the function ring of the category; + We say that `\mathbb{F}_q[T]` is the function ring of the category; `K\{\tau\}` is the Ore polynomial ring of the category. The constant - coefficient of the category is the image of `X` under the base + coefficient of the category is the image of `T` under the base morphism. INPUT: the base ring morphism @@ -66,10 +66,10 @@ class DrinfeldModules(Category_over_base_ring): Drinfeld module:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat Category of Drinfeld modules defined over Finite Field in z of size 11^4 over its base @@ -92,25 +92,25 @@ class DrinfeldModules(Category_over_base_ring): sage: cat.base_morphism() Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 + From: Univariate Polynomial Ring in T over Finite Field of size 11 To: Finite Field in z of size 11^4 over its base - Defn: X |--> z^3 + 7*z^2 + 6*z + 10 + Defn: T |--> z^3 + 7*z^2 + 6*z + 10 The so-called constant coefficient --- which is the same for all - Drinfeld modules in the category --- is simply the image of `X` by + Drinfeld modules in the category --- is simply the image of `T` by the base morphism:: sage: cat.constant_coefficient() z^3 + 7*z^2 + 6*z + 10 - sage: cat.base_morphism()(X) == cat.constant_coefficient() + sage: cat.base_morphism()(T) == cat.constant_coefficient() True Similarly, the function ring-characteristic of the category is - either `0` or the unique monic polynomial in `\mathbb{F}_q[X]` that + either `0` or the unique monic polynomial in `\mathbb{F}_q[T]` that generates the kernel of the base:: sage: cat.characteristic() - X^2 + 7*X + 2 + T^2 + 7*T + 2 sage: cat.base_morphism()(cat.characteristic()) 0 @@ -124,7 +124,7 @@ class DrinfeldModules(Category_over_base_ring): sage: cat.function_ring() is phi.function_ring() True sage: cat.function_ring() - Univariate Polynomial Ring in X over Finite Field of size 11 + Univariate Polynomial Ring in T over Finite Field of size 11 sage: cat.ore_polring() is phi.ore_polring() True sage: cat.ore_polring() @@ -137,7 +137,7 @@ class DrinfeldModules(Category_over_base_ring): sage: psi = cat.object([p_root, 1]) sage: psi - Drinfeld module defined by X |--> t + z^3 + 7*z^2 + 6*z + 10 over base Finite Field in z of size 11^4 over its base + Drinfeld module defined by T |--> t + z^3 + 7*z^2 + 6*z + 10 over base Finite Field in z of size 11^4 over its base sage: psi.category() is cat True @@ -154,7 +154,7 @@ class DrinfeldModules(Category_over_base_ring): sage: rho = cat.random_object(2) sage: rho # random - Drinfeld module defined by X |--> (7*z^3 + 7*z^2 + 10*z + 2)*t^2 + (9*z^3 + 5*z^2 + 2*z + 7)*t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + Drinfeld module defined by T |--> (7*z^3 + 7*z^2 + 10*z + 2)*t^2 + (9*z^3 + 5*z^2 + 2*z + 7)*t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 sage: rho.rank() == 2 True sage: rho.category() is cat @@ -163,10 +163,10 @@ class DrinfeldModules(Category_over_base_ring): TESTS:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: from sage.categories.drinfeld_modules import DrinfeldModules - sage: base = Hom(FqX, K)(0) + sage: base = Hom(A, K)(0) sage: cat = DrinfeldModules(base) Traceback (most recent call last): ... @@ -179,7 +179,7 @@ class DrinfeldModules(Category_over_base_ring): :: - sage: base = Hom(FqX, FqX)(1) + sage: base = Hom(A, A)(1) sage: cat = DrinfeldModules(base) Traceback (most recent call last): ... @@ -220,23 +220,23 @@ def __init__(self, base_field, name='t'): TESTS:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: ore_polring. = OrePolynomialRing(phi.base(), phi.base().frobenius_endomorphism()) sage: cat._ore_polring is ore_polring True sage: i = phi.base().coerce_map_from(K) - sage: base_morphism = Hom(FqX, K)(p_root) + sage: base_morphism = Hom(A, K)(p_root) sage: cat.base() == K.over(base_morphism) True sage: cat._base_morphism == i * base_morphism True - sage: cat._function_ring is FqX + sage: cat._function_ring is A True - sage: cat._constant_coefficient == base_morphism(X) + sage: cat._constant_coefficient == base_morphism(T) True sage: cat._characteristic(cat._constant_coefficient) 0 @@ -251,7 +251,7 @@ def __init__(self, base_field, name='t'): raise TypeError('input must be a field') self._base_field = base_field self._function_ring = base_morphism.domain() - # Check domain of base morphism is Fq[X] + # Check domain of base morphism is Fq[T] function_ring = self._function_ring if not isinstance(function_ring, PolynomialRing_general): raise NotImplementedError('function ring must be a polynomial ' @@ -262,8 +262,8 @@ def __init__(self, base_field, name='t'): raise TypeError('function ring base must be a finite field') # Shortcuts Fq = function_ring_base - FqX = function_ring - X = FqX.gen() + A = function_ring + T = A.gen() K = base_field # A ring extension # Build K{t} d = log(Fq.cardinality(), Fq.characteristic()) @@ -271,13 +271,13 @@ def __init__(self, base_field, name='t'): self._ore_polring = OrePolynomialRing(K, tau, names=name, polcast=False) # Create constant coefficient - self._constant_coefficient = base_morphism(X) + self._constant_coefficient = base_morphism(T) # Create characteristic self._characteristic = None if K.is_finite(): #FIXME: This minpoly is over Fp, not Fq - self._characteristic = FqX(K(base_morphism(X)).minpoly()) - elif FqX.is_subring(K): + self._characteristic = A(K(base_morphism(T)).minpoly()) + elif A.is_subring(K): self._characteristic = Integer(0) super().__init__(base=base_field) @@ -290,10 +290,10 @@ def _latex_(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: latex(cat) \text{Category{ }of{ }Drinfeld{ }modules{ }defined{ }over{ }\Bold{F}_{11^{4}} @@ -310,10 +310,10 @@ def _repr_(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat Category of Drinfeld modules defined over Finite Field in z of size 11^4 over its base @@ -329,10 +329,10 @@ def Homsets(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: from sage.categories.homsets import Homsets sage: cat.Homsets() is Homsets() @@ -349,10 +349,10 @@ def Endsets(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: from sage.categories.homsets import Homsets sage: cat.Endsets() is Homsets().Endsets() @@ -369,17 +369,17 @@ def base_morphism(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat.base_morphism() Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field of size 11 + From: Univariate Polynomial Ring in T over Finite Field of size 11 To: Finite Field in z of size 11^4 over its base - Defn: X |--> z^3 + 7*z^2 + 6*z + 10 - sage: cat.constant_coefficient() == cat.base_morphism()(X) + Defn: T |--> z^3 + 7*z^2 + 6*z + 10 + sage: cat.constant_coefficient() == cat.base_morphism()(T) True """ return self._base_morphism @@ -393,17 +393,17 @@ def characteristic(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat.characteristic() - X^2 + 7*X + 2 + T^2 + 7*T + 2 :: - sage: psi = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) # todo: not tested + sage: psi = DrinfeldModule(A, [Frac(A).gen(), 1]) # todo: not tested sage: psi.category().characteristic() # todo: not tested 0 """ @@ -421,14 +421,14 @@ def constant_coefficient(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat.constant_coefficient() z^3 + 7*z^2 + 6*z + 10 - sage: cat.constant_coefficient() == cat.base()(X) + sage: cat.constant_coefficient() == cat.base()(T) True """ return self._constant_coefficient @@ -442,14 +442,14 @@ def function_ring(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat.function_ring() - Univariate Polynomial Ring in X over Finite Field of size 11 - sage: cat.function_ring() is FqX + Univariate Polynomial Ring in T over Finite Field of size 11 + sage: cat.function_ring() is A True """ return self._function_ring @@ -467,14 +467,14 @@ def object(self, gen): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: psi = cat.object([p_root, 0, 1]) sage: psi - Drinfeld module defined by X |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over base Finite Field in z of size 11^4 over its base + Drinfeld module defined by T |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over base Finite Field in z of size 11^4 over its base sage: t = phi.ore_polring().gen() sage: cat.object(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi True @@ -482,8 +482,8 @@ def object(self, gen): from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule # If gen is not in the Ore polring, an exception is raised gen = self._ore_polring(gen) - X = self._function_ring.gen() - if gen[0] != self._base_morphism(X): + T = self._function_ring.gen() + if gen[0] != self._base_morphism(T): raise ValueError('constant coefficient must equal that of the ' \ 'category') return DrinfeldModule(self._function_ring, gen) @@ -497,10 +497,10 @@ def ore_polring(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat.ore_polring() Ore Polynomial Ring in t over Finite Field in z of size 11^4 over its base twisted by Frob @@ -521,13 +521,13 @@ def random_object(self, rank): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: psi = cat.random_object(3) # random - Drinfeld module defined by X |--> (6*z^3 + 4*z^2 + 10*z + 9)*t^3 + (4*z^3 + 8*z^2 + 8*z)*t^2 + (10*z^3 + 3*z^2 + 6*z)*t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + Drinfeld module defined by T |--> (6*z^3 + 4*z^2 + 10*z + 9)*t^3 + (4*z^3 + 8*z^2 + 8*z)*t^2 + (10*z^3 + 3*z^2 + 6*z)*t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 sage: psi.rank() == 3 True """ @@ -552,10 +552,10 @@ def super_categories(self): EXAMPLES:: sage: Fq = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(FqX, [p_root, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: cat.super_categories() [] @@ -573,18 +573,18 @@ def base(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.base() Finite Field in z12 of size 5^12 over its base The base can be infinite:: - sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) # todo: not tested + sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1]) # todo: not tested sage: sigma.base() # todo: not tested - Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 over its base + Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 over its base """ return self.category().base() @@ -597,19 +597,19 @@ def base_morphism(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.base_morphism() Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + From: Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 To: Finite Field in z12 of size 5^12 over its base - Defn: X |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Defn: T |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 The base codomain can be infinite:: - sage: sigma = DrinfeldModule(FqX, [Frac(FqX).gen(), 1]) # todo: not tested + sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1]) # todo: not tested sage: sigma.base_morphism() # todo: not tested """ return self.category().base_morphism() @@ -623,19 +623,19 @@ def characteristic(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.characteristic() # todo: not tested - X^2 + (4*z2 + 2)*X + 2 + T^2 + (4*z2 + 2)*T + 2 sage: phi.base_morphism()(phi.characteristic()) 0 :: - sage: L = Frac(FqX) # todo: not tested - sage: psi = DrinfeldModule(FqX, [L(1), 0, 0, L(1)]) # todo: not tested + sage: L = Frac(A) # todo: not tested + sage: psi = DrinfeldModule(A, [L(1), 0, 0, L(1)]) # todo: not tested sage: psi.characteristic() # todo: not tested 0 """ @@ -650,11 +650,11 @@ def function_ring(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.function_ring() is FqX + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.function_ring() is A True """ return self.category().function_ring() @@ -668,20 +668,20 @@ def constant_coefficient(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.constant_coefficient() == p_root True - Let `\mathbb{F}_q[X]` be the function ring, and let `\gamma` + Let `\mathbb{F}_q[T]` be the function ring, and let `\gamma` the base of the Drinfeld module. The constant coefficient - equals `\gamma(X)`:: + equals `\gamma(T)`:: sage: cat = phi.category() sage: base = cat.base() - sage: base(X) == phi.constant_coefficient() + sage: base(T) == phi.constant_coefficient() True Naturally, two Drinfeld modules in the same category have the @@ -690,7 +690,7 @@ def constant_coefficient(self): sage: t = phi.ore_polring().gen() sage: psi = cat.object(phi.constant_coefficient() + t^3) sage: psi - Drinfeld module defined by X |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base Reciprocally, it is impossible to create two Drinfeld modules in this category if they do not share the same constant @@ -712,10 +712,10 @@ def ore_polring(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: ore_polring = phi.ore_polring() sage: ore_polring Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 over its base twisted by Frob^2 @@ -729,7 +729,7 @@ def ore_polring(self): The generator of the Drinfeld module is in the Ore polynomial ring:: - sage: phi(X) in ore_polring + sage: phi(T) in ore_polring True """ return self.category().ore_polring() diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index e8319a1f308..5bdf666218c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -29,7 +29,7 @@ class DrinfeldModuleAction(Action): This class represents the module action induced by a Drinfeld module. - Let `\phi` be a Drinfeld module with base `\gamma: \mathbb{F}_q[X] + Let `\phi` be a Drinfeld module with base `\gamma: \mathbb{F}_q[T] \to K`. Let `L/K` be a field extension, let `x \in L`, let `a` be a function ring element; the action is defined as `(a, x) \mapsto \phi_a(x)`. @@ -46,23 +46,23 @@ class DrinfeldModuleAction(Action): EXAMPLES:: sage: Fq. = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by X |--> t^3 + z over base Finite Field in z of size 11^2 over its base + Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z over base Finite Field in z of size 11^2 over its base The action on elements is computed as follows:: - sage: P = X + 1 + sage: P = T + 1 sage: a = z sage: action(P, a) ... 4*z + 2 sage: action(0, K.random_element()) 0 - sage: action(FqX.random_element(), 0) + sage: action(A.random_element(), 0) 0 Finally, given a Drinfeld module action, it is easy to recover the @@ -81,9 +81,9 @@ def __init__(self, drinfeld_module): TESTS:: sage: Fq. = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action._drinfeld_module is phi True @@ -110,17 +110,17 @@ def _act_(self, pol, x): EXAMPLES:: sage: Fq. = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() - sage: P = X + 1 + sage: P = T + 1 sage: a = z sage: action(P, a) 4*z + 2 sage: action(0, K.random_element()) 0 - sage: action(FqX.random_element(), 0) + sage: action(A.random_element(), 0) 0 """ if pol not in self._drinfeld_module.function_ring(): @@ -138,12 +138,12 @@ def _latex_(self): EXAMPLES:: sage: Fq. = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: latex(action) - \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto t^{3} + z\text{{ }over{ }base{ }}\Bold{F}_{11^{2}} + \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\text{Drinfeld{ }module{ }defined{ }by{ }} T \mapsto t^{3} + z\text{{ }over{ }base{ }}\Bold{F}_{11^{2}} """ return f'\\text{{Action{{ }}on{{ }}}}' \ f'{latex(self._base_ring)}\\text{{{{ }}' \ @@ -158,12 +158,12 @@ def _repr_(self): EXAMPLES:: sage: Fq. = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by X |--> t^3 + z over base Finite Field in z of size 11^2 over its base + Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z over base Finite Field in z of size 11^2 over its base """ return f'Action on {self._base_ring} induced by ' \ f'{self._drinfeld_module}' @@ -177,9 +177,9 @@ def drinfeld_module(self): EXAMPLES:: sage: Fq. = GF(11) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z, 0, 0, 1]) + sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action.drinfeld_module() is phi True diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 22d0f3c399f..9f3784bbbc3 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -40,21 +40,21 @@ class DrinfeldModule(Parent, UniqueRepresentation): r""" - This class represents a Drinfeld `\mathbb{F}_q[X]`-module. + This class represents a Drinfeld `\mathbb{F}_q[T]`-module. - Let `\mathbb{F}_q[X]` be a polynomial ring with coefficients in a + Let `\mathbb{F}_q[T]` be a polynomial ring with coefficients in a finite field `\mathbb{F}_q` and let `K` be a field. Fix a ring - morphism `\gamma: \mathbb{F}_q[X] \to K`. We say that the field `K` - is an `\mathbb{F}_q[X]`-field, so that the *base of the Drinfeld - module* is defined as the `\mathbb{F}_q[X]`-field *K*. The base + morphism `\gamma: \mathbb{F}_q[T] \to K`. We say that the field `K` + is an `\mathbb{F}_q[T]`-field, so that the *base of the Drinfeld + module* is defined as the `\mathbb{F}_q[T]`-field *K*. The base uniquely defines the category, and we also refer to it as the *base ring* or *base field*. The *base morphism* is the morphism `\gamma: - \mathbb{F}_q[X] \to K`. + \mathbb{F}_q[T] \to K`. .. NOTE:: Equivalently, the base of the Drinfeld module could be defined as the - base morphism `\gamma: \mathbb{F}_q[X] \to K`. + base morphism `\gamma: \mathbb{F}_q[T] \to K`. .. NOTE:: @@ -62,42 +62,42 @@ class DrinfeldModule(Parent, UniqueRepresentation): See also :class:`sage.categories.drinfeld_modules`. The monic polynomial that generates the kernel of the base morphism - is called the `\mathbb{F}_q[X]`-characteristic of the - `\mathbb{F}_q[X]`-field `K`. It cal also be referred to as the + is called the `\mathbb{F}_q[T]`-characteristic of the + `\mathbb{F}_q[T]`-field `K`. It cal also be referred to as the function-field characteristic of `K`. Let `K\{\tau\}` be the ring of Ore polynomials with coefficients in `K` and Frobenius variable `\tau: x \mapsto x^q`. A Drinfeld - `\mathbb{F}_q[X]`-module over the `\mathbb{F}_q[X]`-field `K` is an - `\mathbb{F}_q`-algebra morphism `\phi: \mathbb{F}_q[X] \to + `\mathbb{F}_q[T]`-module over the `\mathbb{F}_q[T]`-field `K` is an + `\mathbb{F}_q`-algebra morphism `\phi: \mathbb{F}_q[T] \to K\{\tau\}` such that: 1. The image of `\phi` contains nonconstant Ore polynomials. - 2. For every element `a` in the `\mathbb{F}_q[X]`, the constant + 2. For every element `a` in the `\mathbb{F}_q[T]`, the constant coefficient `\phi(a)` is `\gamma(a)`. For `a` in the function ring, `\phi(a)` is denoted `\phi_a`. - The Drinfeld `\mathbb{F}_q[X]`-module `\phi` is uniquely determined - by the image `\phi_X` of `X` — this serves as input of the class. + The Drinfeld `\mathbb{F}_q[T]`-module `\phi` is uniquely determined + by the image `\phi_T` of `T` — this serves as input of the class. A Drinfeld module is said to be finite if the field `K` is. Despite an emphasis on this case, the base field can be any extension of `\mathbb{F}_q`:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z, 4, 1]) + sage: phi = DrinfeldModule(A, [z, 4, 1]) sage: phi - Drinfeld module defined by X |--> t^2 + 4*t + z over base Finite Field in z of size 5^12 over its base + Drinfeld module defined by T |--> t^2 + 4*t + z over base Finite Field in z of size 5^12 over its base :: sage: Fq = GF(49) - sage: FqX. = Fq[] - sage: K. = Frac(FqX) - sage: psi = DrinfeldModule(FqX, [z, X+1]) + sage: A. = Fq[] + sage: K. = Frac(A) + sage: psi = DrinfeldModule(A, [z, T+1]) sage: psi .. NOTE:: @@ -105,11 +105,11 @@ class DrinfeldModule(Parent, UniqueRepresentation): Finite Drinfeld modules are implemented in the class :class:`sage.rings.function_field.drinfeld_modules.finite_drinfeld_module`. - We say that `\mathbb{F}_q[X]` is the function ring of `\phi`; + We say that `\mathbb{F}_q[T]` is the function ring of `\phi`; `K\{\tau\}` is the Ore polynomial ring of `\phi`. Further, the - generator of `\phi` is `\phi_X` and its constant coefficient is the - constant coefficient of `\phi_X`. The - `\mathbb{F}_q[X]`-characteristic of the `\mathbb{F}_q[X]`-field `K` + generator of `\phi` is `\phi_T` and its constant coefficient is the + constant coefficient of `\phi_T`. The + `\mathbb{F}_q[T]`-characteristic of the `\mathbb{F}_q[T]`-field `K` can also be referred to as its function ring-characteristic. Finally, `K` is just referred to as the codomain base. @@ -119,7 +119,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. NOTE:: Drinfeld modules are defined in a larger setting, in which the - polynomial ring `\mathbb{F}_q[X]` is replaced by a more general + polynomial ring `\mathbb{F}_q[T]` is replaced by a more general function ring: the ring of functions in `k` that are regular outside `\infty`, where `k` is a function field over `\mathbb{F}_q` with transcendence degree `1` and `\infty` is a @@ -141,27 +141,27 @@ class DrinfeldModule(Parent, UniqueRepresentation): ring and the generator:: sage: Fq. = GF(3^2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z, 1, 1]) + sage: phi = DrinfeldModule(A, [z, 1, 1]) sage: phi - Drinfeld module defined by X |--> t^2 + t + z over base Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> t^2 + t + z over base Finite Field in z of size 3^12 over its base Note that the definition of the base morphism is implicit; it is defined as the `\mathbb{F}_q`-algebra morphism defined from - `\mathbb{F}_q[X]` to the base ring, and the base ring is a ring + `\mathbb{F}_q[T]` to the base ring, and the base ring is a ring extension over the base morphism whose underlying ring is the compositum of all the parents of the coefficients. The above Drinfeld module is finite; it can also be infinite:: - sage: L = Frac(FqX) - sage: psi = DrinfeldModule(FqX, [L(X), 1, X^3 + X + 1]) + sage: L = Frac(A) + sage: psi = DrinfeldModule(A, [L(T), 1, T^3 + T + 1]) sage: psi - Drinfeld module defined by X |--> (X^3 + X + 1)*t^2 + t + X over base Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - To: Fraction Field of Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 - Defn: X |--> X + Drinfeld module defined by T |--> (T^3 + T + 1)*t^2 + t + T over base Ring morphism: + From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 + To: Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 + Defn: T |--> T :: @@ -171,23 +171,23 @@ class DrinfeldModule(Parent, UniqueRepresentation): False In those examples, we used a list of coefficients (``[z, 1, 1]``) to - represent the generator `\phi_X = z + t + t^2`. One can also use + represent the generator `\phi_T = z + t + t^2`. One can also use regular Ore polynomials:: sage: ore_polring = phi.ore_polring() sage: t = phi.ore_polring().gen() - sage: rho_X = z + t^3 - sage: rho = DrinfeldModule(FqX, rho_X) + sage: rho_T = z + t^3 + sage: rho = DrinfeldModule(A, rho_T) sage: rho - Drinfeld module defined by X |--> t^3 + z over base Finite Field in z of size 3^12 over its base - sage: rho(X) == rho_X + Drinfeld module defined by T |--> t^3 + z over base Finite Field in z of size 3^12 over its base + sage: rho(T) == rho_T True Images under the Drinfeld module are computed by calling the object:: - sage: phi(X) # phi_X, the generator of the Drinfeld module + sage: phi(T) # phi_T, the generator of the Drinfeld module t^2 + t + z - sage: phi(X^3 + X + 1) # phi_(X^3 +X + 1) + sage: phi(T^3 + T + 1) # phi_(T^3 +T + 1) t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 sage: phi(1) # phi_1 1 @@ -208,7 +208,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: cat = phi.category() sage: cat.object([z, 0, 0, 1]) - Drinfeld module defined by X |--> t^3 + z over base Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> t^3 + z over base Finite Field in z of size 3^12 over its base .. RUBRIC:: The base ring of a Drinfeld module @@ -222,9 +222,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.base_morphism() Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 To: Finite Field in z of size 3^12 over its base - Defn: X |--> z + Defn: T |--> z Note that the base ring is *not* the field `K`. Rather, it is a ring extension (see :class:`sage.rings.ring_extension.RingExtension`) @@ -241,9 +241,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.base_morphism() Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 To: Finite Field in z of size 3^12 over its base - Defn: X |--> z + Defn: T |--> z :: @@ -252,28 +252,28 @@ class DrinfeldModule(Parent, UniqueRepresentation): :: - sage: phi.function_ring() # Fq[X] - Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + sage: phi.function_ring() # Fq[T] + Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 :: - sage: phi.gen() # phi_X + sage: phi.gen() # phi_T t^2 + t + z - sage: phi.gen() == phi(X) + sage: phi.gen() == phi(T) True :: - sage: phi.constant_coefficient() # Constant coefficient of phi_X + sage: phi.constant_coefficient() # Constant coefficient of phi_T z :: sage: phi.morphism() # The Drinfeld module as a morphism Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 3^2 + From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 To: Ore Polynomial Ring in t over Finite Field in z of size 3^12 over its base twisted by Frob^2 - Defn: X |--> t^2 + t + z + Defn: T |--> t^2 + t + z One can compute the rank and height:: @@ -287,9 +287,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.j_invariant() # j-invariant 1 - A Drinfeld `\mathbb{F}_q[X]`-module can be seen as an Ore + A Drinfeld `\mathbb{F}_q[T]`-module can be seen as an Ore polynomial with positive degree and constant coefficient - `\gamma(X)`, where `\gamma` is the base morphism. This analogy is + `\gamma(T)`, where `\gamma` is the base morphism. This analogy is the motivation for the following methods:: sage: phi.coefficients() @@ -305,13 +305,13 @@ class DrinfeldModule(Parent, UniqueRepresentation): A morphism of Drinfeld modules `\phi \to \psi` is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a` in - the function ring. In our case, this is equivalent to `f \phi_X = - \psi_X f`. An isogeny is a nonzero morphism. + the function ring. In our case, this is equivalent to `f \phi_T = + \psi_T f`. An isogeny is a nonzero morphism. Use the ``in`` syntax to test if an Ore polynomial defines a morphism:: - sage: phi(X) in Hom(phi, phi) + sage: phi(T) in Hom(phi, phi) True sage: t^6 in Hom(phi, phi) True @@ -380,10 +380,10 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z sage: psi = phi.velu(ore_pol) sage: psi - Drinfeld module defined by X |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over base Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over base Finite Field in z of size 3^12 over its base sage: ore_pol in Hom(phi, psi) True - sage: ore_pol * phi(X) == psi(X) * ore_pol + sage: ore_pol * phi(T) == psi(T) * ore_pol True If the input does not define an isogeny, an exception is raised: @@ -399,8 +399,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. RUBRIC:: The action of a Drinfeld module - The `\mathbb{F}_q[X]`-Drinfeld module `\phi` induces a special left - `\mathbb{F}_q[X]`-module structure on any field extension `L/K`. Let + The `\mathbb{F}_q[T]`-Drinfeld module `\phi` induces a special left + `\mathbb{F}_q[T]`-module structure on any field extension `L/K`. Let `x \in L` and `a` be in the function ring; the action is defined as `(a, x) \mapsto \phi_a(x)`. The method :meth:`action` returns an ``Action`` object representing the Drinfeld module action. @@ -411,18 +411,18 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: action = phi.action() sage: action - Action on Finite Field in z of size 3^12 over its base induced by Drinfeld module defined by X |--> t^2 + t + z over base Finite Field in z of size 3^12 over its base + Action on Finite Field in z of size 3^12 over its base induced by Drinfeld module defined by T |--> t^2 + t + z over base Finite Field in z of size 3^12 over its base The action on elements is computed by calling the action object:: - sage: P = X + 1 + sage: P = T + 1 sage: a = z sage: action(P, a) ... z^9 + 2*z^8 + 2*z^7 + 2*z^6 + 2*z^3 + z^2 sage: action(0, K.random_element()) 0 - sage: action(FqX.random_element(), 0) + sage: action(A.random_element(), 0) 0 .. RUBRIC:: Inverting the Drinfeld module @@ -432,7 +432,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): `a`, one can retrieve `a` (as a morphism, a Drinfeld module is injective, see [Gos1998]_, cor. 4.5.2.):: - sage: a = FqX.random_element() + sage: a = A.random_element() sage: phi.invert(phi(a)) == a True @@ -442,8 +442,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: Fq = GF(2) sage: K. = Fq.extension(2) - sage: FqX. = Fq[] - sage: DrinfeldModule(FqX, [K(1)]) + sage: A. = Fq[] + sage: DrinfeldModule(A, [K(1)]) Traceback (most recent call last): ... ValueError: generator must have positive degree @@ -452,20 +452,20 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: Fq = GF(2) sage: K. = Fq.extension(2) - sage: FqX. = Fq[] - sage: DrinfeldModule(FqX, [K(0), K(1)]) + sage: A. = Fq[] + sage: DrinfeldModule(A, [K(0), K(1)]) Traceback (most recent call last): ... ValueError: constant coefficient must be nonzero The coefficients of the generator must lie in an - `\mathbb{F}_q[X]`-field, where `\mathbb{F}_q[X]` is the function + `\mathbb{F}_q[T]`-field, where `\mathbb{F}_q[T]` is the function ring of the Drinfeld module:: sage: Fq = GF(2) sage: K. = Fq.extension(2) - sage: FqX. = Fq[] - sage: DrinfeldModule(FqX, [z, QQ(1)]) + sage: A. = Fq[] + sage: DrinfeldModule(A, [z, QQ(1)]) Traceback (most recent call last): ... ValueError: function ring base must coerce into base ring @@ -474,8 +474,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: Fq = GF(2) sage: K. = Fq.extension(2) - sage: FqX. = Fq[] - sage: DrinfeldModule(FqX, [1, QQ(1)]) + sage: A. = Fq[] + sage: DrinfeldModule(A, [1, QQ(1)]) Traceback (most recent call last): ... ValueError: function ring base must coerce into base ring @@ -485,7 +485,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: Fq = GF(2) sage: K. = Fq.extension(2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: DrinfeldModule(K, [z, 1, 1]) Traceback (most recent call last): ... @@ -495,9 +495,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: Fq = GF(2) sage: K. = Fq.extension(2) - sage: FqX. = Fq[] - sage: FqXY. = FqX[] - sage: DrinfeldModule(FqXY, [z, 1, 1]) + sage: A. = Fq[] + sage: AY. = A[] + sage: DrinfeldModule(AY, [z, 1, 1]) Traceback (most recent call last): ... TypeError: function ring base must be a finite field @@ -508,8 +508,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: Fq = GF(2) sage: K. = Fq.extension(2) - sage: FqX. = Fq[] - sage: phi = DrinfeldModule(FqX, [z, 1]) + sage: A. = Fq[] + sage: phi = DrinfeldModule(A, [z, 1]) sage: phi.category().object([1, 1, K(1)]) Traceback (most recent call last): ... @@ -519,8 +519,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): :: sage: Fq = K = GF(2) - sage: FqX. = Fq[] - sage: phi = DrinfeldModule(FqX, [1, 1]) + sage: A. = Fq[] + sage: phi = DrinfeldModule(A, [1, 1]) Traceback (most recent call last): ... ValueError: function ring base must coerce into base ring @@ -528,28 +528,28 @@ class DrinfeldModule(Parent, UniqueRepresentation): :: sage: Fq = K = GF(2) - sage: FqX. = Fq[] - sage: phi = DrinfeldModule(FqX, [K(1), 1]) + sage: A. = Fq[] + sage: phi = DrinfeldModule(A, [K(1), 1]) sage: isinstance(phi.ore_polring(), OrePolynomialRing) True Test that the base morphism is correct:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K = Frac(Fq) - sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(1)]) + sage: phi = DrinfeldModule(A, [Fq.gen(), K(1)]) sage: phi.base().codomain() is K True :: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: k = Frac(Fq) - sage: kT. = k[] - sage: K = k.extension(T^3 + T + 1) - sage: phi = DrinfeldModule(FqX, [Fq.gen(), K.gen()]) + sage: kS. = k[] + sage: K = k.extension(S^3 + S + 1) + sage: phi = DrinfeldModule(A, [Fq.gen(), K.gen()]) sage: phi.base().codomain() is K True @@ -557,20 +557,20 @@ class DrinfeldModule(Parent, UniqueRepresentation): of definition of the coefficients:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: k = Frac(Fq) - sage: kT. = k[] - sage: K = k.extension(T^3 + T + 1) - sage: phi = DrinfeldModule(FqX, [K(k.gen()), 1]) + sage: kS. = k[] + sage: K = k.extension(S^3 + S + 1) + sage: phi = DrinfeldModule(A, [K(k.gen()), 1]) sage: phi.base().codomain() is K True :: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [Fq.gen(), K(Fq.gen())]) + sage: phi = DrinfeldModule(A, [Fq.gen(), K(Fq.gen())]) sage: phi.base().codomain() is K True """ @@ -596,17 +596,17 @@ def __classcall_private__(cls, function_ring, gen, name='t'): sage: from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: isinstance(phi, FiniteDrinfeldModule) True :: - sage: K = Frac(FqX) - sage: phi = DrinfeldModule(FqX, [K(X), 1]) + sage: K = Frac(A) + sage: phi = DrinfeldModule(A, [K(T), 1]) sage: isinstance(psi, FiniteDrinfeldModule) False """ @@ -616,7 +616,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): # the category. Another problem is that those lines are # duplicate. As a general comment, there are sanity checks both # here and in the category constructor, which is not ideal. - # Check domain is Fq[X] + # Check domain is Fq[T] if not isinstance(function_ring, PolynomialRing_general): raise NotImplementedError('function ring must be a polynomial ' 'ring') @@ -632,7 +632,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): # Base ring without morphism structure: base_ring_noext = ore_polring.base() name = ore_polring.variable_name() - # `gen` is a list of coefficients (function_ring = Fq[X]): + # `gen` is a list of coefficients (function_ring = Fq[T]): elif isinstance(gen, (list, tuple)): ore_polring = None # Base ring without morphism structure: @@ -688,21 +688,21 @@ def __init__(self, gen, category): TESTS:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: gen = [p_root, z12^3, z12^5] - sage: phi = DrinfeldModule(FqX, gen) + sage: phi = DrinfeldModule(A, gen) sage: ore_polring = phi.ore_polring() sage: phi._base == phi.category().base() True - sage: phi._function_ring == FqX + sage: phi._function_ring == A True sage: phi._gen == ore_polring(gen) True sage: phi._ore_polring == ore_polring True - sage: phi._morphism == Hom(FqX, ore_polring)(phi._gen) + sage: phi._morphism == Hom(A, ore_polring)(phi._gen) True """ self._base = category.base() @@ -728,14 +728,14 @@ def __call__(self, a): TESTS:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) :: - sage: a = X^3 + 4*X + 2 - sage: phi(a) == phi(X)^3 + 4*phi(X) + 2 + sage: a = T^3 + 4*T + 2 + sage: phi(a) == phi(T)^3 + 4*phi(T) + 2 True sage: phi(a)[0] == p_root^3 + 4*p_root + 2 True @@ -746,12 +746,12 @@ def __call__(self, a): 0 sage: phi(1) 1 - sage: phi(X) == phi._gen + sage: phi(T) == phi._gen True :: - sage: a = FqX.random_element(5) + sage: a = A.random_element(5) sage: phi(a)[0] == phi.category().base()(a) True """ @@ -776,10 +776,10 @@ def _Hom_(self, other, category): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: t = phi.ore_polring().gen() sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(isog) @@ -800,12 +800,12 @@ def _check_rank_two(self): TESTS:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi._check_rank_two() - sage: phi = DrinfeldModule(FqX, [p_root, 1]) + sage: phi = DrinfeldModule(A, [p_root, 1]) sage: phi._check_rank_two() Traceback (most recent call last): ... @@ -823,12 +823,12 @@ def _latex_(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: latex(phi) - \text{Drinfeld{ }module{ }defined{ }by{ }} X \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }base{ }}\Bold{F}_{5^{12}} + \text{Drinfeld{ }module{ }defined{ }by{ }} T \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }base{ }}\Bold{F}_{5^{12}} """ return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ f'{latex(self._function_ring.gen())} '\ @@ -844,12 +844,12 @@ def _repr_(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi - Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base """ return f'Drinfeld module defined by {self._function_ring.gen()} ' \ f'|--> {self._gen} over base {self._base}' @@ -866,17 +866,17 @@ def action(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: action = phi.action() sage: action - Action on Finite Field in z12 of size 5^12 over its base induced by Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Action on Finite Field in z12 of size 5^12 over its base induced by Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base The action on elements is computed as follows:: - sage: P = X^2 + X + 1 + sage: P = T^2 + T + 1 sage: a = z12 + 1 sage: action(P, a) 3*z12^11 + 2*z12^10 + 3*z12^9 + 3*z12^7 + 4*z12^5 + z12^4 + z12^3 + 2*z12 + 1 @@ -901,10 +901,10 @@ def coefficient(self, n): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.coefficient(0) 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi.coefficient(0) == p_root @@ -940,10 +940,10 @@ def coefficients(self, sparse=True): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.coefficients() [2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12, z12^3, @@ -952,7 +952,7 @@ def coefficients(self, sparse=True): Careful, the method only returns the nonzero coefficients, unless otherwise specified:: - sage: rho = DrinfeldModule(FqX, [p_root, 0, 0, 0, 1]) + sage: rho = DrinfeldModule(A, [p_root, 0, 0, 0, 1]) sage: rho.coefficients() [2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12, 1] @@ -974,11 +974,11 @@ def gen(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: phi.gen() == phi(X) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.gen() == phi(T) True """ return self._gen @@ -1008,26 +1008,26 @@ def height(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.height() == 1 True sage: phi.is_ordinary() True - sage: L = Frac(FqX) - sage: phi = DrinfeldModule(FqX, [L(2), L(1)]) + sage: L = Frac(A) + sage: phi = DrinfeldModule(A, [L(2), L(1)]) sage: phi.height() Traceback (most recent call last): ... ValueError: height is defined for prime function field characteristic sage: Fq = GF(343) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: phi.height() 2 sage: phi.is_supersingular() @@ -1060,14 +1060,14 @@ def invert(self, ore_pol): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) - sage: a = FqX.random_element() + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: a = A.random_element() sage: phi.invert(phi(a)) == a True - sage: phi.invert(phi(X)) == X + sage: phi.invert(phi(T)) == T True sage: phi.invert(phi(Fq.gen())) == Fq.gen() True @@ -1092,7 +1092,7 @@ def invert(self, ore_pol): TESTS:: - sage: a = FqX.random_element() + sage: a = A.random_element() sage: cat = phi.category() sage: phi_r1 = cat.random_object(1) sage: phi_r1.invert(phi_r1(a)) == a @@ -1121,12 +1121,12 @@ def invert(self, ore_pol): 'module') k = deg // r - X = self._function_ring.gen() + T = self._function_ring.gen() mat_lines = [[0 for _ in range(k+1)] for _ in range(k+1)] for i in range(k+1): - phi_X_i = self(X**i) + phi_T_i = self(T**i) for j in range(i+1): - mat_lines[j][i] = phi_X_i[r*j] + mat_lines[j][i] = phi_T_i[r*j] mat = Matrix(mat_lines) vec = vector([list(ore_pol)[r*j] for j in range(k+1)]) pre_image = self._function_ring(list((mat**(-1)) * vec)) @@ -1146,14 +1146,14 @@ def is_finite(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.is_finite() True - sage: L = Frac(FqX) - sage: psi = DrinfeldModule(FqX, [L(2), L(1)]) + sage: L = Frac(A) + sage: psi = DrinfeldModule(A, [L(2), L(1)]) sage: psi.is_finite() False """ @@ -1165,7 +1165,7 @@ def j_invariant(self): Return the j-invariant of the Drinfeld module if the rank is two; raise a NotImplementedError otherwise. - Assume the rank is two. Write the generator `\phi_X = \omega + + Assume the rank is two. Write the generator `\phi_T = \omega + g\tau + \Delta\tau^2`. The j-invariant is defined by `\frac{g^{q+1}}{\Delta}`, `q` being the order of the base ring of the function ring. In our case, this field is always finite. @@ -1175,22 +1175,22 @@ def j_invariant(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.j_invariant() z12^10 + 4*z12^9 + 3*z12^8 + 2*z12^7 + 3*z12^6 + z12^5 + z12^3 + 4*z12^2 + z12 + 2 - sage: psi = DrinfeldModule(FqX, [p_root, 1, 1]) + sage: psi = DrinfeldModule(A, [p_root, 1, 1]) sage: psi.j_invariant() 1 - sage: rho = DrinfeldModule(FqX, [p_root, 0, 1]) + sage: rho = DrinfeldModule(A, [p_root, 0, 1]) sage: rho.j_invariant() 0 The rank must be two:: - sage: theta = DrinfeldModule(FqX, [p_root, 1, 0]) + sage: theta = DrinfeldModule(A, [p_root, 1, 0]) sage: theta.j_invariant() Traceback (most recent call last): ... @@ -1212,15 +1212,15 @@ def morphism(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.morphism() Ring morphism: - From: Univariate Polynomial Ring in X over Finite Field in z2 of size 5^2 + From: Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 To: Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 over its base twisted by Frob^2 - Defn: X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Defn: T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: from sage.rings.morphism import RingHomomorphism sage: isinstance(phi.morphism(), RingHomomorphism) True @@ -1228,9 +1228,9 @@ def morphism(self): Actually, the ``DrinfeldModule`` method :meth:`__call__` simply class the ``__call__`` method of this morphism:: - sage: phi.morphism()(X) == phi(X) + sage: phi.morphism()(T) == phi(T) True - sage: a = FqX.random_element() + sage: a = A.random_element() sage: phi.morphism()(a) == phi(a) True @@ -1244,7 +1244,7 @@ class the ``__call__`` method of this morphism:: True sage: m.im_gens() [z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12] - sage: phi(X) == m.im_gens()[0] + sage: phi(T) == m.im_gens()[0] True """ return self._morphism @@ -1260,16 +1260,16 @@ def rank(self): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.rank() 2 - sage: psi = DrinfeldModule(FqX, [p_root, 2]) + sage: psi = DrinfeldModule(A, [p_root, 2]) sage: psi.rank() 1 - sage: rho = DrinfeldModule(FqX, [p_root, 0, 0, 0, 1]) + sage: rho = DrinfeldModule(A, [p_root, 0, 0, 0, 1]) sage: rho.rank() 4 """ @@ -1311,21 +1311,21 @@ def velu(self, isog): EXAMPLES:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: t = phi.ore_polring().gen() sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(isog) sage: psi - Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base sage: isog in Hom(phi, psi) True This method works for endomorphisms as well:: - sage: phi.velu(phi(X)) is phi + sage: phi.velu(phi(T)) is phi True sage: phi.velu(t^6) is phi True diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 89fc6744209..5722d04d175 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -44,11 +44,11 @@ class FiniteDrinfeldModule(DrinfeldModule): ``FiniteDrinfeldModule`` depending on the input:: sage: Fq = GF(343) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z6, 0, 5]) + sage: phi = DrinfeldModule(A, [z6, 0, 5]) sage: phi - Drinfeld module defined by X |--> 5*t^2 + z6 over base Finite Field in z6 of size 7^6 over its base + Drinfeld module defined by T |--> 5*t^2 + z6 over base Finite Field in z6 of size 7^6 over its base :: @@ -84,19 +84,19 @@ class FiniteDrinfeldModule(DrinfeldModule): sage: chi = phi.frobenius_charpoly() sage: chi - T^2 + (X + 2*z3^2 + 2*z3 + 1)*T + 2*X^2 + (z3^2 + z3 + 4)*X + 2*z3 + X^2 + (T + 2*z3^2 + 2*z3 + 1)*X + 2*T^2 + (z3^2 + z3 + 4)*T + 2*z3 sage: frob_pol = frobenius_endomorphism.ore_polynomial() - sage: chi(frob_pol, phi(X)) + sage: chi(frob_pol, phi(T)) 0 This makes it possible to compute the Frobenius trace and norm:: sage: phi.frobenius_trace() - 6*X + 5*z3^2 + 5*z3 + 6 + 6*T + 5*z3^2 + 5*z3 + 6 sage: phi.frobenius_trace() == -chi[1] True sage: phi.frobenius_norm() - 2*X^2 + (z3^2 + z3 + 4)*X + 2*z3 + 2*T^2 + (z3^2 + z3 + 4)*T + 2*z3 And to decide if a Drinfeld module is ordinary or supersingular:: @@ -125,11 +125,11 @@ def __init__(self, gen, category): TESTS:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: gen = [p_root, z12^3, z12^5] - sage: phi = DrinfeldModule(FqX, gen) + sage: phi = DrinfeldModule(A, gen) sage: ore_polring = phi.ore_polring() sage: phi._gen == ore_polring(gen) True @@ -157,9 +157,9 @@ def frobenius_endomorphism(self): EXAMPLES:: sage: Fq = GF(343) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: phi.frobenius_endomorphism() Drinfeld Module morphism: From (gen): z6*t^2 + 1 @@ -190,15 +190,15 @@ def frobenius_charpoly(self, var='T'): characteristic polynomial of the Frobenius endomorphism as a bivariate polynomial. - Let `\chi = T^2 - A(X)T + B(X)` be the characteristic polynomial + Let `\chi = X^2 - A(T)X + B(T)` be the characteristic polynomial of the Frobenius endomorphism, let `t^n` be the Ore polynomial that defines the Frobenius endomorphism of `\phi`; by definition, `n` is the degree over `\mathbb{F}_q` of the base - codomain. We have `\chi(t^n)(\phi(X)) = t^{2n} - \phi_A t^n + + codomain. We have `\chi(t^n)(\phi(T)) = t^{2n} - \phi_A t^n + \phi_B = 0`, with `\deg(A) \leq \frac{n}{2}` and `\deg(B) = n`. - Note that the *Frobenius trace* is defined as `A(X)` and the - *Frobenius norm* is defined as `B(X)`. + Note that the *Frobenius trace* is defined as `A(T)` and the + *Frobenius norm* is defined as `B(T)`. INPUT: (default: ``'T'``) the name of the second variable @@ -208,9 +208,9 @@ def frobenius_charpoly(self, var='T'): EXAMPLES:: sage: Fq = GF(343) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: chi = phi.frobenius_charpoly() sage: chi T^2 + ((3*z3^2 + z3 + 4)*X + 4*z3^2 + 6*z3 + 3)*T + (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 @@ -218,24 +218,24 @@ def frobenius_charpoly(self, var='T'): :: sage: frob_pol = phi.frobenius_endomorphism().ore_polynomial() - sage: chi(frob_pol, phi(X)) + sage: chi(frob_pol, phi(T)) 0 :: - sage: A = phi.frobenius_trace() - sage: A - (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 - sage: B = phi.frobenius_norm() - sage: B - (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 + sage: trace = phi.frobenius_trace() + sage: trace + (4*z3^2 + 6*z3 + 3)*T + 3*z3^2 + z3 + 4 + sage: norm = phi.frobenius_norm() + sage: norm + (5*z3^2 + 2*z3)*T^2 + (4*z3^2 + 3*z3)*T + 5*z3^2 + 2*z3 :: sage: n = 2 # Degree over Fq of the base codomain - sage: A.degree() <= n/2 + sage: trace.degree() <= n/2 True - sage: B.degree() == n + sage: norm.degree() == n True ALGORITHM: @@ -249,8 +249,8 @@ def frobenius_charpoly(self, var='T'): computation of the norm and of the trace. """ self._check_rank_two() - A = self._function_ring # Fq[X] - S = PolynomialRing(A, name=var) # Fq[X][T] + A = self._function_ring # Fq[T] + S = PolynomialRing(A, name=var) # Fq[T][T] # Does not work when Fq is not a prime field... # chi = self._gen.reduced_charpoly() # return -chi(A.gen(), S.gen()) @@ -261,10 +261,10 @@ def frobenius_norm(self): Return Frobenius norm of the Drinfeld module, if the rank is two; raise a NotImplementedError otherwise. - Let `\mathbb{F}_q[X]` be the function ring, write `\chi = T^2 - - A(X)T + B(X) \in \mathbb{F}_q[X][T]` for the characteristic + Let `\mathbb{F}_q[T]` be the function ring, write `\chi = X^2 - + A(T)X + B(T) \in \mathbb{F}_q[T][X]` for the characteristic polynomial of the Frobenius endomorphism. The *Frobenius norm* - is defined as the polynomial `B(X) \in \mathbb{F}_q[X]`. + is defined as the polynomial `B(T) \in \mathbb{F}_q[T]`. Let `n` be the degree over `\mathbb{F}_q` of the base codomain. Then the Frobenius norm has degree `n`. @@ -274,12 +274,12 @@ def frobenius_norm(self): EXAMPLES:: sage: Fq = GF(343) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: B = phi.frobenius_norm() sage: B - (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 + (5*z3^2 + 2*z3)*T^2 + (4*z3^2 + 3*z3)*T + 5*z3^2 + 2*z3 :: @@ -315,10 +315,10 @@ def frobenius_trace(self): Return Frobenius norm of the Drinfeld module, if the rank is two; raise a NotImplementedError otherwise. - Let `\mathbb{F}_q[X]` be the function ring, write `\chi = T^2 - - A(X)T + B(X) \in \mathbb{F}_q[X][T]` for the characteristic + Let `\mathbb{F}_q[T]` be the function ring, write `\chi = T^2 - + A(X)T + B(X) \in \mathbb{F}_q[T][T]` for the characteristic polynomial of the Frobenius endomorphism. The *Frobenius norm* - is defined as the polynomial `B(X) \in \mathbb{F}_q[X]`. + is defined as the polynomial `B(T) \in \mathbb{F}_q[T]`. Let `n` be the degree over `\mathbb{F}_q` of the base codomain. Then the Frobenius trace has degree `\leq \frac{n}{2}`. @@ -327,13 +327,13 @@ def frobenius_trace(self): ALGORITHM: - Let `A(X)` denote the Frobenius trace and `B(X)` denote the - Frobenius norm. We begin by computing `B(X)`, see docstring + Let `A(T)` denote the Frobenius trace and `B(T)` denote the + Frobenius norm. We begin by computing `B(T)`, see docstring of method :meth:`frobenius_norm` for details. The characteristic polynomial of the Frobenius yields `t^{2n} - \phi_A t^n + \phi_B = 0`, where `t^n` is the Frobenius endomorphism. As `\phi_B` is now known, we can compute - `\phi_A = (t^{2n} + \phi_B) / t^n`. We get `A(X)` by + `\phi_A = (t^{2n} + \phi_B) / t^n`. We get `A(T)` by inverting this quantity, using the method :meth:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule.invert`, see its docstring for details. @@ -341,12 +341,12 @@ def frobenius_trace(self): EXAMPLES:: sage: Fq = GF(343) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: A = phi.frobenius_trace() sage: A - (4*z3^2 + 6*z3 + 3)*X + 3*z3^2 + z3 + 4 + (4*z3^2 + 6*z3 + 3)*T + 3*z3^2 + z3 + 4 :: @@ -386,9 +386,9 @@ def is_ordinary(self): EXAMPLES:: sage: Fq = GF(343) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: phi.is_ordinary() False sage: phi_p = phi(phi.characteristic()) @@ -398,10 +398,10 @@ def is_ordinary(self): ALGORITHM: Compute the Frobenius trace and test if the - `\mathbb{F}_q[X]` characteristic divides it. + `\mathbb{F}_q[T]` characteristic divides it. We could also test if the image of the - `\mathbb{F}_q[X]`-characteristic under the Drinfeld module + `\mathbb{F}_q[T]`-characteristic under the Drinfeld module is purely inseparable; see [Gek1991]_, Proposition 4.1. """ self._check_rank_two() @@ -422,9 +422,9 @@ def is_supersingular(self): EXAMPLES:: sage: Fq = GF(343) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [1, 0, z6]) + sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: phi.is_supersingular() True sage: phi(phi.characteristic()) # Purely inseparable diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 690d35653b8..812301bdbdf 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -39,10 +39,10 @@ class DrinfeldModuleHomset(Homset): EXAMPLES:: sage: Fq = GF(27) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) - sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: phi = DrinfeldModule(A, [z6, z6, 2]) + sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: hom Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 @@ -64,7 +64,7 @@ class DrinfeldModuleHomset(Homset): The domain and codomain must have the same Drinfeld modules category:: - sage: rho = DrinfeldModule(FqX, [Frac(FqX)(X), 1]) + sage: rho = DrinfeldModule(A, [Frac(A)(T), 1]) sage: Hom(phi, rho) Traceback (most recent call last): ... @@ -72,7 +72,7 @@ class DrinfeldModuleHomset(Homset): :: - sage: sigma = DrinfeldModule(FqX, [1, z6, 2]) + sage: sigma = DrinfeldModule(A, [1, z6, 2]) sage: Hom(phi, sigma) Traceback (most recent call last): ... @@ -152,10 +152,10 @@ def __init__(self, X, Y, category=None, check=True): TESTS:: sage: Fq = GF(27) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) - sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: phi = DrinfeldModule(A, [z6, z6, 2]) + sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: hom.domain() is phi True @@ -183,10 +183,10 @@ def _latex_(self): EXAMPLES:: sage: Fq = GF(27) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) - sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: phi = DrinfeldModule(A, [z6, z6, 2]) + sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: latex(hom) \text{Set{ }of{ }Drinfeld{ }module{ }morphisms{ }from{ }(gen){ }}2 t^{2} + z_{6} t + z_{6}\text{{ }to{ }(gen){ }}2 t^{2} + \left(2 z_{6}^{5} + 2 z_{6}^{4} + 2 z_{6} + 1\right) t + z_{6} @@ -205,10 +205,10 @@ def _repr_(self): EXAMPLES:: sage: Fq = GF(27) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) - sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: phi = DrinfeldModule(A, [z6, z6, 2]) + sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: hom Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 @@ -232,10 +232,10 @@ def __contains__(self, x): In the next examples, the input is an Ore polynomial:: sage: Fq = GF(27) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) - sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: phi = DrinfeldModule(A, [z6, z6, 2]) + sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: end = End(phi) sage: t = phi.ore_polring().gen() @@ -284,10 +284,10 @@ def _element_constructor_(self, *args, **kwds): EXAMPLES:: sage: Fq = GF(27) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(2) - sage: phi = DrinfeldModule(FqX, [z6, z6, 2]) - sage: psi = DrinfeldModule(FqX, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) + sage: phi = DrinfeldModule(A, [z6, z6, 2]) + sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) sage: hom = Hom(phi, psi) sage: end = End(phi) sage: t = phi.ore_polring().gen() diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 6383a9b2aad..a0ef455704e 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -29,10 +29,10 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, r""" This class represents a Drinfeld module morphism. - Let `\phi, \psi` be two Drinfeld modules with base `\gamma: \mathbb{F}_q[X] + Let `\phi, \psi` be two Drinfeld modules with base `\gamma: \mathbb{F}_q[T] \to K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a - \in \mathbb{F}_q[X]`. In our case, this is equivalent to `f \phi_X = \psi_X + \in \mathbb{F}_q[T]`. In our case, this is equivalent to `f \phi_T = \psi_T f`. An *isogeny* is a nonzero morphism. To create a morphism object, the user should never explicitly @@ -40,10 +40,10 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, homset with the defining Ore polynomial:: sage: Fq = GF(25) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(FqX, [p_root, z12^3, z12^5]) + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: t = phi.ore_polring().gen() sage: ore_pol = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(ore_pol) @@ -64,14 +64,14 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, One can get basic data on the morphism:: sage: morphism.domain() - Drinfeld module defined by X |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base sage: morphism.domain() is phi True :: sage: morphism.codomain() - Drinfeld module defined by X |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base sage: morphism.codomain() is psi True @@ -138,10 +138,10 @@ def __classcall_private__(cls, parent, x): TESTS:: sage: Fq = GF(2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism is Hom(phi, psi)(morphism) @@ -183,10 +183,10 @@ def __init__(self, parent, ore_pol): TESTS:: sage: Fq = GF(2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism._domain is phi @@ -211,10 +211,10 @@ def _latex_(self): EXAMPLES:: sage: Fq = GF(2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: latex(morphism) @@ -244,10 +244,10 @@ def _repr_(self): EXAMPLES:: sage: Fq = GF(2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism @@ -268,10 +268,10 @@ def is_zero(self): EXAMPLES:: sage: Fq = GF(2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism.is_zero() @@ -292,10 +292,10 @@ def is_isogeny(self): EXAMPLES:: sage: Fq = GF(2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism.is_isogeny() @@ -328,10 +328,10 @@ def is_isomorphism(self): EXAMPLES:: sage: Fq = GF(2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism.is_isomorphism() @@ -364,10 +364,10 @@ def ore_polynomial(self): EXAMPLES:: sage: Fq = GF(2) - sage: FqX. = Fq[] + sage: A. = Fq[] sage: K. = Fq.extension(6) - sage: phi = DrinfeldModule(FqX, [z6, 1, 1]) - sage: psi = DrinfeldModule(FqX, [z6, z6^4 + z6^2 + 1, 1]) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: ore_pol = morphism.ore_polynomial() @@ -376,7 +376,7 @@ def ore_polynomial(self): :: - sage: ore_pol * phi(X) == psi(X) * ore_pol + sage: ore_pol * phi(T) == psi(T) * ore_pol True """ return self._ore_polynomial From 774f670d75189f4ff2818f3130fc9761d4ceb541 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Mon, 6 Feb 2023 15:54:53 +0100 Subject: [PATCH 225/392] Adress David's comments - line 66: cal - line 79: function ring before definition - line 119: rewrite the note - line 140: other word for "precising" - line 190: pep8 --- .../function_field/drinfeld_modules/drinfeld_module.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 9f3784bbbc3..9066f5722a6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -76,7 +76,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): 2. For every element `a` in the `\mathbb{F}_q[T]`, the constant coefficient `\phi(a)` is `\gamma(a)`. - For `a` in the function ring, `\phi(a)` is denoted `\phi_a`. + For `a` in `\mathbb{F}_q[T]`, `\phi(a)` is denoted `\phi_a`. The Drinfeld `\mathbb{F}_q[T]`-module `\phi` is uniquely determined by the image `\phi_T` of `T` — this serves as input of the class. @@ -137,8 +137,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. RUBRIC:: Construction - A Drinfeld module object is constructed by precising the function - ring and the generator:: + A Drinfeld module object is constructed by giving the function ring + and the generator:: sage: Fq. = GF(3^2) sage: A. = Fq[] @@ -187,7 +187,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi(T) # phi_T, the generator of the Drinfeld module t^2 + t + z - sage: phi(T^3 + T + 1) # phi_(T^3 +T + 1) + sage: phi(T^3 + T + 1) # phi_(T^3 + T + 1) t^6 + (z^11 + z^9 + 2*z^6 + 2*z^4 + 2*z + 1)*t^4 + (2*z^11 + 2*z^10 + z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^3)*t^3 + (2*z^11 + z^10 + z^9 + 2*z^7 + 2*z^6 + z^5 + z^4 + 2*z^3 + 2*z + 2)*t^2 + (2*z^11 + 2*z^8 + 2*z^6 + z^5 + z^4 + 2*z^2)*t + z^3 + z + 1 sage: phi(1) # phi_1 1 From a70c2486b1cdee82305c5e1175353dcd0488d57c Mon Sep 17 00:00:00 2001 From: Kryzar Date: Mon, 6 Feb 2023 16:46:27 +0100 Subject: [PATCH 226/392] Add a LaTeX name for Drinfeld modules There is now an optional `latexname=None` argument given at init. --- src/sage/categories/drinfeld_modules.py | 6 +- .../function_field/drinfeld_modules/action.py | 4 +- .../drinfeld_modules/drinfeld_module.py | 74 ++++++++++++++----- .../finite_drinfeld_module.py | 2 +- .../drinfeld_modules/morphism.py | 4 +- 5 files changed, 63 insertions(+), 27 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 142300e0971..c8b6252ae29 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -137,7 +137,7 @@ class DrinfeldModules(Category_over_base_ring): sage: psi = cat.object([p_root, 1]) sage: psi - Drinfeld module defined by T |--> t + z^3 + 7*z^2 + 6*z + 10 over base Finite Field in z of size 11^4 over its base + Drinfeld module defined by T |--> t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 over its base sage: psi.category() is cat True @@ -474,7 +474,7 @@ def object(self, gen): sage: cat = phi.category() sage: psi = cat.object([p_root, 0, 1]) sage: psi - Drinfeld module defined by T |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over base Finite Field in z of size 11^4 over its base + Drinfeld module defined by T |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 over its base sage: t = phi.ore_polring().gen() sage: cat.object(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi True @@ -690,7 +690,7 @@ def constant_coefficient(self): sage: t = phi.ore_polring().gen() sage: psi = cat.object(phi.constant_coefficient() + t^3) sage: psi - Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base Reciprocally, it is impossible to create two Drinfeld modules in this category if they do not share the same constant diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 5bdf666218c..826ea735f77 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -51,7 +51,7 @@ class DrinfeldModuleAction(Action): sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z over base Finite Field in z of size 11^2 over its base + Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 11^2 over its base The action on elements is computed as follows:: @@ -163,7 +163,7 @@ def _repr_(self): sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z over base Finite Field in z of size 11^2 over its base + Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 11^2 over its base """ return f'Action on {self._base_ring} induced by ' \ f'{self._drinfeld_module}' diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 9066f5722a6..056e63aa145 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -90,7 +90,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z, 4, 1]) sage: phi - Drinfeld module defined by T |--> t^2 + 4*t + z over base Finite Field in z of size 5^12 over its base + Drinfeld module defined by T |--> t^2 + 4*t + z over Finite Field in z of size 5^12 over its base :: @@ -145,7 +145,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z, 1, 1]) sage: phi - Drinfeld module defined by T |--> t^2 + t + z over base Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> t^2 + t + z over Finite Field in z of size 3^12 over its base Note that the definition of the base morphism is implicit; it is defined as the `\mathbb{F}_q`-algebra morphism defined from @@ -158,7 +158,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: L = Frac(A) sage: psi = DrinfeldModule(A, [L(T), 1, T^3 + T + 1]) sage: psi - Drinfeld module defined by T |--> (T^3 + T + 1)*t^2 + t + T over base Ring morphism: + Drinfeld module defined by T |--> (T^3 + T + 1)*t^2 + t + T over Ring morphism: From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 To: Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 Defn: T |--> T @@ -179,7 +179,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: rho_T = z + t^3 sage: rho = DrinfeldModule(A, rho_T) sage: rho - Drinfeld module defined by T |--> t^3 + z over base Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 3^12 over its base sage: rho(T) == rho_T True @@ -192,6 +192,12 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi(1) # phi_1 1 + One can give a LaTeX name to be used for LaTeX representation:: + + sage: sigma = DrinfeldModule(A, [z, 1, 1], latexname='\phi') + sage: latex(sigma) + \phi + .. RUBRIC:: The category of Drinfeld modules Drinfeld modules have their own category (see class @@ -208,7 +214,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: cat = phi.category() sage: cat.object([z, 0, 0, 1]) - Drinfeld module defined by T |--> t^3 + z over base Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 3^12 over its base .. RUBRIC:: The base ring of a Drinfeld module @@ -380,7 +386,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z sage: psi = phi.velu(ore_pol) sage: psi - Drinfeld module defined by T |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over base Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over Finite Field in z of size 3^12 over its base sage: ore_pol in Hom(phi, psi) True sage: ore_pol * phi(T) == psi(T) * ore_pol @@ -411,7 +417,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: action = phi.action() sage: action - Action on Finite Field in z of size 3^12 over its base induced by Drinfeld module defined by T |--> t^2 + t + z over base Finite Field in z of size 3^12 over its base + Action on Finite Field in z of size 3^12 over its base induced by Drinfeld module defined by T |--> t^2 + t + z over Finite Field in z of size 3^12 over its base The action on elements is computed by calling the action object:: @@ -576,7 +582,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): """ @staticmethod - def __classcall_private__(cls, function_ring, gen, name='t'): + def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): """ Check input validity and return a ``DrinfeldModule`` or ``FiniteDrinfeldModule`` object accordingly. @@ -589,6 +595,8 @@ def __classcall_private__(cls, function_ring, gen, name='t'): coefficients or an Ore polynomial - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring gen + - ``latexname`` (default: ``None``) -- the LaTeX name of the Drinfeld + module OUTPUT: a DrinfeldModule or FiniteDrinfeldModule @@ -648,6 +656,10 @@ def __classcall_private__(cls, function_ring, gen, name='t'): base_ring_noext.has_coerce_map_from(function_ring.base_ring())): raise ValueError('function ring base must coerce into base ring') + # Check LaTeX name + if latexname is not None and type(latexname) is not str: + raise ValueError('LaTeX name should be a string') + # Build the category if isinstance(base_ring_noext, RingExtension_generic): base_ring = base_ring_noext @@ -669,7 +681,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): return FiniteDrinfeldModule(gen, category) return cls.__classcall__(cls, gen, category) - def __init__(self, gen, category): + def __init__(self, gen, category, latexname=None): """ Initialize ``self``. @@ -684,6 +696,8 @@ def __init__(self, gen, category): coefficients or an Ore polynomial - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring gen + - ``latexname`` (default: ``None``) -- the LaTeX name of the Drinfeld + module TESTS:: @@ -704,10 +718,13 @@ def __init__(self, gen, category): True sage: phi._morphism == Hom(A, ore_polring)(phi._gen) True + sage: phi._latexname is None + True """ self._base = category.base() self._function_ring = category.function_ring() self._gen = gen + self._latexname = latexname self._morphism = category._function_ring.hom([gen]) self._ore_polring = gen.parent() self._Fq = self._function_ring.base_ring() # Must be last @@ -818,6 +835,9 @@ def _latex_(self): r""" Return a LaTeX representation of the Drinfeld module. + If a LaTeX name was given at init. using `latexname`, use the LaTeX + name. Otherwise, create a representation. + OUTPUT: a string EXAMPLES:: @@ -829,11 +849,27 @@ def _latex_(self): sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: latex(phi) \text{Drinfeld{ }module{ }defined{ }by{ }} T \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }base{ }}\Bold{F}_{5^{12}} + + :: + + sage: psi = DrinfeldModule(A, [p_root, z12^3, z12^5], latexname='\psi') + sage: latex(psi) + \psi + + :: + + sage: psi = DrinfeldModule(A, [p_root, z12^3, z12^5], latexname=1729) + Traceback (most recent call last): + ... + ValueError: LaTeX name should be a string """ - return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ - f'{latex(self._function_ring.gen())} '\ - f'\\mapsto {latex(self._gen)}' \ - f'\\text{{{{ }}over{{ }}base{{ }}}}{latex(self._base)}' + if self._latexname is not None: + return self._latexname + else: + return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ + f'{latex(self._function_ring.gen())} '\ + f'\\mapsto {latex(self._gen)}' \ + f'\\text{{{{ }}over{{ }}base{{ }}}}{latex(self._base)}' def _repr_(self): r""" @@ -849,10 +885,10 @@ def _repr_(self): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi - Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base """ return f'Drinfeld module defined by {self._function_ring.gen()} ' \ - f'|--> {self._gen} over base {self._base}' + f'|--> {self._gen} over {self._base}' def action(self): r""" @@ -872,7 +908,7 @@ def action(self): sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: action = phi.action() sage: action - Action on Finite Field in z12 of size 5^12 over its base induced by Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Action on Finite Field in z12 of size 5^12 over its base induced by Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base The action on elements is computed as follows:: @@ -1190,8 +1226,8 @@ def j_invariant(self): The rank must be two:: - sage: theta = DrinfeldModule(A, [p_root, 1, 0]) - sage: theta.j_invariant() + sage: sigma = DrinfeldModule(A, [p_root, 1, 0]) + sage: sigma.j_invariant() Traceback (most recent call last): ... NotImplementedError: rank must be 2 @@ -1319,7 +1355,7 @@ def velu(self, isog): sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(isog) sage: psi - Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base sage: isog in Hom(phi, psi) True diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 5722d04d175..016a4ee6a12 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -48,7 +48,7 @@ class FiniteDrinfeldModule(DrinfeldModule): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z6, 0, 5]) sage: phi - Drinfeld module defined by T |--> 5*t^2 + z6 over base Finite Field in z6 of size 7^6 over its base + Drinfeld module defined by T |--> 5*t^2 + z6 over Finite Field in z6 of size 7^6 over its base :: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index a0ef455704e..3ff5d40f7b7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -64,14 +64,14 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, One can get basic data on the morphism:: sage: morphism.domain() - Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base sage: morphism.domain() is phi True :: sage: morphism.codomain() - Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over base Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base sage: morphism.codomain() is psi True From b48243cb6617064254041b0050c3effe44050cee Mon Sep 17 00:00:00 2001 From: Kryzar Date: Mon, 6 Feb 2023 17:00:09 +0100 Subject: [PATCH 227/392] (minor) Modify DrinfeldModule.invert docstring --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 056e63aa145..02a42cd224c 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -1082,9 +1082,8 @@ def height(self): def invert(self, ore_pol): r""" - Return the preimage of the input under the Drinfeld module; - raise an exception if the input is not in the image of the - Drinfeld module. + Return the preimage of the input under the Drinfeld module, if it + exists. INPUT: From b8e54956aff8e602e04c8723fcec2a441bccd687 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Mon, 6 Feb 2023 21:39:49 +0100 Subject: [PATCH 228/392] (minor) Typo --- src/sage/rings/function_field/drinfeld_modules/action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 826ea735f77..8c1e68fcd6f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -170,7 +170,7 @@ def _repr_(self): def drinfeld_module(self): r""" - Return the Drinfeld module defining to the action. + Return the Drinfeld module defining the action. OUTPUT: a Drinfeld module From ef94f98219a71e9cfe209e06d06b7083ad51ceb7 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Mon, 6 Feb 2023 21:55:19 +0100 Subject: [PATCH 229/392] Proofread DrinfeldModuleAction --- .../function_field/drinfeld_modules/action.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 8c1e68fcd6f..74399316c92 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -26,20 +26,26 @@ class DrinfeldModuleAction(Action): r""" - This class represents the module action induced by a Drinfeld + This class implements the module action induced by a Drinfeld module. - Let `\phi` be a Drinfeld module with base `\gamma: \mathbb{F}_q[T] - \to K`. Let `L/K` be a field extension, let `x \in L`, let `a` be a - function ring element; the action is defined as `(a, x) \mapsto - \phi_a(x)`. + Let `\phi` be a Drinfeld module over a field `K` and let `L/K` be a + field extension. Let `x \in L` and let `a` be a function ring + element; the action is defined as `(a, x) \mapsto \phi_a(x)`. .. NOTE:: In this implementation, `L` is `K`. - The user should never explicitly instantiate the class - `DrinfeldModuleAction`. + .. NOTE:: + + The user should never explicitly instantiate the class + `DrinfeldModuleAction`. + + .. WARNING:: + + This class may be replaced later on. See issues #34833 and + #34834. INPUT: the Drinfeld module From 9c34fe1657d3ca40c19931130ce6b1e791c0675c Mon Sep 17 00:00:00 2001 From: ymusleh <44390016+ymusleh@users.noreply.github.com> Date: Mon, 6 Feb 2023 16:11:41 -0500 Subject: [PATCH 230/392] Major typo fix --- .../function_field/drinfeld_modules/finite_drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 016a4ee6a12..d4b84500ef6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -378,7 +378,7 @@ def is_ordinary(self): trace. A *supersingular* rank two finite Drinfeld module is a Drinfeld module that is not ordinary. - A rnak two Drinfeld module is *ordinary* if and only if it is + A rank two Drinfeld module is *ordinary* if and only if it is note supersingular; see :meth:`is_supersingular`. OUTPUT: a boolean From 6601038cdf3369e5f3b1b4372b238b080a35f8c7 Mon Sep 17 00:00:00 2001 From: ymusleh <44390016+ymusleh@users.noreply.github.com> Date: Mon, 6 Feb 2023 16:27:43 -0500 Subject: [PATCH 231/392] More extremely critical changes --- .../drinfeld_modules/finite_drinfeld_module.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index d4b84500ef6..c36c0e23ad6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -117,7 +117,7 @@ def __init__(self, gen, category): - ``function_ring`` -- a univariate polynomial ring whose base is a finite field - - ``gen`` -- the generator of the Drinfeld module; as a list of + - ``gen`` -- the generator of the Drinfeld module as a list of coefficients or an Ore polynomial - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring gen @@ -178,7 +178,7 @@ def frobenius_endomorphism(self): def frobenius_charpoly(self, var='T'): r""" Return the characteristic polynomial of the Frobenius - endomorphism, if the rank is two; raise a NotImplementedError + endomorphism if the rank is two. Raise a NotImplementedError otherwise. Let `\mathbb{F}_q` be the base ring of the function ring. The @@ -191,7 +191,7 @@ def frobenius_charpoly(self, var='T'): bivariate polynomial. Let `\chi = X^2 - A(T)X + B(T)` be the characteristic polynomial - of the Frobenius endomorphism, let `t^n` be the Ore polynomial + of the Frobenius endomorphism, and let `t^n` be the Ore polynomial that defines the Frobenius endomorphism of `\phi`; by definition, `n` is the degree over `\mathbb{F}_q` of the base codomain. We have `\chi(t^n)(\phi(T)) = t^{2n} - \phi_A t^n + @@ -245,12 +245,12 @@ def frobenius_charpoly(self, var='T'): See [MS2019]_, Section 4. See docstrings of methods :meth:`frobenius_norm` and - :meth:`frobenius_trace` for furthere details on the + :meth:`frobenius_trace` for further details on the computation of the norm and of the trace. """ self._check_rank_two() A = self._function_ring # Fq[T] - S = PolynomialRing(A, name=var) # Fq[T][T] + S = PolynomialRing(A, name=var) # Fq[T][X] # Does not work when Fq is not a prime field... # chi = self._gen.reduced_charpoly() # return -chi(A.gen(), S.gen()) @@ -259,7 +259,7 @@ def frobenius_charpoly(self, var='T'): def frobenius_norm(self): r""" Return Frobenius norm of the Drinfeld module, if the rank is - two; raise a NotImplementedError otherwise. + two, raise a NotImplementedError otherwise. Let `\mathbb{F}_q[T]` be the function ring, write `\chi = X^2 - A(T)X + B(T) \in \mathbb{F}_q[T][X]` for the characteristic @@ -316,7 +316,7 @@ def frobenius_trace(self): two; raise a NotImplementedError otherwise. Let `\mathbb{F}_q[T]` be the function ring, write `\chi = T^2 - - A(X)T + B(X) \in \mathbb{F}_q[T][T]` for the characteristic + A(X)T + B(X) \in \mathbb{F}_q[T][X]` for the characteristic polynomial of the Frobenius endomorphism. The *Frobenius norm* is defined as the polynomial `B(T) \in \mathbb{F}_q[T]`. @@ -379,7 +379,7 @@ def is_ordinary(self): Drinfeld module that is not ordinary. A rank two Drinfeld module is *ordinary* if and only if it is - note supersingular; see :meth:`is_supersingular`. + not supersingular; see :meth:`is_supersingular`. OUTPUT: a boolean From b4371c84b2e5914c129ae39874bfaf27df1f2178 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Mon, 6 Feb 2023 22:40:05 +0100 Subject: [PATCH 232/392] Manage merge conflict --- src/sage/categories/drinfeld_modules.py | 21 ++-- .../function_field/drinfeld_modules/action.py | 19 +-- .../drinfeld_modules/drinfeld_module.py | 111 ++++++------------ .../finite_drinfeld_module.py | 29 ++--- .../drinfeld_modules/morphism.py | 10 +- 5 files changed, 73 insertions(+), 117 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index c8b6252ae29..e1f74107688 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -36,11 +36,11 @@ class DrinfeldModules(Category_over_base_ring): Let `\mathbb{F}_q[T]` be a polynomial ring with coefficients in a finite field `\mathbb{F}_q` and let `K` be a field. Fix a ring morphism `\gamma: \mathbb{F}_q[T] \to K`. We say that the field `K` - is an `\mathbb{F}_q[T]`-field, so that the *base of the category* is - defined as the `\mathbb{F}_q[T]`-field *K*. The base uniquely - defines the category, and we also refer to it as the *base ring* or - *base field*. The *base morphism* is the morphism `\gamma: - \mathbb{F}_q[T] \to K`. + is an `\mathbb{F}_q[T]`-field, so that the *base field of the + category* is defined as the `\mathbb{F}_q[T]`-field *K*. The base + field uniquely defines the category, and we also refer to it as its + *base*. The *base morphism* is the morphism `\gamma: \mathbb{F}_q[T] + \to K`. .. NOTE:: @@ -57,7 +57,7 @@ class DrinfeldModules(Category_over_base_ring): coefficient of the category is the image of `T` under the base morphism. - INPUT: the base ring morphism + INPUT: the base field .. RUBRIC:: Construction @@ -79,13 +79,10 @@ class DrinfeldModules(Category_over_base_ring): .. RUBRIC:: Properties of the category - The base ring is retrieved using the method :meth:`base` or - :meth:`base_ring`:: + The base field is retrieved using the method :meth:`base`. sage: cat.base() Finite Field in z of size 11^4 over its base - sage: cat.base_ring() - Finite Field in z of size 11^4 over its base Equivalently, one can use :meth:`base_morphism` to retrieve the base morphism:: @@ -114,7 +111,7 @@ class DrinfeldModules(Category_over_base_ring): sage: cat.base_morphism()(cat.characteristic()) 0 - The base ring, base morphism, function ring and Ore polynomial ring + The base field, base morphism, function ring and Ore polynomial ring are the same for the category and its objects:: sage: cat.base() is phi.base() @@ -212,7 +209,7 @@ def __init__(self, base_field, name='t'): INPUT: - - ``base_ring`` -- the base field, which is a ring extension + - ``base_field`` -- the base field, which is a ring extension over a base - ``name`` (default: `'t'`) -- the name of the Ore polynomial variable diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 74399316c92..6fabd091805 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -29,9 +29,10 @@ class DrinfeldModuleAction(Action): This class implements the module action induced by a Drinfeld module. - Let `\phi` be a Drinfeld module over a field `K` and let `L/K` be a - field extension. Let `x \in L` and let `a` be a function ring - element; the action is defined as `(a, x) \mapsto \phi_a(x)`. + Let `\phi` be a Drinfeld `\mathbb{F}_q[T]`-module over a field `K` + and let `L/K` be a field extension. Let `x \in L` and let `a` be a + function ring element; the action is defined as `(a, x) \mapsto + \phi_a(x)`. .. NOTE:: @@ -93,14 +94,14 @@ def __init__(self, drinfeld_module): sage: action = phi.action() sage: action._drinfeld_module is phi True - sage: action._base_ring is phi.base() + sage: action._base is phi.base() True """ if not isinstance(drinfeld_module, DrinfeldModule): raise TypeError('input must be a DrinfeldModule') self._drinfeld_module = drinfeld_module - self._base_ring = drinfeld_module.base() - super().__init__(drinfeld_module.function_ring(), self._base_ring) + self._base = drinfeld_module.base() + super().__init__(drinfeld_module.function_ring(), self._base) def _act_(self, pol, x): r""" @@ -131,7 +132,7 @@ def _act_(self, pol, x): """ if pol not in self._drinfeld_module.function_ring(): raise TypeError('first input must be in the function ring') - if x not in self._base_ring: + if x not in self._base: raise TypeError('second input must be in the field acted upon') return self._drinfeld_module(pol)(x) @@ -152,7 +153,7 @@ def _latex_(self): \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\text{Drinfeld{ }module{ }defined{ }by{ }} T \mapsto t^{3} + z\text{{ }over{ }base{ }}\Bold{F}_{11^{2}} """ return f'\\text{{Action{{ }}on{{ }}}}' \ - f'{latex(self._base_ring)}\\text{{{{ }}' \ + f'{latex(self._base)}\\text{{{{ }}' \ f'induced{{ }}by{{ }}}}{latex(self._drinfeld_module)}' def _repr_(self): @@ -171,7 +172,7 @@ def _repr_(self): sage: action Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 11^2 over its base """ - return f'Action on {self._base_ring} induced by ' \ + return f'Action on {self._base} induced by ' \ f'{self._drinfeld_module}' def drinfeld_module(self): diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 02a42cd224c..ef16fb1bd98 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -45,25 +45,19 @@ class DrinfeldModule(Parent, UniqueRepresentation): Let `\mathbb{F}_q[T]` be a polynomial ring with coefficients in a finite field `\mathbb{F}_q` and let `K` be a field. Fix a ring morphism `\gamma: \mathbb{F}_q[T] \to K`. We say that the field `K` - is an `\mathbb{F}_q[T]`-field, so that the *base of the Drinfeld - module* is defined as the `\mathbb{F}_q[T]`-field *K*. The base - uniquely defines the category, and we also refer to it as the *base - ring* or *base field*. The *base morphism* is the morphism `\gamma: + is an `\mathbb{F}_q[T]`-field, so that the *base field of the + Drinfeld module* is defined as the `\mathbb{F}_q[T]`-field *K*. This + field is the `base` of the category of Drinfeld modules, which it + uniquely defines. The *base morphism* is the morphism `\gamma: \mathbb{F}_q[T] \to K`. - .. NOTE:: - - Equivalently, the base of the Drinfeld module could be defined as the - base morphism `\gamma: \mathbb{F}_q[T] \to K`. - - .. NOTE:: See also :class:`sage.categories.drinfeld_modules`. The monic polynomial that generates the kernel of the base morphism is called the `\mathbb{F}_q[T]`-characteristic of the - `\mathbb{F}_q[T]`-field `K`. It cal also be referred to as the + `\mathbb{F}_q[T]`-field `K`. It can also be referred to as the function-field characteristic of `K`. Let `K\{\tau\}` be the ring of Ore polynomials with coefficients in @@ -111,7 +105,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): constant coefficient of `\phi_T`. The `\mathbb{F}_q[T]`-characteristic of the `\mathbb{F}_q[T]`-field `K` can also be referred to as its function ring-characteristic. - Finally, `K` is just referred to as the codomain base. Classical references on Drinfeld modules include [Gos1998]_, [Rosen2002]_, [VS06]_ and [Gek1991]_. @@ -128,8 +121,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): INPUT: - - ``function_ring`` -- a univariate polynomial ring whose base is a - finite field + - ``function_ring`` -- a univariate polynomial ring whose base field + is a finite field - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring @@ -147,11 +140,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi Drinfeld module defined by T |--> t^2 + t + z over Finite Field in z of size 3^12 over its base - Note that the definition of the base morphism is implicit; it is - defined as the `\mathbb{F}_q`-algebra morphism defined from - `\mathbb{F}_q[T]` to the base ring, and the base ring is a ring - extension over the base morphism whose underlying ring is the - compositum of all the parents of the coefficients. + Note that the definition of the base field is implicit; it is + automatically defined as the compositum of all the parents of the + coefficients. The above Drinfeld module is finite; it can also be infinite:: @@ -216,12 +207,11 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: cat.object([z, 0, 0, 1]) Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 3^12 over its base - .. RUBRIC:: The base ring of a Drinfeld module + .. RUBRIC:: The base field of a Drinfeld module - The base ring of the Drinfeld module is retrieved using - :meth:`base_ring` or :meth:`base`:: + The base field of the Drinfeld module is retrieved using :meth:`base`:: - sage: phi.base_ring() + sage: phi.base() Finite Field in z of size 3^12 over its base The base morphism is retrieved using :meth:`base_morphism`:: @@ -232,11 +222,11 @@ class DrinfeldModule(Parent, UniqueRepresentation): To: Finite Field in z of size 3^12 over its base Defn: T |--> z - Note that the base ring is *not* the field `K`. Rather, it is a ring - extension (see :class:`sage.rings.ring_extension.RingExtension`) - whose underlying ring is `K` and whose base is the base morphism:: + Note that the base field is *not* the field `K`. Rather, it is a ring + extension (see :class:`sage.rings.ring_extension.RingExtension`) whose + underlying ring is `K` and whose base is the base morphism:: - sage: phi.base_ring() is K + sage: phi.base() is K False .. RUBRIC:: Getters @@ -474,7 +464,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: DrinfeldModule(A, [z, QQ(1)]) Traceback (most recent call last): ... - ValueError: function ring base must coerce into base ring + ValueError: function ring base must coerce into base field :: @@ -484,7 +474,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: DrinfeldModule(A, [1, QQ(1)]) Traceback (most recent call last): ... - ValueError: function ring base must coerce into base ring + ValueError: function ring base must coerce into base field The function ring must be an univariate polynomial ring whose base is a finite field:: @@ -529,7 +519,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi = DrinfeldModule(A, [1, 1]) Traceback (most recent call last): ... - ValueError: function ring base must coerce into base ring + ValueError: function ring base must coerce into base field :: @@ -545,40 +535,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: A. = Fq[] sage: K = Frac(Fq) sage: phi = DrinfeldModule(A, [Fq.gen(), K(1)]) - sage: phi.base().codomain() is K - True - - :: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: k = Frac(Fq) - sage: kS. = k[] - sage: K = k.extension(S^3 + S + 1) - sage: phi = DrinfeldModule(A, [Fq.gen(), K.gen()]) - sage: phi.base().codomain() is K - True - - In particular, note that the field `K` may not be the smallest field - of definition of the coefficients:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: k = Frac(Fq) - sage: kS. = k[] - sage: K = k.extension(S^3 + S + 1) - sage: phi = DrinfeldModule(A, [K(k.gen()), 1]) - sage: phi.base().codomain() is K - True - - :: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K = Fq.extension(2) - sage: phi = DrinfeldModule(A, [Fq.gen(), K(Fq.gen())]) - sage: phi.base().codomain() is K - True + sage: phi.base_morphism().codomain() is K """ @staticmethod @@ -638,36 +595,36 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): if isinstance(gen, OrePolynomial): ore_polring = gen.parent() # Base ring without morphism structure: - base_ring_noext = ore_polring.base() + base_field_noext = ore_polring.base() name = ore_polring.variable_name() # `gen` is a list of coefficients (function_ring = Fq[T]): elif isinstance(gen, (list, tuple)): ore_polring = None # Base ring without morphism structure: - base_ring_noext = Sequence(gen).universe() + base_field_noext = Sequence(gen).universe() else: raise TypeError('generator must be list of coefficients or Ore ' 'polynomial') # Constant coefficient must be nonzero: if gen[0].is_zero(): raise ValueError('constant coefficient must be nonzero') - # The coefficients are in a base ring that has coercion from Fq: - if not (hasattr(base_ring_noext, 'has_coerce_map_from') and - base_ring_noext.has_coerce_map_from(function_ring.base_ring())): - raise ValueError('function ring base must coerce into base ring') + # The coefficients are in a base field that has coercion from Fq: + if not (hasattr(base_field_noext, 'has_coerce_map_from') and + base_field_noext.has_coerce_map_from(function_ring.base_field())): + raise ValueError('function ring base must coerce into base field') # Check LaTeX name if latexname is not None and type(latexname) is not str: raise ValueError('LaTeX name should be a string') # Build the category - if isinstance(base_ring_noext, RingExtension_generic): - base_ring = base_ring_noext + if isinstance(base_field_noext, RingExtension_generic): + base_field = base_field_noext else: - base_morphism = Hom(function_ring, base_ring_noext)(gen[0]) - base_ring = base_ring_noext.over(base_morphism) + base_morphism = Hom(function_ring, base_field_noext)(gen[0]) + base_field = base_field_noext.over(base_morphism) - category = DrinfeldModules(base_ring, name=name) + category = DrinfeldModules(base_field, name=name) # Check gen as Ore polynomial ore_polring = category.ore_polring() # Sanity cast @@ -676,7 +633,7 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): raise ValueError('generator must have positive degree') # Instantiate the appropriate class - if base_ring.is_finite(): + if base_field.is_finite(): from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule return FiniteDrinfeldModule(gen, category) return cls.__classcall__(cls, gen, category) @@ -1202,7 +1159,7 @@ def j_invariant(self): Assume the rank is two. Write the generator `\phi_T = \omega + g\tau + \Delta\tau^2`. The j-invariant is defined by - `\frac{g^{q+1}}{\Delta}`, `q` being the order of the base ring + `\frac{g^{q+1}}{\Delta}`, `q` being the order of the base field of the function ring. In our case, this field is always finite. OUTPUT: an element in the base codomain diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index c36c0e23ad6..63e56791d29 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -29,9 +29,9 @@ class FiniteDrinfeldModule(DrinfeldModule): r""" This class represents a finite Drinfeld module. - A *finite Drinfeld module* is a Drinfeld module whose base codomain - is a finite field. In this case, the function field characteristic - is a prime ideal. + A *finite Drinfeld module* is a Drinfeld module whose base field is + finite. In this case, the function field characteristic is a prime + ideal. For general definitions and help on Drinfeld modules, see class :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. @@ -148,7 +148,7 @@ def frobenius_endomorphism(self): Return the Frobenius endomorphism of the Drinfeld module as a morphism object. - Let `q` be the order of the base ring of the function ring. The + Let `q` be the order of the base field of the function ring. The *Frobenius endomorphism* is defined as the endomorphism whose defining Ore polynomial is `t^q`. @@ -181,7 +181,7 @@ def frobenius_charpoly(self, var='T'): endomorphism if the rank is two. Raise a NotImplementedError otherwise. - Let `\mathbb{F}_q` be the base ring of the function ring. The + Let `\mathbb{F}_q` be the base field of the function ring. The *characteristic polynomial `\chi` of the Frobenius endomorphism* is defined in [Gek1991]_. An important feature of this polynomial is that it is a monic univariate polynomial with @@ -193,9 +193,10 @@ def frobenius_charpoly(self, var='T'): Let `\chi = X^2 - A(T)X + B(T)` be the characteristic polynomial of the Frobenius endomorphism, and let `t^n` be the Ore polynomial that defines the Frobenius endomorphism of `\phi`; by - definition, `n` is the degree over `\mathbb{F}_q` of the base - codomain. We have `\chi(t^n)(\phi(T)) = t^{2n} - \phi_A t^n + - \phi_B = 0`, with `\deg(A) \leq \frac{n}{2}` and `\deg(B) = n`. + definition, `n` is the degree over of the base field over + `\mathbb{F}_q`. We have `\chi(t^n)(\phi(T)) = t^{2n} - \phi_A + t^n + \phi_B = 0`, with `\deg(A) \leq \frac{n}{2}` and `\deg(B) + = n`. Note that the *Frobenius trace* is defined as `A(T)` and the *Frobenius norm* is defined as `B(T)`. @@ -232,7 +233,7 @@ def frobenius_charpoly(self, var='T'): :: - sage: n = 2 # Degree over Fq of the base codomain + sage: n = 2 # Degree of the base field over Fq sage: trace.degree() <= n/2 True sage: norm.degree() == n @@ -266,8 +267,8 @@ def frobenius_norm(self): polynomial of the Frobenius endomorphism. The *Frobenius norm* is defined as the polynomial `B(T) \in \mathbb{F}_q[T]`. - Let `n` be the degree over `\mathbb{F}_q` of the base codomain. - Then the Frobenius norm has degree `n`. + Let `n` be the degree of the base field over `\mathbb{F}_q` Then the + Frobenius norm has degree `n`. OUTPUT: an element in the function ring @@ -283,7 +284,7 @@ def frobenius_norm(self): :: - sage: n = 2 # Degree over Fq of the base codomain + sage: n = 2 # Degree of the base field over Fq sage: B.degree() == n True @@ -298,7 +299,7 @@ def frobenius_norm(self): Gekeler, given in [MS2019]_, Section 4, Proposition 3. """ self._check_rank_two() - L = self._base.codomain().over(self._Fq) + L = self._base.over(self._Fq) # Notations from Schost-Musleh: if self._frobenius_norm is None: n = L.degree_over(self._Fq) @@ -362,7 +363,7 @@ def frobenius_trace(self): self._check_rank_two() # Notations from Schost-Musleh: if self._frobenius_trace is None: - n = self._base.codomain().over(self._Fq).degree_over(self._Fq) + n = self._base.over(self._Fq).degree_over(self._Fq) B = self.frobenius_norm() t = self.ore_polring().gen() self._frobenius_trace = self.invert(t**n + (self(B) // t**n)) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 3ff5d40f7b7..18a41c548aa 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -29,11 +29,11 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, r""" This class represents a Drinfeld module morphism. - Let `\phi, \psi` be two Drinfeld modules with base `\gamma: \mathbb{F}_q[T] - \to K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore - polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a - \in \mathbb{F}_q[T]`. In our case, this is equivalent to `f \phi_T = \psi_T - f`. An *isogeny* is a nonzero morphism. + Let `\phi, \psi` be two Drinfeld `\mathbb{F}_q[T]`-modules over a + field `K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an + Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for + every `a \in \mathbb{F}_q[T]`. In our case, this is equivalent to `f + \phi_T = \psi_T f`. An *isogeny* is a nonzero morphism. To create a morphism object, the user should never explicitly instantiate :class:`DrinfeldModuleMorphism`, but rather call the parent From 5e3182eb1d5cc3255263d14188272eb30c4cf9b8 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Mon, 6 Feb 2023 22:44:03 +0100 Subject: [PATCH 233/392] Adress David's suggestions for DrinfeldModuleHomset --- src/sage/rings/function_field/drinfeld_modules/homset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index 812301bdbdf..c90d5535340 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -218,7 +218,7 @@ def _repr_(self): def __contains__(self, x): r""" - Implement the ``in`` operator for the homset; return ``True`` if + Implement the ``in`` operator for the homset; return ``True`` whether the input defines a morphism in the homset. INPUT: @@ -274,7 +274,7 @@ def __contains__(self, x): def _element_constructor_(self, *args, **kwds): r""" - Return the Drinfeld module morphism defined by the input Ore + Return the Drinfeld module morphism defined by the given Ore polynomial. INPUT: an Ore polynomial From 09f27ab8c647af53528fdecee6d9650be09cd00c Mon Sep 17 00:00:00 2001 From: ymusleh <44390016+ymusleh@users.noreply.github.com> Date: Mon, 6 Feb 2023 17:31:09 -0500 Subject: [PATCH 234/392] Huge fix people from paris wouldn't understand Removed stray apostrophe and made punctuation a bit more consistent. --- src/sage/categories/drinfeld_modules.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index e1f74107688..735c4a37101 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -139,7 +139,7 @@ class DrinfeldModules(Category_over_base_ring): True Of course, the constant coefficient of the input must be the same as - the category':: + the category:: sage: cat.object([z, 1]) Traceback (most recent call last): @@ -280,7 +280,7 @@ def __init__(self, base_field, name='t'): def _latex_(self): r""" - Return a latex representation of the category + Return a latex representation of the category. OUTPUT: a string @@ -300,7 +300,7 @@ def _latex_(self): def _repr_(self): r""" - Return a string representation of the category + Return a string representation of the category. OUTPUT: a string @@ -359,7 +359,7 @@ def Endsets(self): def base_morphism(self): r""" - Return the base morphism of the category + Return the base morphism of the category. OUTPUT: a ring morphism From 1863590a8d7fb7307153669a17ba09ee4201cd49 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 7 Feb 2023 09:32:00 +0100 Subject: [PATCH 235/392] fix creation of the ring extension when the top ring is itself an extension --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- src/sage/rings/ring_extension.pyx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index ef16fb1bd98..1abe56fd306 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -610,7 +610,7 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): raise ValueError('constant coefficient must be nonzero') # The coefficients are in a base field that has coercion from Fq: if not (hasattr(base_field_noext, 'has_coerce_map_from') and - base_field_noext.has_coerce_map_from(function_ring.base_field())): + base_field_noext.has_coerce_map_from(function_ring.base_ring())): raise ValueError('function ring base must coerce into base field') # Check LaTeX name diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index b493f57bed9..791cd3d1de8 100644 --- a/src/sage/rings/ring_extension.pyx +++ b/src/sage/rings/ring_extension.pyx @@ -429,6 +429,7 @@ class RingExtensionFactory(UniqueFactory): else: use_generic_constructor = False is_backend_exposed = False + ring = (ring)._backend # We normalize other attributes if gens is not None: From 7cc1eb641a612008e688384a96b4f962a4511415 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 09:59:59 +0100 Subject: [PATCH 236/392] Refactor DrinfeldModule docstring (see details) - Use italics for definitions - Change the order of paragraphs in the begining --- .../drinfeld_modules/drinfeld_module.py | 85 +++++++++---------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index ef16fb1bd98..32a0aee4e31 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -40,32 +40,18 @@ class DrinfeldModule(Parent, UniqueRepresentation): r""" - This class represents a Drinfeld `\mathbb{F}_q[T]`-module. + This class implements Drinfeld `\mathbb{F}_q[T]`-modules. Let `\mathbb{F}_q[T]` be a polynomial ring with coefficients in a finite field `\mathbb{F}_q` and let `K` be a field. Fix a ring - morphism `\gamma: \mathbb{F}_q[T] \to K`. We say that the field `K` - is an `\mathbb{F}_q[T]`-field, so that the *base field of the - Drinfeld module* is defined as the `\mathbb{F}_q[T]`-field *K*. This - field is the `base` of the category of Drinfeld modules, which it - uniquely defines. The *base morphism* is the morphism `\gamma: - \mathbb{F}_q[T] \to K`. - - .. NOTE:: - - See also :class:`sage.categories.drinfeld_modules`. - - The monic polynomial that generates the kernel of the base morphism - is called the `\mathbb{F}_q[T]`-characteristic of the - `\mathbb{F}_q[T]`-field `K`. It can also be referred to as the - function-field characteristic of `K`. - - Let `K\{\tau\}` be the ring of Ore polynomials with coefficients in - `K` and Frobenius variable `\tau: x \mapsto x^q`. A Drinfeld - `\mathbb{F}_q[T]`-module over the `\mathbb{F}_q[T]`-field `K` is an - `\mathbb{F}_q`-algebra morphism `\phi: \mathbb{F}_q[T] \to - K\{\tau\}` such that: - + morphism `\gamma: \mathbb{F}_q[T] \to K`; we say that `K` is an + `\mathbb{F}_q[T]`*-field*. Let `K\{\tau\}` be the ring of Ore + polynomials with coefficients in `K`, whose multiplication is given + by the rule `\tau \lambda = \lambda^q \tau` for any `\lambda \in K`. + + A Drinfeld `\mathbb{F}_q[T]`-module over the `base + \mathbb{F}_q[T]`-field `K` is an `\mathbb{F}_q`-algebra morphism + `\phi: \mathbb{F}_q[T] \to K\{\tau\}` such that: 1. The image of `\phi` contains nonconstant Ore polynomials. 2. For every element `a` in the `\mathbb{F}_q[T]`, the constant coefficient `\phi(a)` is `\gamma(a)`. @@ -75,9 +61,21 @@ class DrinfeldModule(Parent, UniqueRepresentation): The Drinfeld `\mathbb{F}_q[T]`-module `\phi` is uniquely determined by the image `\phi_T` of `T` — this serves as input of the class. - A Drinfeld module is said to be finite if the field `K` is. Despite - an emphasis on this case, the base field can be any extension of - `\mathbb{F}_q`:: + .. NOTE:: + + See also :class:`sage.categories.drinfeld_modules`. + + The *base morphism* is the morphism `\gamma: \mathbb{F}_q[T] \to K`. + The monic polynomial that generates the kernel of `\gamma` is called + the `\mathbb{F}_q[T]`-*characteristic*, or *function-field + characteristic*, of the base field. We say that `\mathbb{F}_q[T]` is + the *function ring* of `\phi`; `K\{\tau\}` is the *Ore polynomial + ring* of `\phi`. Further, the *generator* of `\phi` is `\phi_T` and + its *constant coefficient* is the constant coefficient of `\phi_T`. + + A Drinfeld module is said to be *finite* if the field `K` is. + Despite an emphasis on this case, the base field can be any + extension of `\mathbb{F}_q`:: sage: Fq = GF(25) sage: A. = Fq[] @@ -99,13 +97,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): Finite Drinfeld modules are implemented in the class :class:`sage.rings.function_field.drinfeld_modules.finite_drinfeld_module`. - We say that `\mathbb{F}_q[T]` is the function ring of `\phi`; - `K\{\tau\}` is the Ore polynomial ring of `\phi`. Further, the - generator of `\phi` is `\phi_T` and its constant coefficient is the - constant coefficient of `\phi_T`. The - `\mathbb{F}_q[T]`-characteristic of the `\mathbb{F}_q[T]`-field `K` - can also be referred to as its function ring-characteristic. - Classical references on Drinfeld modules include [Gos1998]_, [Rosen2002]_, [VS06]_ and [Gek1991]_. @@ -140,9 +131,11 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi Drinfeld module defined by T |--> t^2 + t + z over Finite Field in z of size 3^12 over its base - Note that the definition of the base field is implicit; it is - automatically defined as the compositum of all the parents of the - coefficients. + .. NOTE:: + + Note that the definition of the base field is implicit; it is + automatically defined as the compositum of all the parents of + the coefficients. The above Drinfeld module is finite; it can also be infinite:: @@ -166,7 +159,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): regular Ore polynomials:: sage: ore_polring = phi.ore_polring() - sage: t = phi.ore_polring().gen() + sage: t = ore_polring.gen() sage: rho_T = z + t^3 sage: rho = DrinfeldModule(A, rho_T) sage: rho @@ -185,9 +178,9 @@ class DrinfeldModule(Parent, UniqueRepresentation): One can give a LaTeX name to be used for LaTeX representation:: - sage: sigma = DrinfeldModule(A, [z, 1, 1], latexname='\phi') + sage: sigma = DrinfeldModule(A, [z, 1, 1], latexname='\sigma') sage: latex(sigma) - \phi + \sigma .. RUBRIC:: The category of Drinfeld modules @@ -299,10 +292,10 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. RUBRIC:: Morphisms and isogenies - A morphism of Drinfeld modules `\phi \to \psi` is an Ore polynomial - `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a` in - the function ring. In our case, this is equivalent to `f \phi_T = - \psi_T f`. An isogeny is a nonzero morphism. + A *morphism* of Drinfeld modules `\phi \to \psi` is an Ore + polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for + every `a` in the function ring. In our case, this is equivalent to + `f \phi_T = \psi_T f`. An *isogeny* is a nonzero morphism. Use the ``in`` syntax to test if an Ore polynomial defines a morphism:: @@ -421,6 +414,11 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: action(A.random_element(), 0) 0 + .. WARNING:: + + The class ``DrinfeldModuleAction`` may be replaced later on. See + issues #34833 and #34834. + .. RUBRIC:: Inverting the Drinfeld module The morphism that defines a Drinfeld module is injective. Given an @@ -511,7 +509,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): ... ValueError: constant coefficient must equal that of the category - :: sage: Fq = K = GF(2) From 63fc7d9ff4f5dffaf8e7c7e6a208bd516bbe0da0 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 10:15:43 +0100 Subject: [PATCH 237/392] Various changes to doc (see details) - Change first sentences of doctests - Refactor DrinfeldModules doctest according to DrinfeldModule --- src/sage/categories/drinfeld_modules.py | 32 +++++++++---------- .../function_field/drinfeld_modules/action.py | 2 +- .../drinfeld_modules/drinfeld_module.py | 4 +-- .../finite_drinfeld_module.py | 2 +- .../function_field/drinfeld_modules/homset.py | 4 +-- .../drinfeld_modules/morphism.py | 6 ++-- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 735c4a37101..de0a4200b1c 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -30,32 +30,30 @@ class DrinfeldModules(Category_over_base_ring): r""" - This class represents the category of Drinfeld modules on a given - base. + This class implements the category of Drinfeld + `\mathbb{F}_q[T]`-modules on a given base field. Let `\mathbb{F}_q[T]` be a polynomial ring with coefficients in a finite field `\mathbb{F}_q` and let `K` be a field. Fix a ring - morphism `\gamma: \mathbb{F}_q[T] \to K`. We say that the field `K` - is an `\mathbb{F}_q[T]`-field, so that the *base field of the - category* is defined as the `\mathbb{F}_q[T]`-field *K*. The base - field uniquely defines the category, and we also refer to it as its - *base*. The *base morphism* is the morphism `\gamma: \mathbb{F}_q[T] - \to K`. + morphism `\gamma: \mathbb{F}_q[T] \to K`; we say that `K` is an + `\mathbb{F}_q[T]`*-field*. Let `K\{\tau\}` be the ring of Ore + polynomials with coefficients in `K`, whose multiplication is given + by the rule `\tau \lambda = \lambda^q \tau` for any `\lambda \in K`. + + We call `K` the *base field* of the category, and `\gamma` its *base + morphism*. .. NOTE:: Equivalently, the base of the category could be defined as the base morphism `\gamma: \mathbb{F}_q[T] \to K`. - The monic polynomial that generates the kernel of the base morphism - is called the `\mathbb{F}_q[T]`-characteristic of the - `\mathbb{F}_q[T]`-field `K`. It can also be referred to as the - function-field characteristic of `K`. - - We say that `\mathbb{F}_q[T]` is the function ring of the category; - `K\{\tau\}` is the Ore polynomial ring of the category. The constant - coefficient of the category is the image of `T` under the base - morphism. + The monic polynomial that generates the kernel of `\gamma` is called + the `\mathbb{F}_q[T]`-*characteristic*, or *function-field + characteristic*, of the base field. We say that `\mathbb{F}_q[T]` is + the *function ring* of the category; `K\{\tau\}` is the *Ore + polynomial ring*. The constant coefficient of the category is the + image of `T` under the base morphism. INPUT: the base field diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index 6fabd091805..be93b643526 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -27,7 +27,7 @@ class DrinfeldModuleAction(Action): r""" This class implements the module action induced by a Drinfeld - module. + `\mathbb{F}_q[T]`-module. Let `\phi` be a Drinfeld `\mathbb{F}_q[T]`-module over a field `K` and let `L/K` be a field extension. Let `x \in L` and let `a` be a diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 32a0aee4e31..9c66244e225 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -70,8 +70,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): the `\mathbb{F}_q[T]`-*characteristic*, or *function-field characteristic*, of the base field. We say that `\mathbb{F}_q[T]` is the *function ring* of `\phi`; `K\{\tau\}` is the *Ore polynomial - ring* of `\phi`. Further, the *generator* of `\phi` is `\phi_T` and - its *constant coefficient* is the constant coefficient of `\phi_T`. + ring*. Further, the *generator* is `\phi_T` and the *constant + coefficient* is the constant coefficient of `\phi_T`. A Drinfeld module is said to be *finite* if the field `K` is. Despite an emphasis on this case, the base field can be any diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 63e56791d29..9e6f7a3b8c2 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -27,7 +27,7 @@ class FiniteDrinfeldModule(DrinfeldModule): r""" - This class represents a finite Drinfeld module. + This class implements finite Drinfeld `\mathbb{F}_q[T]`-modules. A *finite Drinfeld module* is a Drinfeld module whose base field is finite. In this case, the function field characteristic is a prime diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index c90d5535340..c5391095631 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -28,8 +28,8 @@ class DrinfeldModuleHomset(Homset): r""" - This class represents the set of morphisms between two Drinfeld - modules. + This class implements the set of morphisms between two Drinfeld + `\mathbb{F}_q[T]`-modules. INPUT: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 18a41c548aa..b1b4d8b895d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -27,7 +27,7 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, metaclass=InheritComparisonClasscallMetaclass): r""" - This class represents a Drinfeld module morphism. + This class represents Drinfeld `\mathbb{F}_q[T]`-module morphisms. Let `\phi, \psi` be two Drinfeld `\mathbb{F}_q[T]`-modules over a field `K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an @@ -36,8 +36,8 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, \phi_T = \psi_T f`. An *isogeny* is a nonzero morphism. To create a morphism object, the user should never explicitly - instantiate :class:`DrinfeldModuleMorphism`, but rather call the parent - homset with the defining Ore polynomial:: + instantiate :class:`DrinfeldModuleMorphism`, but rather call the + parent homset with the defining Ore polynomial:: sage: Fq = GF(25) sage: A. = Fq[] From 53fab7d5d3c6d273c7b01f59b004f5a376966ed0 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 10:53:25 +0100 Subject: [PATCH 238/392] Handle NotImplementedError for is_subring --- src/sage/categories/drinfeld_modules.py | 9 ++++++--- .../function_field/drinfeld_modules/drinfeld_module.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index de0a4200b1c..b282fcaca61 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -270,10 +270,13 @@ def __init__(self, base_field, name='t'): # Create characteristic self._characteristic = None if K.is_finite(): - #FIXME: This minpoly is over Fp, not Fq + # FIXME: This minpoly is over Fp, not Fq self._characteristic = A(K(base_morphism(T)).minpoly()) - elif A.is_subring(K): - self._characteristic = Integer(0) + try: + if A.is_subring(K): + self._characteristic = Integer(0) + except NotImplementedError: + pass super().__init__(base=base_field) def _latex_(self): diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 9c66244e225..e45af9dcfad 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -607,7 +607,7 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): raise ValueError('constant coefficient must be nonzero') # The coefficients are in a base field that has coercion from Fq: if not (hasattr(base_field_noext, 'has_coerce_map_from') and - base_field_noext.has_coerce_map_from(function_ring.base_field())): + base_field_noext.has_coerce_map_from(function_ring.base_ring())): raise ValueError('function ring base must coerce into base field') # Check LaTeX name From 09b63288432fee81e4cd5c85819231095d85c48a Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 11:15:47 +0100 Subject: [PATCH 239/392] Adress David's suggestions for Morphism --- src/sage/categories/drinfeld_modules.py | 1 - .../drinfeld_modules/drinfeld_module.py | 3 ++ .../drinfeld_modules/morphism.py | 30 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index b282fcaca61..e26dad7323e 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -182,7 +182,6 @@ class DrinfeldModules(Category_over_base_ring): :: - sage: base = 'I hate Rostropovitch' sage: cat = DrinfeldModules(base) # known bug (blankline) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index e45af9dcfad..0e570a0159b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -619,6 +619,9 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): base_field = base_field_noext else: base_morphism = Hom(function_ring, base_field_noext)(gen[0]) + natural_map = Hom(function_ring, base_field_noext).natural_map() + if base_morphism == natural_map: + base_morphism = natural_map base_field = base_field_noext.over(base_morphism) category = DrinfeldModules(base_field, name=name) diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index b1b4d8b895d..f99be1e93e9 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -29,8 +29,8 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, r""" This class represents Drinfeld `\mathbb{F}_q[T]`-module morphisms. - Let `\phi, \psi` be two Drinfeld `\mathbb{F}_q[T]`-modules over a - field `K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an + Let `\phi` and `\psi` be two Drinfeld `\mathbb{F}_q[T]`-modules over + a field `K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a \in \mathbb{F}_q[T]`. In our case, this is equivalent to `f \phi_T = \psi_T f`. An *isogeny* is a nonzero morphism. @@ -54,7 +54,7 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, To (gen): (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 - The input Ore polynomial must indeed define a morphism:: + The given Ore polynomial must indeed define a morphism:: sage: morphism = Hom(phi, psi)(1) Traceback (most recent call last): @@ -105,20 +105,18 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: End(phi)(0).parent() == End(phi) True - .. NOTE:: + For the sake of completeness, we explain how the user can directly + instantiate the class, even though this should never be explicitly + done:: - For the sake of completeness, we explain how the user can - directly instantiate the class, even though this should never be - explicitly done:: - - sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism - sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) - Drinfeld Module morphism: - From (gen): z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - To (gen): (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 - sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) is morphism - True + sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism + sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) + Drinfeld Module morphism: + From (gen): z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + To (gen): (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) is morphism + True """ @staticmethod From fc9b281d786e01a8f3f2a17bc354525c240cbb31 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 11:21:51 +0100 Subject: [PATCH 240/392] Try to cast the base morphism to a natural map, when possible --- .../function_field/drinfeld_modules/drinfeld_module.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 0e570a0159b..525eded371b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -619,9 +619,12 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): base_field = base_field_noext else: base_morphism = Hom(function_ring, base_field_noext)(gen[0]) - natural_map = Hom(function_ring, base_field_noext).natural_map() - if base_morphism == natural_map: - base_morphism = natural_map + try: + natural_map = Hom(function_ring, base_field_noext).natural_map() + if base_morphism == natural_map: + base_morphism = natural_map + except TypeError: + pass base_field = base_field_noext.over(base_morphism) category = DrinfeldModules(base_field, name=name) From bd430de47d403e3c80acacf101b926776ecb1ea8 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 11:35:45 +0100 Subject: [PATCH 241/392] Fix LaTeX name init --- .../drinfeld_modules/drinfeld_module.py | 11 +++++------ .../drinfeld_modules/finite_drinfeld_module.py | 6 ++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 525eded371b..66293a18638 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -91,6 +91,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K. = Frac(A) sage: psi = DrinfeldModule(A, [z, T+1]) sage: psi + Drinfeld module defined by T |--> (T + 1)*t + T over Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 7^2 over its base .. NOTE:: @@ -142,10 +143,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: L = Frac(A) sage: psi = DrinfeldModule(A, [L(T), 1, T^3 + T + 1]) sage: psi - Drinfeld module defined by T |--> (T^3 + T + 1)*t^2 + t + T over Ring morphism: - From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 - To: Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 - Defn: T |--> T + Drinfeld module defined by T |--> (T^3 + T + 1)*t^2 + t + T over Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 over its base :: @@ -179,6 +177,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): One can give a LaTeX name to be used for LaTeX representation:: sage: sigma = DrinfeldModule(A, [z, 1, 1], latexname='\sigma') + ... sage: latex(sigma) \sigma @@ -638,8 +637,8 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): # Instantiate the appropriate class if base_field.is_finite(): from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule - return FiniteDrinfeldModule(gen, category) - return cls.__classcall__(cls, gen, category) + return FiniteDrinfeldModule(gen, category, latexname) + return cls.__classcall__(cls, gen, category, latexname) def __init__(self, gen, category, latexname=None): """ diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 9e6f7a3b8c2..5a9c7f77099 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -106,7 +106,7 @@ class FiniteDrinfeldModule(DrinfeldModule): False """ - def __init__(self, gen, category): + def __init__(self, gen, category, latexname=None): """ Initialize `self`. @@ -121,6 +121,8 @@ def __init__(self, gen, category): coefficients or an Ore polynomial - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring gen + - ``latexname`` (default: ``None``) -- the LaTeX name of the Drinfeld + module TESTS:: @@ -139,7 +141,7 @@ def __init__(self, gen, category): # added one to ensure that FiniteDrinfeldModule would always # have _frobenius_norm and _frobenius_trace attributes. - super().__init__(gen, category) + super().__init__(gen, category, latexname) self._frobenius_norm = None self._frobenius_trace = None From d8441f51685a7ff34db06ed9ff791bdcde523d7e Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 11:39:44 +0100 Subject: [PATCH 242/392] (fix) Stop using base.codomain --- src/sage/categories/drinfeld_modules.py | 6 +++--- .../function_field/drinfeld_modules/drinfeld_module.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index e26dad7323e..52eec420b13 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -413,7 +413,7 @@ def constant_coefficient(self): r""" Return the constant coefficient of the category. - OUTPUT: an element in the base codomain + OUTPUT: an element in the base field EXAMPLES:: @@ -604,7 +604,7 @@ def base_morphism(self): To: Finite Field in z12 of size 5^12 over its base Defn: T |--> 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - The base codomain can be infinite:: + The base field can be infinite:: sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1]) # todo: not tested sage: sigma.base_morphism() # todo: not tested @@ -660,7 +660,7 @@ def constant_coefficient(self): r""" Return the constant coefficient of the generator. - OUTPUT: an element in the base codomain + OUTPUT: an element in the base field EXAMPLES:: diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 66293a18638..b88066d4d3d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -1108,7 +1108,7 @@ def invert(self, ore_pol): r = self.rank() if ore_pol not in self._ore_polring: raise TypeError('input must be an Ore polynomial') - if ore_pol in self._base.codomain(): + if ore_pol in self._base: return self._Fq(ore_pol) if deg % r != 0: raise ValueError('input must be in the image of the Drinfeld ' From cd96c6f7118c13daba560bf35deb46bb97977f2d Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 11:55:58 +0100 Subject: [PATCH 243/392] Fix some doctests --- .../drinfeld_modules/drinfeld_module.py | 19 +++++++++---------- .../finite_drinfeld_module.py | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index b88066d4d3d..781a82c51fb 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -524,14 +524,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi = DrinfeldModule(A, [K(1), 1]) sage: isinstance(phi.ore_polring(), OrePolynomialRing) True - - Test that the base morphism is correct:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K = Frac(Fq) - sage: phi = DrinfeldModule(A, [Fq.gen(), K(1)]) - sage: phi.base_morphism().codomain() is K """ @staticmethod @@ -812,6 +804,7 @@ def _latex_(self): :: sage: psi = DrinfeldModule(A, [p_root, z12^3, z12^5], latexname='\psi') + ... sage: latex(psi) \psi @@ -1012,13 +1005,18 @@ def height(self): sage: phi.is_ordinary() True - sage: L = Frac(A) + :: + + sage: B. = Fq[] + sage: L = Frac(B) sage: phi = DrinfeldModule(A, [L(2), L(1)]) sage: phi.height() Traceback (most recent call last): ... ValueError: height is defined for prime function field characteristic + :: + sage: Fq = GF(343) sage: A. = Fq[] sage: K. = Fq.extension(2) @@ -1146,7 +1144,8 @@ def is_finite(self): sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi.is_finite() True - sage: L = Frac(A) + sage: B. = Fq[] + sage: L = Frac(B) sage: psi = DrinfeldModule(A, [L(2), L(1)]) sage: psi.is_finite() False diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 5a9c7f77099..4fd8947fd5d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -173,7 +173,7 @@ def frobenius_endomorphism(self): """ t = self.ore_polring().gen() Fq = self._function_ring.base() - #FIXME + # FIXME deg = self._base.over(Fq).degree(Fq) return self._Hom_(self, category=self.category())(t**deg) From ba4fc7663b366ffcbc9194a454129c7ab1d76b06 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 15:54:05 +0100 Subject: [PATCH 244/392] Modify natural map creation --- .../drinfeld_modules/drinfeld_module.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 781a82c51fb..d012c02fde5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -606,16 +606,15 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): raise ValueError('LaTeX name should be a string') # Build the category + T = function_ring.gen() if isinstance(base_field_noext, RingExtension_generic): base_field = base_field_noext + elif base_field_noext.has_coerce_map_from(function_ring) \ + and T == gen[0]: + base_morphism = base_field_noext.coerce_map_from(function_ring) + base_field = base_field_noext.over(base_morphism) else: base_morphism = Hom(function_ring, base_field_noext)(gen[0]) - try: - natural_map = Hom(function_ring, base_field_noext).natural_map() - if base_morphism == natural_map: - base_morphism = natural_map - except TypeError: - pass base_field = base_field_noext.over(base_morphism) category = DrinfeldModules(base_field, name=name) From 7bc38004342d8925cda7fa37e3b2247e73923f25 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 16:58:48 +0100 Subject: [PATCH 245/392] Fix extensions over Fq --- src/sage/categories/drinfeld_modules.py | 3 +-- .../function_field/drinfeld_modules/finite_drinfeld_module.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 52eec420b13..1b79ad1d18e 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -269,8 +269,7 @@ def __init__(self, base_field, name='t'): # Create characteristic self._characteristic = None if K.is_finite(): - # FIXME: This minpoly is over Fp, not Fq - self._characteristic = A(K(base_morphism(T)).minpoly()) + self._characteristic = A(K.over(Fq)(base_morphism(T)).minpoly()) try: if A.is_subring(K): self._characteristic = Integer(0) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 4fd8947fd5d..1460d73e8c6 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -173,7 +173,6 @@ def frobenius_endomorphism(self): """ t = self.ore_polring().gen() Fq = self._function_ring.base() - # FIXME deg = self._base.over(Fq).degree(Fq) return self._Hom_(self, category=self.category())(t**deg) From ff2d38d1e5b2dab7d7c5b8715da3035964f4868f Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Tue, 7 Feb 2023 17:33:54 +0100 Subject: [PATCH 246/392] fix the method monomials_of_degree --- src/sage/rings/polynomial/multi_polynomial_ring_base.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index f6a46ac4898..c8cfc106236 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -444,7 +444,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): 1/2*x^3 + x*y + z^2 - 1/2*x + y + 25 .. SEEALSO:: - + :meth:`lagrange_polynomial` """ # get ring and number of variables @@ -1387,8 +1387,9 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): sage: len(mons) == binomial(3+2-1,2) True """ - from sage.combinat.integer_vector import IntegerVectors - return [self.monomial(*a) for a in IntegerVectors(degree, self.ngens())] + deg_of_gens = [x.degree() for x in self.gens()] + from sage.combinat.integer_vector_weighted import WeightedIntegerVectors + return [self.monomial(*a) for a in WeightedIntegerVectors(degree, deg_of_gens)] def _macaulay_resultant_getS(self, mon_deg_tuple, dlist): r""" From 2407ba051d0f757112a6add7843cc4443f4c6f07 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 17:36:10 +0100 Subject: [PATCH 247/392] Create base_over_constants_field DrinfeldModules method --- src/sage/categories/drinfeld_modules.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 1b79ad1d18e..87478f8bd00 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -275,6 +275,10 @@ def __init__(self, base_field, name='t'): self._characteristic = Integer(0) except NotImplementedError: pass + # Create base over constants field + i = A.coerce_map_from(Fq) + Fq_to_K = self._base_morphism * i + self._base_over_constants_field = base_field.over(Fq_to_K) super().__init__(base=base_field) def _latex_(self): @@ -380,6 +384,26 @@ def base_morphism(self): """ return self._base_morphism + def base_over_constants_field(self): + r""" + Return the base field, seen as an extension over the constants + field `\mathbb{F}_q`. + + OUTPUT: a ring extension + + EXAMPLES:: + + sage: Fq = GF(11) + sage: A. = Fq[] + sage: K. = Fq.extension(4) + sage: p_root = z^3 + 7*z^2 + 6*z + 10 + sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) + sage: cat = phi.category() + sage: cat.base_over_constants_field() + Field in z with defining polynomial x^4 + 8*x^2 + 10*x + 2 over its base + """ + return self._base_over_constants_field + def characteristic(self): r""" Return the function ring-characteristic. From 88505c2e9d53289200a1fedbd385623db1378b52 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 17:40:31 +0100 Subject: [PATCH 248/392] Create base_over_constants_field DrinfeldModule (singular) method --- src/sage/categories/drinfeld_modules.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 87478f8bd00..bbf45fbf5a0 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -634,6 +634,25 @@ def base_morphism(self): """ return self.category().base_morphism() + def base_over_constants_field(self): + r""" + Return the base field, seen as an extension over the constants + field `\mathbb{F}_q`. + + OUTPUT: a ring extension + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.base_over_constants_field() + Field in z12 with defining polynomial x^6 + (4*z2 + 3)*x^5 + x^4 + (3*z2 + 1)*x^3 + x^2 + (4*z2 + 1)*x + z2 over its base + """ + return self.category().base_over_constants_field() + def characteristic(self): r""" Return the function ring-characteristic. From 3cea9023aa6f34748b1dac6c27e0ffa940ce080f Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 17:41:51 +0100 Subject: [PATCH 249/392] (fix) Remove `not tested` comments from DrinfeldModules --- src/sage/categories/drinfeld_modules.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index bbf45fbf5a0..daad92e56ee 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -423,8 +423,8 @@ def characteristic(self): :: - sage: psi = DrinfeldModule(A, [Frac(A).gen(), 1]) # todo: not tested - sage: psi.category().characteristic() # todo: not tested + sage: psi = DrinfeldModule(A, [Frac(A).gen(), 1]) + sage: psi.category().characteristic() 0 """ if self._characteristic is None: @@ -602,8 +602,8 @@ def base(self): The base can be infinite:: - sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1]) # todo: not tested - sage: sigma.base() # todo: not tested + sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1]) + sage: sigma.base() Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 over its base """ return self.category().base() @@ -629,8 +629,8 @@ def base_morphism(self): The base field can be infinite:: - sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1]) # todo: not tested - sage: sigma.base_morphism() # todo: not tested + sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1]) + sage: sigma.base_morphism() """ return self.category().base_morphism() @@ -666,16 +666,17 @@ def characteristic(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: phi.characteristic() # todo: not tested + sage: phi.characteristic() T^2 + (4*z2 + 2)*T + 2 sage: phi.base_morphism()(phi.characteristic()) 0 :: - sage: L = Frac(A) # todo: not tested - sage: psi = DrinfeldModule(A, [L(1), 0, 0, L(1)]) # todo: not tested - sage: psi.characteristic() # todo: not tested + sage: B. = Fq[] + sage: L = Frac(B) + sage: psi = DrinfeldModule(A, [L(1), 0, 0, L(1)]) + sage: psi.characteristic() 0 """ return self.category().characteristic() From eb42cbcbe3aa4483f2d166404b0e00c408ff3cc8 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 18:12:26 +0100 Subject: [PATCH 250/392] Fix invert method and move it for *finite* Drinfeld modules --- src/sage/categories/drinfeld_modules.py | 256 +++++++++--------- .../drinfeld_modules/drinfeld_module.py | 105 ------- .../finite_drinfeld_module.py | 114 ++++++++ 3 files changed, 242 insertions(+), 233 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index daad92e56ee..a8f74cff940 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -634,120 +634,12 @@ def base_morphism(self): """ return self.category().base_morphism() - def base_over_constants_field(self): - r""" - Return the base field, seen as an extension over the constants - field `\mathbb{F}_q`. - - OUTPUT: a ring extension - - EXAMPLES:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: phi.base_over_constants_field() - Field in z12 with defining polynomial x^6 + (4*z2 + 3)*x^5 + x^4 + (3*z2 + 1)*x^3 + x^2 + (4*z2 + 1)*x + z2 over its base - """ - return self.category().base_over_constants_field() - - def characteristic(self): - r""" - Return the function ring-characteristic. - - OUTPUT: a univariate polynomial ring - - EXAMPLES:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: phi.characteristic() - T^2 + (4*z2 + 2)*T + 2 - sage: phi.base_morphism()(phi.characteristic()) - 0 - - :: - - sage: B. = Fq[] - sage: L = Frac(B) - sage: psi = DrinfeldModule(A, [L(1), 0, 0, L(1)]) - sage: psi.characteristic() - 0 - """ - return self.category().characteristic() - - def function_ring(self): - r""" - Return the function ring of the Drinfeld module. - - OUTPUT: a univariate polynomial ring - - EXAMPLES:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: phi.function_ring() is A - True - """ - return self.category().function_ring() - - def constant_coefficient(self): - r""" - Return the constant coefficient of the generator. - - OUTPUT: an element in the base field - - EXAMPLES:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: phi.constant_coefficient() == p_root - True - - Let `\mathbb{F}_q[T]` be the function ring, and let `\gamma` - the base of the Drinfeld module. The constant coefficient - equals `\gamma(T)`:: - - sage: cat = phi.category() - sage: base = cat.base() - sage: base(T) == phi.constant_coefficient() - True - - Naturally, two Drinfeld modules in the same category have the - same constant coefficient:: - - sage: t = phi.ore_polring().gen() - sage: psi = cat.object(phi.constant_coefficient() + t^3) - sage: psi - Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base - - Reciprocally, it is impossible to create two Drinfeld modules in - this category if they do not share the same constant - coefficient:: - - sage: rho = cat.object(phi.constant_coefficient() + 1 + t^3) - Traceback (most recent call last): - ... - ValueError: constant coefficient must equal that of the category - """ - return self.category().constant_coefficient() - - def ore_polring(self): + def base_over_constants_field(self): r""" - Return the Ore polynomial ring of the Drinfeld module. + Return the base field, seen as an extension over the constants + field `\mathbb{F}_q`. - OUTPUT: an Ore polynomial ring + OUTPUT: a ring extension EXAMPLES:: @@ -756,20 +648,128 @@ def ore_polring(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: ore_polring = phi.ore_polring() - sage: ore_polring - Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 over its base twisted by Frob^2 - - The Ore polynomial ring can also be retrieved from the category - of the Drinfeld module:: - - sage: ore_polring is phi.category().ore_polring() - True - - The generator of the Drinfeld module is in the Ore polynomial - ring:: - - sage: phi(T) in ore_polring - True + sage: phi.base_over_constants_field() + Field in z12 with defining polynomial x^6 + (4*z2 + 3)*x^5 + x^4 + (3*z2 + 1)*x^3 + x^2 + (4*z2 + 1)*x + z2 over its base """ - return self.category().ore_polring() + return self.category().base_over_constants_field() + + def characteristic(self): + r""" + Return the function ring-characteristic. + + OUTPUT: a univariate polynomial ring + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.characteristic() + T^2 + (4*z2 + 2)*T + 2 + sage: phi.base_morphism()(phi.characteristic()) + 0 + + :: + + sage: B. = Fq[] + sage: L = Frac(B) + sage: psi = DrinfeldModule(A, [L(1), 0, 0, L(1)]) + sage: psi.characteristic() + 0 + """ + return self.category().characteristic() + + def function_ring(self): + r""" + Return the function ring of the Drinfeld module. + + OUTPUT: a univariate polynomial ring + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.function_ring() is A + True + """ + return self.category().function_ring() + + def constant_coefficient(self): + r""" + Return the constant coefficient of the generator. + + OUTPUT: an element in the base field + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.constant_coefficient() == p_root + True + + Let `\mathbb{F}_q[T]` be the function ring, and let `\gamma` + the base of the Drinfeld module. The constant coefficient + equals `\gamma(T)`:: + + sage: cat = phi.category() + sage: base = cat.base() + sage: base(T) == phi.constant_coefficient() + True + + Naturally, two Drinfeld modules in the same category have the + same constant coefficient:: + + sage: t = phi.ore_polring().gen() + sage: psi = cat.object(phi.constant_coefficient() + t^3) + sage: psi + Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base + + Reciprocally, it is impossible to create two Drinfeld modules in + this category if they do not share the same constant + coefficient:: + + sage: rho = cat.object(phi.constant_coefficient() + 1 + t^3) + Traceback (most recent call last): + ... + ValueError: constant coefficient must equal that of the category + """ + return self.category().constant_coefficient() + + def ore_polring(self): + r""" + Return the Ore polynomial ring of the Drinfeld module. + + OUTPUT: an Ore polynomial ring + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: ore_polring = phi.ore_polring() + sage: ore_polring + Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 over its base twisted by Frob^2 + + The Ore polynomial ring can also be retrieved from the category + of the Drinfeld module:: + + sage: ore_polring is phi.category().ore_polring() + True + + The generator of the Drinfeld module is in the Ore polynomial + ring:: + + sage: phi(T) in ore_polring + True + """ + return self.category().ore_polring() diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index d012c02fde5..cf6743638a4 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -26,9 +26,7 @@ from sage.categories.drinfeld_modules import DrinfeldModules from sage.categories.homset import Hom -from sage.matrix.constructor import Matrix from sage.misc.latex import latex -from sage.modules.free_module_element import vector from sage.rings.integer import Integer from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.polynomial_ring import PolynomialRing_general @@ -418,17 +416,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): The class ``DrinfeldModuleAction`` may be replaced later on. See issues #34833 and #34834. - .. RUBRIC:: Inverting the Drinfeld module - - The morphism that defines a Drinfeld module is injective. Given an - Ore polynomial that equals `\phi_a` for some function ring elelement - `a`, one can retrieve `a` (as a morphism, a Drinfeld module is - injective, see [Gos1998]_, cor. 4.5.2.):: - - sage: a = A.random_element() - sage: phi.invert(phi(a)) == a - True - TESTS: The generator must have positive degree:: @@ -1036,98 +1023,6 @@ def height(self): except NotImplementedError: raise NotImplementedError('height not implemented in this case') - def invert(self, ore_pol): - r""" - Return the preimage of the input under the Drinfeld module, if it - exists. - - INPUT: - - - ``ore_pol`` -- the Ore polynomial whose preimage we want to - compute - - OUTPUT: a function ring element - - EXAMPLES:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: a = A.random_element() - sage: phi.invert(phi(a)) == a - True - sage: phi.invert(phi(T)) == T - True - sage: phi.invert(phi(Fq.gen())) == Fq.gen() - True - - When the input is not in the image of the Drinfeld module, an - exception is raised:: - - sage: t = phi.ore_polring().gen() - sage: phi.invert(t + 1) - Traceback (most recent call last): - ... - ValueError: input must be in the image of the Drinfeld module - sage: phi.invert(t^3 + t^2 + 1) - Traceback (most recent call last): - ... - ValueError: input must be in the image of the Drinfeld module - - ALGORITHM: - - The algorithm relies on the inversion of a linear algebra - system. See [MS2019]_, 3.2.5 for details. - - TESTS:: - - sage: a = A.random_element() - sage: cat = phi.category() - sage: phi_r1 = cat.random_object(1) - sage: phi_r1.invert(phi_r1(a)) == a - True - sage: phi_r2 = cat.random_object(2) - sage: phi_r2.invert(phi_r2(a)) == a - True - sage: phi_r3 = cat.random_object(3) - sage: phi_r3.invert(phi_r3(a)) == a - True - sage: phi_r4 = cat.random_object(4) - sage: phi_r4.invert(phi_r4(a)) == a - True - sage: phi_r5 = cat.random_object(5) - sage: phi_r5.invert(phi_r5(a)) == a - True - """ - deg = ore_pol.degree() - r = self.rank() - if ore_pol not in self._ore_polring: - raise TypeError('input must be an Ore polynomial') - if ore_pol in self._base: - return self._Fq(ore_pol) - if deg % r != 0: - raise ValueError('input must be in the image of the Drinfeld ' - 'module') - - k = deg // r - T = self._function_ring.gen() - mat_lines = [[0 for _ in range(k+1)] for _ in range(k+1)] - for i in range(k+1): - phi_T_i = self(T**i) - for j in range(i+1): - mat_lines[j][i] = phi_T_i[r*j] - mat = Matrix(mat_lines) - vec = vector([list(ore_pol)[r*j] for j in range(k+1)]) - pre_image = self._function_ring(list((mat**(-1)) * vec)) - - if self(pre_image) == ore_pol: - return pre_image - else: - raise ValueError('input must be in the image of the Drinfeld ' - 'module') - def is_finite(self): r""" Return ``True`` whether the Drinfeld module is finite. diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 1460d73e8c6..a5041ec011a 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -21,6 +21,8 @@ # http://www.gnu.org/licenses/ # ***************************************************************************** +from sage.matrix.constructor import Matrix +from sage.modules.free_module_element import vector from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule @@ -104,6 +106,16 @@ class FiniteDrinfeldModule(DrinfeldModule): True sage: phi.is_supersingular() False + + .. RUBRIC:: Inverting the Drinfeld module + + The morphism that defines a Drinfeld module is injective (see + [Gos1998]_, cor. 4.5.2). If the Drinfeld module is finite, one can + retrieve preimages: + + sage: a = A.random_element() + sage: phi.invert(phi(a)) == a + True """ def __init__(self, gen, category, latexname=None): @@ -370,6 +382,108 @@ def frobenius_trace(self): self._frobenius_trace = self.invert(t**n + (self(B) // t**n)) return self._frobenius_trace + def invert(self, ore_pol): + r""" + Return the preimage of the input under the Drinfeld module, if it + exists. + + INPUT: + + - ``ore_pol`` -- the Ore polynomial whose preimage we want to + compute + + OUTPUT: a function ring element + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: a = A.random_element() + sage: phi.invert(phi(a)) == a + True + sage: phi.invert(phi(T)) == T + True + sage: phi.invert(phi(Fq.gen())) == Fq.gen() + True + + When the input is not in the image of the Drinfeld module, an + exception is raised:: + + sage: t = phi.ore_polring().gen() + sage: phi.invert(t + 1) + Traceback (most recent call last): + ... + ValueError: input must be in the image of the Drinfeld module + sage: phi.invert(t^3 + t^2 + 1) + Traceback (most recent call last): + ... + ValueError: input must be in the image of the Drinfeld module + + ALGORITHM: + + The algorithm relies on the inversion of a linear algebra + system. See [MS2019]_, 3.2.5 for details. + + TESTS:: + + sage: a = A.random_element() + sage: cat = phi.category() + sage: phi_r1 = cat.random_object(1) + sage: phi_r1.invert(phi_r1(a)) == a + True + sage: phi_r2 = cat.random_object(2) + sage: phi_r2.invert(phi_r2(a)) == a + True + sage: phi_r3 = cat.random_object(3) + sage: phi_r3.invert(phi_r3(a)) == a + True + sage: phi_r4 = cat.random_object(4) + sage: phi_r4.invert(phi_r4(a)) == a + True + sage: phi_r5 = cat.random_object(5) + sage: phi_r5.invert(phi_r5(a)) == a + True + """ + deg = ore_pol.degree() + r = self.rank() + base_over_Fq = self.base_over_constants_field() + if ore_pol not in self._ore_polring: + raise TypeError('input must be an Ore polynomial') + if ore_pol.degree() == 0: + coord = base_over_Fq(self._base(ore_pol)).vector() + if coord.nonzero_positions == [0]: + return self._Fq(coord[0]) + if ore_pol == 0: + return self._Fq.zero() + if deg % r != 0: + raise ValueError('input must be in the image of the Drinfeld ' + 'module') + + k = deg // r + T = self._function_ring.gen() + mat_lines = [[0 for _ in range(k+1)] for _ in range(k+1)] + for i in range(k+1): + phi_T_i = self(T**i) + for j in range(i+1): + mat_lines[j][i] = phi_T_i[r*j] + mat = Matrix(mat_lines) + vec = vector([list(ore_pol)[r*j] for j in range(k+1)]) + coeffs = list((mat**(-1)) * vec) + coeffs_in_Fq = [] + for coeff in coeffs: + coeff_in_Fq = base_over_Fq(coeff).vector()[0] + coeffs_in_Fq.append(coeff_in_Fq) + pre_image = self._function_ring(coeffs_in_Fq) + + if self(pre_image) == ore_pol: + return pre_image + else: + raise ValueError('input must be in the image of the Drinfeld ' + 'module') + def is_ordinary(self): r""" Return ``True`` whether the Drinfeld module is ordinary; raise a From a37eb292aadbf08ce2b6146ace3d1394f07f326d Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Tue, 7 Feb 2023 18:12:52 +0100 Subject: [PATCH 251/392] fix and add doctests --- src/sage/rings/polynomial/multi_polynomial_ring_base.pyx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index c8cfc106236..f0c679c395b 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -1379,7 +1379,10 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): sage: R. = ZZ[] sage: mons = R.monomials_of_degree(2) sage: mons - [x^2, x*y, x*z, y^2, y*z, z^2] + [z^2, y*z, x*z, y^2, x*y, x^2] + sage: P = PolynomialRing(QQ, 3, 'x,y,z', order=TermOrder('wdeglex', [1,2,1])) + sage: P.monomials_of_degree(2) + [y, z^2, x*z, x^2] The number of such monomials equals `\binom{n+k-1}{k}` where `n` is the number of variables and `k` the degree:: From de74a78c97c36e2641a0dcc389fb5780b344fa50 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 18:17:35 +0100 Subject: [PATCH 252/392] (fix) Identation problem in DrinfeldModules --- src/sage/categories/drinfeld_modules.py | 240 ++++++++++++------------ 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index a8f74cff940..5c1aa204d24 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -653,123 +653,123 @@ def base_over_constants_field(self): """ return self.category().base_over_constants_field() - def characteristic(self): - r""" - Return the function ring-characteristic. - - OUTPUT: a univariate polynomial ring - - EXAMPLES:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: phi.characteristic() - T^2 + (4*z2 + 2)*T + 2 - sage: phi.base_morphism()(phi.characteristic()) - 0 - - :: - - sage: B. = Fq[] - sage: L = Frac(B) - sage: psi = DrinfeldModule(A, [L(1), 0, 0, L(1)]) - sage: psi.characteristic() - 0 - """ - return self.category().characteristic() - - def function_ring(self): - r""" - Return the function ring of the Drinfeld module. - - OUTPUT: a univariate polynomial ring - - EXAMPLES:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: phi.function_ring() is A - True - """ - return self.category().function_ring() - - def constant_coefficient(self): - r""" - Return the constant coefficient of the generator. - - OUTPUT: an element in the base field - - EXAMPLES:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: phi.constant_coefficient() == p_root - True - - Let `\mathbb{F}_q[T]` be the function ring, and let `\gamma` - the base of the Drinfeld module. The constant coefficient - equals `\gamma(T)`:: - - sage: cat = phi.category() - sage: base = cat.base() - sage: base(T) == phi.constant_coefficient() - True - - Naturally, two Drinfeld modules in the same category have the - same constant coefficient:: - - sage: t = phi.ore_polring().gen() - sage: psi = cat.object(phi.constant_coefficient() + t^3) - sage: psi - Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base - - Reciprocally, it is impossible to create two Drinfeld modules in - this category if they do not share the same constant - coefficient:: - - sage: rho = cat.object(phi.constant_coefficient() + 1 + t^3) - Traceback (most recent call last): - ... - ValueError: constant coefficient must equal that of the category - """ - return self.category().constant_coefficient() - - def ore_polring(self): - r""" - Return the Ore polynomial ring of the Drinfeld module. - - OUTPUT: an Ore polynomial ring - - EXAMPLES:: - - sage: Fq = GF(25) - sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: ore_polring = phi.ore_polring() - sage: ore_polring - Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 over its base twisted by Frob^2 - - The Ore polynomial ring can also be retrieved from the category - of the Drinfeld module:: - - sage: ore_polring is phi.category().ore_polring() - True - - The generator of the Drinfeld module is in the Ore polynomial - ring:: - - sage: phi(T) in ore_polring - True - """ - return self.category().ore_polring() + def characteristic(self): + r""" + Return the function ring-characteristic. + + OUTPUT: a univariate polynomial ring + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.characteristic() + T^2 + (4*z2 + 2)*T + 2 + sage: phi.base_morphism()(phi.characteristic()) + 0 + + :: + + sage: B. = Fq[] + sage: L = Frac(B) + sage: psi = DrinfeldModule(A, [L(1), 0, 0, L(1)]) + sage: psi.characteristic() + 0 + """ + return self.category().characteristic() + + def function_ring(self): + r""" + Return the function ring of the Drinfeld module. + + OUTPUT: a univariate polynomial ring + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.function_ring() is A + True + """ + return self.category().function_ring() + + def constant_coefficient(self): + r""" + Return the constant coefficient of the generator. + + OUTPUT: an element in the base field + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.constant_coefficient() == p_root + True + + Let `\mathbb{F}_q[T]` be the function ring, and let `\gamma` + the base of the Drinfeld module. The constant coefficient + equals `\gamma(T)`:: + + sage: cat = phi.category() + sage: base = cat.base() + sage: base(T) == phi.constant_coefficient() + True + + Naturally, two Drinfeld modules in the same category have the + same constant coefficient:: + + sage: t = phi.ore_polring().gen() + sage: psi = cat.object(phi.constant_coefficient() + t^3) + sage: psi + Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base + + Reciprocally, it is impossible to create two Drinfeld modules in + this category if they do not share the same constant + coefficient:: + + sage: rho = cat.object(phi.constant_coefficient() + 1 + t^3) + Traceback (most recent call last): + ... + ValueError: constant coefficient must equal that of the category + """ + return self.category().constant_coefficient() + + def ore_polring(self): + r""" + Return the Ore polynomial ring of the Drinfeld module. + + OUTPUT: an Ore polynomial ring + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: ore_polring = phi.ore_polring() + sage: ore_polring + Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 over its base twisted by Frob^2 + + The Ore polynomial ring can also be retrieved from the category + of the Drinfeld module:: + + sage: ore_polring is phi.category().ore_polring() + True + + The generator of the Drinfeld module is in the Ore polynomial + ring:: + + sage: phi(T) in ore_polring + True + """ + return self.category().ore_polring() From 506fb1561aea4faed202be6fe2a8fa101ec94bb8 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 18:22:37 +0100 Subject: [PATCH 253/392] (fix) Fix some doctest fails --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- .../function_field/drinfeld_modules/finite_drinfeld_module.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index cf6743638a4..68d6bb79abc 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -999,7 +999,7 @@ def height(self): sage: phi.height() Traceback (most recent call last): ... - ValueError: height is defined for prime function field characteristic + NotImplementedError: height not implemented in this case :: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index a5041ec011a..4a0c0984c14 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -188,7 +188,7 @@ def frobenius_endomorphism(self): deg = self._base.over(Fq).degree(Fq) return self._Hom_(self, category=self.category())(t**deg) - def frobenius_charpoly(self, var='T'): + def frobenius_charpoly(self, var='X'): r""" Return the characteristic polynomial of the Frobenius endomorphism if the rank is two. Raise a NotImplementedError @@ -227,7 +227,7 @@ def frobenius_charpoly(self, var='T'): sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: chi = phi.frobenius_charpoly() sage: chi - T^2 + ((3*z3^2 + z3 + 4)*X + 4*z3^2 + 6*z3 + 3)*T + (5*z3^2 + 2*z3)*X^2 + (4*z3^2 + 3*z3)*X + 5*z3^2 + 2*z3 + X^2 + ((3*z3^2 + z3 + 4)*T + 4*z3^2 + 6*z3 + 3)*X + (5*z3^2 + 2*z3)*T^2 + (4*z3^2 + 3*z3)*T + 5*z3^2 + 2*z3 :: From f119d2ce0f1f9b764142db55e1669ce553826ec6 Mon Sep 17 00:00:00 2001 From: DavidAyotte Date: Tue, 7 Feb 2023 19:46:23 +0100 Subject: [PATCH 254/392] fix the ordering and add doctests --- .../polynomial/multi_polynomial_ring_base.pyx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index f0c679c395b..1a0faf98497 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -1380,9 +1380,15 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): sage: mons = R.monomials_of_degree(2) sage: mons [z^2, y*z, x*z, y^2, x*y, x^2] - sage: P = PolynomialRing(QQ, 3, 'x,y,z', order=TermOrder('wdeglex', [1,2,1])) + sage: P = PolynomialRing(QQ, 3, 'x, y, z', order=TermOrder('wdeglex', [1, 2, 1])) sage: P.monomials_of_degree(2) - [y, z^2, x*z, x^2] + [z^2, y, x*z, x^2] + sage: P = PolynomialRing(QQ, 3, 'x, y, z', order='lex') + sage: P.monomials_of_degree(3) + [z^3, y*z^2, y^2*z, y^3, x*z^2, x*y*z, x*y^2, x^2*z, x^2*y, x^3] + sage: P = PolynomialRing(QQ, 3, 'x, y, z', order='invlex') + sage: P.monomials_of_degree(3) + [x^3, x^2*y, x*y^2, y^3, x^2*z, x*y*z, y^2*z, x*z^2, y*z^2, z^3] The number of such monomials equals `\binom{n+k-1}{k}` where `n` is the number of variables and `k` the degree:: @@ -1392,7 +1398,9 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): """ deg_of_gens = [x.degree() for x in self.gens()] from sage.combinat.integer_vector_weighted import WeightedIntegerVectors - return [self.monomial(*a) for a in WeightedIntegerVectors(degree, deg_of_gens)] + mons = [self.monomial(*a) for a in WeightedIntegerVectors(degree, deg_of_gens)] + mons.sort() # This could be implemented in WeightedIntegerVectors instead + return mons def _macaulay_resultant_getS(self, mon_deg_tuple, dlist): r""" From 301aa0a78e588d4b1fe316ead5681007f1794b4c Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 20:59:21 +0100 Subject: [PATCH 255/392] (fix) Fix DrinfeldModules failing doctests --- src/sage/categories/drinfeld_modules.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 5c1aa204d24..c28948eb6e3 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -631,6 +631,10 @@ def base_morphism(self): sage: sigma = DrinfeldModule(A, [Frac(A).gen(), 1]) sage: sigma.base_morphism() + Ring morphism: + From: Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 + To: Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 over its base + Defn: T |--> T """ return self.category().base_morphism() @@ -677,7 +681,9 @@ def characteristic(self): sage: L = Frac(B) sage: psi = DrinfeldModule(A, [L(1), 0, 0, L(1)]) sage: psi.characteristic() - 0 + Traceback (most recent call last): + ... + NotImplementedError: function ring characteristic notimplemented in this case """ return self.category().characteristic() From 90aa9ac02dedee8a2cb76142129df8b887659169 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 21:07:09 +0100 Subject: [PATCH 256/392] Enhance invert method code --- .../drinfeld_modules/finite_drinfeld_module.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 4a0c0984c14..014f587a36e 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -471,12 +471,9 @@ def invert(self, ore_pol): mat_lines[j][i] = phi_T_i[r*j] mat = Matrix(mat_lines) vec = vector([list(ore_pol)[r*j] for j in range(k+1)]) - coeffs = list((mat**(-1)) * vec) - coeffs_in_Fq = [] - for coeff in coeffs: - coeff_in_Fq = base_over_Fq(coeff).vector()[0] - coeffs_in_Fq.append(coeff_in_Fq) - pre_image = self._function_ring(coeffs_in_Fq) + coeffs_K = list((mat**(-1)) * vec) + coeffs_Fq = list(map(lambda x: base_over_Fq(x).vector()[0], coeffs_K)) + pre_image = self._function_ring(coeffs_Fq) if self(pre_image) == ore_pol: return pre_image From d954bc3c5830964dd06d83452b523450f6530180 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Tue, 7 Feb 2023 21:51:34 +0100 Subject: [PATCH 257/392] Lighten DrinfeldModule representation --- src/sage/categories/drinfeld_modules.py | 10 +++--- .../function_field/drinfeld_modules/action.py | 4 +-- .../drinfeld_modules/drinfeld_module.py | 32 ++++++++----------- .../finite_drinfeld_module.py | 2 +- .../drinfeld_modules/morphism.py | 4 +-- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index c28948eb6e3..d3b931397c3 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -132,7 +132,7 @@ class DrinfeldModules(Category_over_base_ring): sage: psi = cat.object([p_root, 1]) sage: psi - Drinfeld module defined by T |--> t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 over its base + Drinfeld module defined by T |--> t + z^3 + 7*z^2 + 6*z + 10 sage: psi.category() is cat True @@ -149,7 +149,7 @@ class DrinfeldModules(Category_over_base_ring): sage: rho = cat.random_object(2) sage: rho # random - Drinfeld module defined by T |--> (7*z^3 + 7*z^2 + 10*z + 2)*t^2 + (9*z^3 + 5*z^2 + 2*z + 7)*t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + Drinfeld module defined by T |--> (7*z^3 + 7*z^2 + 10*z + 2)*t^2 + (9*z^3 + 5*z^2 + 2*z + 7)*t + z^3 + 7*z^2 + 6*z + 10 sage: rho.rank() == 2 True sage: rho.category() is cat @@ -494,7 +494,7 @@ def object(self, gen): sage: cat = phi.category() sage: psi = cat.object([p_root, 0, 1]) sage: psi - Drinfeld module defined by T |--> t^2 + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 over its base + Drinfeld module defined by T |--> t^2 + z^3 + 7*z^2 + 6*z + 10 sage: t = phi.ore_polring().gen() sage: cat.object(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi True @@ -547,7 +547,7 @@ def random_object(self, rank): sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: cat = phi.category() sage: psi = cat.random_object(3) # random - Drinfeld module defined by T |--> (6*z^3 + 4*z^2 + 10*z + 9)*t^3 + (4*z^3 + 8*z^2 + 8*z)*t^2 + (10*z^3 + 3*z^2 + 6*z)*t + z^3 + 7*z^2 + 6*z + 10 over Finite Field in z of size 11^4 + Drinfeld module defined by T |--> (6*z^3 + 4*z^2 + 10*z + 9)*t^3 + (4*z^3 + 8*z^2 + 8*z)*t^2 + (10*z^3 + 3*z^2 + 6*z)*t + z^3 + 7*z^2 + 6*z + 10 sage: psi.rank() == 3 True """ @@ -736,7 +736,7 @@ def constant_coefficient(self): sage: t = phi.ore_polring().gen() sage: psi = cat.object(phi.constant_coefficient() + t^3) sage: psi - Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 Reciprocally, it is impossible to create two Drinfeld modules in this category if they do not share the same constant diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index be93b643526..c324014745d 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -58,7 +58,7 @@ class DrinfeldModuleAction(Action): sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 11^2 over its base + Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z The action on elements is computed as follows:: @@ -170,7 +170,7 @@ def _repr_(self): sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: action - Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 11^2 over its base + Action on Finite Field in z of size 11^2 over its base induced by Drinfeld module defined by T |--> t^3 + z """ return f'Action on {self._base} induced by ' \ f'{self._drinfeld_module}' diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 68d6bb79abc..fdd4997bbf7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -49,10 +49,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): A Drinfeld `\mathbb{F}_q[T]`-module over the `base \mathbb{F}_q[T]`-field `K` is an `\mathbb{F}_q`-algebra morphism - `\phi: \mathbb{F}_q[T] \to K\{\tau\}` such that: - 1. The image of `\phi` contains nonconstant Ore polynomials. - 2. For every element `a` in the `\mathbb{F}_q[T]`, the constant - coefficient `\phi(a)` is `\gamma(a)`. + `\phi: \mathbb{F}_q[T] \to K\{\tau\}` such that `\Im(\phi) \not\subset K` + and `\phi` agrees with `\gamma` on `\mathbb{F}_q`. For `a` in `\mathbb{F}_q[T]`, `\phi(a)` is denoted `\phi_a`. @@ -80,7 +78,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z, 4, 1]) sage: phi - Drinfeld module defined by T |--> t^2 + 4*t + z over Finite Field in z of size 5^12 over its base + Drinfeld module defined by T |--> t^2 + 4*t + z :: @@ -89,7 +87,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K. = Frac(A) sage: psi = DrinfeldModule(A, [z, T+1]) sage: psi - Drinfeld module defined by T |--> (T + 1)*t + T over Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 7^2 over its base + Drinfeld module defined by T |--> (T + 1)*t + T .. NOTE:: @@ -128,7 +126,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: K. = Fq.extension(6) sage: phi = DrinfeldModule(A, [z, 1, 1]) sage: phi - Drinfeld module defined by T |--> t^2 + t + z over Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> t^2 + t + z .. NOTE:: @@ -141,7 +139,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: L = Frac(A) sage: psi = DrinfeldModule(A, [L(T), 1, T^3 + T + 1]) sage: psi - Drinfeld module defined by T |--> (T^3 + T + 1)*t^2 + t + T over Fraction Field of Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 over its base + Drinfeld module defined by T |--> (T^3 + T + 1)*t^2 + t + T :: @@ -159,7 +157,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: rho_T = z + t^3 sage: rho = DrinfeldModule(A, rho_T) sage: rho - Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> t^3 + z sage: rho(T) == rho_T True @@ -195,7 +193,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: cat = phi.category() sage: cat.object([z, 0, 0, 1]) - Drinfeld module defined by T |--> t^3 + z over Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> t^3 + z .. RUBRIC:: The base field of a Drinfeld module @@ -223,8 +221,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): One can retrieve basic properties:: - :: - sage: phi.base_morphism() Ring morphism: From: Univariate Polynomial Ring in T over Finite Field in z2 of size 3^2 @@ -366,7 +362,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z sage: psi = phi.velu(ore_pol) sage: psi - Drinfeld module defined by T |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z over Finite Field in z of size 3^12 over its base + Drinfeld module defined by T |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z sage: ore_pol in Hom(phi, psi) True sage: ore_pol * phi(T) == psi(T) * ore_pol @@ -397,7 +393,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: action = phi.action() sage: action - Action on Finite Field in z of size 3^12 over its base induced by Drinfeld module defined by T |--> t^2 + t + z over Finite Field in z of size 3^12 over its base + Action on Finite Field in z of size 3^12 over its base induced by Drinfeld module defined by T |--> t^2 + t + z The action on elements is computed by calling the action object:: @@ -823,10 +819,10 @@ def _repr_(self): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: phi - Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 """ return f'Drinfeld module defined by {self._function_ring.gen()} ' \ - f'|--> {self._gen} over {self._base}' + f'|--> {self._gen}' def action(self): r""" @@ -846,7 +842,7 @@ def action(self): sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: action = phi.action() sage: action - Action on Finite Field in z12 of size 5^12 over its base induced by Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base + Action on Finite Field in z12 of size 5^12 over its base induced by Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 The action on elements is computed as follows:: @@ -1206,7 +1202,7 @@ def velu(self, isog): sage: isog = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 sage: psi = phi.velu(isog) sage: psi - Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: isog in Hom(phi, psi) True diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 014f587a36e..bc87115f2f5 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -50,7 +50,7 @@ class FiniteDrinfeldModule(DrinfeldModule): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z6, 0, 5]) sage: phi - Drinfeld module defined by T |--> 5*t^2 + z6 over Finite Field in z6 of size 7^6 over its base + Drinfeld module defined by T |--> 5*t^2 + z6 :: diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index f99be1e93e9..48bd560961f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -64,14 +64,14 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, One can get basic data on the morphism:: sage: morphism.domain() - Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: morphism.domain() is phi True :: sage: morphism.codomain() - Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 over Finite Field in z12 of size 5^12 over its base + Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: morphism.codomain() is psi True From 3b2314b55e517e9639600b342ced9dff18993c33 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Tue, 7 Feb 2023 23:53:38 +0100 Subject: [PATCH 258/392] small changes --- .../drinfeld_modules/drinfeld_module.py | 72 ++++++------ .../finite_drinfeld_module.py | 62 +++++----- .../function_field/drinfeld_modules/homset.py | 107 +++++++++--------- .../drinfeld_modules/morphism.py | 18 +-- 4 files changed, 118 insertions(+), 141 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index fdd4997bbf7..a5a5f6dceab 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -47,15 +47,15 @@ class DrinfeldModule(Parent, UniqueRepresentation): polynomials with coefficients in `K`, whose multiplication is given by the rule `\tau \lambda = \lambda^q \tau` for any `\lambda \in K`. - A Drinfeld `\mathbb{F}_q[T]`-module over the `base - \mathbb{F}_q[T]`-field `K` is an `\mathbb{F}_q`-algebra morphism + A Drinfeld `\mathbb{F}_q[T]`-module over the base + `\mathbb{F}_q[T]`-field `K` is an `\mathbb{F}_q`-algebra morphism `\phi: \mathbb{F}_q[T] \to K\{\tau\}` such that `\Im(\phi) \not\subset K` and `\phi` agrees with `\gamma` on `\mathbb{F}_q`. For `a` in `\mathbb{F}_q[T]`, `\phi(a)` is denoted `\phi_a`. The Drinfeld `\mathbb{F}_q[T]`-module `\phi` is uniquely determined - by the image `\phi_T` of `T` — this serves as input of the class. + by the image `\phi_T` of `T`; this serves as input of the class. .. NOTE:: @@ -84,8 +84,8 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: Fq = GF(49) sage: A. = Fq[] - sage: K. = Frac(A) - sage: psi = DrinfeldModule(A, [z, T+1]) + sage: K = Frac(A) + sage: psi = DrinfeldModule(A, [K(T), T+1]) sage: psi Drinfeld module defined by T |--> (T + 1)*t + T @@ -111,8 +111,10 @@ class DrinfeldModule(Parent, UniqueRepresentation): - ``function_ring`` -- a univariate polynomial ring whose base field is a finite field + - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial + - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring generator @@ -131,7 +133,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. NOTE:: Note that the definition of the base field is implicit; it is - automatically defined as the compositum of all the parents of + automatically defined as the compositum of all the parents of the coefficients. The above Drinfeld module is finite; it can also be infinite:: @@ -337,7 +339,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: identity_morphism.ore_polynomial() 1 - It is easy to check if a morphism is an isogeny, endomorphism or + One checks if a morphism is an isogeny, endomorphism or isomorphism:: sage: frobenius_endomorphism.is_isogeny() @@ -355,17 +357,17 @@ class DrinfeldModule(Parent, UniqueRepresentation): .. RUBRIC:: The Vélu formula - Let ``ore_pol`` be a nonzero Ore polynomial. We can decide if there - exists a Drinfeld module ``psi`` such that ``ore_pol`` is an isogeny - from ``self`` to ``psi``. If so, we find ``psi``:: + Let ``P`` be a nonzero Ore polynomial. We can decide if ``P`` + defines an isogeny with a given domain and, if it does, find + the codomain:: - sage: ore_pol = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z - sage: psi = phi.velu(ore_pol) + sage: P = (2*z^6 + z^3 + 2*z^2 + z + 2)*t + z^11 + 2*z^10 + 2*z^9 + 2*z^8 + z^7 + 2*z^6 + z^5 + z^3 + z^2 + z + sage: psi = phi.velu(P) sage: psi Drinfeld module defined by T |--> (2*z^11 + 2*z^9 + z^6 + 2*z^5 + 2*z^4 + 2*z^2 + 1)*t^2 + (2*z^11 + 2*z^10 + 2*z^9 + z^8 + 2*z^7 + 2*z^6 + z^5 + 2*z^4 + 2*z^2 + 2*z)*t + z - sage: ore_pol in Hom(phi, psi) + sage: P in Hom(phi, psi) True - sage: ore_pol * phi(T) == psi(T) * ore_pol + sage: P * phi(T) == psi(T) * P True If the input does not define an isogeny, an exception is raised: @@ -519,14 +521,19 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): - ``function_ring`` -- a univariate polynomial ring whose base is a finite field + - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial + - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring gen + - ``latexname`` (default: ``None``) -- the LaTeX name of the Drinfeld module - OUTPUT: a DrinfeldModule or FiniteDrinfeldModule + OUTPUT: + + A DrinfeldModule or FiniteDrinfeldModule. TESTS:: @@ -618,17 +625,20 @@ def __init__(self, gen, category, latexname=None): """ Initialize ``self``. - Validity of the input is checked in ``__classcall_private__``. - The ``__init__`` just saves attributes. + Validity of the input is checked in meth:`__classcall_private__`. + The meth:`__init__` just saves attributes. INPUT: - ``function_ring`` -- a univariate polynomial ring whose base is a finite field + - ``gen`` -- the generator of the Drinfeld module; as a list of coefficients or an Ore polynomial + - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring gen + - ``latexname`` (default: ``None``) -- the LaTeX name of the Drinfeld module @@ -718,11 +728,10 @@ def _Hom_(self, other, category): INPUT: - ``other`` -- the codomain of the homset + - ``category`` -- the category in which we consider the morphisms, usually ``self.category()`` - OUTPUT: an homset - EXAMPLES:: sage: Fq = GF(25) @@ -768,10 +777,8 @@ def _latex_(self): r""" Return a LaTeX representation of the Drinfeld module. - If a LaTeX name was given at init. using `latexname`, use the LaTeX - name. Otherwise, create a representation. - - OUTPUT: a string + If a LaTeX name was given at initialization, we use it. + Otherwise, we create a representation. EXAMPLES:: @@ -807,9 +814,7 @@ def _latex_(self): def _repr_(self): r""" - Return a string representation of the Drinfeld module. - - OUTPUT: a string + Return a string representation of this Drinfeld module. EXAMPLES:: @@ -844,7 +849,7 @@ def action(self): sage: action Action on Finite Field in z12 of size 5^12 over its base induced by Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - The action on elements is computed as follows:: + The action on elements is computed as follows:: sage: P = T^2 + T + 1 sage: a = z12 + 1 @@ -905,8 +910,6 @@ def coefficients(self, sparse=True): - ``sparse`` -- a boolean - OUTPUT: a list of elements in the base codomain - EXAMPLES:: sage: Fq = GF(25) @@ -939,8 +942,6 @@ def gen(self): r""" Return the generator of the Drinfeld module. - OUTPUT: an Ore polynomial - EXAMPLES:: sage: Fq = GF(25) @@ -973,8 +974,6 @@ def height(self): A rank two Drinfeld module is supersingular if and only if its height equals its rank. - OUTPUT: an integer - EXAMPLES:: sage: Fq = GF(25) @@ -1015,15 +1014,14 @@ def height(self): 'function field characteristic') else: p = self.characteristic() - return Integer((self(p).valuation()) // (p.degree())) + return Integer(self(p).valuation() // p.degree()) except NotImplementedError: raise NotImplementedError('height not implemented in this case') def is_finite(self): r""" - Return ``True`` whether the Drinfeld module is finite. - - OUTPUT: a boolean + Return ``True`` if this Drinfeld module is finite, + ``False`` otherwise. EXAMPLES:: diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index bc87115f2f5..0d8939051ca 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -91,7 +91,7 @@ class FiniteDrinfeldModule(DrinfeldModule): sage: chi(frob_pol, phi(T)) 0 - This makes it possible to compute the Frobenius trace and norm:: + as well as its trace and norm:: sage: phi.frobenius_trace() 6*T + 5*z3^2 + 5*z3 + 6 @@ -100,7 +100,7 @@ class FiniteDrinfeldModule(DrinfeldModule): sage: phi.frobenius_norm() 2*T^2 + (z3^2 + z3 + 4)*T + 2*z3 - And to decide if a Drinfeld module is ordinary or supersingular:: + We can decide if a Drinfeld module is ordinary or supersingular:: sage: phi.is_ordinary() True @@ -129,10 +129,13 @@ def __init__(self, gen, category, latexname=None): - ``function_ring`` -- a univariate polynomial ring whose base is a finite field + - ``gen`` -- the generator of the Drinfeld module as a list of coefficients or an Ore polynomial + - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring gen + - ``latexname`` (default: ``None``) -- the LaTeX name of the Drinfeld module @@ -148,11 +151,9 @@ def __init__(self, gen, category, latexname=None): sage: phi._gen == ore_polring(gen) True """ - # NOTE: There used to be no __init__ here (which was fine). I # added one to ensure that FiniteDrinfeldModule would always # have _frobenius_norm and _frobenius_trace attributes. - super().__init__(gen, category, latexname) self._frobenius_norm = None self._frobenius_trace = None @@ -166,8 +167,6 @@ def frobenius_endomorphism(self): *Frobenius endomorphism* is defined as the endomorphism whose defining Ore polynomial is `t^q`. - OUTPUT: a Drinfeld module morphism - EXAMPLES:: sage: Fq = GF(343) @@ -179,6 +178,9 @@ def frobenius_endomorphism(self): From (gen): z6*t^2 + 1 To (gen): z6*t^2 + 1 Defn: t^2 + + TESTS:: + sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism sage: isinstance(phi.frobenius_endomorphism(), DrinfeldModuleMorphism) True @@ -214,10 +216,9 @@ def frobenius_charpoly(self, var='X'): Note that the *Frobenius trace* is defined as `A(T)` and the *Frobenius norm* is defined as `B(T)`. - INPUT: (default: ``'T'``) the name of the second variable + INPUT: - OUTPUT: an univariate polynomial with coefficients in the - function ring + - ``var`` (default: ``'X'``) -- the name of the second variable EXAMPLES:: @@ -283,8 +284,6 @@ def frobenius_norm(self): Let `n` be the degree of the base field over `\mathbb{F}_q` Then the Frobenius norm has degree `n`. - OUTPUT: an element in the function ring - EXAMPLES:: sage: Fq = GF(343) @@ -331,26 +330,11 @@ def frobenius_trace(self): Let `\mathbb{F}_q[T]` be the function ring, write `\chi = T^2 - A(X)T + B(X) \in \mathbb{F}_q[T][X]` for the characteristic - polynomial of the Frobenius endomorphism. The *Frobenius norm* - is defined as the polynomial `B(T) \in \mathbb{F}_q[T]`. + polynomial of the Frobenius endomorphism. The *Frobenius trace* + is defined as the polynomial `A(T) \in \mathbb{F}_q[T]`. Let `n` be the degree over `\mathbb{F}_q` of the base codomain. - Then the Frobenius trace has degree `\leq \frac{n}{2}`. - - OUTPUT: an element in the function ring - - ALGORITHM: - - Let `A(T)` denote the Frobenius trace and `B(T)` denote the - Frobenius norm. We begin by computing `B(T)`, see docstring - of method :meth:`frobenius_norm` for details. The - characteristic polynomial of the Frobenius yields `t^{2n} - - \phi_A t^n + \phi_B = 0`, where `t^n` is the Frobenius - endomorphism. As `\phi_B` is now known, we can compute - `\phi_A = (t^{2n} + \phi_B) / t^n`. We get `A(T)` by - inverting this quantity, using the method - :meth:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule.invert`, - see its docstring for details. + Then the Frobenius trace has degree at most `\frac{n}{2}`. EXAMPLES:: @@ -372,6 +356,19 @@ def frobenius_trace(self): sage: A == -phi.frobenius_charpoly()[1] True + + ALGORITHM: + + Let `A(T)` denote the Frobenius trace and `B(T)` denote the + Frobenius norm. We begin by computing `B(T)`, see docstring + of method :meth:`frobenius_norm` for details. The + characteristic polynomial of the Frobenius yields `t^{2n} - + \phi_A t^n + \phi_B = 0`, where `t^n` is the Frobenius + endomorphism. As `\phi_B` is now known, we can compute + `\phi_A = (t^{2n} + \phi_B) / t^n`. We get `A(T)` by + inverting this quantity, using the method + :meth:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule.invert`, + see its docstring for details. """ self._check_rank_two() # Notations from Schost-Musleh: @@ -392,8 +389,6 @@ def invert(self, ore_pol): - ``ore_pol`` -- the Ore polynomial whose preimage we want to compute - OUTPUT: a function ring element - EXAMPLES:: sage: Fq = GF(25) @@ -417,6 +412,7 @@ def invert(self, ore_pol): Traceback (most recent call last): ... ValueError: input must be in the image of the Drinfeld module + sage: phi.invert(t^3 + t^2 + 1) Traceback (most recent call last): ... @@ -494,8 +490,6 @@ def is_ordinary(self): A rank two Drinfeld module is *ordinary* if and only if it is not supersingular; see :meth:`is_supersingular`. - OUTPUT: a boolean - EXAMPLES:: sage: Fq = GF(343) @@ -530,8 +524,6 @@ def is_supersingular(self): trace. An *ordinary* rank two finite Drinfeld module is a Drinfeld module that is not supersingular. - OUTPUT: a boolean - EXAMPLES:: sage: Fq = GF(343) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index c5391095631..f9d3fac81e4 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -34,6 +34,7 @@ class DrinfeldModuleHomset(Homset): INPUT: - ``X`` -- the domain + - ``Y`` -- the codomain EXAMPLES:: @@ -43,22 +44,22 @@ class DrinfeldModuleHomset(Homset): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z6, z6, 2]) sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) - sage: hom = Hom(phi, psi) - sage: hom + sage: H = Hom(phi, psi) + sage: H Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 :: sage: from sage.rings.function_field.drinfeld_modules.homset import DrinfeldModuleHomset - sage: isinstance(hom, DrinfeldModuleHomset) + sage: isinstance(H, DrinfeldModuleHomset) True There is a simpler syntax for endomorphisms sets:: - sage: end = End(phi) - sage: end + sage: E = End(phi) + sage: E Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + z6*t + z6 - sage: end is Hom(phi, phi) + sage: E is Hom(phi, phi) True The domain and codomain must have the same Drinfeld modules @@ -80,8 +81,7 @@ class DrinfeldModuleHomset(Homset): One can create morphism objects by calling the homset:: - sage: t = phi.ore_polring().gen() - sage: identity_morphism = end(1) + sage: identity_morphism = E(1) sage: identity_morphism Drinfeld Module morphism: From (gen): 2*t^2 + z6*t + z6 @@ -90,7 +90,8 @@ class DrinfeldModuleHomset(Homset): :: - sage: frobenius_endomorphism = end(t^6) + sage: t = phi.ore_polring().gen() + sage: frobenius_endomorphism = E(t^6) sage: frobenius_endomorphism Drinfeld Module morphism: From (gen): 2*t^2 + z6*t + z6 @@ -99,7 +100,7 @@ class DrinfeldModuleHomset(Homset): :: - sage: isogeny = hom(t + 1) + sage: isogeny = H(t + 1) sage: isogeny Drinfeld Module morphism: From (gen): 2*t^2 + z6*t + z6 @@ -109,28 +110,28 @@ class DrinfeldModuleHomset(Homset): And one can test if an Ore polynomial defines a morphism using the ``in`` syntax:: - sage: 1 in hom + sage: 1 in H False - sage: t^6 in hom + sage: t^6 in H False - sage: t + 1 in hom + sage: t + 1 in H True - sage: 1 in end + sage: 1 in E True - sage: t^6 in end + sage: t^6 in E True - sage: t + 1 in end + sage: t + 1 in E False This also works if the candidate is a morphism object:: - sage: isogeny in hom + sage: isogeny in H True - sage: end(0) in end + sage: E(0) in E True - sage: identity_morphism in hom + sage: identity_morphism in H False - sage: frobenius_endomorphism in hom + sage: frobenius_endomorphism in H False """ @@ -144,9 +145,12 @@ def __init__(self, X, Y, category=None, check=True): INPUT: - ``X`` -- the domain of the homset + - ``Y`` -- the codomain of the homset + - ``category`` (default: ``None``) -- the Drinfeld modules category of the domain and codomain + - ``check`` (default: ``True``) -- check the validity of the category TESTS:: @@ -156,10 +160,10 @@ def __init__(self, X, Y, category=None, check=True): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z6, z6, 2]) sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) - sage: hom = Hom(phi, psi) - sage: hom.domain() is phi + sage: H = Hom(phi, psi) + sage: H.domain() is phi True - sage: hom.codomain() is psi + sage: H.codomain() is psi True """ if category is None: @@ -178,8 +182,6 @@ def _latex_(self): r""" Return a LaTeX representation of the homset. - OUTPUT: a string - EXAMPLES:: sage: Fq = GF(27) @@ -187,8 +189,8 @@ def _latex_(self): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z6, z6, 2]) sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) - sage: hom = Hom(phi, psi) - sage: latex(hom) + sage: H = Hom(phi, psi) + sage: latex(H) \text{Set{ }of{ }Drinfeld{ }module{ }morphisms{ }from{ }(gen){ }}2 t^{2} + z_{6} t + z_{6}\text{{ }to{ }(gen){ }}2 t^{2} + \left(2 z_{6}^{5} + 2 z_{6}^{4} + 2 z_{6} + 1\right) t + z_{6} """ return f'\\text{{Set{{ }}of{{ }}Drinfeld{{ }}module{{ }}morphisms' \ @@ -200,8 +202,6 @@ def _repr_(self): r""" Return a string representation of the homset. - OUTPUT: a string - EXAMPLES:: sage: Fq = GF(27) @@ -209,8 +209,8 @@ def _repr_(self): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z6, z6, 2]) sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) - sage: hom = Hom(phi, psi) - sage: hom + sage: H = Hom(phi, psi) + sage: H Set of Drinfeld module morphisms from (gen) 2*t^2 + z6*t + z6 to (gen) 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 """ return f'Set of Drinfeld module morphisms from (gen) '\ @@ -218,15 +218,12 @@ def _repr_(self): def __contains__(self, x): r""" - Implement the ``in`` operator for the homset; return ``True`` - whether the input defines a morphism in the homset. + Return ``True`` if the input defines a morphism in the homset. INPUT: - ``x`` -- an Ore polynomial or a Drinfeld module morphism - OUTPUT: a boolean - EXAMPLES: In the next examples, the input is an Ore polynomial:: @@ -236,34 +233,34 @@ def __contains__(self, x): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z6, z6, 2]) sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) - sage: hom = Hom(phi, psi) - sage: end = End(phi) + sage: H = Hom(phi, psi) + sage: E = End(phi) sage: t = phi.ore_polring().gen() - sage: 1 in hom + sage: 1 in H False - sage: t^6 in hom + sage: t^6 in H False - sage: t + 1 in hom + sage: t + 1 in H True - sage: 1 in end + sage: 1 in E True - sage: t^6 in end + sage: t^6 in E True - sage: t + 1 in end + sage: t + 1 in E False Whereas the input is now a Drinfeld module morphism:: - sage: isogeny = hom(t + 1) - sage: isogeny in hom + sage: isogeny = H(t + 1) + sage: isogeny in H True - sage: end(0) in end + sage: E(0) in E True - sage: identity_morphism = end(1) - sage: identity_morphism in hom + sage: identity_morphism = E(1) + sage: identity_morphism in H False sage: frobenius_endomorphism = phi.frobenius_endomorphism() - sage: frobenius_endomorphism in hom + sage: frobenius_endomorphism in H False """ try: @@ -279,8 +276,6 @@ def _element_constructor_(self, *args, **kwds): INPUT: an Ore polynomial - OUTPUT: a Drinfeld module morphism - EXAMPLES:: sage: Fq = GF(27) @@ -288,10 +283,10 @@ def _element_constructor_(self, *args, **kwds): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [z6, z6, 2]) sage: psi = DrinfeldModule(A, [z6, 2*z6^5 + 2*z6^4 + 2*z6 + 1, 2]) - sage: hom = Hom(phi, psi) - sage: end = End(phi) + sage: H = Hom(phi, psi) + sage: E = End(phi) sage: t = phi.ore_polring().gen() - sage: identity_morphism = end(1) + sage: identity_morphism = E(1) sage: identity_morphism Drinfeld Module morphism: From (gen): 2*t^2 + z6*t + z6 @@ -300,7 +295,7 @@ def _element_constructor_(self, *args, **kwds): :: - sage: frobenius_endomorphism = end(t^6) + sage: frobenius_endomorphism = E(t^6) sage: frobenius_endomorphism Drinfeld Module morphism: From (gen): 2*t^2 + z6*t + z6 @@ -309,7 +304,7 @@ def _element_constructor_(self, *args, **kwds): :: - sage: isogeny = hom(t + 1) + sage: isogeny = H(t + 1) sage: isogeny Drinfeld Module morphism: From (gen): 2*t^2 + z6*t + z6 diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 48bd560961f..275b49d163f 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -30,7 +30,7 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, This class represents Drinfeld `\mathbb{F}_q[T]`-module morphisms. Let `\phi` and `\psi` be two Drinfeld `\mathbb{F}_q[T]`-modules over - a field `K`. A *morphism of Drinfeld modules `\phi \to \psi`* is an + a field `K`. A *morphism of Drinfeld modules* `\phi \to \psi` is an Ore polynomial `f \in K\{\tau\}` such that `f \phi_a = \psi_a f` for every `a \in \mathbb{F}_q[T]`. In our case, this is equivalent to `f \phi_T = \psi_T f`. An *isogeny* is a nonzero morphism. @@ -68,8 +68,6 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: morphism.domain() is phi True - :: - sage: morphism.codomain() Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: morphism.codomain() is psi @@ -80,8 +78,6 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: morphism.ore_polynomial() t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 - :: - sage: morphism.ore_polynomial() is ore_pol True @@ -126,13 +122,13 @@ def __classcall_private__(cls, parent, x): INPUT: - - ``cls`` -- DrinfeldModuleMorphism + - ``cls`` -- the class ``DrinfeldModuleMorphism`` + - ``parent`` -- the Drinfeld module homset + - ``x`` -- the Ore polynomial defining the morphism or a DrinfeldModuleMorphism - OUTPUT: the morphism object - TESTS:: sage: Fq = GF(2) @@ -176,6 +172,7 @@ def __init__(self, parent, ore_pol): INPUT: - ``parent`` -- the Drinfeld module homset + - ``ore_pol`` -- the Ore polynomial that defines the morphism TESTS:: @@ -194,7 +191,6 @@ def __init__(self, parent, ore_pol): sage: morphism._ore_polynomial == t + z6^5 + z6^2 + 1 True """ - super().__init__(parent) self._domain = parent.domain() self._codomain = parent.codomain() @@ -204,8 +200,6 @@ def _latex_(self): r""" Return a LaTeX representation of the morphism. - OUTPUT: a string - EXAMPLES:: sage: Fq = GF(2) @@ -237,8 +231,6 @@ def _repr_(self): r""" Return a string representation of the morphism. - OUTPUT: a string - EXAMPLES:: sage: Fq = GF(2) From 4ccc577fdb1c7ccd098f18cd0bb23f80257275e9 Mon Sep 17 00:00:00 2001 From: Xavier Caruso Date: Wed, 8 Feb 2023 07:43:19 +0100 Subject: [PATCH 259/392] category --- src/sage/categories/drinfeld_modules.py | 263 +++++++++++------------- 1 file changed, 123 insertions(+), 140 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index d3b931397c3..4e57cf36a5f 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -40,13 +40,10 @@ class DrinfeldModules(Category_over_base_ring): polynomials with coefficients in `K`, whose multiplication is given by the rule `\tau \lambda = \lambda^q \tau` for any `\lambda \in K`. - We call `K` the *base field* of the category, and `\gamma` its *base - morphism*. - - .. NOTE:: - - Equivalently, the base of the category could be defined as the - base morphism `\gamma: \mathbb{F}_q[T] \to K`. + The extension `K`/`\mathbb{F}_q[T]` (represented as an instance of + the class class:`sage.rings.ring_extension.RingExtension`) is the + *base field* of the category; its defining morphism `\gamma` is + called the *base morphism*. The monic polynomial that generates the kernel of `\gamma` is called the `\mathbb{F}_q[T]`-*characteristic*, or *function-field @@ -55,8 +52,6 @@ class DrinfeldModules(Category_over_base_ring): polynomial ring*. The constant coefficient of the category is the image of `T` under the base morphism. - INPUT: the base field - .. RUBRIC:: Construction Generally, Drinfeld modules objects are created before their @@ -68,9 +63,9 @@ class DrinfeldModules(Category_over_base_ring): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat - Category of Drinfeld modules defined over Finite Field in z of size 11^4 over its base + sage: C = phi.category() + sage: C + Category of Drinfeld modules over Finite Field in z of size 11^4 over its base The output tells the user that the category is only defined by its base. @@ -79,13 +74,13 @@ class DrinfeldModules(Category_over_base_ring): The base field is retrieved using the method :meth:`base`. - sage: cat.base() + sage: C.base() Finite Field in z of size 11^4 over its base Equivalently, one can use :meth:`base_morphism` to retrieve the base morphism:: - sage: cat.base_morphism() + sage: C.base_morphism() Ring morphism: From: Univariate Polynomial Ring in T over Finite Field of size 11 To: Finite Field in z of size 11^4 over its base @@ -95,51 +90,54 @@ class DrinfeldModules(Category_over_base_ring): Drinfeld modules in the category --- is simply the image of `T` by the base morphism:: - sage: cat.constant_coefficient() + sage: C.constant_coefficient() z^3 + 7*z^2 + 6*z + 10 - sage: cat.base_morphism()(T) == cat.constant_coefficient() + sage: C.base_morphism()(T) == C.constant_coefficient() True Similarly, the function ring-characteristic of the category is either `0` or the unique monic polynomial in `\mathbb{F}_q[T]` that generates the kernel of the base:: - sage: cat.characteristic() + sage: C.characteristic() T^2 + 7*T + 2 - sage: cat.base_morphism()(cat.characteristic()) + sage: C.base_morphism()(C.characteristic()) 0 The base field, base morphism, function ring and Ore polynomial ring are the same for the category and its objects:: - sage: cat.base() is phi.base() + sage: C.base() is phi.base() True - sage: cat.base_morphism() is phi.base_morphism() + sage: C.base_morphism() is phi.base_morphism() True - sage: cat.function_ring() is phi.function_ring() - True - sage: cat.function_ring() + + sage: C.function_ring() Univariate Polynomial Ring in T over Finite Field of size 11 - sage: cat.ore_polring() is phi.ore_polring() + sage: C.function_ring() is phi.function_ring() True - sage: cat.ore_polring() + + sage: C.ore_polring() Ore Polynomial Ring in t over Finite Field in z of size 11^4 over its base twisted by Frob + sage: C.ore_polring() is phi.ore_polring() + True + .. RUBRIC:: Creating Drinfeld module objects from the category Calling :meth:`object` with an Ore polynomial creates a Drinfeld module object in the category whose generator is the input:: - sage: psi = cat.object([p_root, 1]) + sage: psi = C.object([p_root, 1]) sage: psi Drinfeld module defined by T |--> t + z^3 + 7*z^2 + 6*z + 10 - sage: psi.category() is cat + sage: psi.category() is C True Of course, the constant coefficient of the input must be the same as the category:: - sage: cat.object([z, 1]) + sage: C.object([z, 1]) Traceback (most recent call last): ... ValueError: constant coefficient must equal that of the category @@ -147,12 +145,12 @@ class DrinfeldModules(Category_over_base_ring): It is also possible to create a random object in the category. The input is the desired rank:: - sage: rho = cat.random_object(2) + sage: rho = C.random_object(2) sage: rho # random Drinfeld module defined by T |--> (7*z^3 + 7*z^2 + 10*z + 2)*t^2 + (9*z^3 + 5*z^2 + 2*z + 7)*t + z^3 + 7*z^2 + 6*z + 10 sage: rho.rank() == 2 True - sage: rho.category() is cat + sage: rho.category() is C True TESTS:: @@ -162,20 +160,20 @@ class DrinfeldModules(Category_over_base_ring): sage: K. = Fq.extension(4) sage: from sage.categories.drinfeld_modules import DrinfeldModules sage: base = Hom(A, K)(0) - sage: cat = DrinfeldModules(base) + sage: C = DrinfeldModules(base) Traceback (most recent call last): ... TypeError: base field must be a ring extension - + :: - sage: cat.base().defining_morphism() == cat.base_morphism() + sage: C.base().defining_morphism() == C.base_morphism() True :: sage: base = Hom(A, A)(1) - sage: cat = DrinfeldModules(base) + sage: C = DrinfeldModules(base) Traceback (most recent call last): ... TypeError: base field must be a ring extension @@ -183,7 +181,7 @@ class DrinfeldModules(Category_over_base_ring): :: sage: base = 'I hate Rostropovitch' - sage: cat = DrinfeldModules(base) # known bug (blankline) + sage: C = DrinfeldModules(base) # known bug (blankline) Traceback (most recent call last): ... @@ -193,7 +191,7 @@ class DrinfeldModules(Category_over_base_ring): sage: ZZT. = ZZ[] sage: base = Hom(ZZT, K)(1) - sage: cat = DrinfeldModules(base) # known bug (blankline) + sage: C = DrinfeldModules(base) # known bug (blankline) Traceback (most recent call last): ... @@ -208,7 +206,8 @@ def __init__(self, base_field, name='t'): - ``base_field`` -- the base field, which is a ring extension over a base - - ``name`` (default: `'t'`) -- the name of the Ore polynomial + + - ``name`` (default: ``'t'``) -- the name of the Ore polynomial variable TESTS:: @@ -218,21 +217,21 @@ def __init__(self, base_field, name='t'): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() + sage: C = phi.category() sage: ore_polring. = OrePolynomialRing(phi.base(), phi.base().frobenius_endomorphism()) - sage: cat._ore_polring is ore_polring + sage: C._ore_polring is ore_polring True sage: i = phi.base().coerce_map_from(K) sage: base_morphism = Hom(A, K)(p_root) - sage: cat.base() == K.over(base_morphism) + sage: C.base() == K.over(base_morphism) True - sage: cat._base_morphism == i * base_morphism + sage: C._base_morphism == i * base_morphism True - sage: cat._function_ring is A + sage: C._function_ring is A True - sage: cat._constant_coefficient == base_morphism(T) + sage: C._constant_coefficient == base_morphism(T) True - sage: cat._characteristic(cat._constant_coefficient) + sage: C._characteristic(C._constant_coefficient) 0 """ # Check input is a ring extension @@ -270,11 +269,12 @@ def __init__(self, base_field, name='t'): self._characteristic = None if K.is_finite(): self._characteristic = A(K.over(Fq)(base_morphism(T)).minpoly()) - try: - if A.is_subring(K): - self._characteristic = Integer(0) - except NotImplementedError: - pass + else: + try: + if base_morphism.is_injective(): + self._characteristic = Integer(0) + except NotImplementedError: + pass # Create base over constants field i = A.coerce_map_from(Fq) Fq_to_K = self._base_morphism * i @@ -294,12 +294,12 @@ def _latex_(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: latex(cat) - \text{Category{ }of{ }Drinfeld{ }modules{ }defined{ }over{ }\Bold{F}_{11^{4}} + sage: C = phi.category() + sage: latex(C) + \text{Category{ }of{ }Drinfeld{ }modules{ }over{ }\Bold{F}_{11^{4}} """ return f'\\text{{Category{{ }}of{{ }}Drinfeld{{ }}modules{{ }}' \ - f'defined{{ }}over{{ }}{latex(self._base_field)}' + f'over{{ }}{latex(self._base_field)}' def _repr_(self): r""" @@ -314,18 +314,16 @@ def _repr_(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat - Category of Drinfeld modules defined over Finite Field in z of size 11^4 over its base + sage: C = phi.category() + sage: C + Category of Drinfeld modules over Finite Field in z of size 11^4 over its base """ - return f'Category of Drinfeld modules defined over {self._base_field}' + return f'Category of Drinfeld modules over {self._base_field}' def Homsets(self): r""" Return the category of homsets. - OUTPUT: the category of homsets - EXAMPLES:: sage: Fq = GF(11) @@ -333,9 +331,10 @@ def Homsets(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() + sage: C = phi.category() + sage: from sage.categories.homsets import Homsets - sage: cat.Homsets() is Homsets() + sage: C.Homsets() is Homsets() True """ return Homsets() @@ -344,8 +343,6 @@ def Endsets(self): r""" Return the category of endsets. - OUTPUT: the category of endsets - EXAMPLES:: sage: Fq = GF(11) @@ -353,9 +350,10 @@ def Endsets(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() + sage: C = phi.category() + sage: from sage.categories.homsets import Homsets - sage: cat.Endsets() is Homsets().Endsets() + sage: C.Endsets() is Homsets().Endsets() True """ return Homsets().Endsets() @@ -364,8 +362,6 @@ def base_morphism(self): r""" Return the base morphism of the category. - OUTPUT: a ring morphism - EXAMPLES:: sage: Fq = GF(11) @@ -373,13 +369,14 @@ def base_morphism(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat.base_morphism() + sage: C = phi.category() + sage: C.base_morphism() Ring morphism: From: Univariate Polynomial Ring in T over Finite Field of size 11 To: Finite Field in z of size 11^4 over its base Defn: T |--> z^3 + 7*z^2 + 6*z + 10 - sage: cat.constant_coefficient() == cat.base_morphism()(T) + + sage: C.constant_coefficient() == C.base_morphism()(T) True """ return self._base_morphism @@ -389,8 +386,6 @@ def base_over_constants_field(self): Return the base field, seen as an extension over the constants field `\mathbb{F}_q`. - OUTPUT: a ring extension - EXAMPLES:: sage: Fq = GF(11) @@ -398,8 +393,8 @@ def base_over_constants_field(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat.base_over_constants_field() + sage: C = phi.category() + sage: C.base_over_constants_field() Field in z with defining polynomial x^4 + 8*x^2 + 10*x + 2 over its base """ return self._base_over_constants_field @@ -408,8 +403,6 @@ def characteristic(self): r""" Return the function ring-characteristic. - OUTPUT: `0` or a monic prime polynomial in the function ring - EXAMPLES:: sage: Fq = GF(11) @@ -417,18 +410,19 @@ def characteristic(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat.characteristic() + sage: C = phi.category() + sage: C.characteristic() T^2 + 7*T + 2 :: sage: psi = DrinfeldModule(A, [Frac(A).gen(), 1]) - sage: psi.category().characteristic() + sage: C = psi.category() + sage: C.characteristic() 0 """ if self._characteristic is None: - raise NotImplementedError('function ring characteristic not' \ + raise NotImplementedError('function ring characteristic not ' \ 'implemented in this case') return self._characteristic @@ -436,8 +430,6 @@ def constant_coefficient(self): r""" Return the constant coefficient of the category. - OUTPUT: an element in the base field - EXAMPLES:: sage: Fq = GF(11) @@ -445,10 +437,10 @@ def constant_coefficient(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat.constant_coefficient() + sage: C = phi.category() + sage: C.constant_coefficient() z^3 + 7*z^2 + 6*z + 10 - sage: cat.constant_coefficient() == cat.base()(T) + sage: C.constant_coefficient() == C.base()(T) True """ return self._constant_coefficient @@ -457,8 +449,6 @@ def function_ring(self): r""" Return the function ring of the category. - OUTPUT: a univariate polynomial ring - EXAMPLES:: sage: Fq = GF(11) @@ -466,10 +456,10 @@ def function_ring(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat.function_ring() + sage: C = phi.category() + sage: C.function_ring() Univariate Polynomial Ring in T over Finite Field of size 11 - sage: cat.function_ring() is A + sage: C.function_ring() is A True """ return self._function_ring @@ -479,10 +469,10 @@ def object(self, gen): Return a Drinfeld module object in the category whose generator is the input. - INPUT: the generator of the Drinfeld module, given as an Ore - polynomial or a list of coefficients + INPUT: - OUTPUT: a Drinfeld module in the category + - ``gen`` -- the generator of the Drinfeld module, given as an Ore + polynomial or a list of coefficients EXAMPLES:: @@ -490,13 +480,14 @@ def object(self, gen): sage: A. = Fq[] sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 - sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: psi = cat.object([p_root, 0, 1]) - sage: psi + sage: psi = DrinfeldModule(A, [p_root, 1]) + sage: C = psi.category() + + sage: phi = C.object([p_root, 0, 1]) + sage: phi Drinfeld module defined by T |--> t^2 + z^3 + 7*z^2 + 6*z + 10 sage: t = phi.ore_polring().gen() - sage: cat.object(t^3 + z^3 + 7*z^2 + 6*z + 10) is phi + sage: C.object(t^2 + z^3 + 7*z^2 + 6*z + 10) is phi True """ from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule @@ -510,9 +501,7 @@ def object(self, gen): def ore_polring(self): r""" - Return the Ore polynomial ring of the category. - - OUTPUT: an Ore polynomial ring + Return the Ore polynomial ring of the category EXAMPLES:: @@ -521,22 +510,19 @@ def ore_polring(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat.ore_polring() + sage: C = phi.category() + sage: C.ore_polring() Ore Polynomial Ring in t over Finite Field in z of size 11^4 over its base twisted by Frob - sage: cat.ore_polring() is phi.ore_polring() - True """ return self._ore_polring def random_object(self, rank): r""" - Return a random Drinfeld module in the category, whose rank is - the input. + Return a random Drinfeld module in the category with given rank. - INPUT: an integer, the rank of the Drinfeld module + INPUT: - OUTPUT: a Drinfeld module in the category + - ``rank`` -- an integer, the rank of the Drinfeld module EXAMPLES:: @@ -545,8 +531,9 @@ def random_object(self, rank): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: psi = cat.random_object(3) # random + sage: C = phi.category() + + sage: psi = C.random_object(3) # random Drinfeld module defined by T |--> (6*z^3 + 4*z^2 + 10*z + 9)*t^3 + (4*z^3 + 8*z^2 + 8*z)*t^2 + (10*z^3 + 3*z^2 + 6*z)*t + z^3 + 7*z^2 + 6*z + 10 sage: psi.rank() == 3 True @@ -576,8 +563,8 @@ def super_categories(self): sage: K. = Fq.extension(4) sage: p_root = z^3 + 7*z^2 + 6*z + 10 sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) - sage: cat = phi.category() - sage: cat.super_categories() + sage: C = phi.category() + sage: C.super_categories() [] """ return [] @@ -586,9 +573,10 @@ class ParentMethods: def base(self): r""" - Return the base field of the Drinfeld module. + Return the base field of this Drinfeld module, viewed as + an algebra over the function ring. - OUTPUT: a field, which is a ring extension over a base + This is an instance of the class class:`sage.rings.ring_extension.RingExtension`. EXAMPLES:: @@ -610,9 +598,7 @@ def base(self): def base_morphism(self): r""" - Return the base morphism of the Drinfeld module. - - OUTPUT: a ring morphism + Return the base morphism of this Drinfeld module. EXAMPLES:: @@ -643,7 +629,7 @@ def base_over_constants_field(self): Return the base field, seen as an extension over the constants field `\mathbb{F}_q`. - OUTPUT: a ring extension + This is an instance of the class class:`sage.rings.ring_extension.RingExtension`. EXAMPLES:: @@ -661,8 +647,6 @@ def characteristic(self): r""" Return the function ring-characteristic. - OUTPUT: a univariate polynomial ring - EXAMPLES:: sage: Fq = GF(25) @@ -683,15 +667,13 @@ def characteristic(self): sage: psi.characteristic() Traceback (most recent call last): ... - NotImplementedError: function ring characteristic notimplemented in this case + NotImplementedError: function ring characteristic not implemented in this case """ return self.category().characteristic() def function_ring(self): r""" - Return the function ring of the Drinfeld module. - - OUTPUT: a univariate polynomial ring + Return the function ring of this Drinfeld module. EXAMPLES:: @@ -700,6 +682,8 @@ def function_ring(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi.function_ring() + Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 sage: phi.function_ring() is A True """ @@ -707,7 +691,8 @@ def function_ring(self): def constant_coefficient(self): r""" - Return the constant coefficient of the generator. + Return the constant coefficient of the generator + of this Drinfeld module. OUTPUT: an element in the base field @@ -721,12 +706,12 @@ def constant_coefficient(self): sage: phi.constant_coefficient() == p_root True - Let `\mathbb{F}_q[T]` be the function ring, and let `\gamma` - the base of the Drinfeld module. The constant coefficient - equals `\gamma(T)`:: + Let `\mathbb{F}_q[T]` be the function ring, and let `\gamma` be + the base of the Drinfeld module. The constant coefficient is + `\gamma(T)`:: - sage: cat = phi.category() - sage: base = cat.base() + sage: C = phi.category() + sage: base = C.base() sage: base(T) == phi.constant_coefficient() True @@ -734,7 +719,7 @@ def constant_coefficient(self): same constant coefficient:: sage: t = phi.ore_polring().gen() - sage: psi = cat.object(phi.constant_coefficient() + t^3) + sage: psi = C.object(phi.constant_coefficient() + t^3) sage: psi Drinfeld module defined by T |--> t^3 + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 @@ -742,7 +727,7 @@ def constant_coefficient(self): this category if they do not share the same constant coefficient:: - sage: rho = cat.object(phi.constant_coefficient() + 1 + t^3) + sage: rho = C.object(phi.constant_coefficient() + 1 + t^3) Traceback (most recent call last): ... ValueError: constant coefficient must equal that of the category @@ -751,9 +736,7 @@ def constant_coefficient(self): def ore_polring(self): r""" - Return the Ore polynomial ring of the Drinfeld module. - - OUTPUT: an Ore polynomial ring + Return the Ore polynomial ring of this Drinfeld module. EXAMPLES:: @@ -762,20 +745,20 @@ def ore_polring(self): sage: K. = Fq.extension(6) sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) - sage: ore_polring = phi.ore_polring() - sage: ore_polring + sage: S = phi.ore_polring() + sage: S Ore Polynomial Ring in t over Finite Field in z12 of size 5^12 over its base twisted by Frob^2 The Ore polynomial ring can also be retrieved from the category of the Drinfeld module:: - sage: ore_polring is phi.category().ore_polring() + sage: S is phi.category().ore_polring() True The generator of the Drinfeld module is in the Ore polynomial ring:: - sage: phi(T) in ore_polring + sage: phi(T) in S True """ return self.category().ore_polring() From 297d317b541e0147e9ae3bc30a848244d4304340 Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Wed, 8 Feb 2023 18:03:23 +0530 Subject: [PATCH 260/392] fixing_failing_tests --- src/sage/combinat/posets/linear_extensions.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 47c315a230a..2cdc803f9b5 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -257,13 +257,13 @@ def is_supergreedy(self): Return ``True`` if the linear extension is supergreedy. A linear extension `[e_1, e_2, \ldots, e_n]` is *supergreedy* if - for every `i`, either if there is a minimal element `e_{i+1}` in + for every `i`, either if there is a minimal element `e_{i+1}` in `[e_{i+1}, \ldots, e_n]` which is in the upper cover of `e_j` in `[e_1, \ldots, e_i]` for which j is maximum or if no such element exist `e_{i+1}` is the any of the minimal element in `[e_i+1,\dots, e_n]`. - Informally said a linear extension is supergreedy if it "always + Informally said a linear extension is supergreedy if it "always goes up and receedes the least" loosely speaking, supergreedy linear extensions are depth-first linear extensions. @@ -306,7 +306,6 @@ def next_elements(H, linext): S = [x for x in H.neighbor_out_iterator(linext[k-1]) if x not in linext and all(low in linext for low in H.neighbor_in_iterator(x))] k -= 1 return S - if not self: return True if self[0] not in H.sources(): @@ -319,7 +318,6 @@ def next_elements(H, linext): return False return True - def tau(self, i): r""" Return the operator `\tau_i` on linear extensions ``self`` of a poset. From b7f0ecfea92ddb8bbe4f9bf4d568b38e901f9340 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Wed, 8 Feb 2023 15:23:45 +0100 Subject: [PATCH 261/392] Fix stuff --- src/sage/categories/drinfeld_modules.py | 5 +- .../function_field/drinfeld_modules/action.py | 2 +- .../drinfeld_modules/drinfeld_module.py | 60 +++++++++---- .../finite_drinfeld_module.py | 12 +-- .../function_field/drinfeld_modules/homset.py | 34 +++---- .../drinfeld_modules/morphism.py | 90 +++++++++++-------- 6 files changed, 115 insertions(+), 88 deletions(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index 4e57cf36a5f..f07aeac6049 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -18,6 +18,7 @@ # http://www.gnu.org/licenses/ # ****************************************************************************** +from sage.categories.objects import Objects from sage.categories.category_types import Category_over_base_ring from sage.categories.homsets import Homsets from sage.misc.functional import log @@ -565,9 +566,9 @@ def super_categories(self): sage: phi = DrinfeldModule(A, [p_root, 0, 0, 1]) sage: C = phi.category() sage: C.super_categories() - [] + [Category of objects] """ - return [] + return [Objects()] class ParentMethods: diff --git a/src/sage/rings/function_field/drinfeld_modules/action.py b/src/sage/rings/function_field/drinfeld_modules/action.py index c324014745d..32a00f9ea71 100644 --- a/src/sage/rings/function_field/drinfeld_modules/action.py +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -150,7 +150,7 @@ def _latex_(self): sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) sage: action = phi.action() sage: latex(action) - \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\text{Drinfeld{ }module{ }defined{ }by{ }} T \mapsto t^{3} + z\text{{ }over{ }base{ }}\Bold{F}_{11^{2}} + \text{Action{ }on{ }}\Bold{F}_{11^{2}}\text{{ }induced{ }by{ }}\phi: T \mapsto t^{3} + z """ return f'\\text{{Action{{ }}on{{ }}}}' \ f'{latex(self._base)}\\text{{{{ }}' \ diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index a5a5f6dceab..502785f53d7 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -27,11 +27,13 @@ from sage.categories.drinfeld_modules import DrinfeldModules from sage.categories.homset import Hom from sage.misc.latex import latex +from sage.misc.lazy_string import _LazyString from sage.rings.integer import Integer from sage.rings.polynomial.ore_polynomial_element import OrePolynomial from sage.rings.polynomial.polynomial_ring import PolynomialRing_general from sage.rings.ring_extension import RingExtension_generic from sage.structure.parent import Parent +from sage.structure.sage_object import SageObject from sage.structure.sequence import Sequence from sage.structure.unique_representation import UniqueRepresentation @@ -185,7 +187,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): :class:`sage.categories.drinfeld_modules.DrinfeldModules`):: sage: phi.category() - Category of Drinfeld modules defined over Finite Field in z of size 3^12 over its base + Category of Drinfeld modules over Finite Field in z of size 3^12 over its base sage: phi.category() is psi.category() False sage: phi.category() is rho.category() @@ -316,20 +318,13 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: identity_morphism = hom(1) sage: zero_morphism = hom(0) sage: frobenius_endomorphism - Drinfeld Module morphism: - From (gen): t^2 + t + z - To (gen): t^2 + t + z - Defn: t^6 + Endomorphism of Drinfeld module defined by T |--> t^2 + t + z + Defn: t^6 sage: identity_morphism - Drinfeld Module morphism: - From (gen): t^2 + t + z - To (gen): t^2 + t + z - Defn: 1 + Identity morphism of Drinfeld module defined by T |--> t^2 + t + z sage: zero_morphism - Drinfeld Module morphism: - From (gen): t^2 + t + z - To (gen): t^2 + t + z - Defn: 0 + Endomorphism of Drinfeld module defined by T |--> t^2 + t + z + Defn: 0 The underlying Ore polynomial is retrieved with the method :meth:`ore_polynomial`:: @@ -663,6 +658,10 @@ def __init__(self, gen, category, latexname=None): True sage: phi._latexname is None True + + :: + + sage: TestSuite(phi).run() """ self._base = category.base() self._function_ring = category.function_ring() @@ -788,7 +787,7 @@ def _latex_(self): sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) sage: latex(phi) - \text{Drinfeld{ }module{ }defined{ }by{ }} T \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12}\text{{ }over{ }base{ }}\Bold{F}_{5^{12}} + \phi: T \mapsto z_{12}^{5} t^{2} + z_{12}^{3} t + 2 z_{12}^{11} + 2 z_{12}^{10} + z_{12}^{9} + 3 z_{12}^{8} + z_{12}^{7} + 2 z_{12}^{5} + 2 z_{12}^{4} + 3 z_{12}^{3} + z_{12}^{2} + 2 z_{12} :: @@ -807,10 +806,8 @@ def _latex_(self): if self._latexname is not None: return self._latexname else: - return f'\\text{{Drinfeld{{ }}module{{ }}defined{{ }}by{{ }}}} ' \ - f'{latex(self._function_ring.gen())} '\ - f'\\mapsto {latex(self._gen)}' \ - f'\\text{{{{ }}over{{ }}base{{ }}}}{latex(self._base)}' + return f'\\phi: {latex(self._function_ring.gen())} \\mapsto ' \ + f'{latex(self._gen)}' def _repr_(self): r""" @@ -829,6 +826,33 @@ def _repr_(self): return f'Drinfeld module defined by {self._function_ring.gen()} ' \ f'|--> {self._gen}' + def _test_category(self, **options): + """ + Run generic tests on the method :meth:`.category`. + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: phi._test_category() + + .. NOTE:: + + We reimplemented this method because Drinfeld modules are + parents, and + meth:`sage.structure.parent.Parent._test_category` requires + parents' categories to be subcategories of ``Sets()``. + """ + tester = self._tester(**options) + SageObject._test_category(self, tester=tester) + category = self.category() + # Tests that self inherits methods from the categories + tester.assertTrue(isinstance(self, category.parent_class), + _LazyString("category of %s improperly initialized", (self,), {})) + def action(self): r""" Return the action object diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 0d8939051ca..237da641a18 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -77,10 +77,8 @@ class FiniteDrinfeldModule(DrinfeldModule): sage: frobenius_endomorphism = phi.frobenius_endomorphism() sage: frobenius_endomorphism - Drinfeld Module morphism: - From (gen): 5*t^2 + z6 - To (gen): 5*t^2 + z6 - Defn: t^2 + Endomorphism of Drinfeld module defined by T |--> 5*t^2 + z6 + Defn: t^2 Its characteristic polynomial can be computed:: @@ -174,10 +172,8 @@ def frobenius_endomorphism(self): sage: K. = Fq.extension(2) sage: phi = DrinfeldModule(A, [1, 0, z6]) sage: phi.frobenius_endomorphism() - Drinfeld Module morphism: - From (gen): z6*t^2 + 1 - To (gen): z6*t^2 + 1 - Defn: t^2 + Endomorphism of Drinfeld module defined by T |--> z6*t^2 + 1 + Defn: t^2 TESTS:: diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py index f9d3fac81e4..84fdc4c6e14 100644 --- a/src/sage/rings/function_field/drinfeld_modules/homset.py +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -83,29 +83,24 @@ class DrinfeldModuleHomset(Homset): sage: identity_morphism = E(1) sage: identity_morphism - Drinfeld Module morphism: - From (gen): 2*t^2 + z6*t + z6 - To (gen): 2*t^2 + z6*t + z6 - Defn: 1 + Identity morphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 :: sage: t = phi.ore_polring().gen() sage: frobenius_endomorphism = E(t^6) sage: frobenius_endomorphism - Drinfeld Module morphism: - From (gen): 2*t^2 + z6*t + z6 - To (gen): 2*t^2 + z6*t + z6 - Defn: t^6 + Endomorphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 + Defn: t^6 :: sage: isogeny = H(t + 1) sage: isogeny Drinfeld Module morphism: - From (gen): 2*t^2 + z6*t + z6 - To (gen): 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 - Defn: t + 1 + From: Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 + To: Drinfeld module defined by T |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 + Defn: t + 1 And one can test if an Ore polynomial defines a morphism using the ``in`` syntax:: @@ -288,28 +283,23 @@ def _element_constructor_(self, *args, **kwds): sage: t = phi.ore_polring().gen() sage: identity_morphism = E(1) sage: identity_morphism - Drinfeld Module morphism: - From (gen): 2*t^2 + z6*t + z6 - To (gen): 2*t^2 + z6*t + z6 - Defn: 1 + Identity morphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 :: sage: frobenius_endomorphism = E(t^6) sage: frobenius_endomorphism - Drinfeld Module morphism: - From (gen): 2*t^2 + z6*t + z6 - To (gen): 2*t^2 + z6*t + z6 - Defn: t^6 + Endomorphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 + Defn: t^6 :: sage: isogeny = H(t + 1) sage: isogeny Drinfeld Module morphism: - From (gen): 2*t^2 + z6*t + z6 - To (gen): 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 - Defn: t + 1 + From: Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 + To: Drinfeld module defined by T |--> 2*t^2 + (2*z6^5 + 2*z6^4 + 2*z6 + 1)*t + z6 + Defn: t + 1 """ # NOTE: This used to be self.element_class(self, ...), but this # would call __init__ instead of __classcall_private__. This diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 275b49d163f..40c0caa2635 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -39,20 +39,20 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, instantiate :class:`DrinfeldModuleMorphism`, but rather call the parent homset with the defining Ore polynomial:: - sage: Fq = GF(25) + sage: Fq = GF(4) sage: A. = Fq[] - sage: K. = Fq.extension(6) - sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: K. = Fq.extension(3) + sage: phi = DrinfeldModule(A, [z, z^2 + z, z^2 + z]) sage: t = phi.ore_polring().gen() - sage: ore_pol = t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + sage: ore_pol = t + z^5 + z^3 + z + 1 sage: psi = phi.velu(ore_pol) sage: morphism = Hom(phi, psi)(ore_pol) sage: morphism Drinfeld Module morphism: - From (gen): z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - To (gen): (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + From: Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z)*t + z + To: Drinfeld module defined by T |--> (z^5 + z^2 + z + 1)*t^2 + (z^4 + z + 1)*t + z + Defn: t + z^5 + z^3 + z + 1 + The given Ore polynomial must indeed define a morphism:: @@ -64,20 +64,19 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, One can get basic data on the morphism:: sage: morphism.domain() - Drinfeld module defined by T |--> z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z)*t + z sage: morphism.domain() is phi True sage: morphism.codomain() - Drinfeld module defined by T |--> (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + Drinfeld module defined by T |--> (z^5 + z^2 + z + 1)*t^2 + (z^4 + z + 1)*t + z sage: morphism.codomain() is psi True :: sage: morphism.ore_polynomial() - t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 - + t + z^5 + z^3 + z + 1 sage: morphism.ore_polynomial() is ore_pol True @@ -108,9 +107,9 @@ class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, sage: from sage.rings.function_field.drinfeld_modules.morphism import DrinfeldModuleMorphism sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) Drinfeld Module morphism: - From (gen): z12^5*t^2 + z12^3*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - To (gen): (z12^11 + 3*z12^10 + z12^9 + z12^7 + z12^5 + 4*z12^4 + 4*z12^3 + z12^2 + 1)*t^2 + (2*z12^11 + 4*z12^10 + 2*z12^8 + z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + z12^2 + z12 + 4)*t + 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 - Defn: t + 2*z12^11 + 4*z12^9 + 2*z12^8 + 2*z12^6 + 3*z12^5 + z12^4 + 2*z12^3 + 4*z12^2 + 4*z12 + 4 + From: Drinfeld module defined by T |--> (z^2 + z)*t^2 + (z^2 + z)*t + z + To: Drinfeld module defined by T |--> (z^5 + z^2 + z + 1)*t^2 + (z^4 + z + 1)*t + z + Defn: t + z^5 + z^3 + z + 1 sage: DrinfeldModuleMorphism(Hom(phi, psi), ore_pol) is morphism True """ @@ -210,22 +209,9 @@ def _latex_(self): sage: t = phi.ore_polring().gen() sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: latex(morphism) - \begin{array}{l} - \text{Drinfeld{ }module{ }morphism:}\\ - \text{{ }{ }From (gen):{ }}t^{2} + t + z_{6}\\ - \text{{ }{ }To (gen):{ }}{ }{ }t^{2} + \left(z_{6}^{4} + z_{6}^{2} + 1\right) t + z_{6}\\ - \text{{ }{ }Defn:{ }}t + z_{6}^{5} + z_{6}^{2} + 1 - \end{array} + t + z_{6}^{5} + z_{6}^{2} + 1 """ - return f'\\begin{{array}}{{l}}\n' \ - f'\\text{{Drinfeld{{ }}module{{ }}morphism:}}\\\\\n' \ - f'\\text{{{{ }}{{ }}From (gen):{{ }}}}'\ - f'{latex(self._domain.gen())}\\\\\n' \ - f'\\text{{{{ }}{{ }}To (gen):{{ }}}}{{ }}{{ }}' \ - f'{latex(self._codomain.gen())}\\\\\n' \ - f'\\text{{{{ }}{{ }}Defn:{{ }}}}' \ - f'{latex(self._ore_polynomial)}\n' \ - f'\\end{{array}}' + return f'{latex(self._ore_polynomial)}' def _repr_(self): r""" @@ -242,14 +228,20 @@ def _repr_(self): sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) sage: morphism Drinfeld Module morphism: - From (gen): t^2 + t + z6 - To (gen): t^2 + (z6^4 + z6^2 + 1)*t + z6 - Defn: t + z6^5 + z6^2 + 1 + From: Drinfeld module defined by T |--> t^2 + t + z6 + To: Drinfeld module defined by T |--> t^2 + (z6^4 + z6^2 + 1)*t + z6 + Defn: t + z6^5 + z6^2 + 1 """ - return f'Drinfeld Module morphism:\n' \ - f' From (gen): {self._domain.gen()}\n' \ - f' To (gen): {self._codomain.gen()}\n' \ - f' Defn: {self._ore_polynomial}' + if self.is_identity(): + return f'Identity morphism of {self._domain}' + elif self.is_endomorphism(): + return f'Endomorphism of {self._domain}\n' \ + f' Defn: {self._ore_polynomial}' + else: + return f'Drinfeld Module morphism:\n' \ + f' From: {self._domain}\n' \ + f' To: {self._codomain}\n' \ + f' Defn: {self._ore_polynomial}' def is_zero(self): r""" @@ -275,6 +267,30 @@ def is_zero(self): """ return self._ore_polynomial.is_zero() + def is_identity(self): + r""" + Return ``True`` whether the morphism is the identity morphism. + + EXAMPLES:: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: morphism = End(phi)(1) + sage: morphism.is_identity() + True + + :: + + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_polring().gen() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: morphism.is_identity() + False + """ + return self._ore_polynomial == 1 + def is_isogeny(self): r""" Return ``True`` whether the morphism is an isogeny. From 30617f991360e2575347be4aaf75391a56428297 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Wed, 8 Feb 2023 15:32:33 +0100 Subject: [PATCH 262/392] Add hash methods --- .../drinfeld_modules/drinfeld_module.py | 16 ++++++++++++++++ .../drinfeld_modules/morphism.py | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 502785f53d7..67e4ab963e8 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -853,6 +853,22 @@ def _test_category(self, **options): tester.assertTrue(isinstance(self, category.parent_class), _LazyString("category of %s improperly initialized", (self,), {})) + def __hash__(self): + r""" + Return a hash of ``self``. + + EXAMPLES:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: p_root = 2*z12^11 + 2*z12^10 + z12^9 + 3*z12^8 + z12^7 + 2*z12^5 + 2*z12^4 + 3*z12^3 + z12^2 + 2*z12 + sage: phi = DrinfeldModule(A, [p_root, z12^3, z12^5]) + sage: hash(phi) # random + -6894299335185957188 + """ + return hash((self.base(), self._gen)) + def action(self): r""" Return the action object diff --git a/src/sage/rings/function_field/drinfeld_modules/morphism.py b/src/sage/rings/function_field/drinfeld_modules/morphism.py index 40c0caa2635..dab86c43efa 100644 --- a/src/sage/rings/function_field/drinfeld_modules/morphism.py +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -243,6 +243,24 @@ def _repr_(self): f' To: {self._codomain}\n' \ f' Defn: {self._ore_polynomial}' + def __hash__(self): + r""" + Return a hash of ``self``. + + EXAMPLES:: + + sage: Fq = GF(2) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z6, 1, 1]) + sage: psi = DrinfeldModule(A, [z6, z6^4 + z6^2 + 1, 1]) + sage: t = phi.ore_polring().gen() + sage: morphism = Hom(phi, psi)(t + z6^5 + z6^2 + 1) + sage: hash(morphism) # random + -4214883752078138009 + """ + return hash((self._domain, self._codomain, self._ore_polynomial)) + def is_zero(self): r""" Return ``True`` whether the morphism is the zero morphism. From 39022557c93db40e11523a19587fac90d34b6e4b Mon Sep 17 00:00:00 2001 From: Kryzar Date: Wed, 8 Feb 2023 15:37:40 +0100 Subject: [PATCH 263/392] (fix) Typo --- src/sage/categories/drinfeld_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/categories/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py index f07aeac6049..7720d3fde76 100644 --- a/src/sage/categories/drinfeld_modules.py +++ b/src/sage/categories/drinfeld_modules.py @@ -473,7 +473,7 @@ def object(self, gen): INPUT: - ``gen`` -- the generator of the Drinfeld module, given as an Ore - polynomial or a list of coefficients + polynomial or a list of coefficients EXAMPLES:: From f02b32bc31e272d124c3ea77f6f3834b572ede71 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Thu, 9 Feb 2023 11:15:02 +0100 Subject: [PATCH 264/392] Document argument is_open in ManifoldSubset.complement and differnce --- src/sage/manifolds/subset.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/sage/manifolds/subset.py b/src/sage/manifolds/subset.py index 43fc76a95d6..3e9f03a579a 100644 --- a/src/sage/manifolds/subset.py +++ b/src/sage/manifolds/subset.py @@ -2629,11 +2629,13 @@ def complement(self, superset=None, name=None, latex_name=None, is_open=False): - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the complement in the case the latter has to be created; the default is built upon the symbol `\setminus` + - ``is_open`` -- (default: ``False``) if ``True``, the created subset + is assumed to be open with respect to the manifold's topology OUTPUT: - - instance of :class:`ManifoldSubset` representing the - subset that is difference of ``superset`` minus ``self`` + - instance of :class:`ManifoldSubset` representing the subset that + is ``superset`` minus ``self`` EXAMPLES:: @@ -2650,6 +2652,15 @@ def complement(self, superset=None, name=None, latex_name=None, is_open=False): ... TypeError: superset must be a superset of self + Demanding that the complement is open makes ``self`` a closed subset:: + + sage: A.is_closed() # False a priori + False + sage: A.complement(is_open=True) + Open subset M_minus_A of the 2-dimensional topological manifold M + sage: A.is_closed() + True + """ if superset is None: superset = self.manifold() @@ -2672,11 +2683,13 @@ def difference(self, other, name=None, latex_name=None, is_open=False): - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the difference in the case the latter has to be created; the default is built upon the symbol `\setminus` + - ``is_open`` -- (default: ``False``) if ``True``, the created subset + is assumed to be open with respect to the manifold's topology OUTPUT: - - instance of :class:`ManifoldSubset` representing the - subset that is difference of ``self`` minus ``other`` + - instance of :class:`ManifoldSubset` representing the subset that is + ``self`` minus ``other`` EXAMPLES:: @@ -2706,6 +2719,13 @@ def difference(self, other, name=None, latex_name=None, is_open=False): sage: M.difference(O, is_open=True) Open subset CO2 of the 2-dimensional topological manifold M + Since `O` is open and we have asked `M\setminus O` to be open, `O` + is a clopen set (if `O\neq M` and `O\neq\emptyset`, this implies that + `M` is not connected):: + + sage: O.is_closed() and O.is_open() + True + """ # See if it has been created already diffs = [] From 15e1b50c34f3529c808165974797ad4744c55119 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Thu, 26 Jan 2023 18:11:47 +0000 Subject: [PATCH 265/392] Store Williamson type matrices as strings --- src/sage/combinat/matrices/hadamard_matrix.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 342fa853af3..bb8b52ee97b 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -347,31 +347,29 @@ def williamson_type_quadruples_smallcases(n, existence=False): ValueError: The Williamson type quadruple of order 123 is not yet implemented. """ db = { - 1: ([1], [1], [1], [1]), - 7: ([1, -1, -1, 1, 1, -1, -1], - [1, -1, 1, -1, -1, 1, -1], - [1, 1, -1, -1, -1, -1, 1], - [1, -1, -1, -1, -1, -1, -1]), - 9: ([1, -1, -1, -1, 1, 1, -1, -1, -1], - [1, -1, -1, 1, -1, -1, 1, -1, -1], - [1, -1, 1, -1, -1, -1, -1, 1, -1], - [1, 1, -1, -1, -1, -1, -1, -1, 1]), - 29: ([1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1], - [1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1], - [1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1], - [1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1]), - 43: ([1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1], - [1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1], - [1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1], - [1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1]), + 1: ('+', '+', '+', '+'), + 7: ('+--++--', '+-+--+-', '++----+', '+------'), + 9: ('+---++---', '+--+--+--', '+-+----+-', '++------+'), + 29: ('+++---++--+-+----+-+--++---++', + '+-+---++--+-++++++-+--++---+-', + '++++-++-+---++++++---+-++-+++', + '++--+--+-+++-++++-+++-+--+--+'), + 43: ('++---++++-+--+--++--------++--+--+-++++---+', + '+++-+-++--+-+-++++-+----+-++++-+-+--++-+-++', + '++-++++++----+-+--++-++-++--+-+----++++++-+', + '+---++--++++-+-+++-++--++-+++-+-++++--++---'), } + def pmtoZ(s): + return [1 if x == '+' else -1 for x in s] + if existence: return n in db if n not in db: raise ValueError("The Williamson type quadruple of order %s is not yet implemented." % n) - a, b, c, d = map(vector, db[n]) + + a, b, c, d = map(lambda s: vector(pmtoZ(s)), db[n]) return a, b, c, d From 5832798800e7c8b646423599889cdbff000ab45e Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Thu, 26 Jan 2023 18:44:26 +0000 Subject: [PATCH 266/392] Add more williamson type matrices --- src/sage/combinat/matrices/hadamard_matrix.py | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index bb8b52ee97b..9a7d0df284a 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -312,7 +312,7 @@ def williamson_type_quadruples_smallcases(n, existence=False): Williamson construction of Hadamard matrices. Namely, the function returns the first row of 4 `n\times n` circulant matrices with the properties described in :func:`sage.combinat.matrices.hadamard_matrix.hadamard_matrix_williamson_type`. - The matrices for n=29 and n=43 are given in [Ha83]_. + The matrices for `n = 3, 5, ..., 29, 37, 43` are given in [Ha83]_. INPUT: @@ -348,12 +348,32 @@ def williamson_type_quadruples_smallcases(n, existence=False): """ db = { 1: ('+', '+', '+', '+'), + 3: ('+++', '+--', '+--', '+--'), + 5: ('+-++-', '++--+', '+----', '+----'), 7: ('+--++--', '+-+--+-', '++----+', '+------'), 9: ('+---++---', '+--+--+--', '+-+----+-', '++------+'), - 29: ('+++---++--+-+----+-+--++---++', - '+-+---++--+-++++++-+--++---+-', - '++++-++-+---++++++---+-++-+++', - '++--+--+-+++-++++-+++-+--+--+'), + 11: ('++--------+', '++-+-++-+-+', '++-++--++-+', '+-++----++-'), + 13: ('++++-+--+-+++', '+---+-++-+---', '++---+--+---+', '++---+--+---+'), + 15: ('+-+---++++---+-', '++-++------++-+', + '++-++++--++++-+', '++-++-+--+-++-+'), + 17: ('+---+++----+++---', '++-+---+--+---+-+', + '+--+-++++++++-+--', '+-++-+++--+++-++-'), + 19: ('++--+++-+--+-+++--+', '++-++--+-++-+--++-+', + '+-+---++++++++---+-', '++--+-++++++++-+--+'), + 21: ('+--++++---++---++++--', '++++-+---+--+---+-+++', + '++--+-+-++--++-+-+--+', '++-+++++-+--+-+++++-+'), + 23: ('++---+---+-++-+---+---+', '+-++-++--++++++--++-++-', + '+++---++-+-++-+-++---++', '+++-+++-+------+-+++-++'), + 25: ('++++-+-+-+--++--+-+-+-+++', '++--+--+-++++++++-+--+--+', + '+++--+--++++--++++--+--++', '+-+--+++--++++++--+++--+-'), + 27: ('+--+--+-+++--++--+++-+--+--', '+++-++-+---++--++---+-++-++', + '+---+++++-+-++++-+-+++++---', '+---+++++-+-++++-+-+++++---'), + 29: ('+++---++--+-+----+-+--++---++', '+-+---++--+-++++++-+--++---+-', + '++++-++-+---++++++---+-++-+++', '++--+--+-+++-++++-+++-+--+--+'), + 37: ('+--+-+-+-++---+--++++--+---++-+-+-+--', + '+---++-++--+-+-++----++-+-+--++-++---', + '+++++-+-----++----++----++-----+-++++', + '+--+++-+-----+----++----+-----+-+++--'), 43: ('++---++++-+--+--++--------++--+--+-++++---+', '+++-+-++--+-+-++++-+----+-++++-+-+--++-+-++', '++-++++++----+-+--++-++-++--+-+----++++++-+', @@ -397,10 +417,10 @@ def williamson_hadamard_matrix_smallcases(n, existence=False, check=True): 116 x 116 dense matrix over Integer Ring... sage: williamson_hadamard_matrix_smallcases(172) 172 x 172 dense matrix over Integer Ring... - sage: williamson_hadamard_matrix_smallcases(100) + sage: williamson_hadamard_matrix_smallcases(1000) Traceback (most recent call last): ... - ValueError: The Williamson type Hadamard matrix of order 100 is not yet implemented. + ValueError: The Williamson type Hadamard matrix of order 1000 is not yet implemented. """ assert n % 4 == 0 From 80eddce6b09208250626f5ac37c55df3e009853b Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Thu, 26 Jan 2023 19:28:08 +0000 Subject: [PATCH 267/392] Add other Williamson type matrices --- src/doc/en/reference/references/index.rst | 5 +++ src/sage/combinat/matrices/hadamard_matrix.py | 37 ++++++++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index caeacee26ab..a10b5889127 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4109,6 +4109,11 @@ REFERENCES: of a genus 2 Jacobian*, Mathematics of Computation 88 (2019), 889-929. :doi:`10.1090/mcom/3358`. +.. [Lon2013] \S. London, + *Constructing New Turyn Type Sequences, T-Sequences and Hadamard Matrices*. + PhD Thesis, University of Illinois at Chicago, 2013. + https://hdl.handle.net/10027/9916 + .. [LOS2012] \C. Lecouvey, M. Okado, M. Shimozono. "Affine crystals, one-dimensional sums and parabolic Lusztig `q`-analogues". Mathematische Zeitschrift. **271** (2012). Issue 3-4. diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 9a7d0df284a..c48f8fc8505 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -312,7 +312,8 @@ def williamson_type_quadruples_smallcases(n, existence=False): Williamson construction of Hadamard matrices. Namely, the function returns the first row of 4 `n\times n` circulant matrices with the properties described in :func:`sage.combinat.matrices.hadamard_matrix.hadamard_matrix_williamson_type`. - The matrices for `n = 3, 5, ..., 29, 37, 43` are given in [Ha83]_. + The matrices for `n = 3, 5, ..., 29, 37, 43` are given in [Ha83]_. The matrices + for `n = 31, 33, 39, 41, 45, 49, 51, 55, 57, 61, 63` are given in [Lon2013]_. INPUT: @@ -370,14 +371,32 @@ def williamson_type_quadruples_smallcases(n, existence=False): '+---+++++-+-++++-+-+++++---', '+---+++++-+-++++-+-+++++---'), 29: ('+++---++--+-+----+-+--++---++', '+-+---++--+-++++++-+--++---+-', '++++-++-+---++++++---+-++-+++', '++--+--+-+++-++++-+++-+--+--+'), - 37: ('+--+-+-+-++---+--++++--+---++-+-+-+--', - '+---++-++--+-+-++----++-+-+--++-++---', - '+++++-+-----++----++----++-----+-++++', - '+--+++-+-----+----++----+-----+-+++--'), - 43: ('++---++++-+--+--++--------++--+--+-++++---+', - '+++-+-++--+-+-++++-+----+-++++-+-+--++-+-++', - '++-++++++----+-+--++-++-++--+-+----++++++-+', - '+---++--++++-+-+++-++--++-+++-+-++++--++---'), + 31: ('++++++-+--+---++++---+--+-+++++', '+--++---+-+-++----++-+-+---++--', + '+--++---+-+-++----++-+-+---++--', '+-----+-++-+++----+++-++-+-----'), + 33: ('++++++-+-+-+++------+++-+-+-+++++', '++-+-++-+----+++--+++----+-++-+-+', + '++--++-+++-+--+-++-+--+-+++-++--+', '+--++--+++++-++----++-+++++--++--'), + 37: ('+--+-+-+-++---+--++++--+---++-+-+-+--', '+---++-++--+-+-++----++-+-+--++-++---', + '+++++-+-----++----++----++-----+-++++', '+--+++-+-----+----++----+-----+-+++--'), + 39: ('+++--+-+-----+--++----++--+-----+-+--++', '+++--++-+---+-+--+----+--+-+---+-++--++', + '++++---+--++----+-+--+-+----++--+---+++', '+---++-+-+-----+++-++-+++-----+-+-++---'), + 41: ('++++--+-++++-++--++----++--++-++++-+--+++', '++++--+-++++-++--++----++--++-++++-+--+++', + '+++-++-+-+-+-----+++--+++-----+-+-+-++-++', '+--+--+-+-+-+++++---++---+++++-+-+-+--+--'), + 43: ('++---++++-+--+--++--------++--+--+-++++---+', '+++-+-++--+-+-++++-+----+-++++-+-+--++-+-++', + '++-++++++----+-+--++-++-++--+-+----++++++-+', '+---++--++++-+-+++-++--++-+++-+-++++--++---'), + 45: ('+++++-++----+-++--++-++-++--++-+----++-++++', '+++---++--+-+-+-++--------++-+-+-+--++---++', + '++-+-++++-+--+--+++--++--+++--+--+-++++-+-+', '+-++-----++++-+-+++-++++-+++-+-++++-----++-'), + 49: ('++++-++-+---++-+++---++-++-++---+++-++---+-++-+++', '++++-++-+---++-+++---++-++-++---+++-++---+-++-+++', + '+----+-++++--+-+++-+-+++--+++-+-+++-+--++++-+----', '+++++-+----++-+---+-+---++---+-+---+-++----+-++++'), + 51: ('+---+++-++-+-+++--+++++--++--+++++--+++-+-++-+++---', '----+++-++-+-+++--+++++--++--+++++--+++-+-++-+++---', + '-+--+----+-+++-+-+++++--+--+--+++++-+-+++-+----+--+', '-+--+----+-+++-+-+++++--+--+--+++++-+-+++-+----+--+'), + 55: ('+-+--+-+-++--+-+++++-+++--++++--+++-+++++-+--++-+-+--+-', '--+--+-+-++--+-+++++-+++--++++--+++-+++++-+--++-+-+--+-', + '+++----++-++--++----+-+-++++++++-+-+----++--++-++----++', '+++----++-++--++----+-+-++++++++-+-+----++--++-++----++'), + 57: ('+---++-+--++++-+++-++---+-++++++-+---++-+++-++++--+-++---', '----++-+--++++-+++-++---+-++++++-+---++-+++-++++--+-++---', + '--+-+-+++--+--+-++---+++++-++++-+++++---++-+--+--+++-+-+-', '--+-+-+++--+--+-++---+++++-++++-+++++---++-+--+--+++-+-+-'), + 61: ('++--+--++--+-+-++++--+-----+------+-----+--++++-+-+--++--+--+', '++--+--++--+-+-++++--+-----+------+-----+--++++-+-+--++--+--+', + '+---+-+-++++---++--+-++-+---++++++---+-++-+--++---++++-+-+---', '++++-+-+----+++--++-+--+-+++------+++-+--+-++--+++----+-+-+++'), + 63: ('++-+++--++-++--+--+-++-+-+++--------+++-+-++-+--+--++-++--+++-+', '-+-+++--++-++--+--+-++-+-+++--------+++-+-++-+--+--++-++--+++-+', + '++++-++-+-++++-+---+---+++---++++++---+++---+---+-++++-+-++-+++', '++++-++-+-++++-+---+---+++---++++++---+++---+---+-++++-+-++-+++'), } def pmtoZ(s): From 1e460a9fa9ac9d676bbd9f6456d7467a78948f94 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 08:58:24 +0000 Subject: [PATCH 268/392] Rename supplementary_difference_set_from_rel_diff_set --- .../combinat/designs/difference_family.py | 28 +++++++++---------- src/sage/combinat/matrices/hadamard_matrix.py | 10 +++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index a32f8193e26..3e8747fc167 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -1618,8 +1618,8 @@ def is_supplementary_difference_set(Ks, v, lmbda): EXAMPLES:: - sage: from sage.combinat.designs.difference_family import supplementary_difference_set, is_supplementary_difference_set - sage: S1, S2, S3, S4 = supplementary_difference_set(17) + sage: from sage.combinat.designs.difference_family import supplementary_difference_set_from_rel_diff_set, is_supplementary_difference_set + sage: S1, S2, S3, S4 = supplementary_difference_set_from_rel_diff_set(17) sage: is_supplementary_difference_set([S1, S2, S3, S4], 16, 16) True sage: is_supplementary_difference_set([S1, S2, S3, S4], 16, 14) @@ -1651,7 +1651,7 @@ def is_supplementary_difference_set(Ks, v, lmbda): return True -def supplementary_difference_set(q, existence=False, check=True): +def supplementary_difference_set_from_rel_diff_set(q, existence=False, check=True): r"""Construct `4-\{2v; v, v+1, v, v; 2v\}` supplementary difference sets where `q=2v+1`. The sets are created from relative difference sets as detailed in Theorem 3.3 of [Spe1975]_. this construction @@ -1682,8 +1682,8 @@ def supplementary_difference_set(q, existence=False, check=True): EXAMPLES:: - sage: from sage.combinat.designs.difference_family import supplementary_difference_set - sage: supplementary_difference_set(17) #random + sage: from sage.combinat.designs.difference_family import supplementary_difference_set_from_rel_diff_set + sage: supplementary_difference_set_from_rel_diff_set(17) #random ([0, 2, 5, 6, 8, 10, 13, 14], [0, 1, 2, 6, 7, 9, 10, 14, 15], [0, 1, 2, 6, 11, 12, 13, 15], @@ -1691,31 +1691,31 @@ def supplementary_difference_set(q, existence=False, check=True): If existence is ``True``, the function returns a boolean:: - sage: supplementary_difference_set(7, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(7, existence=True) False - sage: supplementary_difference_set(17, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(17, existence=True) True TESTS:: sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set - sage: is_supplementary_difference_set(supplementary_difference_set(17), 16, 16) + sage: is_supplementary_difference_set(supplementary_difference_set_from_rel_diff_set(17), 16, 16) True - sage: is_supplementary_difference_set(supplementary_difference_set(9), 8, 8) + sage: is_supplementary_difference_set(supplementary_difference_set_from_rel_diff_set(9), 8, 8) True - sage: supplementary_difference_set(7) + sage: supplementary_difference_set_from_rel_diff_set(7) Traceback (most recent call last): ... ValueError: There is no s for which m-1 is an odd prime power - sage: supplementary_difference_set(8) + sage: supplementary_difference_set_from_rel_diff_set(8) Traceback (most recent call last): ... ValueError: q must be an odd prime power - sage: supplementary_difference_set(8, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(8, existence=True) False - sage: supplementary_difference_set(7, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(7, existence=True) False - sage: supplementary_difference_set(1, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(1, existence=True) False .. SEEALSO:: diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index c48f8fc8505..cd66b394948 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -1163,7 +1163,7 @@ def hadamard_matrix_spence_construction(n, existence=False, check=True): r"""Create an Hadamard matrix of order `n` using Spence construction. This construction (detailed in [Spe1975]_), uses supplementary difference sets implemented in - :func:`sage.combinat.designs.difference_family.supplementary_difference_set` to create the + :func:`sage.combinat.designs.difference_family.supplementary_difference_set_from_rel_diff_set` to create the desired matrix. INPUT: @@ -1210,19 +1210,19 @@ def hadamard_matrix_spence_construction(n, existence=False, check=True): ... AssertionError """ - from sage.combinat.designs.difference_family import supplementary_difference_set + from sage.combinat.designs.difference_family import supplementary_difference_set_from_rel_diff_set assert n % 4 == 0 and n > 0 q = n//4 if existence: - return supplementary_difference_set(q, existence=True) + return supplementary_difference_set_from_rel_diff_set(q, existence=True) - if not supplementary_difference_set(q, existence=True): + if not supplementary_difference_set_from_rel_diff_set(q, existence=True): raise ValueError(f'The order {n} is not covered by Spence construction.') - S1, S2, S3, S4 = supplementary_difference_set(q, check=False) + S1, S2, S3, S4 = supplementary_difference_set_from_rel_diff_set(q, check=False) A1 = matrix.circulant([1 if j in S1 else -1 for j in range(q-1)]) A2 = matrix.circulant([1 if j in S4 else -1 for j in range(q-1)]) From e715e0db51169225b6c7e308d955bf634b2dfb7a Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 12:10:26 +0000 Subject: [PATCH 269/392] Extract construction of sds --- .../combinat/designs/difference_family.py | 106 +++++++++++++++--- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 3e8747fc167..5cfe9c69c96 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -1629,7 +1629,7 @@ def is_supplementary_difference_set(Ks, v, lmbda): .. SEEALSO:: - :func:`supplementary_difference_set` + :func:`supplementary_difference_set_from_rel_diff_set` """ from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup @@ -1823,7 +1823,7 @@ def get_fixed_relative_difference_set(rel_diff_set, as_elements=False): `\{td | d\in R\}= R`. In addition, the set returned by this function will contain the element `0`. This is needed in the - construction of supplementary difference sets (see :func:`supplementary_difference_set`). + construction of supplementary difference sets (see :func:`supplementary_difference_set_from_rel_diff_set`). INPUT: @@ -2155,6 +2155,88 @@ def skew_supplementary_difference_set(n, existence=False, check=True): 267: [1, 4, 16, 64, 67, 91, 97, 121, 217, 223, 256], } + if existence: + return n in indices + + if n not in indices: + raise ValueError(f'Skew SDS of order {n} not yet implemented.') + + S1, S2, S3, S4 = _construction_supplementary_difference_set(n, H_db[n], indices[n], cosets_gens[n], check=False) + + if check: + lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n + assert is_supplementary_difference_set([S1, S2, S3, S4], n, lmbda) + assert _is_skew_set(S1, n) + + return S1, S2, S3, S4 + + +def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check=True): + r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. + + This construction is described in [Djo1994]_. + + Let H be a subgroup of Zmod(n) of order `t`. We construct the `2s = n/t` cosets + `\alpha_0, .., \alpha_{2s-1}` by using the elements contained in a sequence `A`: + `\alpha_{2i} = a_iH` and `\alpha_{2i+1} = -\alpha_{2i}`. + + Then, we use four indices sets `J_1, J_2, J_3, J_4` to construct the four + supplementary difference sets: `S_i = \bigcup_{j\in J__i} \alpha_i`. Note that + if `J_i` contains the value `-1`, this function will add `0` to the set `S_i`. + + To construct a coset `\alpha_{2i}` by listing it directly, replace the `2i`-th + element of the list `A` with the desired set. + + INPUT: + + - ``n`` -- integer, the parameter of the supplementary difference set. + + - ``H`` -- list of integers, the set `H` used to construct the cosets. + + - ``indices`` -- list containing four list of integers, which are the sets + `J_1, J_2, J_3, J_4` described above. + + - ``cosets_gen`` -- list containing integers or list of integers, is the set `A` + described above. + + - ``check`` -- boolean (default True). If true, check that the sets + are supplementary difference sets. Setting this parameter to False may speed + up the computation considerably. + + OUTPUT: + + The function returns the 4 sets (containing integers modulo `n`). + + TESTS:: + + sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set, _construction_supplementary_difference_set + sage: H = [1, 10, -11] + sage: cosets_gen = [1, 2, 3, 5, 6, 9] + sage: indices = [[0, 3, 5, 7, 9, 10], [0, 5, 6, 7, 8], [1, 2, 6, 7, 9], [2, 6, 8, 9, 10]] + sage: _construction_supplementary_difference_set(37, H, indices, cosets_gen) + ([1, 10, 26, 35, 17, 22, 34, 7, 33, 32, 24, 18, 31, 14, 29, 9, 16, 12], + [1, 10, 26, 34, 7, 33, 5, 13, 19, 32, 24, 18, 6, 23, 8], + [36, 27, 11, 2, 20, 15, 5, 13, 19, 32, 24, 18, 31, 14, 29], + [2, 20, 15, 5, 13, 19, 6, 23, 8, 31, 14, 29, 9, 16, 12]) + sage: H = [1, 16, 22] + sage: cosets_gen = [1, 2, 3, 4, 6, 8, [13]] + sage: indices = [[1, 3, 5, 6, 8, 10, 12], [0, 1, 5, 8, 12, 13], [1, 3, 4, 7, 9, 12, 13], [0, 1, 2, 3, 7, 8]] + sage: _construction_supplementary_difference_set(39, H, indices, cosets_gen) + ([38, 23, 17, 37, 7, 34, 36, 30, 12, 4, 25, 10, 6, 18, 15, 8, 11, 20, 13], + [1, 16, 22, 38, 23, 17, 36, 30, 12, 6, 18, 15, 13, 26], + [38, 23, 17, 37, 7, 34, 3, 9, 27, 35, 14, 29, 33, 21, 24, 13, 26], + [1, 16, 22, 38, 23, 17, 2, 32, 5, 37, 7, 34, 35, 14, 29, 6, 18, 15]) + sage: H = [1, 4, 11, 16, 21, -2, -8] + sage: cosets_gen = [1, 3, 7] + sage: indices = [[1, 2, 4], [1, 2, 4], [0, 2, 3], [3, 4, -1]] + sage: sets = _construction_supplementary_difference_set(43, H, indices, cosets_gen, check=False) + sage: is_supplementary_difference_set(sets, 43, 35) + True + + .. SEEALSO:: + + :func:`skew_supplementary_difference_set` + """ def generate_set(index_set, cosets): S = [] for idx in index_set: @@ -2164,17 +2246,11 @@ def generate_set(index_set, cosets): S += cosets[idx] return S - if existence: - return n in indices - - if n not in indices: - raise ValueError(f'Skew SDS of order {n} not yet implemented.') - Z = Zmod(n) - H = list(map(Z, H_db[n])) + H = list(map(Z, H)) cosets = [] - for el in cosets_gens[n]: + for el in cosets_gen: if isinstance(el, list): even_coset = [Z(x) for x in el] else: @@ -2183,18 +2259,18 @@ def generate_set(index_set, cosets): cosets.append(even_coset) cosets.append(odd_coset) - S1 = generate_set(indices[n][0], cosets) - S2 = generate_set(indices[n][1], cosets) - S3 = generate_set(indices[n][2], cosets) - S4 = generate_set(indices[n][3], cosets) + S1 = generate_set(indices[0], cosets) + S2 = generate_set(indices[1], cosets) + S3 = generate_set(indices[2], cosets) + S4 = generate_set(indices[3], cosets) if check: lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n assert is_supplementary_difference_set([S1, S2, S3, S4], n, lmbda) - assert _is_skew_set(S1, n) return S1, S2, S3, S4 + def _is_skew_set(S, n): r"""Check if `S` is a skew set over the set of integers modulo `n`. From 4a5989e334b52307fdfbe3054d8450267cd9f694 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 12:46:15 +0000 Subject: [PATCH 270/392] Add construction for SDS --- src/doc/en/reference/references/index.rst | 12 +- .../combinat/designs/difference_family.py | 106 +++++++++++++++++- src/sage/combinat/matrices/hadamard_matrix.py | 2 +- 3 files changed, 113 insertions(+), 7 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index a10b5889127..8a8081c28f5 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2051,10 +2051,15 @@ REFERENCES: *Ten New Orders for Hadamard Matrices of Skew Type*, Publikacije Elektrotehničkog fakulteta. Serija Matematika 2 (1992): 47-59. -.. [Djo1994] \D. Đoković. +.. [Djo1994a] \D. Đoković. *Five New Orders for Hadamard Matrices of Skew Type*, Australasian Journal of Combinatorics 10 (1994): 259-264. +.. [Djo1994b] \D. Đoković. + *Two Hadamard matrices of order 956 of Goethals-Seidel type*, + Combinatorica 14(3) (1994): 375-377. + :doi:`10.1007/BF01212983` + .. [Djo2008a] \D. Đoković. *Skew-Hadamard matrices of orders 188 and 388 exist*, International Mathematical Forum 3 no.22 (2008): 1063-1068. @@ -2065,6 +2070,11 @@ REFERENCES: Journal of Combinatorial Designs 16 (2008): 493-498. :arxiv:`0706.1973` +.. [Djo2008c] \D. Đoković. + *Hadamard matrices of order 764 exist*, + Combinatorica 28(4) (2008): 487-489. + :doi:`10.1007/s00493-008-2384-z` + .. [Djo2023a] \D. Đoković. *Skew-Hadamard matrices of order 276*. :arxiv:`10.48550/ARXIV.2301.02751` diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 5cfe9c69c96..be6e029b0dc 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -1929,12 +1929,12 @@ def is_fixed_relative_difference_set(R, q): def skew_supplementary_difference_set(n, existence=False, check=True): r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `S_1` is skew and `n_1 + n_2 + n_3 + n_4 = n+\lambda`. - These sets are constructed from available data, as described in [Djo1994]_. The set `S_1 \subset G` is + These sets are constructed from available data, as described in [Djo1994a]_. The set `S_1 \subset G` is always skew, i.e. `S_1 \cap (-S_1) = \emptyset` and `S_1 \cup (-S_1) = G \setminus \{0\}`. The data is taken from: - * `n = 103, 151`: [Djo1994]_ + * `n = 103, 151`: [Djo1994a]_ * `n = 67, 113, 127, 157, 163, 181, 241`: [Djo1992a]_ * `n = 37, 43`: [Djo1992b]_ * `n = 39, 49, 65, 93, 121, 129, 133, 217, 219, 267`: [Djo1992c]_ @@ -2174,7 +2174,7 @@ def skew_supplementary_difference_set(n, existence=False, check=True): def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check=True): r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. - This construction is described in [Djo1994]_. + This construction is described in [Djo1994a]_. Let H be a subgroup of Zmod(n) of order `t`. We construct the `2s = n/t` cosets `\alpha_0, .., \alpha_{2s-1}` by using the elements contained in a sequence `A`: @@ -2184,7 +2184,7 @@ def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check= supplementary difference sets: `S_i = \bigcup_{j\in J__i} \alpha_i`. Note that if `J_i` contains the value `-1`, this function will add `0` to the set `S_i`. - To construct a coset `\alpha_{2i}` by listing it directly, replace the `2i`-th + To construct a coset `\alpha_{2i}` by listing it directly, replace the `2i`-th element of the list `A` with the desired set. INPUT: @@ -2271,10 +2271,106 @@ def generate_set(index_set, cosets): return S1, S2, S3, S4 +def supplementary_difference_set(n, existence=False, check=True): + r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where skew and `n_1 + n_2 + n_3 + n_4 = n+\lambda`. + + These sets are constructed from available data, as described in [Djo1994a]_. + + The data for `n=191` is taken from [Djo2008c]_, and date for `n=239` is from [Djo1994b]_. + Additional SDS are constructed using :func:`skew_supplementary_difference_set`. + + INPUT: + + - ``n`` -- integer, the parameter of the supplementary difference set. + + - ``existence`` -- boolean (dafault False). If true, only check whether the + supplementary difference sets can be constructed. + + - ``check`` -- boolean (default True). If true, check that the sets are + supplementary difference sets before returning them. Setting this parameter + to False may speed up the computation considerably. + + OUTPUT: + + If ``existence`` is false, the function returns the 4 sets (containing integers + modulo `n`), or raises an error if data for the given ``n`` is not available. + If ``existence`` is true, the function returns a boolean representing whether + skew supplementary difference sets can be constructed. + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import supplementary_difference_set + sage: S1, S2, S3, S4 = supplementary_difference_set(191) + + If existence is ``True``, the function returns a boolean :: + + sage: supplementary_difference_set(191, existence=True) + True + sage: supplementary_difference_set(17, existence=True) + False + + TESTS:: + + sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set + sage: S1, S2, S3, S4 = supplementary_difference_set(191, check=False) + sage: is_supplementary_difference_set([S1, S2, S3, S4], 191, len(S1)+len(S2)+len(S3)+len(S4)-191) + True + sage: S1, S2, S3, S4 = supplementary_difference_set(37, check=False) + sage: supplementary_difference_set(7) + Traceback (most recent call last): + ... + ValueError: SDS of order 7 not yet implemented. + sage: supplementary_difference_set(7, existence=True) + False + sage: supplementary_difference_set(127, existence=True) + True + """ + + indices = { + 191: [[1, 7, 9, 10, 11, 13, 17, 18, 25, 26, 30, 31, 33, 34, 35, 36, 37], + [1, 4, 7, 9, 11, 12, 13, 14, 19, 21, 22, 23, 24, 25, 26, 29, 36, 37], + [0, 3, 4, 5, 7, 8, 9, 16, 17, 19, 24, 25, 29, 30, 31, 33, 35, 37], + [1, 3, 4, 5, 8, 11, 14, 18, 19, 20, 21, 23, 24, 25, 28, 29, 30, 32, 34, 35]], + 239: [[0, 1, 2, 3, 4, 5, 6, 7, 14, 18, 19, 21, 24, 25, 29, 30], + [0, 1, 3, 7, 9, 12, 15, 18, 20, 22, 26, 28, 29, 30, 31, 32, 33], + [2, 3, 4, 5, 8, 9, 10, 11, 13, 17, 19, 21, 22, 24, 27, 31, 32], + [0, 1, 2, 3, 6, 7, 8, 11, 13, 15, 17, 18, 19, 22, 25, 26, 27, 32, 33]], + } + + cosets_gens = { + 191: [1, 2, 3, 4, 6, 8, 9, 11, 12, 13, 16, 17, 18, 19, 22, 32, 36, 38, 41], + 239: [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 14, 15, 18, 21, 28, 35, 42], + } + + H_db = { + 191: [1, 39, 184, 109, 49], + 239: [1, 10, 24, 44, 98, 100, 201], + } + + if existence: + return n in indices or skew_supplementary_difference_set(n, existence=True) + + sets = None + if n in indices: + sets = _construction_supplementary_difference_set(n, H_db[n], indices[n], cosets_gens[n], check=False) + elif skew_supplementary_difference_set(n, existence=True): + sets = skew_supplementary_difference_set(n, check=False) + + if sets is None: + raise ValueError(f'SDS of order {n} not yet implemented.') + + S1, S2, S3, S4 = sets + if check: + lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n + assert is_supplementary_difference_set([S1, S2, S3, S4], n, lmbda) + + return S1, S2, S3, S4 + + def _is_skew_set(S, n): r"""Check if `S` is a skew set over the set of integers modulo `n`. - From [Djo1994]_, a set `S \subset G` (where `G` is a finite abelian group of order `n`) is of skew + From [Djo1994a]_, a set `S \subset G` (where `G` is a finite abelian group of order `n`) is of skew type if `S_1 \cap (-S_1) = \emptyset` and `S_1 \cup (-S_1) = G\setminus \{0\}`. INPUT: diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index cd66b394948..fdf0958e127 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -2290,7 +2290,7 @@ def skew_hadamard_matrix_324(): r""" Construct a skew Hadamard matrix of order 324. - The construction is taken from [Djo1994]_. It uses four supplementary difference sets `S_1, S_2, S_3, S_4`, + The construction is taken from [Djo1994a]_. It uses four supplementary difference sets `S_1, S_2, S_3, S_4`, with `S_1` of skew type. These are then used to generate four matrices of order `81`, which are inserted into the Goethals-Seidel array. From ccff87ed38ed8c6bbd42c27c7d3e4ff08ff1e35c Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 13:34:58 +0000 Subject: [PATCH 271/392] Add construction for hadamard matrix from sds --- src/sage/combinat/matrices/hadamard_matrix.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index fdf0958e127..ff64da31099 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -768,6 +768,92 @@ def _construction_goethals_seidel_matrix(A, B, C, D): [-D*R, -C.T*R, B.T*R, A]]) +def hadamard_matrix_from_sds(n, existence=False, check=True): + r"""Construction of Hadamard matrices from supplementary difference sets. + + Given four SDS with parameters `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` with + `n_1 + n_2 + n_3 + n_4 = n+\lambda` we can construct four (-1,+1) sequences `a_i = (a_{i,0},...,a_{i,n-1})` + where `a_{i,j} = -1` iff `j \in S_i`. This will be the fist rows of four circulant + matrices `A_1, A_2, A_3, A_4` which, when plugged into the Goethals-Seidel array, create an + Hadamard matrix of order `4n` (see [Djo1994b]_). + + The supplementary difference sets are taken from + :func:`sage.combinat.designs.difference_family.supplementary_difference_set`. + + INPUT: + + - ``n`` -- integer, the order of the matrix to be constructed. + + - ``check`` -- boolean: if True (default), check the the matrix is a Hadamard + before returning. + + - ``existence`` -- boolean (default False): if True, only check if the matrix exists. + + OUTPUT: + + If ``existence`` is false, returns the Hadamard matrix of order `n`. It raises + an error if no data is available to construct the matrix of the given order, + or if `n` is not a multiple of `4`. + If ``existence`` is true, returns a boolean representing whether the matrix + can be constructed or not. + + EXAMPLES: + + By default The function returns the Hadamard matrix :: + + sage: from sage.combinat.matrices.hadamard_matrix import hadamard_matrix_from_sds + sage: hadamard_matrix_from_sds(148) + 148 x 148 dense matrix over Integer Ring... + + If ``existence`` is set to True, the function returns a boolean :: + + sage: hadamard_matrix_from_sds(764, existence=True) + True + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import hadamard_matrix_from_sds, is_hadamard_matrix + sage: is_hadamard_matrix(hadamard_matrix_from_sds(172)) + True + sage: hadamard_matrix_from_sds(64, existence=True) + False + sage: hadamard_matrix_from_sds(64) + Traceback (most recent call last): + ... + ValueError: SDS of order 16 not yet implemented. + sage: hadamard_matrix_from_sds(14) + Traceback (most recent call last): + ... + ValueError: n must be a positive multiple of four. + """ + from sage.combinat.designs.difference_family import supplementary_difference_set + + if n <= 0 or n % 4 != 0: + raise ValueError(f'n must be a positive multiple of four.') + t = n // 4 + + if existence: + return supplementary_difference_set(t, existence=True) + + S1, S2, S3, S4 = supplementary_difference_set(t, check=False) + a = [-1 if i in S1 else 1 for i in range(t)] + b = [-1 if i in S2 else 1 for i in range(t)] + c = [-1 if i in S3 else 1 for i in range(t)] + d = [-1 if i in S4 else 1 for i in range(t)] + + if n == 956: + a, b, c, d = [-el for el in d], a, b, c + + A, B, C, D = map(matrix.circulant, [a, b, c, d]) + if check: + assert A*A.T+B*B.T+C*C.T+D*D.T == 4*t*I(t) + + H = _construction_goethals_seidel_matrix(A, B, C, D) + if check: + assert is_hadamard_matrix(H) + return H + + def hadamard_matrix_cooper_wallis_construction(x1, x2, x3, x4, A, B, C, D, check=True): r""" Create an Hadamard matrix using the contruction detailed in [CW1972]_. @@ -1531,6 +1617,10 @@ def hadamard_matrix(n, existence=False, check=True): if existence: return True M = turyn_type_hadamard_matrix_smallcases(n, check=False) + elif hadamard_matrix_from_sds(n, existence=True): + if existence: + return True + M = hadamard_matrix_from_sds(n, check=False) elif hadamard_matrix_spence_construction(n, existence=True): if existence: return True From 2fd3af6d507d628f47a5892c845de2bce8c6ab3e Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 18:27:59 +0000 Subject: [PATCH 272/392] Create function to construct symmetric conference matrices --- src/sage/combinat/matrices/hadamard_matrix.py | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index ff64da31099..e05f05ad559 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -172,6 +172,56 @@ def hadamard_matrix_paleyI(n, normalize=True): return H +def symmetric_conference_matrix_paley(n): + r""" + Construct a symmetric conference matrix of order n. + + A conference matrix is an `n\times n` matrix `C` with 0s on the main diagonal + and 1s and -1s elsewhere, satisfying `CC^\top=(n-1)I`. This construction assumes + that `q = n-1` is a prime power, with `q \cong 1 \mod 4`. See [Hora]_ or [Lon2013]_. + + These matrices are used in the :func:`hadamard_matrix_paleyII`. + + INPUT: + + - ``n`` -- integer, the order of the symmetric conference matrix to consruct. + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import symmetric_conference_matrix_paley + sage: symmetric_conference_matrix_paley(6) + [ 0 1 1 1 1 1] + [ 1 0 1 -1 -1 1] + [ 1 1 0 1 -1 -1] + [ 1 -1 1 0 1 -1] + [ 1 -1 -1 1 0 1] + [ 1 1 -1 -1 1 0] + + TESTS:: + + sage: symmetric_conference_matrix_paley(5) + Traceback (most recent call last): + ... + ValueError: The order 5 is not covered by Paley construction of symmetric conference matrices. + """ + q = n - 1 + if not (is_prime_power(q) and (q % 4 == 1)): + raise ValueError("The order %s is not covered by Paley construction of symmetric conference matrices." % n) + + from sage.rings.finite_rings.finite_field_constructor import FiniteField + K = FiniteField(q, 'x') + K_list = list(K) + K_list.insert(0, K.zero()) + H = matrix(ZZ, [[(1 if (x-y).is_square() else -1) + for x in K_list] + for y in K_list]) + for i in range(n): + H[0, i] = 1 + H[i, 0] = 1 + H[i, i] = 0 + return H + + def hadamard_matrix_paleyII(n): r""" Implement the Paley type II construction. @@ -219,17 +269,7 @@ def hadamard_matrix_paleyII(n): if not (n % 2 == 0 and is_prime_power(q) and (q % 4 == 1)): raise ValueError("The order %s is not covered by the Paley type II construction." % n) - from sage.rings.finite_rings.finite_field_constructor import FiniteField - K = FiniteField(q, 'x') - K_list = list(K) - K_list.insert(0, K.zero()) - H = matrix(ZZ, [[(1 if (x-y).is_square() else -1) - for x in K_list] - for y in K_list]) - for i in range(q+1): - H[0, i] = 1 - H[i, 0] = 1 - H[i, i] = 0 + H = symmetric_conference_matrix_paley(q+1) tr = { 0: matrix(2, 2, [ 1, -1, -1, -1]), 1: matrix(2, 2, [ 1, 1, 1, -1]), From 2b38a4c0bcdc1ef13d2f395b4c5b9292164cda52 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 19:23:12 +0000 Subject: [PATCH 273/392] Add miyamoto construction --- src/doc/en/reference/references/index.rst | 5 + src/sage/combinat/matrices/hadamard_matrix.py | 112 ++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 8a8081c28f5..6c8b4f644d7 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4499,6 +4499,11 @@ REFERENCES: .. [Mit2008] \A. Mitra. *On the construction of M-sequences via primitive polynomials with a fast identification method*, International Journal of Electronics and Communication Engineering 2(9) (2008): 1991-1996. +.. [Miy1991] \M. Miyamoto. + *A construction of Hadamard matrices*, + Journal of Combinatorial Theory, Series A 57(1) (1991): 86-108. + :doi:`10.1016/0097-3165(91)90008-5` + .. [MKO1998] Hans Munthe--Kaas and Brynjulf Owren. *Computations in a free Lie algebra*. (1998). `Downloadable from Munthe-Kaas's website diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index e05f05ad559..760d73e6f86 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -280,6 +280,114 @@ def hadamard_matrix_paleyII(n): return normalise_hadamard(H) +def hadamard_matrix_miyamoto_construction(n, existence=False, check=True): + r""" + Construct Hadamard matrix using Miyamoto construction. + + If `q = n/4` is a prime power, and there exists an Hadamard matrix of order + `q-1`, then a Hadamard matrix of order `n` can be constructed (see [Miy1991]_). + + INPUT: + + - ``n`` -- integer, the order of the matrix to be constructed. + + - ``check`` -- boolean: if True (default), check the the matrix is a Hadamard + before returning. + + - ``existence`` -- boolean (default False): if True, only check if the matrix exists. + + OUTPUT: + + If ``existence`` is false, returns the Hadamard matrix of order `n`. It raises + an error if no data is available to construct the matrix of the given order, + or if `n` is not a multiple of `4`. + If ``existence`` is true, returns a boolean representing whether the matrix + can be constructed or not. + + EXAMPLES: + + By default the function returns the Hadamard matrix :: + + sage: from sage.combinat.matrices.hadamard_matrix import hadamard_matrix_miyamoto_construction + sage: hadamard_matrix_miyamoto_construction(20) + 20 x 20 dense matrix over Integer Ring... + + If ``existence`` is set to True, the function returns a boolean :: + + sage: hadamard_matrix_miyamoto_construction(36, existence=True) + True + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import is_hadamard_matrix + sage: is_hadamard_matrix(hadamard_matrix_miyamoto_construction(68, check=False)) + True + sage: hadamard_matrix_miyamoto_construction(64, existence=True) + False + sage: hadamard_matrix_miyamoto_construction(64) + Traceback (most recent call last): + ... + ValueError: The order 64 is not covered by Miyamoto construction. + sage: hadamard_matrix_miyamoto_construction(14) + Traceback (most recent call last): + ... + ValueError: No Hadamard matrix of order 14 exists. + """ + if n < 0 or n % 4 != 0: + raise ValueError(f'No Hadamard matrix of order {n} exists.') + + q = n // 4 + if existence: + return is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True) + + if not (is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True)): + raise ValueError(f'The order {n} is not covered by Miyamoto construction.') + + m = (q-1) // 2 + + C = symmetric_conference_matrix_paley(q + 1) + + neg = [i for i in range(2, m+2) if C[1, i] == -1] + pos = [i for i in range(m+2, 2*m+2) if C[1, i] == 1] + + for i, j in zip(neg, pos): + C.swap_rows(i, j) + C.swap_columns(i, j) + + C1 = -C.submatrix(row=2, col=2, nrows=m, ncols=m) + C2 = C.submatrix(row=2, col=m+2, nrows=m, ncols=m) + C4 = C.submatrix(row=m+2, col=m+2, nrows=m, ncols=m) + + K = hadamard_matrix(q - 1) + K1 = K.submatrix(row=0, col=0, nrows=(q-1)//2, ncols=(q-1)//2) + K2 = K.submatrix(row=0, col=(q-1)//2, nrows=(q-1)//2, ncols=(q-1)//2) + K3 = -K.submatrix(row=(q-1)//2, col=0, nrows=(q-1)//2, ncols=(q-1)//2) + K4 = K.submatrix(row=(q-1)//2, col=(q-1)//2, nrows=(q-1)//2, ncols=(q-1)//2) + + Zr = zero_matrix(m) + Us = [[C1, C2, Zr, Zr], [C2.T, C4, Zr, Zr], [Zr, Zr, C1, C2], [Zr, Zr, C2.T, C4]] + Vs = [[I(m), Zr, K1, K2], [Zr, I(m), K3, K4], [K1.T, K3.T, I(m), Zr], [K2.T, K4.T, Zr, I(m)]] + + def T(i, j): + return block_matrix([[Us[i][j]+Vs[i][j], Us[i][j]-Vs[i][j]], + [Us[i][j]-Vs[i][j], Us[i][j]+Vs[i][j]]]) + + e = matrix([[1] * (2*m)]) + one = matrix([1]) + H = block_matrix([[ one, -e, one, e, one, e, one, e], + [-e.T, T(0, 0), e.T, T(0, 1), e.T, T(0, 2), e.T, T(0, 3)], + [-one, -e, one, -e, one, e, -one, -e], + [-e.T, -T(1, 0), -e.T, T(1, 1), e.T, T(1, 2), -e.T, -T(1, 3)], + [-one, -e, -one, -e, one, -e, one, e], + [-e.T, -T(2, 0), -e.T, -T(2, 1), -e.T, T(2, 2), e.T, T(2, 3)], + [-one, -e, one, e, -one, -e, one, -e], + [-e.T, -T(3, 0), e.T, T(3, 1), -e.T, -T(3, 2), -e.T, T(3, 3)]]) + + if check: + assert is_hadamard_matrix(H) + return H + + def hadamard_matrix_williamson_type(a, b, c, d, check=True): r""" Construction of Williamson type Hadamard matrix. @@ -1657,6 +1765,10 @@ def hadamard_matrix(n, existence=False, check=True): if existence: return True M = turyn_type_hadamard_matrix_smallcases(n, check=False) + elif hadamard_matrix_miyamoto_construction(n, existence=True): + if existence: + return True + M = hadamard_matrix_miyamoto_construction(n, check=False) elif hadamard_matrix_from_sds(n, existence=True): if existence: return True From 75b9ed363721c615f0c922e4ef608c33da61754c Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Thu, 9 Feb 2023 23:41:07 +0530 Subject: [PATCH 274/392] minor documentation correction --- src/sage/combinat/posets/linear_extensions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 2cdc803f9b5..a2dfb7b26a1 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -283,8 +283,6 @@ def is_supergreedy(self): sage: for l in Q.linear_extensions(): ....: if not l.is_supergreedy(): ....: print(l) - [0, 1, 2, 3, 4] - [0, 2, 3, 1, 4] [0, 2, 1, 3, 4] TESTS:: From 78dcd21a0d5116b19487aad8219fd5bd368ba93a Mon Sep 17 00:00:00 2001 From: David Roe Date: Fri, 18 Feb 2022 03:44:44 -0500 Subject: [PATCH 275/392] Convert result of multivariate polynomial evaluation into correct parent --- .../multi_polynomial_libsingular.pyx | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index f3c0bc09542..4a004bfb9ef 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -2056,6 +2056,15 @@ cdef class MPolynomial_libsingular(MPolynomial): 9 sage: a.parent() is QQ True + + See :trac:`33373`:: + + sage: k. = GF(2^4) + sage: R. = PolynomialRing(k, 1) + sage: f = R(1) + sage: S. = PolynomialRing(k, 1) + sage: f(y).parent() + Multivariate Polynomial Ring in y over Finite Field in a of size 2^4 """ if len(kwds) > 0: f = self.subs(**kwds) @@ -2075,29 +2084,28 @@ cdef class MPolynomial_libsingular(MPolynomial): if l != parent._ring.N: raise TypeError("number of arguments does not match number of variables in parent") + res_parent = coercion_model.common_parent(parent._base, *x) + cdef poly *res # ownership will be transferred to us in the else block try: # Attempt evaluation via singular. coerced_x = [parent.coerce(e) for e in x] except TypeError: # give up, evaluate functional - y = parent.base_ring().zero() + sage_res = parent.base_ring().zero() for (m,c) in self.dict().iteritems(): - y += c*mul([ x[i]**m[i] for i in m.nonzero_positions()]) - return y - - cdef poly *res # ownership will be transferred to us in the next line - singular_polynomial_call(&res, self._poly, _ring, coerced_x, MPolynomial_libsingular_get_element) - res_parent = coercion_model.common_parent(parent._base, *x) - - if res == NULL: - return res_parent(0) - if p_LmIsConstant(res, _ring): - sage_res = si2sa( p_GetCoeff(res, _ring), _ring, parent._base ) - p_Delete(&res, _ring) # sage_res contains copy + sage_res += c*mul([ x[i]**m[i] for i in m.nonzero_positions()]) else: - sage_res = new_MP(parent, res) # pass on ownership of res to sage_res + singular_polynomial_call(&res, self._poly, _ring, coerced_x, MPolynomial_libsingular_get_element) + + if res == NULL: + return res_parent(0) + if p_LmIsConstant(res, _ring): + sage_res = si2sa( p_GetCoeff(res, _ring), _ring, parent._base ) + p_Delete(&res, _ring) # sage_res contains copy + else: + sage_res = new_MP(parent, res) # pass on ownership of res to sage_res - if parent(sage_res) is not res_parent: + if sage_res.parent() is not res_parent: sage_res = res_parent(sage_res) return sage_res From fbaf715324f08cc9372617528d887f19e018e7ea Mon Sep 17 00:00:00 2001 From: Kryzar Date: Thu, 9 Feb 2023 21:36:05 +0100 Subject: [PATCH 276/392] Refactor latexname --- .../drinfeld_modules/drinfeld_module.py | 52 +++++-------------- .../finite_drinfeld_module.py | 7 +-- 2 files changed, 15 insertions(+), 44 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 67e4ab963e8..4d49705c82b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -27,6 +27,7 @@ from sage.categories.drinfeld_modules import DrinfeldModules from sage.categories.homset import Hom from sage.misc.latex import latex +from sage.misc.latex import latex_variable_name from sage.misc.lazy_string import _LazyString from sage.rings.integer import Integer from sage.rings.polynomial.ore_polynomial_element import OrePolynomial @@ -174,13 +175,6 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi(1) # phi_1 1 - One can give a LaTeX name to be used for LaTeX representation:: - - sage: sigma = DrinfeldModule(A, [z, 1, 1], latexname='\sigma') - ... - sage: latex(sigma) - \sigma - .. RUBRIC:: The category of Drinfeld modules Drinfeld modules have their own category (see class @@ -507,7 +501,7 @@ class DrinfeldModule(Parent, UniqueRepresentation): """ @staticmethod - def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): + def __classcall_private__(cls, function_ring, gen, name='t'): """ Check input validity and return a ``DrinfeldModule`` or ``FiniteDrinfeldModule`` object accordingly. @@ -523,9 +517,6 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring gen - - ``latexname`` (default: ``None``) -- the LaTeX name of the Drinfeld - module - OUTPUT: A DrinfeldModule or FiniteDrinfeldModule. @@ -586,10 +577,6 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): base_field_noext.has_coerce_map_from(function_ring.base_ring())): raise ValueError('function ring base must coerce into base field') - # Check LaTeX name - if latexname is not None and type(latexname) is not str: - raise ValueError('LaTeX name should be a string') - # Build the category T = function_ring.gen() if isinstance(base_field_noext, RingExtension_generic): @@ -613,10 +600,10 @@ def __classcall_private__(cls, function_ring, gen, name='t', latexname=None): # Instantiate the appropriate class if base_field.is_finite(): from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule - return FiniteDrinfeldModule(gen, category, latexname) - return cls.__classcall__(cls, gen, category, latexname) + return FiniteDrinfeldModule(gen, category) + return cls.__classcall__(cls, gen, category) - def __init__(self, gen, category, latexname=None): + def __init__(self, gen, category): """ Initialize ``self``. @@ -634,9 +621,6 @@ def __init__(self, gen, category, latexname=None): - ``name`` (default: ``'t'``) -- the name of the Ore polynomial ring gen - - ``latexname`` (default: ``None``) -- the LaTeX name of the Drinfeld - module - TESTS:: sage: Fq = GF(25) @@ -656,8 +640,6 @@ def __init__(self, gen, category, latexname=None): True sage: phi._morphism == Hom(A, ore_polring)(phi._gen) True - sage: phi._latexname is None - True :: @@ -666,7 +648,6 @@ def __init__(self, gen, category, latexname=None): self._base = category.base() self._function_ring = category.function_ring() self._gen = gen - self._latexname = latexname self._morphism = category._function_ring.hom([gen]) self._ore_polring = gen.parent() self._Fq = self._function_ring.base_ring() # Must be last @@ -776,8 +757,8 @@ def _latex_(self): r""" Return a LaTeX representation of the Drinfeld module. - If a LaTeX name was given at initialization, we use it. - Otherwise, we create a representation. + If a representation name is given with meth:`rename`, it is + taken into account for LaTeX representation. EXAMPLES:: @@ -791,20 +772,13 @@ def _latex_(self): :: - sage: psi = DrinfeldModule(A, [p_root, z12^3, z12^5], latexname='\psi') - ... - sage: latex(psi) - \psi - - :: - - sage: psi = DrinfeldModule(A, [p_root, z12^3, z12^5], latexname=1729) - Traceback (most recent call last): - ... - ValueError: LaTeX name should be a string + sage: phi.rename('phi') + sage: latex(phi) + \phi + sage: phi.reset_name() """ - if self._latexname is not None: - return self._latexname + if hasattr(self, '__custom_name'): + return latex_variable_name(getattr(self, '__custom_name')) else: return f'\\phi: {latex(self._function_ring.gen())} \\mapsto ' \ f'{latex(self._gen)}' diff --git a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py index 237da641a18..114d1730b9b 100644 --- a/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -116,7 +116,7 @@ class FiniteDrinfeldModule(DrinfeldModule): True """ - def __init__(self, gen, category, latexname=None): + def __init__(self, gen, category): """ Initialize `self`. @@ -134,9 +134,6 @@ def __init__(self, gen, category, latexname=None): - ``name`` (default: `'t'`) -- the name of the Ore polynomial ring gen - - ``latexname`` (default: ``None``) -- the LaTeX name of the Drinfeld - module - TESTS:: sage: Fq = GF(25) @@ -152,7 +149,7 @@ def __init__(self, gen, category, latexname=None): # NOTE: There used to be no __init__ here (which was fine). I # added one to ensure that FiniteDrinfeldModule would always # have _frobenius_norm and _frobenius_trace attributes. - super().__init__(gen, category, latexname) + super().__init__(gen, category) self._frobenius_norm = None self._frobenius_trace = None From 14879847094a9c6b490117bfd49da9395e8962d1 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Thu, 9 Feb 2023 21:42:19 +0100 Subject: [PATCH 277/392] Enhance an error message --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 4d49705c82b..9544f66c007 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -589,6 +589,11 @@ def __classcall_private__(cls, function_ring, gen, name='t'): base_morphism = Hom(function_ring, base_field_noext)(gen[0]) base_field = base_field_noext.over(base_morphism) + # This test is also done in the category. We put it here also + # to have a friendlier error message + if not base_field.is_field(): + raise ValueError('generator coefficients must live in a field') + category = DrinfeldModules(base_field, name=name) # Check gen as Ore polynomial From 0e6cee3d7aa71458b17eb073346fcc4090920b23 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Thu, 9 Feb 2023 23:08:40 +0100 Subject: [PATCH 278/392] (fix) Fix PEP8 typo --- .../rings/function_field/drinfeld_modules/drinfeld_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index 9544f66c007..c2f05bbdf56 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -589,7 +589,7 @@ def __classcall_private__(cls, function_ring, gen, name='t'): base_morphism = Hom(function_ring, base_field_noext)(gen[0]) base_field = base_field_noext.over(base_morphism) - # This test is also done in the category. We put it here also + # This test is also done in the category. We put it here also # to have a friendlier error message if not base_field.is_field(): raise ValueError('generator coefficients must live in a field') From 6e5e52ea2eb50d47be1b6b92027151ef88988ded Mon Sep 17 00:00:00 2001 From: Sanjay Rijal <37138338+zovelsanj@users.noreply.github.com> Date: Fri, 10 Feb 2023 20:45:20 +0545 Subject: [PATCH 279/392] updated sage installation method with pip Added sage library installation instructions using pip. Installing sage in a python virtual environment can be troublesome as `sage` and `sagemath` libraries, not at all related to this sage, exist in PiPy. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 1a0582d87c5..38cf9271134 100644 --- a/README.md +++ b/README.md @@ -394,6 +394,18 @@ in the Installation Guide. or JupyterLab installation, as described in [section "Launching SageMath"](https://doc.sagemath.org/html/en/installation/launching.html) in the installation manual. + +Alternative Installation using PiPy +--------------- + +You can find `sage` and `sagemath` pip packages but it is worth nothing that they are not at all realted to this `sage`. Indeed `pip install sage` installs `sage 0.0.0` which is literally an empty package. For installation of this `sage` you need to install `sagemath-standard`. First activate your python virtual environment and follow these steps: + + $ python3 -m pip install sage_conf + $ ls $(sage-config SAGE_SPKG_WHEELS) + $ python3 -m pip install $(sage-config SAGE_SPKG_WHEELS)/*.whl + $ python3 -m pip install sagemath-standard + +You need to install the `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels you can install the sage library, `sagemath-standard`. Troubleshooting --------------- From 7eff8f6072154edd9a19b09199b6d41417aafdf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 10 Feb 2023 20:36:25 +0100 Subject: [PATCH 280/392] removing some unused imports --- .../finite_dimensional_algebra_element.pxd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pxd b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pxd index e5f098c67d7..3230f283da8 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pxd +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pxd @@ -1,4 +1,4 @@ -from sage.structure.element cimport AlgebraElement, Element, Vector, parent +from sage.structure.element cimport AlgebraElement, Vector from sage.matrix.matrix cimport Matrix cdef class FiniteDimensionalAlgebraElement(AlgebraElement): From b5cebda49aeb6de0c0b27b8647766d0e74b2d4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 10 Feb 2023 21:11:48 +0100 Subject: [PATCH 281/392] add back import --- .../finite_dimensional_algebra_element.pxd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pxd b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pxd index 3230f283da8..c13b8dbab07 100644 --- a/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pxd +++ b/src/sage/algebras/finite_dimensional_algebras/finite_dimensional_algebra_element.pxd @@ -1,4 +1,4 @@ -from sage.structure.element cimport AlgebraElement, Vector +from sage.structure.element cimport AlgebraElement, Element, Vector from sage.matrix.matrix cimport Matrix cdef class FiniteDimensionalAlgebraElement(AlgebraElement): From 52b23a4fc02b89cfe61e3f24045ce14cefed76d0 Mon Sep 17 00:00:00 2001 From: "Alex J. Best" Date: Fri, 10 Feb 2023 16:24:49 +0100 Subject: [PATCH 282/392] bump eclib to 20221012 --- build/pkgs/eclib/checksums.ini | 6 +++--- build/pkgs/eclib/package-version.txt | 2 +- build/pkgs/eclib/spkg-configure.m4 | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/pkgs/eclib/checksums.ini b/build/pkgs/eclib/checksums.ini index 8154d34c1ba..ddf84c8173f 100644 --- a/build/pkgs/eclib/checksums.ini +++ b/build/pkgs/eclib/checksums.ini @@ -1,5 +1,5 @@ tarball=eclib-VERSION.tar.bz2 -sha1=2e86bc02e43edfb43473ecb1ae8e7b67cfe87e3c -md5=bb6fc7cb57c01c45a033276e1a94028f -cksum=3974905173 +sha1=7c8b64bd9a1b8f4f489690a53c1f329afc953f2c +md5=03a87ae2b490f11b81ec6b305cbc8087 +cksum=111064162 upstream_url=https://github.com/JohnCremona/eclib/releases/download/VERSION/eclib-VERSION.tar.bz2 diff --git a/build/pkgs/eclib/package-version.txt b/build/pkgs/eclib/package-version.txt index 774b03215ac..6f50c16be9c 100644 --- a/build/pkgs/eclib/package-version.txt +++ b/build/pkgs/eclib/package-version.txt @@ -1 +1 @@ -20220621 +20221012 diff --git a/build/pkgs/eclib/spkg-configure.m4 b/build/pkgs/eclib/spkg-configure.m4 index 471f40e0aee..2d8d19a7e6e 100644 --- a/build/pkgs/eclib/spkg-configure.m4 +++ b/build/pkgs/eclib/spkg-configure.m4 @@ -1,7 +1,7 @@ SAGE_SPKG_CONFIGURE([eclib], [ SAGE_SPKG_DEPCHECK([ntl pari flint], [ dnl Trac #31443, #34029: use existing eclib only if the version reported by pkg-config is correct - m4_pushdef([SAGE_ECLIB_VER],["20220621"]) + m4_pushdef([SAGE_ECLIB_VER],["20221012"]) PKG_CHECK_MODULES([ECLIB], [eclib = SAGE_ECLIB_VER], [ AC_CACHE_CHECK([for mwrank version == SAGE_ECLIB_VER], [ac_cv_path_MWRANK], [ AC_PATH_PROGS_FEATURE_CHECK([MWRANK], [mwrank], [ From e78344c1c384b353924bc1a8d0a86a71778f15f5 Mon Sep 17 00:00:00 2001 From: Alex J Best Date: Fri, 10 Feb 2023 22:47:30 +0000 Subject: [PATCH 283/392] conform to doc requirements so that the docs look beautiful --- src/sage/algebras/free_algebra_quotient.py | 2 ++ src/sage/algebras/iwahori_hecke_algebra.py | 2 ++ .../algebras/quatalg/quaternion_algebra.py | 4 ++-- src/sage/categories/cartesian_product.py | 2 +- src/sage/categories/domains.py | 24 +++++++++---------- src/sage/categories/examples/sets_cat.py | 2 +- src/sage/categories/magmas.py | 2 ++ src/sage/coding/abstract_code.py | 8 +++---- .../coding/guruswami_sudan/interpolation.py | 2 +- src/sage/coding/linear_code_no_metric.py | 2 +- src/sage/combinat/colored_permutations.py | 2 ++ src/sage/combinat/ordered_tree.py | 2 +- src/sage/combinat/permutation.py | 4 ++-- src/sage/combinat/posets/hasse_diagram.py | 2 +- src/sage/combinat/root_system/type_affine.py | 2 +- src/sage/combinat/sf/dual.py | 2 +- src/sage/combinat/sf/sfa.py | 2 +- src/sage/combinat/subset.py | 4 ++-- src/sage/combinat/tableau.py | 2 +- src/sage/combinat/words/paths.py | 2 +- src/sage/functions/special.py | 2 +- src/sage/game_theory/normal_form_game.py | 4 ++-- src/sage/geometry/cone.py | 2 +- src/sage/geometry/fan.py | 2 +- src/sage/geometry/lattice_polytope.py | 2 +- src/sage/geometry/newton_polygon.py | 4 +++- src/sage/geometry/polyhedron/base3.py | 2 +- src/sage/geometry/polyhedron/base_QQ.py | 2 +- src/sage/graphs/generic_graph.py | 6 ++--- src/sage/groups/perm_gps/permgroup.py | 4 ++-- src/sage/groups/perm_gps/permgroup_named.py | 2 +- src/sage/interfaces/expect.py | 18 ++++---------- src/sage/interfaces/frobby.py | 2 +- src/sage/interfaces/interface.py | 2 +- src/sage/interfaces/quit.py | 2 +- src/sage/interfaces/r.py | 4 ++-- src/sage/interfaces/tides.py | 2 ++ src/sage/manifolds/utilities.py | 2 +- src/sage/matroids/constructor.py | 2 +- src/sage/misc/bindable_class.py | 2 +- src/sage/misc/decorators.py | 2 +- src/sage/misc/functional.py | 2 +- src/sage/misc/misc.py | 2 +- .../modform_hecketriangle/abstract_space.py | 2 +- .../modform_hecketriangle/analytic_type.py | 8 ++++--- src/sage/modules/fg_pid/fgp_element.py | 2 +- src/sage/modules/torsion_quadratic_module.py | 2 +- src/sage/numerical/linear_tensor.py | 2 +- src/sage/plot/plot3d/plot3d.py | 2 +- src/sage/quadratic_forms/genera/genus.py | 2 +- src/sage/quivers/morphism.py | 2 +- .../repl/ipython_kernel/widgets_sagenb.py | 4 ++-- src/sage/rings/lazy_series.py | 2 +- src/sage/rings/number_field/number_field.py | 6 ++--- .../number_field/number_field_ideal_rel.py | 2 +- src/sage/rings/number_field/structure.py | 2 +- src/sage/rings/padics/CR_template.pxi | 6 ++--- src/sage/rings/padics/factory.py | 2 +- src/sage/rings/padics/lattice_precision.py | 4 ++-- src/sage/rings/padics/padic_base_leaves.py | 6 ++--- .../rings/padics/padic_extension_generic.py | 2 +- .../rings/padics/padic_lattice_element.py | 2 +- src/sage/rings/padics/padic_valuation.py | 2 +- .../polynomial/infinite_polynomial_ring.py | 2 +- .../polynomial/laurent_polynomial_ideal.py | 2 ++ .../polynomial/laurent_polynomial_ring.py | 2 +- .../rings/polynomial/ore_function_element.py | 4 ++-- .../rings/polynomial/ore_function_field.py | 2 +- src/sage/rings/polynomial/pbori/ll.py | 2 ++ src/sage/rings/polynomial/polynomial_ring.py | 2 +- src/sage/rings/polynomial/term_order.py | 2 +- src/sage/schemes/affine/affine_morphism.py | 4 ++-- .../elliptic_curves/ell_finite_field.py | 2 +- .../schemes/elliptic_curves/hom_velusqrt.py | 2 +- src/sage/sets/finite_set_maps.py | 2 +- src/sage/sets/set.py | 2 +- src/sage/symbolic/operators.py | 2 ++ 77 files changed, 127 insertions(+), 115 deletions(-) diff --git a/src/sage/algebras/free_algebra_quotient.py b/src/sage/algebras/free_algebra_quotient.py index 583eb5f9ae8..4d5000b3df4 100644 --- a/src/sage/algebras/free_algebra_quotient.py +++ b/src/sage/algebras/free_algebra_quotient.py @@ -295,6 +295,8 @@ def module(self): """ The free module of the algebra. + EXAMPLES:: + sage: H = sage.algebras.free_algebra_quotient.hamilton_quatalg(QQ)[0]; H Free algebra quotient on 3 generators ('i', 'j', 'k') and dimension 4 over Rational Field sage: H.module() diff --git a/src/sage/algebras/iwahori_hecke_algebra.py b/src/sage/algebras/iwahori_hecke_algebra.py index 900039c7909..6df42729b70 100644 --- a/src/sage/algebras/iwahori_hecke_algebra.py +++ b/src/sage/algebras/iwahori_hecke_algebra.py @@ -2491,6 +2491,8 @@ def __init__(self, IHAlgebra, prefix=None): r""" Initialize the `A`-basis of the Iwahori-Hecke algebra ``IHAlgebra``. + EXAMPLES:: + sage: R. = LaurentPolynomialRing(QQ) sage: H = IwahoriHeckeAlgebra('A3', v**2) sage: A = H.A() diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 45a4d512c0a..0bca394c6a9 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -1337,8 +1337,8 @@ def __init__(self, A, basis, check=True): sage: type(R) - Over QQ and number fields it is checked whether the given - basis actually gives an order (as a module over the maximal order): + Over QQ and number fields it is checked whether the given + basis actually gives an order (as a module over the maximal order):: sage: A. = QuaternionAlgebra(-1,-1) sage: A.quaternion_order([1,i,j,i-j]) diff --git a/src/sage/categories/cartesian_product.py b/src/sage/categories/cartesian_product.py index e6fbf670750..f61cca89629 100644 --- a/src/sage/categories/cartesian_product.py +++ b/src/sage/categories/cartesian_product.py @@ -155,7 +155,7 @@ def __call__(self, args, **kwds): sage: _.category() Category of Cartesian products of finite enumerated sets - Check that the empty product is handled correctly: + Check that the empty product is handled correctly:: sage: C = cartesian_product([]) sage: C diff --git a/src/sage/categories/domains.py b/src/sage/categories/domains.py index 2cdbb4cd2ce..c40f95ba880 100644 --- a/src/sage/categories/domains.py +++ b/src/sage/categories/domains.py @@ -57,18 +57,18 @@ def _test_zero_divisors(self, **options): In rings whose elements can not be represented exactly, there may be zero divisors in practice, even though these rings do not have them in theory. For such inexact rings, these tests - are not performed: - - sage: R = ZpFM(5); R - 5-adic Ring of fixed modulus 5^20 - sage: R.is_exact() - False - sage: a = R(5^19) - sage: a.is_zero() - False - sage: (a*a).is_zero() - True - sage: R._test_zero_divisors() + are not performed:: + + sage: R = ZpFM(5); R + 5-adic Ring of fixed modulus 5^20 + sage: R.is_exact() + False + sage: a = R(5^19) + sage: a.is_zero() + False + sage: (a*a).is_zero() + True + sage: R._test_zero_divisors() EXAMPLES:: diff --git a/src/sage/categories/examples/sets_cat.py b/src/sage/categories/examples/sets_cat.py index 93cc264580d..111fd9f3a2c 100644 --- a/src/sage/categories/examples/sets_cat.py +++ b/src/sage/categories/examples/sets_cat.py @@ -160,7 +160,7 @@ class PrimeNumbers_Abstract(UniqueRepresentation, Parent): datastructure will then be constructed by inheriting from :class:`PrimeNumbers_Abstract`. - This is used by: + This is used by:: sage: P = Sets().example("facade") sage: P = Sets().example("inherits") diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index 975ea0491b3..43327b13de6 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -711,6 +711,8 @@ def one(self): r""" Return the unit element of ``self``. + EXAMPLES:: + sage: from sage.combinat.root_system.extended_affine_weyl_group import ExtendedAffineWeylGroup sage: PvW0 = ExtendedAffineWeylGroup(['A',2,1]).PvW0() sage: PvW0 in Magmas().Unital().Realizations() diff --git a/src/sage/coding/abstract_code.py b/src/sage/coding/abstract_code.py index 238a165c021..c041e8d0eb2 100644 --- a/src/sage/coding/abstract_code.py +++ b/src/sage/coding/abstract_code.py @@ -337,7 +337,7 @@ def __iter__(self): ....: super().__init__(10) We check we get a sensible error message while asking for an - iterator over the elements of our new class: + iterator over the elements of our new class:: sage: C = MyCode() sage: list(C) @@ -365,7 +365,7 @@ def __contains__(self, c): ....: super().__init__(length) We check we get a sensible error message while asking if an element is - in our new class: + in our new class:: sage: C = MyCode(3) sage: vector((1, 0, 0, 0, 0, 1, 1)) in C @@ -461,7 +461,7 @@ def _repr_(self): ....: super().__init__(10) We check we get a sensible error message while asking for a string - representation of an instance of our new class: + representation of an instance of our new class:: sage: C = MyCode() sage: C #random @@ -489,7 +489,7 @@ def _latex_(self): ....: super().__init__(10) We check we get a sensible error message while asking for a string - representation of an instance of our new class: + representation of an instance of our new class:: sage: C = MyCode() sage: latex(C) diff --git a/src/sage/coding/guruswami_sudan/interpolation.py b/src/sage/coding/guruswami_sudan/interpolation.py index e1f9755faa3..77eba69c5c3 100644 --- a/src/sage/coding/guruswami_sudan/interpolation.py +++ b/src/sage/coding/guruswami_sudan/interpolation.py @@ -184,7 +184,7 @@ def _interpolation_matrix_problem(points, tau, parameters, wy): EXAMPLES: The following parameters arise from Guruswami-Sudan decoding of an [6,2,5] - GRS code over F(11) with multiplicity 2 and list size 4. + GRS code over F(11) with multiplicity 2 and list size 4.:: sage: from sage.coding.guruswami_sudan.interpolation import _interpolation_matrix_problem sage: F = GF(11) diff --git a/src/sage/coding/linear_code_no_metric.py b/src/sage/coding/linear_code_no_metric.py index 9610c4e31ce..932ad7ad937 100644 --- a/src/sage/coding/linear_code_no_metric.py +++ b/src/sage/coding/linear_code_no_metric.py @@ -538,7 +538,7 @@ def systematic_generator_matrix(self, systematic_positions=None): [1 2 0 1] [0 0 1 2] - Specific systematic positions can also be requested: + Specific systematic positions can also be requested:: sage: C.systematic_generator_matrix(systematic_positions=[3,2]) [1 2 0 1] diff --git a/src/sage/combinat/colored_permutations.py b/src/sage/combinat/colored_permutations.py index 4ace496fbdb..b5907c5859d 100644 --- a/src/sage/combinat/colored_permutations.py +++ b/src/sage/combinat/colored_permutations.py @@ -87,6 +87,8 @@ def __len__(self): """ Return the length of the one line form of ``self``. + EXAMPLES:: + sage: C = ColoredPermutations(2, 3) sage: s1,s2,t = C.gens() sage: len(s1) diff --git a/src/sage/combinat/ordered_tree.py b/src/sage/combinat/ordered_tree.py index fce1668bd22..702b83cb051 100644 --- a/src/sage/combinat/ordered_tree.py +++ b/src/sage/combinat/ordered_tree.py @@ -224,7 +224,7 @@ def _auto_parent(cls): .. NOTE:: - It is possible to bypass the automatic parent mechanism using: + It is possible to bypass the automatic parent mechanism using:: sage: t1 = OrderedTree.__new__(OrderedTree, Parent(), []) sage: t1.__init__(Parent(), []) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index e64650bde6e..e35ee0c893e 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -4271,7 +4271,7 @@ def permutohedron_join(self, other, side="right") -> Permutation: sage: p.permutohedron_join(p) [1] - The left permutohedron: + The left permutohedron:: sage: p = Permutation([3,1,2]) sage: q = Permutation([1,3,2]) @@ -4387,7 +4387,7 @@ def permutohedron_meet(self, other, side="right") -> Permutation: sage: p.permutohedron_meet(p) [1] - The left permutohedron: + The left permutohedron:: sage: p = Permutation([3,1,2]) sage: q = Permutation([1,3,2]) diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index d857092f9c7..778253d0807 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -1994,7 +1994,7 @@ def orthocomplementations_iterator(self): [] Unique orthocomplementations; second is not uniquely complemented, - but has only one orthocomplementation. + but has only one orthocomplementation:: sage: H = posets.BooleanLattice(4)._hasse_diagram # Uniquely complemented sage: len(list(H.orthocomplementations_iterator())) diff --git a/src/sage/combinat/root_system/type_affine.py b/src/sage/combinat/root_system/type_affine.py index 12bf38eb52a..5ce62dfb7be 100644 --- a/src/sage/combinat/root_system/type_affine.py +++ b/src/sage/combinat/root_system/type_affine.py @@ -51,7 +51,7 @@ class AmbientSpace(CombinatorialFreeModule): \delta,\delta\rangle=0` and similarly for the null coroot. In the current implementation, `\Lambda_0` and the null coroot - are identified: + are identified:: sage: L = RootSystem(["A",3,1]).ambient_space() sage: Lambda = L.fundamental_weights() diff --git a/src/sage/combinat/sf/dual.py b/src/sage/combinat/sf/dual.py index 77e8b439d8c..d4d19461701 100644 --- a/src/sage/combinat/sf/dual.py +++ b/src/sage/combinat/sf/dual.py @@ -214,7 +214,7 @@ def _self_to_dual(self, x): sage: h._self_to_dual(h([2,1]) + 3*h[1,1,1]) 21*m[1, 1, 1] + 11*m[2, 1] + 4*m[3] - This is for internal use only. Please use instead: + This is for internal use only. Please use instead:: sage: m(h([2,1]) + 3*h[1,1,1]) 21*m[1, 1, 1] + 11*m[2, 1] + 4*m[3] diff --git a/src/sage/combinat/sf/sfa.py b/src/sage/combinat/sf/sfa.py index b435764a746..19b67895169 100644 --- a/src/sage/combinat/sf/sfa.py +++ b/src/sage/combinat/sf/sfa.py @@ -501,7 +501,7 @@ def _repr_(self): sage: Sym.macdonald(q=1,t=3).P() Sym in the Macdonald P with q=1 and t=3 basis - Hall-Littlewood polynomials: + Hall-Littlewood polynomials:: sage: Sym.hall_littlewood().P() Sym in the Hall-Littlewood P basis diff --git a/src/sage/combinat/subset.py b/src/sage/combinat/subset.py index 5cae6958510..b4801b69e59 100644 --- a/src/sage/combinat/subset.py +++ b/src/sage/combinat/subset.py @@ -342,9 +342,9 @@ def cardinality(self): sage: Subsets(3).cardinality() 8 - TESTS:: + TESTS: - ``__len__`` should return a Python int. + ``__len__`` should return a Python int.:: sage: S = Subsets(Set([1,2,3])) sage: len(S) diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index 1105a3ea177..4a6bf8270f1 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -233,7 +233,7 @@ def __init__(self, parent, t, check=True): A tableau is shallowly immutable. See :trac:`15862`. The entries themselves may be mutable objects, though in that case the - resulting Tableau should be unhashable. + resulting Tableau should be unhashable.:: sage: T = Tableau([[1,2],[2]]) sage: t0 = T[0] diff --git a/src/sage/combinat/words/paths.py b/src/sage/combinat/words/paths.py index 3d0ee41a4c4..b579aeae1ae 100644 --- a/src/sage/combinat/words/paths.py +++ b/src/sage/combinat/words/paths.py @@ -384,7 +384,7 @@ def __init__(self, alphabet, steps): True If size of alphabet is twice the number of steps, then opposite - vectors are used for the second part of the alphabet. + vectors are used for the second part of the alphabet:: sage: WordPaths('abcd',[(2,1),(2,4)]) Word Paths over 4 steps diff --git a/src/sage/functions/special.py b/src/sage/functions/special.py index 56f96f2ef53..faa6a73cc7e 100644 --- a/src/sage/functions/special.py +++ b/src/sage/functions/special.py @@ -538,7 +538,7 @@ def _eval_(self, z, m): sage: elliptic_e(z, 1) elliptic_e(z, 1) - Here arccoth doesn't have 1 in its domain, so we just hold the expression: + Here arccoth doesn't have 1 in its domain, so we just hold the expression:: sage: elliptic_e(arccoth(1), x^2*e) elliptic_e(+Infinity, x^2*e) diff --git a/src/sage/game_theory/normal_form_game.py b/src/sage/game_theory/normal_form_game.py index 40b3365d5e3..f8cf43485b5 100644 --- a/src/sage/game_theory/normal_form_game.py +++ b/src/sage/game_theory/normal_form_game.py @@ -2743,8 +2743,8 @@ def _is_degenerate_pure(self, certificate=False): sage: g._is_degenerate_pure() True - Whilst this game is not degenerate in pure strategies, it is - actually degenerate, but only in mixed strategies. + Whilst this game is not degenerate in pure strategies, it is + actually degenerate, but only in mixed strategies:: sage: A = matrix([[3, 0], [0, 3], [1.5, 1.5]]) sage: B = matrix([[4, 3], [2, 6], [3, 1]]) diff --git a/src/sage/geometry/cone.py b/src/sage/geometry/cone.py index 3d9cb0766fc..6cd764bacbc 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -6369,7 +6369,7 @@ def random_cone(lattice=None, min_ambient_dim=0, max_ambient_dim=None, TESTS: It's hard to test the output of a random process, but we can at - least make sure that we get a cone back. + least make sure that we get a cone back:: sage: from sage.geometry.cone import is_Cone sage: K = random_cone(max_ambient_dim=6, max_rays=10) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 26d814fb437..cd8041262a4 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -1390,7 +1390,7 @@ def _compute_cone_lattice(self): We use different algorithms depending on available information. One of the common cases is a fan which is KNOWN to be complete, i.e. we do - not even need to check if it is complete. + not even need to check if it is complete:: sage: fan = toric_varieties.P1xP1().fan() # optional - palp sage: fan.cone_lattice() # indirect doctest # optional - palp diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index 0b30f206cbe..0c762ad771c 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -3783,7 +3783,7 @@ def points(self, *args, **kwds): M( 0, 0, 0) in 3-d lattice M - Only two of the above points: + Only two of the above points:: sage: p.points(1, 3) # optional - palp M(0, 1, 0), diff --git a/src/sage/geometry/newton_polygon.py b/src/sage/geometry/newton_polygon.py index 4f253741ab7..0ec4bcb560e 100644 --- a/src/sage/geometry/newton_polygon.py +++ b/src/sage/geometry/newton_polygon.py @@ -630,13 +630,15 @@ def __init__(self): """ Parent class for all Newton polygons. + EXAMPLES:: + sage: from sage.geometry.newton_polygon import ParentNewtonPolygon sage: ParentNewtonPolygon() Parent for Newton polygons TESTS: - This class is a singleton. + This class is a singleton.:: sage: ParentNewtonPolygon() is ParentNewtonPolygon() True diff --git a/src/sage/geometry/polyhedron/base3.py b/src/sage/geometry/polyhedron/base3.py index 32a335ce586..5c160c907f6 100644 --- a/src/sage/geometry/polyhedron/base3.py +++ b/src/sage/geometry/polyhedron/base3.py @@ -604,7 +604,7 @@ def face_generator(self, face_dimension=None, algorithm=None, **kwds): A 1-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 2 vertices, A 1-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 2 vertices] - Check that we catch incorrect algorithms: + Check that we catch incorrect algorithms:: sage: list(P.face_generator(2, algorithm='integrate'))[:4] Traceback (most recent call last): diff --git a/src/sage/geometry/polyhedron/base_QQ.py b/src/sage/geometry/polyhedron/base_QQ.py index 0efcb15f1a2..c94b087a11d 100644 --- a/src/sage/geometry/polyhedron/base_QQ.py +++ b/src/sage/geometry/polyhedron/base_QQ.py @@ -119,7 +119,7 @@ def integral_points_count(self, verbose=False, use_Hrepresentation=False, 27 We enlarge the polyhedron to force the use of the generating function methods - implemented in LattE integrale, rather than explicit enumeration. + implemented in LattE integrale, rather than explicit enumeration:: sage: (1000000000*P).integral_points_count(verbose=True) # optional - latte_int This is LattE integrale... diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index fa48183ecc4..03ab9b57b53 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -5605,7 +5605,7 @@ def layout_planar(self, set_embedding=False, on_embedding=None, sage: g.layout(layout='planar', external_face=(3,1)) {0: [2, 1], 1: [0, 2], 2: [1, 1], 3: [1, 0]} - Choose the embedding: + Choose the embedding:: sage: H = graphs.LadderGraph(4) sage: em = {0:[1,4], 4:[0,5], 1:[5,2,0], 5:[4,6,1], 2:[1,3,6], 6:[7,5,2], 3:[7,2], 7:[3,6]} @@ -6356,7 +6356,7 @@ def num_faces(self, embedding=None): ... ValueError: no embedding is provided and the graph is not planar - Ticket :trac:`22003` is fixed: + Ticket :trac:`22003` is fixed:: sage: Graph(1).num_faces() 1 @@ -20622,7 +20622,7 @@ def plot(self, **options): 9: (0.47..., 0.15...)} sage: P = G.plot(save_pos=True, layout='spring') - The following illustrates the format of a position dictionary. + The following illustrates the format of a position dictionary:: sage: G.get_pos() # currently random across platforms, see #9593 {0: [1.17..., -0.855...], diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index ebdf7c4c6eb..bca0c00d843 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -654,7 +654,7 @@ def gap(self): TESTS: - see that this method does not harm pickling: + see that this method does not harm pickling:: sage: A4 = PermutationGroup([[(1,2,3)],[(2,3,4)]]) sage: A4.gap() @@ -662,7 +662,7 @@ def gap(self): sage: TestSuite(A4).run() the following test shows, that support for the ``self._libgap`` - attribute is needed in the constructor of the class: + attribute is needed in the constructor of the class:: sage: PG = PGU(6,2) sage: g, h = PG.gens() diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index a3be9f974fe..879c36a3764 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -599,7 +599,7 @@ def algebra(self, base_ring, category=None): Category of finite dimensional unital cellular semigroup algebras over Rational Field - In the following case, a usual group algebra is returned: + In the following case, a usual group algebra is returned:: sage: S = SymmetricGroup([2,3,5]) sage: S.algebra(QQ) diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py index 8903a454df5..58802e8aeb0 100644 --- a/src/sage/interfaces/expect.py +++ b/src/sage/interfaces/expect.py @@ -1181,15 +1181,13 @@ def _expect_expr(self, expr=None, timeout=None): sage: singular._sendstr('def abc = 10 + 15;\n') Then we tell singular to print 10, which is an arbitrary number - different from the expected result 35. + different from the expected result 35:: sage: singular._sendstr('10;\n') Here an exception is raised because 25 hasn't appeared yet in the output stream. The key thing is that this doesn't lock, but instead - quickly raises an exception. - - :: + quickly raises an exception:: sage: t = walltime() sage: try: @@ -1203,21 +1201,15 @@ def _expect_expr(self, expr=None, timeout=None): sage: w = walltime(t); 0.3 < w < 10 True - We tell Singular to print abc, which equals 25. - - :: + We tell Singular to print abc, which equals 25:: sage: singular._sendstr('abc;\n') - Now 25 is in the output stream, so we can wait for it. - - :: + Now 25 is in the output stream, so we can wait for it:: sage: singular._expect_expr('25') - This gives us everything before the 25, including the 10 we printed earlier. - - :: + This gives us everything before the 25, including the 10 we printed earlier:: sage: singular._expect.before.decode('ascii') '...10\r\n> ' diff --git a/src/sage/interfaces/frobby.py b/src/sage/interfaces/frobby.py index 9f514422528..21668a48d31 100644 --- a/src/sage/interfaces/frobby.py +++ b/src/sage/interfaces/frobby.py @@ -123,7 +123,7 @@ def alexander_dual(self, monomial_ideal): True We see how it is much faster to compute this with frobby than the built-in - procedure for simplicial complexes. + procedure for simplicial complexes:: sage: t=simplicial_complexes.PoincareHomologyThreeSphere() # optional - frobby sage: R=PolynomialRing(QQ,16,'x') # optional - frobby diff --git a/src/sage/interfaces/interface.py b/src/sage/interfaces/interface.py index 5ed25e6138d..876f8e8824c 100644 --- a/src/sage/interfaces/interface.py +++ b/src/sage/interfaces/interface.py @@ -880,7 +880,7 @@ def _reduce(self): `"'abc'"` instead. That is dependant on the Elements `is_string` function to be implemented correctly. This has gone wrong in the past and remained uncaught by the doctests because the original identifier was reused. This test makes sure - that does not happen again: + that does not happen again:: sage: a = r("'abc'") # optional - rpy2 sage: b = dumps(a) # optional - rpy2 diff --git a/src/sage/interfaces/quit.py b/src/sage/interfaces/quit.py index 3bbd3ae24d3..e068c324b13 100644 --- a/src/sage/interfaces/quit.py +++ b/src/sage/interfaces/quit.py @@ -149,7 +149,7 @@ def invalidate_all(): sage: b (invalid PARI/GP interpreter object -- The pari session in which this object was defined is no longer running.) - However the maxima and gp sessions should still work out, though with their state reset: + However the maxima and gp sessions should still work out, though with their state reset:: sage: a = maxima(2); b = gp(3) sage: a, b diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py index 0eabaccecf7..0e0076a0cd6 100644 --- a/src/sage/interfaces/r.py +++ b/src/sage/interfaces/r.py @@ -512,7 +512,7 @@ def _lazy_init(self): sage: my_r._initialized # optional - rpy2 True - And on package import: + And on package import:: sage: my_r = R() # optional - rpy2 sage: my_r._initialized # optional - rpy2 @@ -521,7 +521,7 @@ def _lazy_init(self): sage: my_r._initialized # optional - rpy2 True - And when fetching help pages: + And when fetching help pages:: sage: my_r = R() # optional - rpy2 sage: my_r._initialized # optional - rpy2 diff --git a/src/sage/interfaces/tides.py b/src/sage/interfaces/tides.py index 906f95396e5..f09639dd849 100644 --- a/src/sage/interfaces/tides.py +++ b/src/sage/interfaces/tides.py @@ -356,6 +356,8 @@ def remove_constants(l1,l2): Given two lists, remove the entries in the first that are real constants, and also the corresponding elements in the second one. + EXAMPLES:: + sage: from sage.interfaces.tides import subexpressions_list, remove_constants sage: f(a)=[1+cos(7)*a] sage: l1, l2 = subexpressions_list(f) diff --git a/src/sage/manifolds/utilities.py b/src/sage/manifolds/utilities.py index de83d63326f..e082e2584af 100644 --- a/src/sage/manifolds/utilities.py +++ b/src/sage/manifolds/utilities.py @@ -1040,7 +1040,7 @@ def _latex_(self): sage: latex(ExpressionNice(fun)) \frac{\partial\,f_{x}}{\partial y} - If latex_name, it should be used in LaTeX output: + If latex_name, it should be used in LaTeX output:: sage: f = function('f_x', latex_name=r"{\cal F}")(x,y) sage: fun = f.diff(y) diff --git a/src/sage/matroids/constructor.py b/src/sage/matroids/constructor.py index 1fb2cd2b93e..dc5610f1536 100644 --- a/src/sage/matroids/constructor.py +++ b/src/sage/matroids/constructor.py @@ -360,7 +360,7 @@ def Matroid(groundset=None, data=None, **kwds): [0, 1, 2, 3] The GraphicMatroid object forces its graph to be connected. If a - disconnected graph is used as input, it will connect the components. + disconnected graph is used as input, it will connect the components:: sage: G1 = graphs.CycleGraph(3); G2 = graphs.DiamondGraph() sage: G = G1.disjoint_union(G2) diff --git a/src/sage/misc/bindable_class.py b/src/sage/misc/bindable_class.py index 92f57ef48cb..d995ad65fc8 100644 --- a/src/sage/misc/bindable_class.py +++ b/src/sage/misc/bindable_class.py @@ -160,7 +160,7 @@ class BoundClass(functools.partial): sage: c = x.Inner; c > - Introspection works, at least partially: + Introspection works, at least partially:: sage: sage_getdoc(c).strip() 'Some documentation for Outer.Inner' diff --git a/src/sage/misc/decorators.py b/src/sage/misc/decorators.py index dd9123f5004..d876634b027 100644 --- a/src/sage/misc/decorators.py +++ b/src/sage/misc/decorators.py @@ -389,7 +389,7 @@ def __call__(self, func): [('arrow_options', {'size': 5})] Demonstrate that the introspected argument specification of the - wrapped function is updated (see :trac:`9976`). + wrapped function is updated (see :trac:`9976`):: sage: from sage.misc.sageinspect import sage_getargspec sage: sage_getargspec(f) diff --git a/src/sage/misc/functional.py b/src/sage/misc/functional.py index 7d2ee692e13..356077005a7 100644 --- a/src/sage/misc/functional.py +++ b/src/sage/misc/functional.py @@ -1935,7 +1935,7 @@ def sqrt(x, *args, **kwds): sage: sqrt(2).n(prec=100) 1.4142135623730950488016887242 - Or one can input a numerical type. + Or one can input a numerical type:: sage: sqrt(2.) 1.41421356237310 diff --git a/src/sage/misc/misc.py b/src/sage/misc/misc.py index bae968c42f3..2089ff8ece2 100644 --- a/src/sage/misc/misc.py +++ b/src/sage/misc/misc.py @@ -1413,7 +1413,7 @@ def inject_variable(name, value, warn=True): sage: a 272 - That's because warn seem to not reissue twice the same warning: + That's because warn seem to not reissue twice the same warning:: sage: from warnings import warn sage: warn("blah") diff --git a/src/sage/modular/modform_hecketriangle/abstract_space.py b/src/sage/modular/modform_hecketriangle/abstract_space.py index efd12339000..c14a59f54d6 100644 --- a/src/sage/modular/modform_hecketriangle/abstract_space.py +++ b/src/sage/modular/modform_hecketriangle/abstract_space.py @@ -1963,7 +1963,7 @@ def construct_quasi_form(self, laurent_series, order_1=ZZ(0), check=True, ration sage: el == constructed_el True - If a q_basis is available the construction uses a different algorithm which we also check:: + If a q_basis is available the construction uses a different algorithm which we also check:: sage: basis = QF.q_basis(min_exp=-1) sage: QF(qexp) == constructed_el diff --git a/src/sage/modular/modform_hecketriangle/analytic_type.py b/src/sage/modular/modform_hecketriangle/analytic_type.py index 3b3b691e181..68adb2c513f 100644 --- a/src/sage/modular/modform_hecketriangle/analytic_type.py +++ b/src/sage/modular/modform_hecketriangle/analytic_type.py @@ -170,6 +170,8 @@ def analytic_name(self): r""" Return a string representation of the analytic type. + EXAMPLES:: + sage: from sage.modular.modform_hecketriangle.analytic_type import AnalyticType sage: AT = AnalyticType() sage: AT(["quasi", "weak"]).analytic_name() @@ -328,9 +330,9 @@ class AnalyticType(FiniteLatticePoset): sage: el.analytic_type() quasi modular - Similarly the type of the ring element ``el2 = E4/Delta - E6/Delta`` is - ``weakly holomorphic`` despite the fact that the sum (``el2``) describes - a function which is holomorphic at infinity. + Similarly the type of the ring element ``el2 = E4/Delta - E6/Delta`` is + ``weakly holomorphic`` despite the fact that the sum (``el2``) describes + a function which is holomorphic at infinity:: sage: from sage.modular.modform_hecketriangle.graded_ring import WeakModularFormsRing sage: x,y,z,d = var("x,y,z,d") diff --git a/src/sage/modules/fg_pid/fgp_element.py b/src/sage/modules/fg_pid/fgp_element.py index dadf8745f88..42ac52dfb38 100644 --- a/src/sage/modules/fg_pid/fgp_element.py +++ b/src/sage/modules/fg_pid/fgp_element.py @@ -156,7 +156,7 @@ def _add_(self, other): sage: 0 + x (1, 0) - We test canonical coercion from V and W. + We test canonical coercion from V and W.:: sage: Q.0 + V.0 (1, 8) diff --git a/src/sage/modules/torsion_quadratic_module.py b/src/sage/modules/torsion_quadratic_module.py index 3414c487a80..8268f7f6be2 100644 --- a/src/sage/modules/torsion_quadratic_module.py +++ b/src/sage/modules/torsion_quadratic_module.py @@ -850,7 +850,7 @@ def orthogonal_group(self, gens=None, check=False): [1 0 0] [0 0 1] - We compute the kernel of the action of the orthogonal group of `L` on the discriminant group. + We compute the kernel of the action of the orthogonal group of `L` on the discriminant group.:: sage: L = IntegralLattice('A4') sage: O = L.orthogonal_group() diff --git a/src/sage/numerical/linear_tensor.py b/src/sage/numerical/linear_tensor.py index 26d94b12cc6..34dc8e934ff 100644 --- a/src/sage/numerical/linear_tensor.py +++ b/src/sage/numerical/linear_tensor.py @@ -389,7 +389,7 @@ def _element_constructor_(self, x): sage: type(_) - Construct from scalar: + Construct from scalar:: sage: LT(123) # indirect doctest (123.0, 123.0) diff --git a/src/sage/plot/plot3d/plot3d.py b/src/sage/plot/plot3d/plot3d.py index 174765980f7..3771780f75d 100644 --- a/src/sage/plot/plot3d/plot3d.py +++ b/src/sage/plot/plot3d/plot3d.py @@ -248,7 +248,7 @@ def __init__(self, dep_var, indep_vars): Because the base :class:`_Coordinates` class automatically checks the initializing variables with the transform method, :class:`_Coordinates` - cannot be instantiated by itself. We test a subclass. + cannot be instantiated by itself. We test a subclass.:: sage: from sage.plot.plot3d.plot3d import _ArbitraryCoordinates as arb sage: x,y,z=var('x,y,z') diff --git a/src/sage/quadratic_forms/genera/genus.py b/src/sage/quadratic_forms/genera/genus.py index 0fc43f33c62..5451df7ae29 100644 --- a/src/sage/quadratic_forms/genera/genus.py +++ b/src/sage/quadratic_forms/genera/genus.py @@ -3045,7 +3045,7 @@ def representative(self): Genus symbol at 3: 1^3 3^1 A representative of ``g`` is not known yet. - Let us trigger its computation: + Let us trigger its computation:: sage: g.representative() [ 0 0 0 2] diff --git a/src/sage/quivers/morphism.py b/src/sage/quivers/morphism.py index b8bb25657eb..febc2c3136c 100644 --- a/src/sage/quivers/morphism.py +++ b/src/sage/quivers/morphism.py @@ -1108,7 +1108,7 @@ def algebraic_dual(self): Representation with dimension vector (5, 2, 1, 1, 4) The algebraic dual of an indecomposable projective is the indecomposable - projective of the same vertex in the opposite quiver. + projective of the same vertex in the opposite quiver.:: sage: Q.reverse().P(QQ, 4) Representation with dimension vector (5, 2, 1, 1, 4) diff --git a/src/sage/repl/ipython_kernel/widgets_sagenb.py b/src/sage/repl/ipython_kernel/widgets_sagenb.py index 76f4f52ac4a..01a2bc42d06 100644 --- a/src/sage/repl/ipython_kernel/widgets_sagenb.py +++ b/src/sage/repl/ipython_kernel/widgets_sagenb.py @@ -91,7 +91,7 @@ def input_box(default=None, label=None, type=None, width=80, height=1): 9 With a different ``type``, the text is evaluated and ``type`` is - called on it: + called on it:: sage: w = input_box("4+5", type=float) sage: w @@ -470,7 +470,7 @@ def selector(values, label=None, default=None, nrows=None, ncols=None, width=Non sage: selector([(1,"one"), (2,"two"), (3,"three")], buttons=True) ToggleButtons(options=(('one', 1), ('two', 2), ('three', 3)), value=1) - The values can be any kind of object: + The values can be any kind of object:: sage: selector([sin(x^2), GF(29), EllipticCurve('37a1')]) Dropdown(options=(sin(x^2), Finite Field of size 29, Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field), value=sin(x^2)) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index e77b1a7cd2e..df73af12a9d 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -3975,7 +3975,7 @@ def derivative(self, *args): TESTS: - Check the derivative of the logarithm: + Check the derivative of the logarithm:: sage: L. = LazyLaurentSeriesRing(QQ) sage: -log(1-z).derivative() diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 8c02fa8e494..01fa87fa306 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -1708,7 +1708,7 @@ def _element_constructor_(self, x, check=True): a^2 An error is raised when a PARI element with an incorrect - modulus is given: + modulus is given:: sage: K(pari("Mod(-5/3*q^2 + 5/3*q - 1/6, q^3 - 999)")) Traceback (most recent call last): @@ -8693,8 +8693,8 @@ def _subfields_helper(self, degree=0, name=None, both_maps=True, optimize=False) (Number Field in a0 with defining polynomial x^2 - 23 with a0 = -4.795831523312720?, -4.795831523312719) - If we take a different embedding of the large field, we get a - different embedding of the degree 2 subfield:: + If we take a different embedding of the large field, we get a + different embedding of the degree 2 subfield:: sage: K. = NumberField(x^4 - 23, embedding=-50) sage: L2, _, _ = K.subfields(2)[0]; L2, CDF(L2.gen()) # indirect doctest diff --git a/src/sage/rings/number_field/number_field_ideal_rel.py b/src/sage/rings/number_field/number_field_ideal_rel.py index 192c8f15034..1292219f843 100644 --- a/src/sage/rings/number_field/number_field_ideal_rel.py +++ b/src/sage/rings/number_field/number_field_ideal_rel.py @@ -170,7 +170,7 @@ def absolute_ideal(self, names = 'a'): sage: J.absolute_ideal().norm() 4 - Now pass 'm' as the name for the generator of the absolute field: + Now pass 'm' as the name for the generator of the absolute field:: sage: J.absolute_ideal('m') Fractional ideal (m^2) diff --git a/src/sage/rings/number_field/structure.py b/src/sage/rings/number_field/structure.py index f933affe775..52e6ed6d503 100644 --- a/src/sage/rings/number_field/structure.py +++ b/src/sage/rings/number_field/structure.py @@ -151,7 +151,7 @@ class NameChange(NumberFieldStructure): sage: NameChange(K) - Check for memory leaks: + Check for memory leaks:: sage: u=id(NumberField(x^2-5,'a').absolute_field('b')) sage: import gc diff --git a/src/sage/rings/padics/CR_template.pxi b/src/sage/rings/padics/CR_template.pxi index 1b3134f3195..73f87ed27e9 100644 --- a/src/sage/rings/padics/CR_template.pxi +++ b/src/sage/rings/padics/CR_template.pxi @@ -905,13 +905,13 @@ cdef class CRElement(pAdicTemplateElement): sage: b = R(0); b.add_bigoh(3) O(7^3) - The precision never increases:: + The precision never increases:: sage: R(4).add_bigoh(2).add_bigoh(4) 4 + O(7^2) - Another example that illustrates that the precision does - not increase:: + Another example that illustrates that the precision does + not increase:: sage: k = Qp(3,5) sage: a = k(1234123412/3^70); a diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index 044dcae0bed..67da897436c 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -3019,7 +3019,7 @@ def ZpER(p, prec=None, halt=None, secure=False, *args, **kwds): 40 However, both the default precision and the halting precision can be - customized at the creation of the parent as follows: + customized at the creation of the parent as follows:: sage: S = ZpER(5, prec=10, halt=100) sage: S.default_prec() diff --git a/src/sage/rings/padics/lattice_precision.py b/src/sage/rings/padics/lattice_precision.py index 6b1386e4121..407179f9e70 100644 --- a/src/sage/rings/padics/lattice_precision.py +++ b/src/sage/rings/padics/lattice_precision.py @@ -974,7 +974,7 @@ def precision_lattice(self, elements=None): [ 0 2048] If the precision module does not project to a lattice, - an error is raised. + an error is raised.:: sage: R = ZpLF(2, label='precision_lattice') sage: prec = R.precision() @@ -2689,7 +2689,7 @@ def precision_lattice(self, elements=None): [ 0 2048] If the precision module does not project to a lattice, - an error is raised. + an error is raised.:: sage: prec.precision_lattice([x, y, u, v]) Traceback (most recent call last): diff --git a/src/sage/rings/padics/padic_base_leaves.py b/src/sage/rings/padics/padic_base_leaves.py index f27a5db5a6c..0f0042032c5 100644 --- a/src/sage/rings/padics/padic_base_leaves.py +++ b/src/sage/rings/padics/padic_base_leaves.py @@ -904,7 +904,7 @@ def _coerce_map_from_(self, R): True Note that coerce map does not exist between ``p``-adic rings with - lattice precision and other ``p``-adic rings. + lattice precision and other ``p``-adic rings.:: sage: S = Zp(2) sage: R.has_coerce_map_from(S) @@ -913,7 +913,7 @@ def _coerce_map_from_(self, R): False Similarly there is no coercion maps between ``p``-adic rings with - different labels. + different labels.:: sage: R2 = ZpLC(2, label='coerce') sage: R.has_coerce_map_from(R2) @@ -1033,7 +1033,7 @@ def _coerce_map_from_(self, R): True Note that coerce map does not exist between ``p``-adic fields with - lattice precision and other ``p``-adic rings. + lattice precision and other ``p``-adic rings.:: sage: L = Qp(2) sage: K.has_coerce_map_from(L) diff --git a/src/sage/rings/padics/padic_extension_generic.py b/src/sage/rings/padics/padic_extension_generic.py index ff2d0a5ec6c..1d15f83caad 100644 --- a/src/sage/rings/padics/padic_extension_generic.py +++ b/src/sage/rings/padics/padic_extension_generic.py @@ -510,7 +510,7 @@ def construction(self, forbid_frac_field=False): sage: c(R0) == R True - For a field, by default we return a fraction field functor. + For a field, by default we return a fraction field functor.:: sage: K. = Qq(25, 8) sage: c, R = K.construction(); R diff --git a/src/sage/rings/padics/padic_lattice_element.py b/src/sage/rings/padics/padic_lattice_element.py index ace965211bf..5a195ca9b0f 100644 --- a/src/sage/rings/padics/padic_lattice_element.py +++ b/src/sage/rings/padics/padic_lattice_element.py @@ -1010,7 +1010,7 @@ def unit_part(self): sage: b.unit_part() 1 + 16*17 + O(17^3) - If the element is indistinguishable from zero, an error is raised. + If the element is indistinguishable from zero, an error is raised.:: sage: c = R(0, 5); c O(17^5) diff --git a/src/sage/rings/padics/padic_valuation.py b/src/sage/rings/padics/padic_valuation.py index 883e36f5c96..5af3e28d3e4 100644 --- a/src/sage/rings/padics/padic_valuation.py +++ b/src/sage/rings/padics/padic_valuation.py @@ -439,7 +439,7 @@ class pAdicValuation_base(DiscreteValuation): sage: QQ.valuation(5) 5-adic valuation - For `p`-adic rings, ``p`` has to match the `p` of the ring. + For `p`-adic rings, ``p`` has to match the `p` of the ring.:: sage: v = valuations.pAdicValuation(Zp(3), 2); v Traceback (most recent call last): diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index 1bc126c1e00..200414984a8 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_ring.py +++ b/src/sage/rings/polynomial/infinite_polynomial_ring.py @@ -646,7 +646,7 @@ class InfinitePolynomialRing_sparse(CommutativeRing): sage: Z = InfinitePolynomialRing_sparse(QQ, ['x','y'], 'lex') Nevertheless, since infinite polynomial rings are supposed to be unique - parent structures, they do not evaluate equal. + parent structures, they do not evaluate equal.:: sage: Z == X False diff --git a/src/sage/rings/polynomial/laurent_polynomial_ideal.py b/src/sage/rings/polynomial/laurent_polynomial_ideal.py index 76d1b495274..0168a56e267 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ideal.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ideal.py @@ -103,6 +103,8 @@ def set_hint(self, hint): to speed up computation of the associated ideal in some cases; normally the end user will have no need to work with it directly. + EXAMPLES:: + sage: P. = LaurentPolynomialRing(QQ, 3) sage: I = P.ideal([x^2*y + 3*x*y^2]) sage: I.hint() diff --git a/src/sage/rings/polynomial/laurent_polynomial_ring.py b/src/sage/rings/polynomial/laurent_polynomial_ring.py index c10faf46568..0ec8ecb15f7 100644 --- a/src/sage/rings/polynomial/laurent_polynomial_ring.py +++ b/src/sage/rings/polynomial/laurent_polynomial_ring.py @@ -731,7 +731,7 @@ def ideal(self, *args, **kwds): TESTS: - check that :trac:`26421` is fixed: + check that :trac:`26421` is fixed:: sage: R. = LaurentPolynomialRing(ZZ) sage: P. = PolynomialRing(R) diff --git a/src/sage/rings/polynomial/ore_function_element.py b/src/sage/rings/polynomial/ore_function_element.py index d21ed8d1ed4..0d2800665e0 100644 --- a/src/sage/rings/polynomial/ore_function_element.py +++ b/src/sage/rings/polynomial/ore_function_element.py @@ -845,7 +845,7 @@ def reduced_trace(self, var=None): 3/(z^2 + 2) The reduced trace lies in the center of `S`, which is the fraction field - of a univariate polynomial ring in the variable `z = x^3` over `GF(5)`. + of a univariate polynomial ring in the variable `z = x^3` over `GF(5)`.:: sage: tr.parent() Fraction Field of Univariate Polynomial Ring in z over Finite Field of size 5 @@ -906,7 +906,7 @@ def reduced_norm(self, var=None): (z + 2)/(z^2 + 4) The reduced norm lies in the center of `S`, which is the fraction field - of a univariate polynomial ring in the variable `z = x^3` over `GF(5)`. + of a univariate polynomial ring in the variable `z = x^3` over `GF(5)`.:: sage: N.parent() Fraction Field of Univariate Polynomial Ring in z over Finite Field of size 5 diff --git a/src/sage/rings/polynomial/ore_function_field.py b/src/sage/rings/polynomial/ore_function_field.py index f223f9799dd..34e6e2a6756 100644 --- a/src/sage/rings/polynomial/ore_function_field.py +++ b/src/sage/rings/polynomial/ore_function_field.py @@ -35,7 +35,7 @@ sage: g (d - 1/t)^(-1) * t -The left numerator and right denominator are accessible as follows: +The left numerator and right denominator are accessible as follows:: sage: g.left_numerator() t diff --git a/src/sage/rings/polynomial/pbori/ll.py b/src/sage/rings/polynomial/pbori/ll.py index b12985797a7..a48e7e2a84a 100644 --- a/src/sage/rings/polynomial/pbori/ll.py +++ b/src/sage/rings/polynomial/pbori/ll.py @@ -284,6 +284,8 @@ def invert(self, poly): r""" Inverted map to initial ring. + EXAMPLES:: + sage: from sage.rings.polynomial.pbori.pbori import * sage: from sage.rings.polynomial.pbori.blocks import declare_ring, Block sage: to_ring = declare_ring([Block("x", 10)], globals()) diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index bd34d113f5f..4759184cd83 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -1862,7 +1862,7 @@ def weil_polynomials(self, d, q, sign=1, lead=1): sage: all(p.is_weil_polynomial() for p in L) True - Setting multiple leading coefficients: + Setting multiple leading coefficients:: sage: R. = QQ[] sage: l = R.weil_polynomials(4,2,lead=((1,0),(2,4),(1,2))) diff --git a/src/sage/rings/polynomial/term_order.py b/src/sage/rings/polynomial/term_order.py index 6763f2016c7..613110a34eb 100644 --- a/src/sage/rings/polynomial/term_order.py +++ b/src/sage/rings/polynomial/term_order.py @@ -1790,7 +1790,7 @@ def singular_moreblocks(self): TESTS: The 'degneglex' ordering is somehow special: SINGULAR handles it - using an extra weight vector block. + using an extra weight vector block.:: sage: T = TermOrder("degneglex", 2) sage: P = PolynomialRing(QQ,2, names='x', order=T) diff --git a/src/sage/schemes/affine/affine_morphism.py b/src/sage/schemes/affine/affine_morphism.py index 32c2e47e494..0c489c65794 100644 --- a/src/sage/schemes/affine/affine_morphism.py +++ b/src/sage/schemes/affine/affine_morphism.py @@ -158,7 +158,7 @@ def __init__(self, parent, polys, check=True): Defn: Defined on coordinates by sending (x, y) to ((5*x^3 + 3*x*y^2 - y^3)/(x^3 - 1), (x^2*y + 3)/(x^3 - 1)) - If you pass in quotient ring elements, they are reduced:: + If you pass in quotient ring elements, they are reduced:: sage: A. = AffineSpace(QQ, 3) sage: X = A.subscheme([x-y]) @@ -171,7 +171,7 @@ def __init__(self, parent, polys, check=True): Defn: Defined on coordinates by sending (x, y, z) to (y, y, 2*y) - You must use the ambient space variables to create rational functions:: + You must use the ambient space variables to create rational functions:: sage: A. = AffineSpace(QQ, 3) sage: X = A.subscheme([x^2-y^2]) diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index 419c08cf9ca..65738e4c727 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -1062,7 +1062,7 @@ def is_isogenous(self, other, field=None, proof=True): ... ValueError: Curves have different base fields: use the field parameter. - When the field is given: + When the field is given:: sage: E1 = EllipticCurve(GF(13^2,'a'),[2,7]); E1 Elliptic Curve defined by y^2 = x^3 + 2*x + 7 over Finite Field in a of size 13^2 diff --git a/src/sage/schemes/elliptic_curves/hom_velusqrt.py b/src/sage/schemes/elliptic_curves/hom_velusqrt.py index 22ae56018a3..6ed7a5c3d15 100644 --- a/src/sage/schemes/elliptic_curves/hom_velusqrt.py +++ b/src/sage/schemes/elliptic_curves/hom_velusqrt.py @@ -515,7 +515,7 @@ def _point_outside_subgroup(P): ... ValueError: ECDLog problem has no solution (...) - An example where the group is non-cyclic: + An example where the group is non-cyclic:: sage: E. = EllipticCurve(GF(71^2), [0,1]) sage: E.abelian_group() diff --git a/src/sage/sets/finite_set_maps.py b/src/sage/sets/finite_set_maps.py index ce5029d8032..c4c9ab4540a 100644 --- a/src/sage/sets/finite_set_maps.py +++ b/src/sage/sets/finite_set_maps.py @@ -305,7 +305,7 @@ def an_element(self): An exception :class:`~sage.categories.sets_cat.EmptySetError` is raised if this set is empty, that is if the codomain is - empty and the domain is not. + empty and the domain is not.:: sage: M = FiniteSetMaps(4, 0) sage: M.cardinality() diff --git a/src/sage/sets/set.py b/src/sage/sets/set.py index b76e6b064ef..1796483222d 100644 --- a/src/sage/sets/set.py +++ b/src/sage/sets/set.py @@ -616,7 +616,7 @@ def __contains__(self, x): False Finite fields better illustrate the difference between - ``__contains__`` for objects and their underlying sets. + ``__contains__`` for objects and their underlying sets.:: sage: X = Set(GF(7)) sage: X diff --git a/src/sage/symbolic/operators.py b/src/sage/symbolic/operators.py index a48b0e8e393..2f700ea298f 100644 --- a/src/sage/symbolic/operators.py +++ b/src/sage/symbolic/operators.py @@ -169,6 +169,8 @@ def change_function(self, new): Return a new function derivative operator with the same parameter set but for a new function. + EXAMPLES:: + sage: from sage.symbolic.operators import FDerivativeOperator sage: f = function('foo') sage: b = function('bar') From a2f990133f9f22bff2bf94cfc226eab1f8d4d382 Mon Sep 17 00:00:00 2001 From: Sanjay Rijal <37138338+zovelsanj@users.noreply.github.com> Date: Sat, 11 Feb 2023 12:55:48 +0545 Subject: [PATCH 284/392] fixed spelling errors --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 38cf9271134..a525a5e8334 100644 --- a/README.md +++ b/README.md @@ -398,14 +398,14 @@ in the Installation Guide. Alternative Installation using PiPy --------------- -You can find `sage` and `sagemath` pip packages but it is worth nothing that they are not at all realted to this `sage`. Indeed `pip install sage` installs `sage 0.0.0` which is literally an empty package. For installation of this `sage` you need to install `sagemath-standard`. First activate your python virtual environment and follow these steps: +You can find `sage` and `sagemath` pip packages but it is worth noting that they are not at all related to this `sage`. Indeed `pip install sage` installs `sage 0.0.0` which is literally an empty package. For installation of this `sage` you need to install `sagemath-standard`. First, activate your python virtual environment and follow these steps: $ python3 -m pip install sage_conf $ ls $(sage-config SAGE_SPKG_WHEELS) $ python3 -m pip install $(sage-config SAGE_SPKG_WHEELS)/*.whl $ python3 -m pip install sagemath-standard -You need to install the `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels you can install the sage library, `sagemath-standard`. +You need to install the `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels, you can install the sage library, `sagemath-standard`. Troubleshooting --------------- From 279b54b81592d0d028a56b5fe6c95d84eb49c128 Mon Sep 17 00:00:00 2001 From: Sanjay Rijal <37138338+zovelsanj@users.noreply.github.com> Date: Sat, 11 Feb 2023 13:09:16 +0545 Subject: [PATCH 285/392] pip packages instructions added in note Fixed the pip installation instructions according to this [comment](https://github.com/sagemath/sage/pull/35070#discussion_r1103552497). --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a525a5e8334..8ce83ce1f36 100644 --- a/README.md +++ b/README.md @@ -398,7 +398,7 @@ in the Installation Guide. Alternative Installation using PiPy --------------- -You can find `sage` and `sagemath` pip packages but it is worth noting that they are not at all related to this `sage`. Indeed `pip install sage` installs `sage 0.0.0` which is literally an empty package. For installation of this `sage` you need to install `sagemath-standard`. First, activate your python virtual environment and follow these steps: +For installation of `sage` in python using `pip` you need to install `sagemath-standard`. First, activate your python virtual environment and follow these steps: $ python3 -m pip install sage_conf $ ls $(sage-config SAGE_SPKG_WHEELS) @@ -407,6 +407,8 @@ You can find `sage` and `sagemath` pip packages but it is worth noting that they You need to install the `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels, you can install the sage library, `sagemath-standard`. +**NOTE:** You can find `sage` and `sagemath` pip packages but it is worth noting that they are not at all related to this `sage`. Indeed `pip install sage` installs `sage 0.0.0` which is literally an empty package. + Troubleshooting --------------- From 254a2f3c5feccb9322ef0a21f0a7b99074c95899 Mon Sep 17 00:00:00 2001 From: Sanjay Rijal <37138338+zovelsanj@users.noreply.github.com> Date: Sat, 11 Feb 2023 13:18:52 +0545 Subject: [PATCH 286/392] fixed typos changed `PiPy` to 'PyPi' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ce83ce1f36..dbb4a2c63b6 100644 --- a/README.md +++ b/README.md @@ -395,7 +395,7 @@ in the Installation Guide. "Launching SageMath"](https://doc.sagemath.org/html/en/installation/launching.html) in the installation manual. -Alternative Installation using PiPy +Alternative Installation using PyPi --------------- For installation of `sage` in python using `pip` you need to install `sagemath-standard`. First, activate your python virtual environment and follow these steps: From d92cce3880c92767c9fd1078277ffcd53a298f3d Mon Sep 17 00:00:00 2001 From: Sanjay Rijal <37138338+zovelsanj@users.noreply.github.com> Date: Sat, 11 Feb 2023 13:40:52 +0545 Subject: [PATCH 287/392] fixed a typo changed PyPi tp PyPI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dbb4a2c63b6..5117319bcf3 100644 --- a/README.md +++ b/README.md @@ -395,7 +395,7 @@ in the Installation Guide. "Launching SageMath"](https://doc.sagemath.org/html/en/installation/launching.html) in the installation manual. -Alternative Installation using PyPi +Alternative Installation using PyPI --------------- For installation of `sage` in python using `pip` you need to install `sagemath-standard`. First, activate your python virtual environment and follow these steps: From d127e67922836c35530caa7d5cde82d8775964b9 Mon Sep 17 00:00:00 2001 From: Jonathan Kliem Date: Fri, 9 Dec 2022 14:51:18 +0100 Subject: [PATCH 288/392] fix: Update email adresses --- src/sage/data_structures/sparse_bitset.pxd | 2 +- src/sage/geometry/polyhedron/base.py | 2 +- src/sage/geometry/polyhedron/base0.py | 2 +- src/sage/geometry/polyhedron/base1.py | 2 +- src/sage/geometry/polyhedron/base2.py | 2 +- src/sage/geometry/polyhedron/base3.py | 2 +- src/sage/geometry/polyhedron/base4.py | 2 +- src/sage/geometry/polyhedron/base5.py | 2 +- src/sage/geometry/polyhedron/base6.py | 2 +- src/sage/geometry/polyhedron/base7.py | 2 +- src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx | 2 +- .../polyhedron/combinatorial_polyhedron/combinatorial_face.pyx | 2 +- .../polyhedron/combinatorial_polyhedron/conversions.pyx | 2 +- .../polyhedron/combinatorial_polyhedron/face_data_structure.pxd | 2 +- .../polyhedron/combinatorial_polyhedron/face_iterator.pyx | 2 +- .../combinatorial_polyhedron/face_list_data_structure.pxd | 2 +- .../combinatorial_polyhedron/face_list_data_structure.pyx | 2 +- .../polyhedron/combinatorial_polyhedron/list_of_faces.pyx | 2 +- .../combinatorial_polyhedron/polyhedron_face_lattice.pyx | 2 +- src/sage/graphs/generic_graph.py | 2 +- src/sage/rings/number_field/order.py | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/sage/data_structures/sparse_bitset.pxd b/src/sage/data_structures/sparse_bitset.pxd index 742ac26c6a5..9b95c55675b 100644 --- a/src/sage/data_structures/sparse_bitset.pxd +++ b/src/sage/data_structures/sparse_bitset.pxd @@ -6,7 +6,7 @@ This is a regular bitset to which we will add additional structure. In particular some representation of which limbs even contain data. """ # **************************************************************************** -# Copyright (C) 2020 Jonathan Kliem +# Copyright (C) 2020 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 77a11c53d21..66160608fa1 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -21,7 +21,7 @@ # Copyright (C) 2019 Julian Ritter # Copyright (C) 2019-2020 Laith Rastanawi # Copyright (C) 2019-2020 Sophia Elia -# Copyright (C) 2019-2021 Jonathan Kliem +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/base0.py b/src/sage/geometry/polyhedron/base0.py index 7ed7e777374..3f6f9d31f7a 100644 --- a/src/sage/geometry/polyhedron/base0.py +++ b/src/sage/geometry/polyhedron/base0.py @@ -21,7 +21,7 @@ # Copyright (C) 2019 Julian Ritter # Copyright (C) 2019-2020 Laith Rastanawi # Copyright (C) 2019-2020 Sophia Elia -# Copyright (C) 2019-2021 Jonathan Kliem +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/base1.py b/src/sage/geometry/polyhedron/base1.py index daac9aa6c05..9236996fb0a 100644 --- a/src/sage/geometry/polyhedron/base1.py +++ b/src/sage/geometry/polyhedron/base1.py @@ -24,7 +24,7 @@ # Copyright (C) 2019 Julian Ritter # Copyright (C) 2019-2020 Laith Rastanawi # Copyright (C) 2019-2020 Sophia Elia -# Copyright (C) 2019-2021 Jonathan Kliem +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/base2.py b/src/sage/geometry/polyhedron/base2.py index 9afeeaaef1f..6078d473a27 100644 --- a/src/sage/geometry/polyhedron/base2.py +++ b/src/sage/geometry/polyhedron/base2.py @@ -21,7 +21,7 @@ # Copyright (C) 2019 Julian Ritter # Copyright (C) 2019-2020 Laith Rastanawi # Copyright (C) 2019-2020 Sophia Elia -# Copyright (C) 2019-2021 Jonathan Kliem +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/base3.py b/src/sage/geometry/polyhedron/base3.py index 32a335ce586..7245c6addbb 100644 --- a/src/sage/geometry/polyhedron/base3.py +++ b/src/sage/geometry/polyhedron/base3.py @@ -23,7 +23,7 @@ # Copyright (C) 2019 Julian Ritter # Copyright (C) 2019-2020 Laith Rastanawi # Copyright (C) 2019-2020 Sophia Elia -# Copyright (C) 2019-2021 Jonathan Kliem +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/base4.py b/src/sage/geometry/polyhedron/base4.py index 7e8aeb32890..30da8c35733 100644 --- a/src/sage/geometry/polyhedron/base4.py +++ b/src/sage/geometry/polyhedron/base4.py @@ -24,7 +24,7 @@ # Copyright (C) 2019 Julian Ritter # Copyright (C) 2019-2020 Laith Rastanawi # Copyright (C) 2019-2020 Sophia Elia -# Copyright (C) 2019-2021 Jonathan Kliem +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/base5.py b/src/sage/geometry/polyhedron/base5.py index 04d1fa0314b..8d505175862 100644 --- a/src/sage/geometry/polyhedron/base5.py +++ b/src/sage/geometry/polyhedron/base5.py @@ -23,7 +23,7 @@ # Copyright (C) 2019 Julian Ritter # Copyright (C) 2019-2020 Laith Rastanawi # Copyright (C) 2019-2020 Sophia Elia -# Copyright (C) 2019-2021 Jonathan Kliem +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/base6.py b/src/sage/geometry/polyhedron/base6.py index 2dd1117ad79..0d0b715d863 100644 --- a/src/sage/geometry/polyhedron/base6.py +++ b/src/sage/geometry/polyhedron/base6.py @@ -21,7 +21,7 @@ # Copyright (C) 2019 Julian Ritter # Copyright (C) 2019-2020 Laith Rastanawi # Copyright (C) 2019-2020 Sophia Elia -# Copyright (C) 2019-2021 Jonathan Kliem +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/base7.py b/src/sage/geometry/polyhedron/base7.py index db828e2eb0a..a7da012ef18 100644 --- a/src/sage/geometry/polyhedron/base7.py +++ b/src/sage/geometry/polyhedron/base7.py @@ -21,7 +21,7 @@ # Copyright (C) 2019 Julian Ritter # Copyright (C) 2019-2020 Laith Rastanawi # Copyright (C) 2019-2020 Sophia Elia -# Copyright (C) 2019-2021 Jonathan Kliem +# Copyright (C) 2019-2021 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index 102f38c5eef..b828dfbc9c8 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -73,7 +73,7 @@ AUTHOR: """ # **************************************************************************** -# Copyright (C) 2019 Jonathan Kliem +# Copyright (C) 2019 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx index c1a0b996a8a..8287d36e6a0 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx @@ -55,7 +55,7 @@ AUTHOR: """ # **************************************************************************** -# Copyright (C) 2019 Jonathan Kliem +# Copyright (C) 2019 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx index b4af45206e5..999acbd1f52 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx @@ -58,7 +58,7 @@ AUTHOR: """ #***************************************************************************** -# Copyright (C) 2019 Jonathan Kliem +# Copyright (C) 2019 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd index eea0e3b4da8..f37bcd36085 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd @@ -2,7 +2,7 @@ Cython data structure for combinatorial faces. """ # **************************************************************************** -# Copyright (C) 2020 Jonathan Kliem +# Copyright (C) 2020 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx index b8f9f0a27b0..59258c4a7f8 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx @@ -165,7 +165,7 @@ AUTHOR: """ #***************************************************************************** -# Copyright (C) 2019 Jonathan Kliem +# Copyright (C) 2019 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd index b43e50a6aae..79b319e1982 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd @@ -2,7 +2,7 @@ Inline cython methods for lists of faces. """ # **************************************************************************** -# Copyright (C) 2020 Jonathan Kliem +# Copyright (C) 2020 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx index e6c9aa2b134..ec9c23d090d 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx @@ -2,7 +2,7 @@ Sorting of a list of faces. """ # **************************************************************************** -# Copyright (C) 2020 Jonathan Kliem +# Copyright (C) 2020 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx index 9639f10b6b7..c71eaded3b9 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx @@ -81,7 +81,7 @@ AUTHOR: """ # **************************************************************************** -# Copyright (C) 2019 Jonathan Kliem +# Copyright (C) 2019 Jonathan Kliem # # 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx index 4e9ba7e0e99..a1c4152bf42 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx @@ -51,7 +51,7 @@ AUTHOR: """ #***************************************************************************** -# Copyright (C) 2019 Jonathan Kliem +# Copyright (C) 2019 Jonathan Kliem # # 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 diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index fa48183ecc4..92e8b3f3e45 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -421,7 +421,7 @@ # 2018 Erik M. Bray # Meghana M Reddy # 2019 Rajat Mittal -# 2020 Jonathan Kliem +# 2020 Jonathan Kliem # # 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 diff --git a/src/sage/rings/number_field/order.py b/src/sage/rings/number_field/order.py index f0b0c0f9656..47630513140 100644 --- a/src/sage/rings/number_field/order.py +++ b/src/sage/rings/number_field/order.py @@ -65,7 +65,7 @@ # 2020 John H. Palmieri # 2020 Thierry Monteil # 2021 Antonio Rojas -# 2021 Jonathan Kliem +# 2021 Jonathan Kliem # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of From 2f2732e838abec8706fda1ea5f01186dc1f65cf4 Mon Sep 17 00:00:00 2001 From: Jonathan Kliem Date: Sat, 10 Dec 2022 16:26:12 +0100 Subject: [PATCH 289/392] refactor: Move list of pairs to dedicated class --- src/sage/data_structures/binary_list.pxd | 14 + src/sage/data_structures/binary_list.pyx | 118 ++++++++ .../combinatorial_polyhedron/base.pxd | 39 +-- .../combinatorial_polyhedron/base.pyx | 265 +++++------------- 4 files changed, 214 insertions(+), 222 deletions(-) create mode 100644 src/sage/data_structures/binary_list.pxd create mode 100644 src/sage/data_structures/binary_list.pyx diff --git a/src/sage/data_structures/binary_list.pxd b/src/sage/data_structures/binary_list.pxd new file mode 100644 index 00000000000..a0ea05fbef1 --- /dev/null +++ b/src/sage/data_structures/binary_list.pxd @@ -0,0 +1,14 @@ +cimport cython + +cdef struct pair_s: + size_t first + size_t second + +@cython.final +cdef class BinaryList: + cdef pair_s** _lists + cdef size_t length + + cdef inline int enlarge(self) except -1 + cdef inline int add(self, size_t first, size_t second) except -1 + cdef inline pair_s* get(self, size_t index) except NULL diff --git a/src/sage/data_structures/binary_list.pyx b/src/sage/data_structures/binary_list.pyx new file mode 100644 index 00000000000..c35c4871892 --- /dev/null +++ b/src/sage/data_structures/binary_list.pyx @@ -0,0 +1,118 @@ +r""" +A data structure to store lists of integer pairs of large size. +""" + +# **************************************************************************** +# Copyright (C) 2022 Jonathan Kliem +# +# 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 cysignals.memory cimport check_reallocarray, check_allocarray, sig_free +from sage.rings.integer cimport smallInteger + +# Should be a power of two. +# Should be neither exposed nor modified. +cdef size_t length_per_list = 16348 + +cdef class BinaryList: + def __dealloc__(self): + cdef size_t n_lists = self.length // length_per_list + cdef size_t i + for i in range(n_lists): + sig_free(self._lists[i]) + sig_free(self._lists) + + cdef inline int enlarge(self) except -1: + """ + Increase size of list by one. + """ + if self.length % length_per_list: + self.length += 1 + return 0 + + cdef size_t n_lists = self.length // length_per_list + self._lists = check_reallocarray(self._lists, n_lists + 1, sizeof(pair_s*)) + self._lists[n_lists] = check_allocarray(length_per_list, sizeof(pair_s)) + self.length += 1 + + cdef inline pair_s* get(self, size_t index) except NULL: + if not (0 <= index < self.length): + raise IndexError + + cdef size_t list_index = index // length_per_list + cdef size_t index_in_list = index - list_index * length_per_list + + return &self._lists[list_index][index_in_list] + + def __getitem__(self, size_t index): + r""" + Get item of specified index. + + EXAMPLES:: + + sage: from sage.data_structures.binary_list import BinaryList + sage: l = BinaryList() + sage: l[0] = [1, 5] + sage: l[0] + (1, 5) + sage: l[1] + Traceback (most recent call last): + ... + IndexError + sage: l[-1] + Traceback (most recent call last): + ... + OverflowError: can't convert negative value to size_t + """ + cdef pair_s pair = self.get(index)[0] + return (smallInteger(pair.first), smallInteger(pair.second)) + + def __setitem__(self, size_t index, value): + r""" + Set item of specified index. + + Allows increasing the size of the list by at most 1. + + EXAMPLES:: + + sage: from sage.data_structures.binary_list import BinaryList + sage: l = BinaryList() + sage: l[0] = (2, 1) + sage: l[1] = (1, 2) + sage: l[0] + (2, 1) + sage: l[1] + (1, 2) + sage: l[10] = (5, 3) + Traceback (most recent call last): + ... + IndexError + sage: l[2] = 2 + Traceback (most recent call last): + ... + TypeError: 'sage.rings.integer.Integer' object is not iterable + """ + cdef size_t first, second + (first, second) = value + + if (index == self.length): + self.add(first, second) + return + + cdef pair_s* pair_pt = self.get(index) + pair_pt[0].first = first + pair_pt[0].second = second + + cdef inline int add(self, size_t first, size_t second) except -1: + """ + Add a pair to the list. + """ + self.enlarge() + cdef pair_s* last = self.get(self.length - 1) + last[0].first = first + last[0].second = second diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd index 7c2a14192da..51a70d18950 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd @@ -1,9 +1,10 @@ cimport cython -from sage.structure.sage_object cimport SageObject -from .face_iterator cimport FaceIterator, CombinatorialFace -from .list_of_faces cimport ListOfFaces -from .face_data_structure cimport face_t -from .polyhedron_face_lattice cimport PolyhedronFaceLattice +from sage.data_structures.binary_list cimport BinaryList +from sage.structure.sage_object cimport SageObject +from .face_iterator cimport FaceIterator, CombinatorialFace +from .list_of_faces cimport ListOfFaces +from .face_data_structure cimport face_t +from .polyhedron_face_lattice cimport PolyhedronFaceLattice @cython.final cdef class CombinatorialPolyhedron(SageObject): @@ -24,22 +25,10 @@ cdef class CombinatorialPolyhedron(SageObject): cdef tuple _far_face_tuple cdef tuple _f_vector - # Edges, ridges and incidences are stored in a pointer of pointers. - # The first edge has vertices ``edges[0][0]`` and ``edges[0][1]``, - # the second edge has vertices ``edges[0][2]`` and ``edges[0][3]``, etc. - # There are ``_length_edges_list`` edges in ``edges[i]``, so the edge - # ``_length_edges_list + 1`` has vertices ``edges[1][0]`` and ``edges[1][1]``. - # Likewise for ridges and incidences. - cdef size_t _length_edges_list - - - cdef size_t **_edges # stores edges labeled by vertex indices - cdef size_t _n_edges - cdef size_t **_ridges # stores ridges labeled by facet indices - cdef size_t _n_ridges - cdef size_t **_face_lattice_incidences # stores incidences in Hasse diagram labeled indices of the faces - cdef size_t _n_face_lattice_incidences - cdef PolyhedronFaceLattice _all_faces # class to generate Hasse diagram incidences + cdef BinaryList _edges # stores edges labeled by vertex indices + cdef BinaryList _ridges # stores ridges labeled by facet indices + cdef BinaryList _face_lattice_incidences # stores incidences in Hasse diagram labeled indices of the faces + cdef PolyhedronFaceLattice _all_faces # class to generate Hasse diagram incidences cdef tuple Vrep(self) cdef tuple facet_names(self) @@ -69,10 +58,6 @@ cdef class CombinatorialPolyhedron(SageObject): cdef int _compute_edges_or_ridges(self, int dual, bint do_edges) except -1 cdef size_t _compute_edges_or_ridges_with_iterator( self, FaceIterator face_iter, const bint do_atom_rep, const bint do_f_vector, - size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt, - size_t* f_vector) except -1 - cdef int _compute_face_lattice_incidences(self) except -1 + BinaryList edges, size_t* f_vector) except -1 - cdef inline int _set_edge(self, size_t a, size_t b, size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt) except -1 - cdef inline void _free_edges(self, size_t ***edges_pt, size_t counter) - cdef inline size_t _get_edge(self, size_t **edges, size_t edge_number, size_t vertex) except -1 + cdef int _compute_face_lattice_incidences(self) except -1 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index b828dfbc9c8..c9690bcc452 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -84,7 +84,7 @@ AUTHOR: import numbers from memory_allocator cimport MemoryAllocator -from cysignals.memory cimport check_malloc, check_allocarray, check_reallocarray, check_calloc, sig_free +from cysignals.memory cimport check_calloc, sig_free from sage.rings.integer import Integer from sage.graphs.graph import Graph @@ -102,8 +102,9 @@ from .conversions \ from .conversions cimport Vrep_list_to_bit_rep from sage.misc.cachefunc import cached_method +from sage.data_structures.binary_list cimport pair_s from sage.rings.integer cimport smallInteger -from cysignals.signals cimport sig_check, sig_block, sig_unblock +from cysignals.signals cimport sig_check from .face_data_structure cimport face_len_atoms, face_init, face_free from .face_iterator cimport iter_t, parallel_f_vector @@ -347,15 +348,6 @@ cdef class CombinatorialPolyhedron(SageObject): self._all_faces = None self._n_facets = -1 - # ``_length_edges_list`` should not be touched in an instance - # of :class:`CombinatorialPolyhedron`. This number can be altered, - # but should probably be a power of `2` (for memory usage). - # ``_length_edges_list`` shouldn't be too small for speed and - # shouldn't be too large, as ``ridges``, ``edges`` and ``incidences`` - # each have a memory overhead of - # ``self._length_edges_list*2*sizeof(size_t *)``. - self._length_edges_list = 16348 - def __init__(self, data, Vrep=None, facets=None, unbounded=False, far_face=None): r""" Initialize :class:`CombinatorialPolyhedron`. @@ -578,9 +570,6 @@ cdef class CombinatorialPolyhedron(SageObject): """ if not self._bounded: face_free(self._far_face) - self._free_edges(&self._edges, self._n_edges) - self._free_edges(&self._ridges, self._n_ridges) - self._free_edges(&self._face_lattice_incidences, self._n_face_lattice_incidences) def _repr_(self): r""" @@ -1244,14 +1233,14 @@ cdef class CombinatorialPolyhedron(SageObject): return smallInteger(i) # Getting the indices of the `i`-th edge. - def vertex_one(size_t i): - return f(self._get_edge(self._edges, i, 0)) + cdef pair_s edge - def vertex_two(size_t i): - return f(self._get_edge(self._edges, i, 1)) + def get_edge(size_t i): + edge = self._edges.get(i)[0] + return (f(edge.first), f(edge.second)) cdef size_t j - return tuple((vertex_one(j), vertex_two(j)) for j in range(self._n_edges)) + return tuple(get_edge(j) for j in range(self._edges.length)) def vertex_graph(self, names=True, algorithm=None): r""" @@ -1339,14 +1328,14 @@ cdef class CombinatorialPolyhedron(SageObject): from sage.matrix.constructor import matrix cdef Matrix_dense adjacency_matrix = matrix( ZZ, self.n_Vrepresentation(), self.n_Vrepresentation(), 0) - cdef size_t i, a, b + cdef size_t i + cdef pair_s edge self._compute_edges(self._algorithm_to_dual(algorithm)) - for i in range(self._n_edges): - a = self._get_edge(self._edges, i, 0) - b = self._get_edge(self._edges, i, 1) - adjacency_matrix.set_unsafe_int(a, b, 1) - adjacency_matrix.set_unsafe_int(b, a, 1) + for i in range(self._edges.length): + edge = self._edges.get(i)[0] + adjacency_matrix.set_unsafe_int(edge.first, edge.second, 1) + adjacency_matrix.set_unsafe_int(edge.second, edge.first, 1) adjacency_matrix.set_immutable() return adjacency_matrix @@ -1459,7 +1448,7 @@ cdef class CombinatorialPolyhedron(SageObject): deprecation(31834, "the keyword ``add_equalities`` is deprecated; use ``add_equations``", 3) add_equations = True self._compute_ridges(self._algorithm_to_dual(algorithm)) - n_ridges = self._n_ridges + cdef size_t n_ridges = self._ridges.length # Mapping the indices of the Vepr to the names, if requested. if self.facet_names() is not None and names is True: @@ -1469,23 +1458,21 @@ cdef class CombinatorialPolyhedron(SageObject): def f(size_t i): return smallInteger(i) - # Getting the indices of the `i`-th ridge. - def facet_one(size_t i): - return f(self._get_edge(self._ridges, i, 0)) - - def facet_two(size_t i): - return f(self._get_edge(self._ridges, i, 1)) + cdef pair_s ridge - cdef size_t j if add_equations and names: - # Also getting the equations for each facet. - return tuple( - (((facet_one(i),) + self.equations()), - ((facet_two(i),) + self.equations())) - for i in range(n_ridges)) + def get_ridge(size_t i): + ridge = self._ridges.get(i)[0] + return ((f(ridge.first),) + self.equations(), + (f(ridge.second),) + self.equations()) + else: - return tuple((facet_one(i), facet_two(i)) - for i in range(n_ridges)) + def get_ridge(size_t i): + ridge = self._ridges.get(i)[0] + return (f(ridge.first), f(ridge.second)) + + cdef size_t j + return tuple(get_ridge(j) for j in range(n_ridges)) @cached_method def facet_adjacency_matrix(self, algorithm=None): @@ -1529,14 +1516,14 @@ cdef class CombinatorialPolyhedron(SageObject): from sage.matrix.constructor import matrix cdef Matrix_dense adjacency_matrix = matrix( ZZ, self.n_facets(), self.n_facets(), 0) - cdef size_t i, a, b + cdef size_t i + cdef pair_s ridge self._compute_ridges(self._algorithm_to_dual(algorithm)) - for i in range(self._n_ridges): - a = self._get_edge(self._ridges, i, 0) - b = self._get_edge(self._ridges, i, 1) - adjacency_matrix.set_unsafe_int(a, b, 1) - adjacency_matrix.set_unsafe_int(b, a, 1) + for i in range(self._ridges.length): + ridge = self._ridges.get(i)[0] + adjacency_matrix.set_unsafe_int(ridge.first, ridge.second, 1) + adjacency_matrix.set_unsafe_int(ridge.second, ridge.first, 1) adjacency_matrix.set_immutable() return adjacency_matrix @@ -2911,23 +2898,13 @@ cdef class CombinatorialPolyhedron(SageObject): if not self._face_lattice_incidences: # compute all incidences. self._compute_face_lattice_incidences() - if self._face_lattice_incidences is NULL: + if self._face_lattice_incidences is None: raise TypeError("could not determine face lattice") - cdef size_t **incidences = self._face_lattice_incidences - cdef size_t n_incidences = self._n_face_lattice_incidences - - # Getting the indices of the `i`-th incidence. - def face_one(size_t i): - return smallInteger(self._get_edge(incidences, i, 0)) - - def face_two(size_t i): - return smallInteger(self._get_edge(incidences, i, 1)) - # Edges of the face-lattice/Hasse diagram. cdef size_t j - edges = tuple((face_one(j), face_two(j)) - for j in range(n_incidences)) + cdef size_t n_incidences = self._face_lattice_incidences.length + edges = tuple(self._face_lattice_incidences[j] for j in range(n_incidences)) V = tuple(smallInteger(i) for i in range(sum(self._f_vector))) @@ -3560,7 +3537,7 @@ cdef class CombinatorialPolyhedron(SageObject): See :meth:`CombinatorialPolyhedron.edges` and :meth:`CombinatorialPolyhedron.ridges`. """ - if (self._edges is not NULL and do_edges) or (self._ridges is not NULL and not do_edges): + if (self._edges is not None and do_edges) or (self._ridges is not None and not do_edges): return 0 # There is no need to recompute. if dual == -1: @@ -3598,19 +3575,16 @@ cdef class CombinatorialPolyhedron(SageObject): cdef FaceIterator face_iter cdef int dim = self.dimension() - cdef size_t **edges = NULL - cdef size_t counter = 0 # the number of edges so far - cdef size_t current_length = 1 # dynamically enlarge **edges + cdef BinaryList edges = BinaryList() cdef int output_dim_init = 1 if do_edges else dim - 2 cdef bint do_f_vector = False cdef size_t* f_vector = NULL try: - edges = check_malloc(sizeof(size_t*)) if dim == 1 and (do_edges or self.n_facets() > 1): # In this case there is an edge/ridge, but its not a proper face. - self._set_edge(0, 1, &edges, &counter, ¤t_length) + edges.add(0, 1) elif dim <= 1 or self.n_facets() == 0: # There is no edge/ridge. @@ -3631,7 +3605,7 @@ cdef class CombinatorialPolyhedron(SageObject): do_f_vector = False face_iter = self._face_iter(dual, output_dim_init) self._compute_edges_or_ridges_with_iterator(face_iter, (dual ^ do_edges), do_f_vector, - &edges, &counter, ¤t_length, f_vector) + edges, f_vector) # Success, copy the data to ``CombinatorialPolyhedron``. @@ -3657,36 +3631,24 @@ cdef class CombinatorialPolyhedron(SageObject): # Copy the edge or ridges. if do_edges: - sig_block() - self._n_edges = counter self._edges = edges - edges = NULL - counter = 0 - sig_unblock() else: - sig_block() - self._n_ridges = counter self._ridges = edges - edges = NULL - counter = 0 - sig_unblock() finally: - self._free_edges(&edges, counter) sig_free(f_vector) - if do_edges and self._edges is NULL: + if do_edges and self._edges is None: raise ValueError('could not determine edges') - elif not do_edges and self._ridges is NULL: + elif not do_edges and self._ridges is None: raise ValueError('could not determine ridges') cdef size_t _compute_edges_or_ridges_with_iterator( self, FaceIterator face_iter, const bint do_atom_rep, const bint do_f_vector, - size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt, - size_t* f_vector) except -1: + BinaryList edges, size_t* f_vector) except -1: r""" See :meth:`CombinatorialPolyhedron._compute_edges`. """ - cdef size_t a,b # facets of an edge + cdef size_t a, b # facets of an edge cdef int dim = self.dimension() # The dimension in which to record the edges or ridges. @@ -3717,7 +3679,7 @@ cdef class CombinatorialPolyhedron(SageObject): # Copy the information. a = face_iter.structure.coatom_rep[0] b = face_iter.structure.coatom_rep[1] - self._set_edge(a, b, edges_pt, counter_pt, current_length_pt) + edges.add(a, b) d = face_iter.next_dimension() cdef int _compute_face_lattice_incidences(self) except -1: @@ -3729,7 +3691,6 @@ cdef class CombinatorialPolyhedron(SageObject): if self._face_lattice_incidences: return 1 # There is no need to recompute the incidences. - cdef size_t len_incidence_list = self._length_edges_list cdef int dim = self.dimension() f_vector = self.f_vector() self._record_all_faces() # set up ``self._all_faces`` @@ -3753,12 +3714,7 @@ cdef class CombinatorialPolyhedron(SageObject): # For ``dimension_one`` we add: cdef size_t already_seen_next # = sum(f_vector[j] for j in range(dimension_two + 2)) - # For each incidence we determine its location in ``incidences`` - # by ``incidences[one][two]``. - cdef size_t **incidences = NULL - - cdef size_t counter = 0 # the number of incidences so far - cdef size_t current_length = 1 # dynamically enlarge **incidences + cdef BinaryList incidences = BinaryList() if all_faces is None: raise ValueError("could not determine a list of all faces") @@ -3771,117 +3727,36 @@ cdef class CombinatorialPolyhedron(SageObject): dimension_one += 1 dimension_two = -1 - try: - incidences = check_malloc(sizeof(size_t*)) - while (dimension_one < dim + 1): - already_seen = sum(f_vector[j] for j in range(dimension_two + 1)) - already_seen_next = already_seen + f_vector[dimension_two + 1] + while (dimension_one < dim + 1): + already_seen = sum(f_vector[j] for j in range(dimension_two + 1)) + already_seen_next = already_seen + f_vector[dimension_two + 1] + if all_faces.dual: + # If ``dual``, then ``all_faces`` has the dimensions reversed. + all_faces.incidence_init(dim - 1 - dimension_two, dim - 1 - dimension_one) + else: + all_faces.incidence_init(dimension_one, dimension_two) + + # Get all incidences for fixed ``[dimension_one, dimension_two]``. + while all_faces.next_incidence(&second, &first): if all_faces.dual: - # If ``dual``, then ``all_faces`` has the dimensions reversed. - all_faces.incidence_init(dim - 1 - dimension_two, dim - 1 - dimension_one) + # If ``dual``, then ``second`` and ``first are flipped. + second += already_seen + first += already_seen_next + incidences.add(second, first) else: - all_faces.incidence_init(dimension_one, dimension_two) - - # Get all incidences for fixed ``[dimension_one, dimension_two]``. - while all_faces.next_incidence(&second, &first): - if all_faces.dual: - # If ``dual``, then ``second`` and ``first are flipped. - second += already_seen - first += already_seen_next - self._set_edge(second, first, &incidences, &counter, ¤t_length) - else: - second += already_seen_next - first += already_seen - self._set_edge(first, second, &incidences, &counter, ¤t_length) + second += already_seen_next + first += already_seen + incidences.add(first, second) - sig_check() + sig_check() - # Increase dimensions. - dimension_one += 1 - dimension_two = dimension_one - 1 - - # Success, copy the data to ``CombinatorialPolyhedron``. - sig_block() - self._face_lattice_incidences = incidences - self._n_face_lattice_incidences = counter - incidences = NULL - counter = 0 - sig_unblock() - finally: - self._free_edges(&incidences, counter) - - cdef inline int _set_edge(self, size_t a, size_t b, size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt) except -1: - r""" - Set an edge in an edge list. - - Sets the values of all pointers accordingly. - - INPUT: - - - ``a``,``b`` -- the vertices of the edge - - ``edges_pt`` -- pointer to the list of lists; might point to ``NULL`` - when ``current_length_pt[0] == 0`` - - ``counter_pt`` -- pointer to the number of edges - - ``current_length_pt`` -- pointer to the length of ``edges_pt[0]`` - """ - cdef size_t len_edge_list = self._length_edges_list - # Determine the position in ``edges``. - cdef size_t one = counter_pt[0] // len_edge_list - cdef size_t two = counter_pt[0] % len_edge_list - - if unlikely(current_length_pt[0] == 0): - edges_pt[0] = check_malloc(sizeof(size_t*)) - current_length_pt[0] = 1 - - # Enlarge ``edges`` if needed. - if unlikely(two == 0): - if unlikely(one + 1 > current_length_pt[0]): - # enlarge **edges - current_length_pt[0] = 2*current_length_pt[0] - edges_pt[0] = check_reallocarray(edges_pt[0], current_length_pt[0], sizeof(size_t*)) - - edges_pt[0][one] = check_allocarray(2 * len_edge_list, sizeof(size_t)) - - edges_pt[0][one][2*two] = a - edges_pt[0][one][2*two + 1] = b - counter_pt[0] = counter_pt[0] + 1 - - cdef inline void _free_edges(self, size_t ***edges_pt, size_t counter): - r""" - Free the memory allocated for the edges. - """ - if edges_pt[0] is NULL: - return - - cdef size_t len_edge_list = self._length_edges_list - # Determine the position in ``edges``. - cdef size_t one = counter // len_edge_list - cdef size_t i - - for i in range(one): - sig_free(edges_pt[0][i]) - - sig_free(edges_pt[0]) - - cdef inline size_t _get_edge(self, size_t **edges, size_t edge_number, size_t vertex) except -1: - r""" - Get a vertex of an edge in an edge list. - - INPUT: - - - ``edges`` -- the edges list - - ``edge_number`` -- the number of the edge to obtain - - ``vertex`` -- one of ``0``, ``1``; the vertex to obtain - - OUTPUT: The specified vertex of the specified edge. - """ - cdef size_t len_edge_list = self._length_edges_list - # Determine the position in ``edges``. - cdef size_t one = edge_number // len_edge_list - cdef size_t two = edge_number % len_edge_list + # Increase dimensions. + dimension_one += 1 + dimension_two = dimension_one - 1 - return edges[one][2*two + vertex] + # Success, copy the data to ``CombinatorialPolyhedron``. + self._face_lattice_incidences = incidences def _record_all_faces(self): r""" From 31bfb691b9b61091aecefae106acf340fbb85367 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Sat, 11 Feb 2023 16:29:49 +0100 Subject: [PATCH 290/392] fix docstrings, simplify some tests --- src/sage/combinat/bijectionist.py | 757 +++++++++++++++--------------- 1 file changed, 372 insertions(+), 385 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 8a9989176d6..783a5ff4714 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -36,319 +36,313 @@ A guided tour ============= - EXAMPLES: - - We find a statistic `s` such that - `(s, wex, fix) \sim (llis, des, adj)`:: - - sage: N = 3 - sage: A = B = [pi for n in range(N+1) for pi in Permutations(n)] - sage: alpha1 = lambda p: len(p.weak_excedences()) - sage: alpha2 = lambda p: len(p.fixed_points()) - sage: beta1 = lambda p: len(p.descents(final_descent=True)) if p else 0 - sage: beta2 = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) - sage: tau = Permutation.longest_increasing_subsequence_length - sage: def rotate_permutation(p): - ....: cycle = Permutation(tuple(range(1, len(p)+1))) - ....: return Permutation([cycle.inverse()(p(cycle(i))) for i in range(1, len(p)+1)]) - sage: bij = Bijectionist(A, B, tau) - sage: bij.set_statistics((len, len), (alpha1, beta1), (alpha2, beta2)) - sage: a, b = bij.statistics_table() - sage: table(a, header_row=True, frame=True) - +-----------+--------+--------+--------+ - | a | α_1(a) | α_2(a) | α_3(a) | - +===========+========+========+========+ - | [] | 0 | 0 | 0 | - +-----------+--------+--------+--------+ - | [1] | 1 | 1 | 1 | - +-----------+--------+--------+--------+ - | [1, 2] | 2 | 2 | 2 | - +-----------+--------+--------+--------+ - | [2, 1] | 2 | 1 | 0 | - +-----------+--------+--------+--------+ - | [1, 2, 3] | 3 | 3 | 3 | - +-----------+--------+--------+--------+ - | [1, 3, 2] | 3 | 2 | 1 | - +-----------+--------+--------+--------+ - | [2, 1, 3] | 3 | 2 | 1 | - +-----------+--------+--------+--------+ - | [2, 3, 1] | 3 | 2 | 0 | - +-----------+--------+--------+--------+ - | [3, 1, 2] | 3 | 1 | 0 | - +-----------+--------+--------+--------+ - | [3, 2, 1] | 3 | 2 | 1 | - +-----------+--------+--------+--------+ - - sage: table(b, header_row=True, frame=True) - +-----------+---+--------+--------+--------+ - | b | τ | β_1(b) | β_2(b) | β_3(b) | - +===========+===+========+========+========+ - | [] | 0 | 0 | 0 | 0 | - +-----------+---+--------+--------+--------+ - | [1] | 1 | 1 | 1 | 1 | - +-----------+---+--------+--------+--------+ - | [1, 2] | 2 | 2 | 1 | 0 | - +-----------+---+--------+--------+--------+ - | [2, 1] | 1 | 2 | 2 | 2 | - +-----------+---+--------+--------+--------+ - | [1, 2, 3] | 3 | 3 | 1 | 0 | - +-----------+---+--------+--------+--------+ - | [1, 3, 2] | 2 | 3 | 2 | 1 | - +-----------+---+--------+--------+--------+ - | [2, 1, 3] | 2 | 3 | 2 | 1 | - +-----------+---+--------+--------+--------+ - | [2, 3, 1] | 2 | 3 | 2 | 1 | - +-----------+---+--------+--------+--------+ - | [3, 1, 2] | 2 | 3 | 2 | 0 | - +-----------+---+--------+--------+--------+ - | [3, 2, 1] | 1 | 3 | 3 | 3 | - +-----------+---+--------+--------+--------+ - - sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition - sage: bij.set_constant_blocks(orbit_decomposition(A, rotate_permutation)) - sage: bij.constant_blocks() - {{[1, 3, 2], [2, 1, 3], [3, 2, 1]}} - sage: next(bij.solutions_iterator()) - {[]: 0, - [1]: 1, - [1, 2]: 1, - [1, 2, 3]: 1, - [1, 3, 2]: 2, - [2, 1]: 2, - [2, 1, 3]: 2, - [2, 3, 1]: 2, - [3, 1, 2]: 3, - [3, 2, 1]: 2} - - There is no rotation invariant statistic on non crossing set partitions which is equidistributed - with the Strahler number on ordered trees:: - - sage: N = 8; - sage: A = [SetPartition(d.to_noncrossing_partition()) for n in range(N) for d in DyckWords(n)] - sage: B = [t for n in range(1, N+1) for t in OrderedTrees(n)] - sage: theta = lambda m: SetPartition([[i % m.size() + 1 for i in b] for b in m]) - - The following code is equivalent to ``tau = findstat(397)``:: - - sage: def tau(T): - ....: if len(T) == 0: - ....: return 1 - ....: else: - ....: l = [tau(S) for S in T] - ....: m = max(l) - ....: if l.count(m) == 1: - ....: return m - ....: else: - ....: return m+1 - sage: bij = Bijectionist(A, B, tau) - sage: bij.set_statistics((lambda a: a.size(), lambda b: b.node_number()-1)) - sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition - sage: bij.set_constant_blocks(orbit_decomposition(A, theta)) - sage: list(bij.solutions_iterator()) - [] - - An example identifying `s` and `S`:: - - sage: N = 4 - sage: A = [dyck_word for n in range(1, N) for dyck_word in DyckWords(n)] - sage: B = [binary_tree for n in range(1, N) for binary_tree in BinaryTrees(n)] - sage: concat_path = lambda D1, D2: DyckWord(list(D1) + list(D2)) - sage: concat_tree = lambda B1, B2: concat_path(B1.to_dyck_word(), - ....: B2.to_dyck_word()).to_binary_tree() - sage: bij = Bijectionist(A, B) - sage: bij.set_intertwining_relations((2, concat_path, concat_tree)) - sage: bij.set_statistics((lambda d: d.semilength(), lambda t: t.node_number())) - sage: for D in sorted(bij.minimal_subdistributions_iterator(), key=lambda x: (len(x[0][0]), x)): - ....: ascii_art(D) - ( [ /\ ], [ o ] ) - ( [ o ] ) - ( [ \ ] ) - ( [ /\/\ ], [ o ] ) - ( [ o ] ) - ( [ /\ ] [ / ] ) - ( [ / \ ], [ o ] ) - ( [ o ] ) - ( [ \ ] ) - ( [ o ] ) - ( [ \ ] ) - ( [ /\/\/\ ], [ o ] ) - ( [ o ] ) - ( [ \ ] ) - ( [ o ] ) - ( [ /\ ] [ / ] ) - ( [ /\/ \ ], [ o ] ) - ( [ o ] ) - ( [ /\ ] [ / \ ] ) - ( [ / \/\ ], [ o o ] ) - ( [ o, o ] ) - ( [ / / ] ) - ( [ /\ ] [ o o ] ) - ( [ /\/\ / \ ] [ \ / ] ) - ( [ / \, / \ ], [ o o ] ) - - The output is in a form suitable for FindStat:: - - sage: findmap(list(bij.minimal_subdistributions_iterator())) # optional -- internet - 0: Mp00034 (quality [100]) - 1: Mp00061oMp00023 (quality [100]) - 2: Mp00018oMp00140 (quality [100]) - - TESTS:: - - sage: N = 4; A = B = [permutation for n in range(N) for permutation in Permutations(n)] - sage: theta = lambda pi: Permutation([x+1 if x != len(pi) else 1 for x in pi[-1:]+pi[:-1]]) - sage: def tau(pi): - ....: n = len(pi) - ....: return sum([1 for i in range(1, n+1) for j in range(1, n+1) - ....: if i Date: Sat, 11 Feb 2023 16:35:29 +0100 Subject: [PATCH 291/392] change lambda to def --- src/sage/combinat/bijectionist.py | 114 +++++++++++++++--------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 783a5ff4714..6e756891977 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -42,10 +42,10 @@ sage: N = 3 sage: A = B = [pi for n in range(N+1) for pi in Permutations(n)] - sage: alpha1 = lambda p: len(p.weak_excedences()) - sage: alpha2 = lambda p: len(p.fixed_points()) - sage: beta1 = lambda p: len(p.descents(final_descent=True)) if p else 0 - sage: beta2 = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: def alpha1(p): return len(p.weak_excedences()) + sage: def alpha2(p): return len(p.fixed_points()) + sage: def beta1(p): return len(p.descents(final_descent=True)) if p else 0 + sage: def beta2(p): return len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) sage: tau = Permutation.longest_increasing_subsequence_length sage: def rotate_permutation(p): ....: cycle = Permutation(tuple(range(1, len(p)+1))) @@ -125,7 +125,7 @@ sage: N = 8; sage: A = [SetPartition(d.to_noncrossing_partition()) for n in range(N) for d in DyckWords(n)] sage: B = [t for n in range(1, N+1) for t in OrderedTrees(n)] - sage: theta = lambda m: SetPartition([[i % m.size() + 1 for i in b] for b in m]) + sage: def theta(m): return SetPartition([[i % m.size() + 1 for i in b] for b in m]) The following code is equivalent to ``tau = findstat(397)``:: @@ -195,7 +195,7 @@ TESTS:: sage: N = 4; A = B = [permutation for n in range(N) for permutation in Permutations(n)] - sage: theta = lambda pi: Permutation([x+1 if x != len(pi) else 1 for x in pi[-1:]+pi[:-1]]) + sage: def theta(pi): return Permutation([x+1 if x != len(pi) else 1 for x in pi[-1:]+pi[:-1]]) sage: def tau(pi): ....: n = len(pi) ....: return sum([1 for i in range(1, n+1) for j in range(1, n+1) @@ -213,9 +213,9 @@ A test including intertwining relations:: sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] - sage: alpha = lambda D: (D.area(), D.bounce()) - sage: beta = lambda D: (D.bounce(), D.area()) - sage: tau = lambda D: D.number_of_touch_points() + sage: def alpha(D): return (D.area(), D.bounce()) + sage: def beta(D): return (D.bounce(), D.area()) + sage: def tau(D): return D.number_of_touch_points() The following looks correct:: @@ -263,9 +263,9 @@ Repeating some tests, but using the constructor instead of set_XXX() methods: sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] - sage: alpha = lambda D: (D.area(), D.bounce()) - sage: beta = lambda D: (D.bounce(), D.area()) - sage: tau = lambda D: D.number_of_touch_points() + sage: def alpha(D): return (D.area(), D.bounce()) + sage: def beta(D): return (D.bounce(), D.area()) + sage: def tau(D): return D.number_of_touch_points() sage: bij = Bijectionist(A, B, tau, alpha_beta=((lambda d: d.semilength(), lambda d: d.semilength()),)) sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): @@ -276,8 +276,8 @@ Constant blocks:: sage: A = B = 'abcd' - sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] - sage: rho = lambda s1, s2: (s1 + s2) % 2 + sage: def pi(p1, p2): return 'abcdefgh'[A.index(p1) + A.index(p2)] + sage: def rho(s1, s2): return (s1 + s2) % 2 sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2, P=[['a', 'c']], pi_rho=((2, pi, rho),)) sage: list(bij.solutions_iterator()) [{'a': 0, 'b': 1, 'c': 0, 'd': 1}] @@ -298,7 +298,7 @@ Intertwining relations:: - sage: concat = lambda p1, p2: Permutation(p1 + [i + len(p1) for i in p2]) + sage: def concat(p1, p2): return Permutation(p1 + [i + len(p1) for i in p2]) sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: bij = Bijectionist(A, B, Permutation.number_of_fixed_points, alpha_beta=((len, len),), pi_rho=((2, concat, lambda x, y: x + y),)) @@ -311,9 +311,9 @@ Statistics:: sage: N = 4; A = B = [permutation for n in range(N) for permutation in Permutations(n)] - sage: wex = lambda p: len(p.weak_excedences()) - sage: fix = lambda p: len(p.fixed_points()) - sage: des = lambda p: len(p.descents(final_descent=True)) if p else 0 + sage: def wex(p): return len(p.weak_excedences()) + sage: def fix(p): return len(p.fixed_points()) + sage: def des(p): return len(p.descents(final_descent=True)) if p else 0 sage: bij = Bijectionist(A, B, fix, alpha_beta=((wex, des), (len, len))) sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): ....: print(solution) @@ -567,8 +567,8 @@ def set_constant_blocks(self, P): We now add a map that combines some blocks:: - sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] - sage: rho = lambda s1, s2: (s1 + s2) % 2 + sage: def pi(p1, p2): return 'abcdefgh'[A.index(p1) + A.index(p2)] + sage: def rho(s1, s2): return (s1 + s2) % 2 sage: bij.set_intertwining_relations((2, pi, rho)) sage: list(bij.solutions_iterator()) [{'a': 0, 'b': 1, 'c': 0, 'd': 1}] @@ -672,10 +672,10 @@ def set_statistics(self, *alpha_beta): of fixed points of `S(\pi)` equals `s(\pi)`:: sage: N = 4; A = B = [permutation for n in range(N) for permutation in Permutations(n)] - sage: wex = lambda p: len(p.weak_excedences()) - sage: fix = lambda p: len(p.fixed_points()) - sage: des = lambda p: len(p.descents(final_descent=True)) if p else 0 - sage: adj = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: def wex(p): return len(p.weak_excedences()) + sage: def fix(p): return len(p.fixed_points()) + sage: def des(p): return len(p.descents(final_descent=True)) if p else 0 + sage: def adj(p): return len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) sage: bij = Bijectionist(A, B, fix) sage: bij.set_statistics((wex, des), (len, len)) sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): @@ -708,9 +708,9 @@ def set_statistics(self, *alpha_beta): Calling ``set_statistics`` without arguments should restore the previous state:: sage: N = 3; A = B = [permutation for n in range(N) for permutation in Permutations(n)] - sage: wex = lambda p: len(p.weak_excedences()) - sage: fix = lambda p: len(p.fixed_points()) - sage: des = lambda p: len(p.descents(final_descent=True)) if p else 0 + sage: def wex(p): return len(p.weak_excedences()) + sage: def fix(p): return len(p.fixed_points()) + sage: def des(p): return len(p.descents(final_descent=True)) if p else 0 sage: bij = Bijectionist(A, B, fix) sage: bij.set_statistics((wex, des), (len, len)) sage: for solution in bij.solutions_iterator(): @@ -783,10 +783,10 @@ def statistics_fibers(self): sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: tau = Permutation.longest_increasing_subsequence_length - sage: wex = lambda p: len(p.weak_excedences()) - sage: fix = lambda p: len(p.fixed_points()) - sage: des = lambda p: len(p.descents(final_descent=True)) if p else 0 - sage: adj = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: def wex(p): return len(p.weak_excedences()) + sage: def fix(p): return len(p.fixed_points()) + sage: def des(p): return len(p.descents(final_descent=True)) if p else 0 + sage: def adj(p): return len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) sage: bij = Bijectionist(A, B, tau) sage: bij.set_statistics((len, len), (wex, des), (fix, adj)) sage: table([[key, AB[0], AB[1]] for key, AB in bij.statistics_fibers().items()]) @@ -828,10 +828,10 @@ def statistics_table(self, header=True): sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: tau = Permutation.longest_increasing_subsequence_length - sage: wex = lambda p: len(p.weak_excedences()) - sage: fix = lambda p: len(p.fixed_points()) - sage: des = lambda p: len(p.descents(final_descent=True)) if p else 0 - sage: adj = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: def wex(p): return len(p.weak_excedences()) + sage: def fix(p): return len(p.fixed_points()) + sage: def des(p): return len(p.descents(final_descent=True)) if p else 0 + sage: def adj(p): return len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) sage: bij = Bijectionist(A, B, tau) sage: bij.set_statistics((wex, des), (fix, adj)) sage: a, b = bij.statistics_table() @@ -1193,8 +1193,8 @@ def set_distributions(self, *elements_distributions): Another example with statistics:: sage: bij = Bijectionist(A, B, tau) - sage: alpha = lambda p: p(1) if len(p) > 0 else 0 - sage: beta = lambda p: p(1) if len(p) > 0 else 0 + sage: def alpha(p): return p(1) if len(p) > 0 else 0 + sage: def beta(p): return p(1) if len(p) > 0 else 0 sage: bij.set_statistics((alpha, beta), (len, len)) sage: for sol in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): ....: print(sol) @@ -1304,7 +1304,7 @@ def set_intertwining_relations(self, *pi_rho): of the second permutation by the length of the first permutation:: - sage: concat = lambda p1, p2: Permutation(p1 + [i + len(p1) for i in p2]) + sage: def concat(p1, p2): return Permutation(p1 + [i + len(p1) for i in p2]) We may be interested in statistics on permutations which are equidistributed with the number of fixed points, such that @@ -1496,7 +1496,7 @@ def _forced_constant_blocks(self): sage: N = 4 sage: A = B = [permutation for n in range(2, N) for permutation in Permutations(n)] - sage: tau = lambda p: p[0] if len(p) else 0 + sage: def tau(p): return p[0] if len(p) else 0 sage: add_n = lambda p1: Permutation(p1 + [1 + len(p1)]) sage: add_1 = lambda p1: Permutation([1] + [1 + i for i in p1]) sage: bij = Bijectionist(A, B, tau) @@ -1528,8 +1528,8 @@ def _forced_constant_blocks(self): sage: bij.constant_blocks() {{[2, 3, 1], [3, 1, 2]}} - sage: concat = lambda p1, p2: Permutation(p1 + [i + len(p1) for i in p2]) - sage: union = lambda p1, p2: Partition(sorted(list(p1) + list(p2), reverse=True)) + sage: def concat(p1, p2): return Permutation(p1 + [i + len(p1) for i in p2]) + sage: def union(p1, p2): return Partition(sorted(list(p1) + list(p2), reverse=True)) sage: bij.set_intertwining_relations((2, concat, union)) In this case we do not discover constant blocks by looking at the intertwining_relations only:: @@ -1546,10 +1546,10 @@ def _forced_constant_blocks(self): sage: N = 4 sage: A = B = [permutation for n in range(N + 1) for permutation in Permutations(n)] - sage: alpha1 = lambda p: len(p.weak_excedences()) - sage: alpha2 = lambda p: len(p.fixed_points()) - sage: beta1 = lambda p: len(p.descents(final_descent=True)) if p else 0 - sage: beta2 = lambda p: len([e for (e, f) in zip(p, p[1:] + [0]) if e == f + 1]) + sage: def alpha1(p): return len(p.weak_excedences()) + sage: def alpha2(p): return len(p.fixed_points()) + sage: def beta1(p): return len(p.descents(final_descent=True)) if p else 0 + sage: def beta2(p): return len([e for (e, f) in zip(p, p[1:] + [0]) if e == f + 1]) sage: tau = Permutation.longest_increasing_subsequence_length sage: def rotate_permutation(p): ....: cycle = Permutation(tuple(range(1, len(p) + 1))) @@ -1826,7 +1826,7 @@ def minimal_subdistributions_iterator(self): Another example:: sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] - sage: tau = lambda D: D.number_of_touch_points() + sage: def tau(D): return D.number_of_touch_points() sage: bij = Bijectionist(A, B, tau) sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): @@ -1974,7 +1974,7 @@ def minimal_subdistributions_blocks_iterator(self): Another example:: sage: N = 2; A = B = [dyck_word for n in range(N+1) for dyck_word in DyckWords(n)] - sage: tau = lambda D: D.number_of_touch_points() + sage: def tau(D): return D.number_of_touch_points() sage: bij = Bijectionist(A, B, tau) sage: bij.set_statistics((lambda d: d.semilength(), lambda d: d.semilength())) sage: for solution in sorted(list(bij.solutions_iterator()), key=lambda d: tuple(sorted(d.items()))): @@ -2143,8 +2143,8 @@ def _preprocess_intertwining_relations(self): sage: A = B = 'abcd' sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) - sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] - sage: rho = lambda s1, s2: (s1 + s2) % 2 + sage: def pi(p1, p2): return 'abcdefgh'[A.index(p1) + A.index(p2)] + sage: def rho(s1, s2): return (s1 + s2) % 2 sage: bij.set_intertwining_relations((2, pi, rho)) sage: bij._preprocess_intertwining_relations() sage: bij._P @@ -2909,8 +2909,8 @@ def add_intertwining_relation_constraints(self): sage: A = B = 'abcd' sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 2) - sage: pi = lambda p1, p2: 'abcdefgh'[A.index(p1) + A.index(p2)] - sage: rho = lambda s1, s2: (s1 + s2) % 2 + sage: def pi(p1, p2): return 'abcdefgh'[A.index(p1) + A.index(p2)] + sage: def rho(s1, s2): return (s1 + s2) % 2 sage: bij.set_intertwining_relations((2, pi, rho)) sage: from sage.combinat.bijectionist import _BijectionistMILP sage: bmilp = _BijectionistMILP(bij) # indirect doctest @@ -3127,8 +3127,8 @@ def _non_copying_intersection(sets): Note that adding ``[(2,-2,-1), (2,2,-1), (2,-2,1), (2,2,1)]`` makes it take (seemingly) forever.:: - sage: c1 = lambda a, b: (a[0]+b[0], a[1]*b[1], a[2]*b[2]) - sage: c2 = lambda a: (a[0], -a[1], a[2]) + sage: def c1(a, b): return (a[0]+b[0], a[1]*b[1], a[2]*b[2]) + sage: def c2(a): return (a[0], -a[1], a[2]) sage: bij = Bijectionist(sum(As, []), sum(Bs, [])) sage: bij.set_statistics((lambda x: x[0], lambda x: x[0])) @@ -3168,10 +3168,10 @@ def _non_copying_intersection(sets): Our benchmark example:: sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition - sage: alpha1 = lambda p: len(p.weak_excedences()) - sage: alpha2 = lambda p: len(p.fixed_points()) - sage: beta1 = lambda p: len(p.descents(final_descent=True)) if p else 0 - sage: beta2 = lambda p: len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) + sage: def alpha1(p): return len(p.weak_excedences()) + sage: def alpha2(p): return len(p.fixed_points()) + sage: def beta1(p): return len(p.descents(final_descent=True)) if p else 0 + sage: def beta2(p): return len([e for (e, f) in zip(p, p[1:]+[0]) if e == f+1]) sage: gamma = Permutation.longest_increasing_subsequence_length sage: def rotate_permutation(p): ....: cycle = Permutation(tuple(range(1, len(p)+1))) From 18f421034b807e159fb88e807be7bf7fc85ba877 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sat, 11 Feb 2023 20:12:22 -0800 Subject: [PATCH 292/392] Remove 'docker' from the names of the Docker images published on ghcr.io --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5eb3e79633b..72fbcefe294 100644 --- a/tox.ini +++ b/tox.ini @@ -647,7 +647,7 @@ commands = docker-{arm64,armhf}: docker run --rm --privileged multiarch/qemu-user-static:register --reset docker: bash -c 'if [ x"{env:DOCKER_CONFIG_FILE:}" != x ]; then mkdir -p {envdir}/.docker && ln -sf $(realpath "{env:DOCKER_CONFIG_FILE:}") {envdir}/.docker/; fi' docker: bash -c 'for docker_target in {env:DOCKER_TARGETS:with-targets}; do \ - docker: BUILD_IMAGE_STEM=sage-$(echo {envname} | sed s/-incremental//); \ + docker: BUILD_IMAGE_STEM=sage-$(echo {envname} | sed "s/-docker//;s/-incremental//"); \ docker: BUILD_IMAGE=$DOCKER_PUSH_REPOSITORY$BUILD_IMAGE_STEM-$docker_target; \ docker: BUILD_TAG=$(git describe --dirty --always); \ docker: TAG_ARGS=$(for tag in $BUILD_TAG {env:EXTRA_DOCKER_TAGS:}; do echo --tag $BUILD_IMAGE:$tag; done); \ From 52d2a5962a18f6f8cda79e36b865c3fe94d1c6f8 Mon Sep 17 00:00:00 2001 From: Rohan Garg <76916164+Sandstorm831@users.noreply.github.com> Date: Sun, 12 Feb 2023 11:18:54 +0530 Subject: [PATCH 293/392] Update linear_extensions.py --- src/sage/combinat/posets/linear_extensions.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index a2dfb7b26a1..3cfdee843b1 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -255,13 +255,15 @@ def is_greedy(self): def is_supergreedy(self): r"""" Return ``True`` if the linear extension is supergreedy. - - A linear extension `[e_1, e_2, \ldots, e_n]` is *supergreedy* if - for every `i`, either if there is a minimal element `e_{i+1}` in - `[e_{i+1}, \ldots, e_n]` which is in the upper cover of `e_j` in - `[e_1, \ldots, e_i]` for which j is maximum or if no such element - exist `e_{i+1}` is the any of the minimal element in - `[e_i+1,\dots, e_n]`. + + A linear extension $[x_1 Date: Sun, 12 Feb 2023 13:03:48 +0545 Subject: [PATCH 294/392] updated sage pip note updated the ambiguity note on `sage` and `sagemath` as per [#35070](https://github.com/sagemath/sage/pull/35070#discussion_r1103746908) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5117319bcf3..3685bb8c4b3 100644 --- a/README.md +++ b/README.md @@ -407,7 +407,7 @@ For installation of `sage` in python using `pip` you need to install `sagemath-s You need to install the `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels, you can install the sage library, `sagemath-standard`. -**NOTE:** You can find `sage` and `sagemath` pip packages but it is worth noting that they are not at all related to this `sage`. Indeed `pip install sage` installs `sage 0.0.0` which is literally an empty package. +**NOTE:** You can find `sage` and `sagemath` pip packages but it is worth noting that when importing `sage` with these packages you will encounter `ModuleNotFoundError: No module named 'sage'`. Indeed `pip install sage` installs `sage 0.0.0` which is literally an empty package. Troubleshooting --------------- From 1ef47d116cfb9fb2ec67caebe2235c47eb61c100 Mon Sep 17 00:00:00 2001 From: Jonathan Kliem Date: Sun, 12 Feb 2023 11:02:53 +0100 Subject: [PATCH 295/392] refactor: BinaryList -> ListOfPairs --- .../{binary_list.pxd => list_of_pairs.pxd} | 2 +- .../{binary_list.pyx => list_of_pairs.pyx} | 10 +++++----- .../combinatorial_polyhedron/base.pxd | 20 +++++++++---------- .../combinatorial_polyhedron/base.pyx | 12 +++++------ 4 files changed, 22 insertions(+), 22 deletions(-) rename src/sage/data_structures/{binary_list.pxd => list_of_pairs.pxd} (92%) rename src/sage/data_structures/{binary_list.pyx => list_of_pairs.pyx} (93%) diff --git a/src/sage/data_structures/binary_list.pxd b/src/sage/data_structures/list_of_pairs.pxd similarity index 92% rename from src/sage/data_structures/binary_list.pxd rename to src/sage/data_structures/list_of_pairs.pxd index a0ea05fbef1..4dbb57c201c 100644 --- a/src/sage/data_structures/binary_list.pxd +++ b/src/sage/data_structures/list_of_pairs.pxd @@ -5,7 +5,7 @@ cdef struct pair_s: size_t second @cython.final -cdef class BinaryList: +cdef class ListOfPairs: cdef pair_s** _lists cdef size_t length diff --git a/src/sage/data_structures/binary_list.pyx b/src/sage/data_structures/list_of_pairs.pyx similarity index 93% rename from src/sage/data_structures/binary_list.pyx rename to src/sage/data_structures/list_of_pairs.pyx index c35c4871892..03ad6268277 100644 --- a/src/sage/data_structures/binary_list.pyx +++ b/src/sage/data_structures/list_of_pairs.pyx @@ -19,7 +19,7 @@ from sage.rings.integer cimport smallInteger # Should be neither exposed nor modified. cdef size_t length_per_list = 16348 -cdef class BinaryList: +cdef class ListOfPairs: def __dealloc__(self): cdef size_t n_lists = self.length // length_per_list cdef size_t i @@ -55,8 +55,8 @@ cdef class BinaryList: EXAMPLES:: - sage: from sage.data_structures.binary_list import BinaryList - sage: l = BinaryList() + sage: from sage.data_structures.list_of_pairs import ListOfPairs + sage: l = ListOfPairs() sage: l[0] = [1, 5] sage: l[0] (1, 5) @@ -80,8 +80,8 @@ cdef class BinaryList: EXAMPLES:: - sage: from sage.data_structures.binary_list import BinaryList - sage: l = BinaryList() + sage: from sage.data_structures.list_of_pairs import ListOfPairs + sage: l = ListOfPairs() sage: l[0] = (2, 1) sage: l[1] = (1, 2) sage: l[0] diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd index 51a70d18950..494213512ff 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd @@ -1,10 +1,10 @@ cimport cython -from sage.data_structures.binary_list cimport BinaryList -from sage.structure.sage_object cimport SageObject -from .face_iterator cimport FaceIterator, CombinatorialFace -from .list_of_faces cimport ListOfFaces -from .face_data_structure cimport face_t -from .polyhedron_face_lattice cimport PolyhedronFaceLattice +from sage.data_structures.list_of_pairs cimport ListOfPairs +from sage.structure.sage_object cimport SageObject +from .face_iterator cimport FaceIterator, CombinatorialFace +from .list_of_faces cimport ListOfFaces +from .face_data_structure cimport face_t +from .polyhedron_face_lattice cimport PolyhedronFaceLattice @cython.final cdef class CombinatorialPolyhedron(SageObject): @@ -25,9 +25,9 @@ cdef class CombinatorialPolyhedron(SageObject): cdef tuple _far_face_tuple cdef tuple _f_vector - cdef BinaryList _edges # stores edges labeled by vertex indices - cdef BinaryList _ridges # stores ridges labeled by facet indices - cdef BinaryList _face_lattice_incidences # stores incidences in Hasse diagram labeled indices of the faces + cdef ListOfPairs _edges # stores edges labeled by vertex indices + cdef ListOfPairs _ridges # stores ridges labeled by facet indices + cdef ListOfPairs _face_lattice_incidences # stores incidences in Hasse diagram labeled indices of the faces cdef PolyhedronFaceLattice _all_faces # class to generate Hasse diagram incidences cdef tuple Vrep(self) @@ -58,6 +58,6 @@ cdef class CombinatorialPolyhedron(SageObject): cdef int _compute_edges_or_ridges(self, int dual, bint do_edges) except -1 cdef size_t _compute_edges_or_ridges_with_iterator( self, FaceIterator face_iter, const bint do_atom_rep, const bint do_f_vector, - BinaryList edges, size_t* f_vector) except -1 + ListOfPairs edges, size_t* f_vector) except -1 cdef int _compute_face_lattice_incidences(self) except -1 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index c9690bcc452..62ee57c25ff 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -102,9 +102,9 @@ from .conversions \ from .conversions cimport Vrep_list_to_bit_rep from sage.misc.cachefunc import cached_method -from sage.data_structures.binary_list cimport pair_s -from sage.rings.integer cimport smallInteger -from cysignals.signals cimport sig_check +from sage.data_structures.list_of_pairs cimport pair_s +from sage.rings.integer cimport smallInteger +from cysignals.signals cimport sig_check from .face_data_structure cimport face_len_atoms, face_init, face_free from .face_iterator cimport iter_t, parallel_f_vector @@ -3575,7 +3575,7 @@ cdef class CombinatorialPolyhedron(SageObject): cdef FaceIterator face_iter cdef int dim = self.dimension() - cdef BinaryList edges = BinaryList() + cdef ListOfPairs edges = ListOfPairs() cdef int output_dim_init = 1 if do_edges else dim - 2 cdef bint do_f_vector = False @@ -3644,7 +3644,7 @@ cdef class CombinatorialPolyhedron(SageObject): cdef size_t _compute_edges_or_ridges_with_iterator( self, FaceIterator face_iter, const bint do_atom_rep, const bint do_f_vector, - BinaryList edges, size_t* f_vector) except -1: + ListOfPairs edges, size_t* f_vector) except -1: r""" See :meth:`CombinatorialPolyhedron._compute_edges`. """ @@ -3714,7 +3714,7 @@ cdef class CombinatorialPolyhedron(SageObject): # For ``dimension_one`` we add: cdef size_t already_seen_next # = sum(f_vector[j] for j in range(dimension_two + 2)) - cdef BinaryList incidences = BinaryList() + cdef ListOfPairs incidences = ListOfPairs() if all_faces is None: raise ValueError("could not determine a list of all faces") From 6da7a3d501dcd9d43bdc25bc18dca2ab8b489d1e Mon Sep 17 00:00:00 2001 From: Rohan Garg <76916164+Sandstorm831@users.noreply.github.com> Date: Sun, 12 Feb 2023 22:28:34 +0530 Subject: [PATCH 296/392] corrected TeX maths in docstrings --- src/sage/combinat/posets/linear_extensions.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 3cfdee843b1..e17bd172108 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -256,14 +256,14 @@ def is_supergreedy(self): r"""" Return ``True`` if the linear extension is supergreedy. - A linear extension $[x_1 Date: Sun, 12 Feb 2023 17:39:59 +0000 Subject: [PATCH 297/392] tagged tests at long time --- src/sage/combinat/designs/difference_family.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index be6e029b0dc..c9949678e3f 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -2300,7 +2300,7 @@ def supplementary_difference_set(n, existence=False, check=True): EXAMPLES:: sage: from sage.combinat.designs.difference_family import supplementary_difference_set - sage: S1, S2, S3, S4 = supplementary_difference_set(191) + sage: S1, S2, S3, S4 = supplementary_difference_set(191) # long time If existence is ``True``, the function returns a boolean :: @@ -2313,7 +2313,7 @@ def supplementary_difference_set(n, existence=False, check=True): sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set sage: S1, S2, S3, S4 = supplementary_difference_set(191, check=False) - sage: is_supplementary_difference_set([S1, S2, S3, S4], 191, len(S1)+len(S2)+len(S3)+len(S4)-191) + sage: is_supplementary_difference_set([S1, S2, S3, S4], 191, len(S1)+len(S2)+len(S3)+len(S4)-191) # long time True sage: S1, S2, S3, S4 = supplementary_difference_set(37, check=False) sage: supplementary_difference_set(7) From 4fc747e217d8331c18e88b7b398bfb728f1f2425 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Sun, 12 Feb 2023 18:48:42 +0000 Subject: [PATCH 298/392] further fixes to supergreedy docstring --- src/sage/combinat/posets/linear_extensions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index e17bd172108..1a90a411b59 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -257,16 +257,16 @@ def is_supergreedy(self): Return ``True`` if the linear extension is supergreedy. A linear extension `[x_1 Date: Sun, 12 Feb 2023 14:01:51 -0800 Subject: [PATCH 299/392] src/sage/rings/polynomial/infinite_polynomial_element.py: Use InfinitePolynomial in isinstance tests, not InfinitePolynomial_sparse --- src/sage/rings/polynomial/infinite_polynomial_element.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index a56e4f1a1b4..b51ba3fe08a 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -1267,14 +1267,14 @@ def __call__(self, *args, **kwargs): V = [] for kw in kwargs: value = kwargs[kw] - if isinstance(value, InfinitePolynomial_sparse): + if isinstance(value, InfinitePolynomial): kwargs[kw] = value._p V.append(kw) if hasattr(value._p, 'variables'): V.extend([str(x) for x in value._p.variables()]) args = list(args) for i, arg in enumerate(args): - if isinstance(arg, InfinitePolynomial_sparse): + if isinstance(arg, InfinitePolynomial): args[i] = arg._p if hasattr(arg._p, 'variables'): V.extend([str(x) for x in arg._p.variables()]) @@ -1550,11 +1550,11 @@ def __call__(self, *args, **kwargs): # Replace any InfinitePolynomials by their underlying polynomials for kw in kwargs: value = kwargs[kw] - if isinstance(value, InfinitePolynomial_sparse): + if isinstance(value, InfinitePolynomial): kwargs[kw] = value._p args = list(args) for i, arg in enumerate(args): - if isinstance(arg, InfinitePolynomial_sparse): + if isinstance(arg, InfinitePolynomial): args[i] = arg._p self._p = self.parent().polynomial_ring()(self._p) res = self._p(*args, **kwargs) From 118f756ddd5baff0974d46c9dc8192ae7e5a352c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 12 Feb 2023 15:36:22 -0800 Subject: [PATCH 300/392] tox.ini: Fix up --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 72fbcefe294..9ce8086648b 100644 --- a/tox.ini +++ b/tox.ini @@ -647,7 +647,7 @@ commands = docker-{arm64,armhf}: docker run --rm --privileged multiarch/qemu-user-static:register --reset docker: bash -c 'if [ x"{env:DOCKER_CONFIG_FILE:}" != x ]; then mkdir -p {envdir}/.docker && ln -sf $(realpath "{env:DOCKER_CONFIG_FILE:}") {envdir}/.docker/; fi' docker: bash -c 'for docker_target in {env:DOCKER_TARGETS:with-targets}; do \ - docker: BUILD_IMAGE_STEM=sage-$(echo {envname} | sed "s/-docker//;s/-incremental//"); \ + docker: BUILD_IMAGE_STEM=sage-$(echo {envname} | sed "s/docker-//;s/-incremental//"); \ docker: BUILD_IMAGE=$DOCKER_PUSH_REPOSITORY$BUILD_IMAGE_STEM-$docker_target; \ docker: BUILD_TAG=$(git describe --dirty --always); \ docker: TAG_ARGS=$(for tag in $BUILD_TAG {env:EXTRA_DOCKER_TAGS:}; do echo --tag $BUILD_IMAGE:$tag; done); \ From 8f5b79901fa874f60f308b4ee93ac867de8fd784 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Sun, 12 Feb 2023 20:47:15 -0800 Subject: [PATCH 301/392] src/sage/combinat/schubert_polynomial.py: Use isinstance(..., InfinitePolynomial) without _sparse --- src/sage/combinat/schubert_polynomial.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/schubert_polynomial.py b/src/sage/combinat/schubert_polynomial.py index 8650e599300..a0a682e985e 100644 --- a/src/sage/combinat/schubert_polynomial.py +++ b/src/sage/combinat/schubert_polynomial.py @@ -80,7 +80,7 @@ from sage.misc.cachefunc import cached_method from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ -from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial_sparse +from sage.rings.polynomial.infinite_polynomial_element import InfinitePolynomial from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.multi_polynomial import MPolynomial import sage.libs.symmetrica.all as symmetrica @@ -463,7 +463,7 @@ def _element_constructor_(self, x): return self._from_dict({perm: self.base_ring().one()}) elif isinstance(x, MPolynomial): return symmetrica.t_POLYNOM_SCHUBERT(x) - elif isinstance(x, InfinitePolynomial_sparse): + elif isinstance(x, InfinitePolynomial): R = x.polynomial().parent() # massage the term order to be what symmetrica expects S = PolynomialRing(R.base_ring(), From 2f127ffa65568d02e4c372c03f27e8e2765b1f65 Mon Sep 17 00:00:00 2001 From: Sebastian Oehms Date: Fri, 10 Feb 2023 18:52:52 +0100 Subject: [PATCH 302/392] add_note_to_knotinfo_interface initial --- src/sage/knots/knotinfo.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index cefd33aebb4..a17f0f7e3b7 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -48,6 +48,12 @@ the according documentation of :meth:`KnotInfoBase.homfly_polynomial`, :meth:`KnotInfoBase.jones_polynomial` and :meth:`KnotInfoBase.alexander_polynomial`. +Furthermore, note that not all columns available in the database are visible on the web +pages (see also the related note under :meth:`KnotInfoBase.khovanov_polynomial`). +It is planned to remove non-visible columns from the database in the future (see +the `Python Wrapper `__ for +updated information). + EXAMPLES:: sage: from sage.knots.knotinfo import KnotInfo @@ -1714,9 +1720,23 @@ def khovanov_polynomial(self, var1='q', var2='t', base_ring=ZZ, original=False): .. NOTE :: - The Khovanov polynomial given in KnotInfo corresponds to the mirror - image of the given knot for a `list of 140 exceptions - `__. + The data used here were calculated with the program + `KhoHo `__. They are no longer + visible on the website as of October 30, 2022. Instead, data + computed with `KnotJob `__ + are now displayed. The latter program is more accurate in terms of + orientation and reflection as it is based on `PD` code. + + Even if they are not visible on the website, the data produced by + `KhoHo` are still available in the database. But maybe this will be + discontinued (check out the `Python wrapper `__ for updated information). + This interface will be adapted to the changes in an upcoming + release. + + Since the results of `KhoHo` were computed using the `DT` notation, + the Khovanov polynomial returned by this method belongs to the + mirror image of the given knot for a `list of 140 exceptions + `__. EXAMPLES:: From 9e3ebef4b2f6f5d0266d32b1563cb179ad8e25bd Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 13 Feb 2023 08:02:40 +0100 Subject: [PATCH 303/392] fix typos --- src/sage/knots/knotinfo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py index a17f0f7e3b7..8233b55077f 100644 --- a/src/sage/knots/knotinfo.py +++ b/src/sage/knots/knotinfo.py @@ -1725,15 +1725,15 @@ def khovanov_polynomial(self, var1='q', var2='t', base_ring=ZZ, original=False): visible on the website as of October 30, 2022. Instead, data computed with `KnotJob `__ are now displayed. The latter program is more accurate in terms of - orientation and reflection as it is based on `PD` code. + orientation and reflection as it is based on ``PD`` code. Even if they are not visible on the website, the data produced by - `KhoHo` are still available in the database. But maybe this will be + ``KhoHo`` are still available in the database. But maybe this will be discontinued (check out the `Python wrapper `__ for updated information). This interface will be adapted to the changes in an upcoming release. - Since the results of `KhoHo` were computed using the `DT` notation, + Since the results of ``KhoHo`` were computed using the ``DT`` notation, the Khovanov polynomial returned by this method belongs to the mirror image of the given knot for a `list of 140 exceptions `__. From bb8cc91edf2b789fa42fb7191b6a1e8969431b5f Mon Sep 17 00:00:00 2001 From: Rohan Garg <76916164+Sandstorm831@users.noreply.github.com> Date: Mon, 13 Feb 2023 17:56:40 +0530 Subject: [PATCH 304/392] Added Reference --- src/sage/combinat/posets/linear_extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 1a90a411b59..502ccb4cf23 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -268,6 +268,7 @@ def is_supergreedy(self): Informally, a linear extension is supergreedy if it "always goes up and receedes the least"; in other words, supergreedy linear extensions are depth-first linear extensions. + `Reference `_ EXAMPLES:: From dc3009a0c780e328d4ed18ef36d86a602e1168d1 Mon Sep 17 00:00:00 2001 From: Martin Rubey Date: Mon, 13 Feb 2023 15:27:54 +0100 Subject: [PATCH 305/392] reviewer's suggestions --- src/sage/combinat/bijectionist.py | 145 +++++++++++++++++------------- 1 file changed, 83 insertions(+), 62 deletions(-) diff --git a/src/sage/combinat/bijectionist.py b/src/sage/combinat/bijectionist.py index 6e756891977..8844f4f3602 100644 --- a/src/sage/combinat/bijectionist.py +++ b/src/sage/combinat/bijectionist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- r""" A bijectionist's toolkit @@ -36,9 +35,23 @@ A guided tour ============= -EXAMPLES: +Consider the following combinatorial statistics on a permutation: -We find a statistic `s` such that `(s, wex, fix) \sim (llis, des, adj)`:: + * `wex`, the number of weak excedences, + * `fix`, the number of fixed points, + * `des`, the number of descents (after appending `0`), + * `adj`, the number of adjacencies (after appending `0`), and + * `llis`, the length of a longest increasing subsequence. + +Moreover, let `rot` be action of rotation on a permutation, i.e., the +conjugation with the long cycle. + +It is known that there must exist a statistic `s` on permutations, +which is equidistributed with `llis` but additionally invariant under +`rot`. However, at least very small cases do not contradict the +possibility that one can even find a statistic `s`, invariant under +`rot` and such that `(s, wex, fix) \sim (llis, des, adj)`. Let us +check this for permutations of size at most `3`:: sage: N = 3 sage: A = B = [pi for n in range(N+1) for pi in Permutations(n)] @@ -119,15 +132,17 @@ [3, 1, 2]: 3, [3, 2, 1]: 2} -There is no rotation invariant statistic on non crossing set partitions which -is equidistributed with the Strahler number on ordered trees:: +On the other hand, we can check that there is no rotation invariant +statistic on non-crossing set partitions which is equidistributed +with the Strahler number on ordered trees:: - sage: N = 8; + sage: N = 8 sage: A = [SetPartition(d.to_noncrossing_partition()) for n in range(N) for d in DyckWords(n)] sage: B = [t for n in range(1, N+1) for t in OrderedTrees(n)] sage: def theta(m): return SetPartition([[i % m.size() + 1 for i in b] for b in m]) -The following code is equivalent to ``tau = findstat(397)``:: +Code for the Strahler number can be obtained from FindStat. The +following code is equivalent to ``tau = findstat(397)``:: sage: def tau(T): ....: if len(T) == 0: @@ -146,7 +161,8 @@ sage: list(bij.solutions_iterator()) [] -An example identifying `s` and `S`:: +Next we demonstrate how to search for a bijection, instead An example +identifying `s` and `S`:: sage: N = 4 sage: A = [dyck_word for n in range(1, N) for dyck_word in DyckWords(n)] @@ -342,7 +358,7 @@ sage: bij = Bijectionist(A, B, tau, value_restrictions=((Permutation([1, 2]), [4, 5]),)) Traceback (most recent call last): ... - ValueError: No possible values found for singleton block [[1, 2]] + ValueError: no possible values found for singleton block [[1, 2]] """ # **************************************************************************** @@ -392,9 +408,8 @@ class Bijectionist(SageObject): - ``pi_rho`` -- (optional) a list of triples ``(k, pi, rho)``, where - - ``pi`` -- a ``k``-ary operation composing objects in ``A`` and - - - ``rho`` -- a ``k``-ary function composing statistic values in `Z` + * ``pi`` -- a ``k``-ary operation composing objects in ``A`` and + * ``rho`` -- a ``k``-ary function composing statistic values in ``Z`` - ``elements_distributions`` -- (optional) a list of pairs ``(tA, tZ)``, specifying the distributions of ``tA`` @@ -468,7 +483,7 @@ class Bijectionist(SageObject): methods a second time overrides the previous specification. """ - def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], + def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=None, pi_rho=tuple(), phi_psi=tuple(), Q=None, elements_distributions=tuple(), value_restrictions=tuple(), solver=None, key=None): @@ -518,6 +533,8 @@ def __init__(self, A, B, tau=None, alpha_beta=tuple(), P=[], except TypeError: self._Z = list(self._Z) self._sorter["Z"] = lambda x: list(x) + if P is None: + P = [] # set optional inputs self.set_statistics(*alpha_beta) @@ -701,7 +718,7 @@ def set_statistics(self, *alpha_beta): sage: bij.set_statistics((wex, fix)) Traceback (most recent call last): ... - ValueError: Statistics alpha and beta are not equidistributed! + ValueError: statistics alpha and beta are not equidistributed TESTS: @@ -750,13 +767,13 @@ def set_statistics(self, *alpha_beta): for b in self._B: v = self._beta(b) if v not in self._statistics_fibers: - raise ValueError(f"Statistics alpha and beta do not have the same image, {v} is not a value of alpha, but of beta!") + raise ValueError(f"statistics alpha and beta do not have the same image, {v} is not a value of alpha, but of beta") self._statistics_fibers[v][1].append(b) # check compatibility if not all(len(fiber[0]) == len(fiber[1]) for fiber in self._statistics_fibers.values()): - raise ValueError("Statistics alpha and beta are not equidistributed!") + raise ValueError("statistics alpha and beta are not equidistributed") self._W = list(self._statistics_fibers) @@ -810,8 +827,8 @@ def statistics_table(self, header=True): INPUT: - - ``header`` -- (optional, default: ``True``) whether to include a - header with the standard greek letters. + - ``header`` -- (default: ``True``) whether to include a + header with the standard Greek letters OUTPUT: @@ -1023,7 +1040,7 @@ def set_value_restrictions(self, *value_restrictions): sage: bij._compute_possible_block_values() Traceback (most recent call last): ... - ValueError: No possible values found for singleton block [[1, 2]] + ValueError: no possible values found for singleton block [[1, 2]] sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: tau = Permutation.longest_increasing_subsequence_length @@ -1033,7 +1050,7 @@ def set_value_restrictions(self, *value_restrictions): sage: bij._compute_possible_block_values() Traceback (most recent call last): ... - ValueError: No possible values found for block [[1, 2], [2, 1]] + ValueError: no possible values found for block [[1, 2], [2, 1]] sage: A = B = [permutation for n in range(4) for permutation in Permutations(n)] sage: tau = Permutation.longest_increasing_subsequence_length @@ -1041,7 +1058,7 @@ def set_value_restrictions(self, *value_restrictions): sage: bij.set_value_restrictions(((1, 2), [4, 5, 6])) Traceback (most recent call last): ... - AssertionError: Element (1, 2) was not found in A + AssertionError: element (1, 2) was not found in A """ # it might be much cheaper to construct the sets as subsets @@ -1052,7 +1069,7 @@ def set_value_restrictions(self, *value_restrictions): set_Z = set(self._Z) self._restrictions_possible_values = {a: set_Z for a in self._A} for a, values in value_restrictions: - assert a in self._A, f"Element {a} was not found in A" + assert a in self._A, f"element {a} was not found in A" self._restrictions_possible_values[a] = self._restrictions_possible_values[a].intersection(values) def _compute_possible_block_values(self): @@ -1073,7 +1090,7 @@ def _compute_possible_block_values(self): sage: bij._compute_possible_block_values() Traceback (most recent call last): ... - ValueError: No possible values found for singleton block [[1, 2]] + ValueError: no possible values found for singleton block [[1, 2]] """ self._possible_block_values = {} # P -> Power(Z) @@ -1083,9 +1100,9 @@ def _compute_possible_block_values(self): self._possible_block_values[p] = _non_copying_intersection(sets) if not self._possible_block_values[p]: if len(block) == 1: - raise ValueError(f"No possible values found for singleton block {block}") + raise ValueError(f"no possible values found for singleton block {block}") else: - raise ValueError(f"No possible values found for block {block}") + raise ValueError(f"no possible values found for block {block}") def set_distributions(self, *elements_distributions): r""" @@ -1229,11 +1246,11 @@ def set_distributions(self, *elements_distributions): sage: bij.set_distributions(([Permutation([1, 2, 3, 4])], [1])) Traceback (most recent call last): ... - ValueError: Element [1, 2, 3, 4] was not found in A! + ValueError: element [1, 2, 3, 4] was not found in A sage: bij.set_distributions(([Permutation([1, 2, 3])], [-1])) Traceback (most recent call last): ... - ValueError: Value -1 was not found in tau(A)! + ValueError: value -1 was not found in tau(A) Note that the same error occurs when an element that is not the first element of the list is not in `A`. @@ -1244,9 +1261,9 @@ def set_distributions(self, *elements_distributions): assert len(tA) == len(tZ), f"{tA} and {tZ} are not of the same size!" for a, z in zip(tA, tZ): if a not in self._A: - raise ValueError(f"Element {a} was not found in A!") + raise ValueError(f"element {a} was not found in A") if z not in self._Z: - raise ValueError(f"Value {z} was not found in tau(A)!") + raise ValueError(f"value {z} was not found in tau(A)") self._elements_distributions = tuple(elements_distributions) def set_intertwining_relations(self, *pi_rho): @@ -1442,7 +1459,7 @@ def set_homomesic(self, Q): INPUT: - - ``Q`` -- a set partition of ``A``. + - ``Q`` -- a set partition of ``A`` EXAMPLES:: @@ -1698,15 +1715,15 @@ def possible_values(self, p=None, optimal=False): INPUT: - - ``p`` -- (optional, default: ``None``) -- a block of `P`, or - an element of a block of `P`, or a list of these + - ``p`` -- (optional) a block of `P`, or an element of a + block of `P`, or a list of these - - ``optimal`` -- (optional, default: ``False``) whether or - not to compute the minimal possible set of statistic values. + - ``optimal`` -- (default: ``False``) whether or not to + compute the minimal possible set of statistic values .. NOTE:: - computing the minimal possible set of statistic values + Computing the minimal possible set of statistic values may be computationally expensive. .. TODO:: @@ -1758,6 +1775,7 @@ def possible_values(self, p=None, optimal=False): {'a': {0, 1}, 'b': {0, 1}} sage: bij.possible_values(p="a", optimal=True) {'a': set(), 'b': set()} + """ # convert input to set of block representatives blocks = set() @@ -1896,15 +1914,16 @@ def _find_counterexample(self, P, s0, d, on_blocks): INPUT: - - ``P``, the representatives of the blocks, or `A` if - ``on_blocks`` is ``False``. + - ``P`` -- the representatives of the blocks, or `A` if + ``on_blocks`` is ``False`` - - ``s0``, a solution. + - ``s0`` -- a solution - - ``d``, a subset of `A`, in the form of a dict from `A` to `\{0, 1\}`. + - ``d`` -- a subset of `A`, in the form of a dict from `A` to + `\{0, 1\}` - - ``on_blocks``, whether to return the counter example on - blocks or on elements. + - ``on_blocks`` -- whether to return the counterexample on + blocks or on elements EXAMPLES:: @@ -1920,6 +1939,7 @@ def _find_counterexample(self, P, s0, d, on_blocks): sage: d = {'a': 1, 'b': 0, 'c': 0, 'd': 0, 'e': 0} sage: bij._find_counterexample(bij._A, s0, d, False) {'a': 2, 'b': 2, 'c': 1, 'd': 3, 'e': 1} + """ bmilp = self._bmilp for z in self._Z: @@ -2446,8 +2466,9 @@ def solutions_iterator(self): class _BijectionistMILP(): r""" - Wrapper class for the MixedIntegerLinearProgram (MILP). This - class is used to manage the MILP, add constraints, solve the + Wrapper class for the MixedIntegerLinearProgram (MILP). + + This class is used to manage the MILP, add constraints, solve the problem and check for uniqueness of solution values. """ def __init__(self, bijectionist: Bijectionist, solutions=None): @@ -2458,10 +2479,10 @@ def __init__(self, bijectionist: Bijectionist, solutions=None): - ``bijectionist`` -- an instance of :class:`Bijectionist`. - - ``solutions`` -- (optional, default: ``None``) a list of solutions of - the problem, each provided as a dictionary mapping `(a, z)` to a - Boolean, such that at least one element from each block of `P` - appears as `a`. + - ``solutions`` -- (optional) a list of solutions of the + problem, each provided as a dictionary mapping `(a, z)` to + a boolean, such that at least one element from each block + of `P` appears as `a`. .. TODO:: @@ -2585,8 +2606,8 @@ def _prepare_solution(self, on_blocks, solution): INPUT: - - ``on_blocks``, whether to return the solution on blocks or - on all elements + - ``on_blocks`` -- whether to return the solution on blocks + or on all elements TESTS:: @@ -2621,10 +2642,10 @@ def solutions_iterator(self, on_blocks, additional_constraints): INPUT: - ``additional_constraints`` -- a list of constraints for the - underlying MILP. + underlying MILP - ``on_blocks``, whether to return the solution on blocks or - on all elements. + on all elements TESTS:: @@ -2689,8 +2710,8 @@ def _add_solution(self, solution): INPUT: - - ``solution``, a dictionary from the indices of the MILP to - Boolean. + - ``solution`` -- a dictionary from the indices of the MILP to + a boolean EXAMPLES:: @@ -2727,10 +2748,10 @@ def _is_solution(self, constraint, values): INPUT: - - ``constraint``, a + - ``constraint`` -- a :class:`sage.numerical.linear_functions.LinearConstraint`. - - ``values``, a candidate for a solution of the MILP as a + - ``values`` -- a candidate for a solution of the MILP as a dictionary from pairs `(a, z)\in A\times Z` to `0` or `1`, specifying whether `a` is mapped to `z`. @@ -2750,7 +2771,7 @@ def _is_solution(self, constraint, values): """ index_block_value_dict = {} for (p, z), v in self._x.items(): - variable_index = next(iter(v.dict().keys())) + variable_index = next(iter(v.dict())) index_block_value_dict[variable_index] = (p, z) def evaluate(f): @@ -2793,8 +2814,8 @@ def add_alpha_beta_constraints(self): W = self._bijectionist._W Z = self._bijectionist._Z zero = self.milp.linear_functions_parent().zero() - AZ_matrix = [[zero]*len(W) for _ in range(len(Z))] - B_matrix = [[zero]*len(W) for _ in range(len(Z))] + AZ_matrix = [[zero] * len(W) for _ in range(len(Z))] + B_matrix = [[zero] * len(W) for _ in range(len(Z))] W_dict = {w: i for i, w in enumerate(W)} Z_dict = {z: i for i, z in enumerate(Z)} @@ -2922,7 +2943,7 @@ def add_intertwining_relation_constraints(self): P = self._bijectionist._P for composition_index, pi_rho in enumerate(self._bijectionist._pi_rho): pi_blocks = set() - for a_tuple in itertools.product(*([A]*pi_rho.numargs)): + for a_tuple in itertools.product(A, repeat=pi_rho.numargs): if pi_rho.domain is not None and not pi_rho.domain(*a_tuple): continue a = pi_rho.pi(*a_tuple) @@ -3048,7 +3069,7 @@ def _invert_dict(d): INPUT: - - ``d``, a ``dict``. + - ``d`` -- a dict EXAMPLES:: @@ -3071,7 +3092,7 @@ def _disjoint_set_roots(d): INPUT: - - ``d``, a ``sage.sets.disjoint_set.DisjointSet_of_hashables`` + - ``d`` -- a :class:`sage.sets.disjoint_set.DisjointSet_of_hashables` EXAMPLES:: @@ -3125,7 +3146,7 @@ def _non_copying_intersection(sets): ....: [(3,i,j) for i in [-2,-1,0,1,2] for j in [-1,1]]] Note that adding ``[(2,-2,-1), (2,2,-1), (2,-2,1), (2,2,1)]`` makes -it take (seemingly) forever.:: +it take (seemingly) forever:: sage: def c1(a, b): return (a[0]+b[0], a[1]*b[1], a[2]*b[2]) sage: def c2(a): return (a[0], -a[1], a[2]) From 0d3ec5e28433ed8859f39a0bddc3b24b466913a0 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Mon, 13 Feb 2023 15:44:29 +0000 Subject: [PATCH 306/392] Fix docstrings --- src/sage/combinat/designs/difference_family.py | 6 +++--- src/sage/combinat/matrices/hadamard_matrix.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index c9949678e3f..61653c14685 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -2272,18 +2272,18 @@ def generate_set(index_set, cosets): def supplementary_difference_set(n, existence=False, check=True): - r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where skew and `n_1 + n_2 + n_3 + n_4 = n+\lambda`. + r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. These sets are constructed from available data, as described in [Djo1994a]_. - The data for `n=191` is taken from [Djo2008c]_, and date for `n=239` is from [Djo1994b]_. + The data for `n=191` is taken from [Djo2008c]_, and data for `n=239` is from [Djo1994b]_. Additional SDS are constructed using :func:`skew_supplementary_difference_set`. INPUT: - ``n`` -- integer, the parameter of the supplementary difference set. - - ``existence`` -- boolean (dafault False). If true, only check whether the + - ``existence`` -- boolean (default False). If true, only check whether the supplementary difference sets can be constructed. - ``check`` -- boolean (default True). If true, check that the sets are diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 760d73e6f86..b65a412ab63 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -180,7 +180,7 @@ def symmetric_conference_matrix_paley(n): and 1s and -1s elsewhere, satisfying `CC^\top=(n-1)I`. This construction assumes that `q = n-1` is a prime power, with `q \cong 1 \mod 4`. See [Hora]_ or [Lon2013]_. - These matrices are used in the :func:`hadamard_matrix_paleyII`. + These matrices are used in :func:`hadamard_matrix_paleyII`. INPUT: @@ -300,7 +300,7 @@ def hadamard_matrix_miyamoto_construction(n, existence=False, check=True): If ``existence`` is false, returns the Hadamard matrix of order `n`. It raises an error if no data is available to construct the matrix of the given order, - or if `n` is not a multiple of `4`. + or if `n` does not satisfies the constraints. If ``existence`` is true, returns a boolean representing whether the matrix can be constructed or not. @@ -338,7 +338,7 @@ def hadamard_matrix_miyamoto_construction(n, existence=False, check=True): q = n // 4 if existence: - return is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True) + return is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True) is True if not (is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True)): raise ValueError(f'The order {n} is not covered by Miyamoto construction.') @@ -921,7 +921,7 @@ def hadamard_matrix_from_sds(n, existence=False, check=True): Given four SDS with parameters `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` with `n_1 + n_2 + n_3 + n_4 = n+\lambda` we can construct four (-1,+1) sequences `a_i = (a_{i,0},...,a_{i,n-1})` - where `a_{i,j} = -1` iff `j \in S_i`. This will be the fist rows of four circulant + where `a_{i,j} = -1` iff `j \in S_i`. These will be the fist rows of four circulant matrices `A_1, A_2, A_3, A_4` which, when plugged into the Goethals-Seidel array, create an Hadamard matrix of order `4n` (see [Djo1994b]_). From 2367bc9685579e0a554a1371f6b919e532ab6845 Mon Sep 17 00:00:00 2001 From: Jonathan Kliem Date: Mon, 13 Feb 2023 20:27:15 +0100 Subject: [PATCH 307/392] improvements suggested by reviewer --- src/sage/data_structures/list_of_pairs.pyx | 5 +++- .../combinatorial_polyhedron/base.pyx | 27 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/sage/data_structures/list_of_pairs.pyx b/src/sage/data_structures/list_of_pairs.pyx index 03ad6268277..29e91492ced 100644 --- a/src/sage/data_structures/list_of_pairs.pyx +++ b/src/sage/data_structures/list_of_pairs.pyx @@ -41,6 +41,9 @@ cdef class ListOfPairs: self.length += 1 cdef inline pair_s* get(self, size_t index) except NULL: + """ + Return a pointer to a pair of the list corresponding to the ``index``. + """ if not (0 <= index < self.length): raise IndexError @@ -100,7 +103,7 @@ cdef class ListOfPairs: cdef size_t first, second (first, second) = value - if (index == self.length): + if index == self.length: self.add(first, second) return diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index 62ee57c25ff..1d5334a97b3 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -1461,18 +1461,15 @@ cdef class CombinatorialPolyhedron(SageObject): cdef pair_s ridge if add_equations and names: - def get_ridge(size_t i): - ridge = self._ridges.get(i)[0] - return ((f(ridge.first),) + self.equations(), - (f(ridge.second),) + self.equations()) - + return tuple( + ((f(self._ridges.get(i)[0].first),) + self.equations(), + (f(self._ridges.get(i)[0].second),) + self.equations()) + for i in range (n_ridges)) else: - def get_ridge(size_t i): - ridge = self._ridges.get(i)[0] - return (f(ridge.first), f(ridge.second)) - - cdef size_t j - return tuple(get_ridge(j) for j in range(n_ridges)) + return tuple( + (f(self._ridges.get(i)[0].first), + f(self._ridges.get(i)[0].second)) + for i in range (n_ridges)) @cached_method def facet_adjacency_matrix(self, algorithm=None): @@ -2110,7 +2107,7 @@ cdef class CombinatorialPolyhedron(SageObject): # For each face in the iterator, check if its a simplex. face_iter.structure.lowest_dimension = 2 # every 1-face is a simplex d = face_iter.next_dimension() - while (d < dim): + while d < dim: sig_check() if face_iter.n_atom_rep() == d + 1: # The current face is a simplex. @@ -2221,7 +2218,7 @@ cdef class CombinatorialPolyhedron(SageObject): # For each coface in the iterator, check if its a simplex. coface_iter.structure.lowest_dimension = 2 # every coface of dimension 1 is a simplex d = coface_iter.next_dimension() - while (d < dim): + while d < dim: sig_check() if coface_iter.n_atom_rep() == d + 1: # The current coface is a simplex. @@ -3721,13 +3718,13 @@ cdef class CombinatorialPolyhedron(SageObject): dimension_one = 0 if dim > -1: - while (f_vector[dimension_one + 1] == 0): + while f_vector[dimension_one + 1] == 0: # Taking care of cases, where there might be no faces # of dimension 0, 1, etc (``n_lines > 0``). dimension_one += 1 dimension_two = -1 - while (dimension_one < dim + 1): + while dimension_one < dim + 1: already_seen = sum(f_vector[j] for j in range(dimension_two + 1)) already_seen_next = already_seen + f_vector[dimension_two + 1] From 98974d57db4e76bece9f13d87e5d3083e72a3cc7 Mon Sep 17 00:00:00 2001 From: Jonathan Kliem Date: Mon, 13 Feb 2023 20:40:21 +0100 Subject: [PATCH 308/392] use attribute access to derefence --- src/sage/data_structures/list_of_pairs.pyx | 10 ++--- .../combinatorial_polyhedron/base.pyx | 44 ++++++++----------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/sage/data_structures/list_of_pairs.pyx b/src/sage/data_structures/list_of_pairs.pyx index 29e91492ced..258320353a0 100644 --- a/src/sage/data_structures/list_of_pairs.pyx +++ b/src/sage/data_structures/list_of_pairs.pyx @@ -72,7 +72,7 @@ cdef class ListOfPairs: ... OverflowError: can't convert negative value to size_t """ - cdef pair_s pair = self.get(index)[0] + cdef pair_s* pair = self.get(index) return (smallInteger(pair.first), smallInteger(pair.second)) def __setitem__(self, size_t index, value): @@ -108,8 +108,8 @@ cdef class ListOfPairs: return cdef pair_s* pair_pt = self.get(index) - pair_pt[0].first = first - pair_pt[0].second = second + pair_pt.first = first + pair_pt.second = second cdef inline int add(self, size_t first, size_t second) except -1: """ @@ -117,5 +117,5 @@ cdef class ListOfPairs: """ self.enlarge() cdef pair_s* last = self.get(self.length - 1) - last[0].first = first - last[0].second = second + last.first = first + last.second = second diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index 1d5334a97b3..f88585f1d4a 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -102,9 +102,8 @@ from .conversions \ from .conversions cimport Vrep_list_to_bit_rep from sage.misc.cachefunc import cached_method -from sage.data_structures.list_of_pairs cimport pair_s -from sage.rings.integer cimport smallInteger -from cysignals.signals cimport sig_check +from sage.rings.integer cimport smallInteger +from cysignals.signals cimport sig_check from .face_data_structure cimport face_len_atoms, face_init, face_free from .face_iterator cimport iter_t, parallel_f_vector @@ -1232,15 +1231,10 @@ cdef class CombinatorialPolyhedron(SageObject): def f(size_t i): return smallInteger(i) - # Getting the indices of the `i`-th edge. - cdef pair_s edge - - def get_edge(size_t i): - edge = self._edges.get(i)[0] - return (f(edge.first), f(edge.second)) - cdef size_t j - return tuple(get_edge(j) for j in range(self._edges.length)) + return tuple((f(self._edges.get(j).first), + f(self._edges.get(j).second)) + for j in range(self._edges.length)) def vertex_graph(self, names=True, algorithm=None): r""" @@ -1328,14 +1322,14 @@ cdef class CombinatorialPolyhedron(SageObject): from sage.matrix.constructor import matrix cdef Matrix_dense adjacency_matrix = matrix( ZZ, self.n_Vrepresentation(), self.n_Vrepresentation(), 0) - cdef size_t i - cdef pair_s edge + cdef size_t i, first, second self._compute_edges(self._algorithm_to_dual(algorithm)) for i in range(self._edges.length): - edge = self._edges.get(i)[0] - adjacency_matrix.set_unsafe_int(edge.first, edge.second, 1) - adjacency_matrix.set_unsafe_int(edge.second, edge.first, 1) + first = self._edges.get(i).first + second = self._edges.get(i).second + adjacency_matrix.set_unsafe_int(first, second, 1) + adjacency_matrix.set_unsafe_int(second, first, 1) adjacency_matrix.set_immutable() return adjacency_matrix @@ -1458,17 +1452,15 @@ cdef class CombinatorialPolyhedron(SageObject): def f(size_t i): return smallInteger(i) - cdef pair_s ridge - if add_equations and names: return tuple( - ((f(self._ridges.get(i)[0].first),) + self.equations(), - (f(self._ridges.get(i)[0].second),) + self.equations()) + ((f(self._ridges.get(i).first),) + self.equations(), + (f(self._ridges.get(i).second),) + self.equations()) for i in range (n_ridges)) else: return tuple( - (f(self._ridges.get(i)[0].first), - f(self._ridges.get(i)[0].second)) + (f(self._ridges.get(i).first), + f(self._ridges.get(i).second)) for i in range (n_ridges)) @cached_method @@ -1514,13 +1506,13 @@ cdef class CombinatorialPolyhedron(SageObject): cdef Matrix_dense adjacency_matrix = matrix( ZZ, self.n_facets(), self.n_facets(), 0) cdef size_t i - cdef pair_s ridge self._compute_ridges(self._algorithm_to_dual(algorithm)) for i in range(self._ridges.length): - ridge = self._ridges.get(i)[0] - adjacency_matrix.set_unsafe_int(ridge.first, ridge.second, 1) - adjacency_matrix.set_unsafe_int(ridge.second, ridge.first, 1) + first = self._ridges.get(i).first + second = self._ridges.get(i).second + adjacency_matrix.set_unsafe_int(first, second, 1) + adjacency_matrix.set_unsafe_int(second, first, 1) adjacency_matrix.set_immutable() return adjacency_matrix From 40db71da2dd06f7793d78e0d68ca171ec752a574 Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Tue, 14 Feb 2023 18:54:25 +0530 Subject: [PATCH 309/392] adding reference --- src/doc/en/reference/references/index.rst | 4 ++++ src/sage/combinat/posets/linear_extensions.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 5a7eb0a163c..8b9cbe67583 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -3821,6 +3821,10 @@ REFERENCES: *Bethe ansatz and inverse scattering transform in a periodic box-ball system*, Nuclear Phys. B **747**, no. 3 (2006), 354--397. +.. [KTZ1987] Kierstead, H.A., Trotter, W.T. & Zhou, B. Representing an ordered + set as the intersection of super greedy linear extensions. Order 4, + 293-311 (1987). + :doi:`10.1007/BF00337892` .. [Kuh1987] \W. Kühnel, "Minimal triangulations of Kummer varieties", Abh. Math. Sem. Univ. Hamburg 57 (1987), 7-20. diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 502ccb4cf23..23d2688bd72 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -268,7 +268,7 @@ def is_supergreedy(self): Informally, a linear extension is supergreedy if it "always goes up and receedes the least"; in other words, supergreedy linear extensions are depth-first linear extensions. - `Reference `_ + For more details see [KTZ1987]_. EXAMPLES:: From 62ac140fddb5b34d944e5c2b2f88a92340e2790b Mon Sep 17 00:00:00 2001 From: Sanjay Rijal <37138338+zovelsanj@users.noreply.github.com> Date: Wed, 15 Feb 2023 11:06:51 +0545 Subject: [PATCH 310/392] reduced information Reduced `sage` and `sagemath` information as per [#35070](https://github.com/sagemath/sage/pull/35070#discussion_r1103826628) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3685bb8c4b3..40043bf7510 100644 --- a/README.md +++ b/README.md @@ -407,7 +407,7 @@ For installation of `sage` in python using `pip` you need to install `sagemath-s You need to install the `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels, you can install the sage library, `sagemath-standard`. -**NOTE:** You can find `sage` and `sagemath` pip packages but it is worth noting that when importing `sage` with these packages you will encounter `ModuleNotFoundError: No module named 'sage'`. Indeed `pip install sage` installs `sage 0.0.0` which is literally an empty package. +**NOTE:** You can find `sage` and `sagemath` pip packages but with these packages, you will encounter `ModuleNotFoundError`. Troubleshooting --------------- From ee2149d4bc810c0185adcad9478874533672e520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6ppe?= Date: Tue, 14 Feb 2023 21:59:11 -0800 Subject: [PATCH 311/392] README.md: Fix wording Co-authored-by: Alex J Best --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40043bf7510..dbc22093aca 100644 --- a/README.md +++ b/README.md @@ -405,7 +405,7 @@ For installation of `sage` in python using `pip` you need to install `sagemath-s $ python3 -m pip install $(sage-config SAGE_SPKG_WHEELS)/*.whl $ python3 -m pip install sagemath-standard -You need to install the `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels, you can install the sage library, `sagemath-standard`. +You need to install `sage_conf`, a wheelhouse of various python packages. You can list the wheels using `ls $(sage-config SAGE_SPKG_WHEELS)`. After manual installation of these wheels, you can install the sage library, `sagemath-standard`. **NOTE:** You can find `sage` and `sagemath` pip packages but with these packages, you will encounter `ModuleNotFoundError`. From 53cbf193a750cbc4bf39cf41afd2200632e37a55 Mon Sep 17 00:00:00 2001 From: Kryzar Date: Wed, 15 Feb 2023 17:13:16 +0100 Subject: [PATCH 312/392] (fix) Fix an indentation problem in the doc --- .../function_field/drinfeld_modules/drinfeld_module.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py index c2f05bbdf56..e38df609682 100644 --- a/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -267,10 +267,10 @@ class DrinfeldModule(Parent, UniqueRepresentation): sage: phi.j_invariant() # j-invariant 1 - A Drinfeld `\mathbb{F}_q[T]`-module can be seen as an Ore - polynomial with positive degree and constant coefficient - `\gamma(T)`, where `\gamma` is the base morphism. This analogy is - the motivation for the following methods:: + A Drinfeld `\mathbb{F}_q[T]`-module can be seen as an Ore polynomial + with positive degree and constant coefficient `\gamma(T)`, where + `\gamma` is the base morphism. This analogy is the motivation for + the following methods:: sage: phi.coefficients() [z, 1, 1] From 9aac7409cd45f387ab304ece9bdca1b344e6e3be Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Thu, 26 Jan 2023 18:11:47 +0000 Subject: [PATCH 313/392] Store Williamson type matrices as strings --- src/sage/combinat/matrices/hadamard_matrix.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index bf915399d20..df799d902ce 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -354,31 +354,29 @@ def williamson_type_quadruples_smallcases(n, existence=False): ValueError: The Williamson type quadruple of order 123 is not yet implemented. """ db = { - 1: ([1], [1], [1], [1]), - 7: ([1, -1, -1, 1, 1, -1, -1], - [1, -1, 1, -1, -1, 1, -1], - [1, 1, -1, -1, -1, -1, 1], - [1, -1, -1, -1, -1, -1, -1]), - 9: ([1, -1, -1, -1, 1, 1, -1, -1, -1], - [1, -1, -1, 1, -1, -1, 1, -1, -1], - [1, -1, 1, -1, -1, -1, -1, 1, -1], - [1, 1, -1, -1, -1, -1, -1, -1, 1]), - 29: ([1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1], - [1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1], - [1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1], - [1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1]), - 43: ([1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1], - [1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1], - [1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1], - [1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1]), + 1: ('+', '+', '+', '+'), + 7: ('+--++--', '+-+--+-', '++----+', '+------'), + 9: ('+---++---', '+--+--+--', '+-+----+-', '++------+'), + 29: ('+++---++--+-+----+-+--++---++', + '+-+---++--+-++++++-+--++---+-', + '++++-++-+---++++++---+-++-+++', + '++--+--+-+++-++++-+++-+--+--+'), + 43: ('++---++++-+--+--++--------++--+--+-++++---+', + '+++-+-++--+-+-++++-+----+-++++-+-+--++-+-++', + '++-++++++----+-+--++-++-++--+-+----++++++-+', + '+---++--++++-+-+++-++--++-+++-+-++++--++---'), } + def pmtoZ(s): + return [1 if x == '+' else -1 for x in s] + if existence: return n in db if n not in db: raise ValueError("The Williamson type quadruple of order %s is not yet implemented." % n) - a, b, c, d = map(vector, db[n]) + + a, b, c, d = map(lambda s: vector(pmtoZ(s)), db[n]) return a, b, c, d From 225770934c8e057c0a404beb748c8bb691bc73a4 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Thu, 26 Jan 2023 18:44:26 +0000 Subject: [PATCH 314/392] Add more williamson type matrices --- src/sage/combinat/matrices/hadamard_matrix.py | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index df799d902ce..3354952afd6 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -319,7 +319,7 @@ def williamson_type_quadruples_smallcases(n, existence=False): Williamson construction of Hadamard matrices. Namely, the function returns the first row of 4 `n\times n` circulant matrices with the properties described in :func:`sage.combinat.matrices.hadamard_matrix.hadamard_matrix_williamson_type`. - The matrices for n=29 and n=43 are given in [Ha83]_. + The matrices for `n = 3, 5, ..., 29, 37, 43` are given in [Ha83]_. INPUT: @@ -355,12 +355,32 @@ def williamson_type_quadruples_smallcases(n, existence=False): """ db = { 1: ('+', '+', '+', '+'), + 3: ('+++', '+--', '+--', '+--'), + 5: ('+-++-', '++--+', '+----', '+----'), 7: ('+--++--', '+-+--+-', '++----+', '+------'), 9: ('+---++---', '+--+--+--', '+-+----+-', '++------+'), - 29: ('+++---++--+-+----+-+--++---++', - '+-+---++--+-++++++-+--++---+-', - '++++-++-+---++++++---+-++-+++', - '++--+--+-+++-++++-+++-+--+--+'), + 11: ('++--------+', '++-+-++-+-+', '++-++--++-+', '+-++----++-'), + 13: ('++++-+--+-+++', '+---+-++-+---', '++---+--+---+', '++---+--+---+'), + 15: ('+-+---++++---+-', '++-++------++-+', + '++-++++--++++-+', '++-++-+--+-++-+'), + 17: ('+---+++----+++---', '++-+---+--+---+-+', + '+--+-++++++++-+--', '+-++-+++--+++-++-'), + 19: ('++--+++-+--+-+++--+', '++-++--+-++-+--++-+', + '+-+---++++++++---+-', '++--+-++++++++-+--+'), + 21: ('+--++++---++---++++--', '++++-+---+--+---+-+++', + '++--+-+-++--++-+-+--+', '++-+++++-+--+-+++++-+'), + 23: ('++---+---+-++-+---+---+', '+-++-++--++++++--++-++-', + '+++---++-+-++-+-++---++', '+++-+++-+------+-+++-++'), + 25: ('++++-+-+-+--++--+-+-+-+++', '++--+--+-++++++++-+--+--+', + '+++--+--++++--++++--+--++', '+-+--+++--++++++--+++--+-'), + 27: ('+--+--+-+++--++--+++-+--+--', '+++-++-+---++--++---+-++-++', + '+---+++++-+-++++-+-+++++---', '+---+++++-+-++++-+-+++++---'), + 29: ('+++---++--+-+----+-+--++---++', '+-+---++--+-++++++-+--++---+-', + '++++-++-+---++++++---+-++-+++', '++--+--+-+++-++++-+++-+--+--+'), + 37: ('+--+-+-+-++---+--++++--+---++-+-+-+--', + '+---++-++--+-+-++----++-+-+--++-++---', + '+++++-+-----++----++----++-----+-++++', + '+--+++-+-----+----++----+-----+-+++--'), 43: ('++---++++-+--+--++--------++--+--+-++++---+', '+++-+-++--+-+-++++-+----+-++++-+-+--++-+-++', '++-++++++----+-+--++-++-++--+-+----++++++-+', @@ -404,10 +424,10 @@ def williamson_hadamard_matrix_smallcases(n, existence=False, check=True): 116 x 116 dense matrix over Integer Ring... sage: williamson_hadamard_matrix_smallcases(172) 172 x 172 dense matrix over Integer Ring... - sage: williamson_hadamard_matrix_smallcases(100) + sage: williamson_hadamard_matrix_smallcases(1000) Traceback (most recent call last): ... - ValueError: The Williamson type Hadamard matrix of order 100 is not yet implemented. + ValueError: The Williamson type Hadamard matrix of order 1000 is not yet implemented. """ assert n % 4 == 0 From 9a9d661f80aadcb07e4572cf2d5da173199a713b Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Thu, 26 Jan 2023 19:28:08 +0000 Subject: [PATCH 315/392] Add other Williamson type matrices --- src/doc/en/reference/references/index.rst | 5 +++ src/sage/combinat/matrices/hadamard_matrix.py | 37 ++++++++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index cf75fe5eb36..1e218493aa9 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4118,6 +4118,11 @@ REFERENCES: of a genus 2 Jacobian*, Mathematics of Computation 88 (2019), 889-929. :doi:`10.1090/mcom/3358`. +.. [Lon2013] \S. London, + *Constructing New Turyn Type Sequences, T-Sequences and Hadamard Matrices*. + PhD Thesis, University of Illinois at Chicago, 2013. + https://hdl.handle.net/10027/9916 + .. [LOS2012] \C. Lecouvey, M. Okado, M. Shimozono. "Affine crystals, one-dimensional sums and parabolic Lusztig `q`-analogues". Mathematische Zeitschrift. **271** (2012). Issue 3-4. diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 3354952afd6..5f4c829684d 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -319,7 +319,8 @@ def williamson_type_quadruples_smallcases(n, existence=False): Williamson construction of Hadamard matrices. Namely, the function returns the first row of 4 `n\times n` circulant matrices with the properties described in :func:`sage.combinat.matrices.hadamard_matrix.hadamard_matrix_williamson_type`. - The matrices for `n = 3, 5, ..., 29, 37, 43` are given in [Ha83]_. + The matrices for `n = 3, 5, ..., 29, 37, 43` are given in [Ha83]_. The matrices + for `n = 31, 33, 39, 41, 45, 49, 51, 55, 57, 61, 63` are given in [Lon2013]_. INPUT: @@ -377,14 +378,32 @@ def williamson_type_quadruples_smallcases(n, existence=False): '+---+++++-+-++++-+-+++++---', '+---+++++-+-++++-+-+++++---'), 29: ('+++---++--+-+----+-+--++---++', '+-+---++--+-++++++-+--++---+-', '++++-++-+---++++++---+-++-+++', '++--+--+-+++-++++-+++-+--+--+'), - 37: ('+--+-+-+-++---+--++++--+---++-+-+-+--', - '+---++-++--+-+-++----++-+-+--++-++---', - '+++++-+-----++----++----++-----+-++++', - '+--+++-+-----+----++----+-----+-+++--'), - 43: ('++---++++-+--+--++--------++--+--+-++++---+', - '+++-+-++--+-+-++++-+----+-++++-+-+--++-+-++', - '++-++++++----+-+--++-++-++--+-+----++++++-+', - '+---++--++++-+-+++-++--++-+++-+-++++--++---'), + 31: ('++++++-+--+---++++---+--+-+++++', '+--++---+-+-++----++-+-+---++--', + '+--++---+-+-++----++-+-+---++--', '+-----+-++-+++----+++-++-+-----'), + 33: ('++++++-+-+-+++------+++-+-+-+++++', '++-+-++-+----+++--+++----+-++-+-+', + '++--++-+++-+--+-++-+--+-+++-++--+', '+--++--+++++-++----++-+++++--++--'), + 37: ('+--+-+-+-++---+--++++--+---++-+-+-+--', '+---++-++--+-+-++----++-+-+--++-++---', + '+++++-+-----++----++----++-----+-++++', '+--+++-+-----+----++----+-----+-+++--'), + 39: ('+++--+-+-----+--++----++--+-----+-+--++', '+++--++-+---+-+--+----+--+-+---+-++--++', + '++++---+--++----+-+--+-+----++--+---+++', '+---++-+-+-----+++-++-+++-----+-+-++---'), + 41: ('++++--+-++++-++--++----++--++-++++-+--+++', '++++--+-++++-++--++----++--++-++++-+--+++', + '+++-++-+-+-+-----+++--+++-----+-+-+-++-++', '+--+--+-+-+-+++++---++---+++++-+-+-+--+--'), + 43: ('++---++++-+--+--++--------++--+--+-++++---+', '+++-+-++--+-+-++++-+----+-++++-+-+--++-+-++', + '++-++++++----+-+--++-++-++--+-+----++++++-+', '+---++--++++-+-+++-++--++-+++-+-++++--++---'), + 45: ('+++++-++----+-++--++-++-++--++-+----++-++++', '+++---++--+-+-+-++--------++-+-+-+--++---++', + '++-+-++++-+--+--+++--++--+++--+--+-++++-+-+', '+-++-----++++-+-+++-++++-+++-+-++++-----++-'), + 49: ('++++-++-+---++-+++---++-++-++---+++-++---+-++-+++', '++++-++-+---++-+++---++-++-++---+++-++---+-++-+++', + '+----+-++++--+-+++-+-+++--+++-+-+++-+--++++-+----', '+++++-+----++-+---+-+---++---+-+---+-++----+-++++'), + 51: ('+---+++-++-+-+++--+++++--++--+++++--+++-+-++-+++---', '----+++-++-+-+++--+++++--++--+++++--+++-+-++-+++---', + '-+--+----+-+++-+-+++++--+--+--+++++-+-+++-+----+--+', '-+--+----+-+++-+-+++++--+--+--+++++-+-+++-+----+--+'), + 55: ('+-+--+-+-++--+-+++++-+++--++++--+++-+++++-+--++-+-+--+-', '--+--+-+-++--+-+++++-+++--++++--+++-+++++-+--++-+-+--+-', + '+++----++-++--++----+-+-++++++++-+-+----++--++-++----++', '+++----++-++--++----+-+-++++++++-+-+----++--++-++----++'), + 57: ('+---++-+--++++-+++-++---+-++++++-+---++-+++-++++--+-++---', '----++-+--++++-+++-++---+-++++++-+---++-+++-++++--+-++---', + '--+-+-+++--+--+-++---+++++-++++-+++++---++-+--+--+++-+-+-', '--+-+-+++--+--+-++---+++++-++++-+++++---++-+--+--+++-+-+-'), + 61: ('++--+--++--+-+-++++--+-----+------+-----+--++++-+-+--++--+--+', '++--+--++--+-+-++++--+-----+------+-----+--++++-+-+--++--+--+', + '+---+-+-++++---++--+-++-+---++++++---+-++-+--++---++++-+-+---', '++++-+-+----+++--++-+--+-+++------+++-+--+-++--+++----+-+-+++'), + 63: ('++-+++--++-++--+--+-++-+-+++--------+++-+-++-+--+--++-++--+++-+', '-+-+++--++-++--+--+-++-+-+++--------+++-+-++-+--+--++-++--+++-+', + '++++-++-+-++++-+---+---+++---++++++---+++---+---+-++++-+-++-+++', '++++-++-+-++++-+---+---+++---++++++---+++---+---+-++++-+-++-+++'), } def pmtoZ(s): From 95073d25c5a0c5395b50e79516c5e4094acbf91c Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 08:58:24 +0000 Subject: [PATCH 316/392] Rename supplementary_difference_set_from_rel_diff_set --- .../combinat/designs/difference_family.py | 28 +++++++++---------- src/sage/combinat/matrices/hadamard_matrix.py | 10 +++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 4257d4489e4..53e03023947 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -1618,8 +1618,8 @@ def is_supplementary_difference_set(Ks, v, lmbda): EXAMPLES:: - sage: from sage.combinat.designs.difference_family import supplementary_difference_set, is_supplementary_difference_set - sage: S1, S2, S3, S4 = supplementary_difference_set(17) + sage: from sage.combinat.designs.difference_family import supplementary_difference_set_from_rel_diff_set, is_supplementary_difference_set + sage: S1, S2, S3, S4 = supplementary_difference_set_from_rel_diff_set(17) sage: is_supplementary_difference_set([S1, S2, S3, S4], 16, 16) True sage: is_supplementary_difference_set([S1, S2, S3, S4], 16, 14) @@ -1651,7 +1651,7 @@ def is_supplementary_difference_set(Ks, v, lmbda): return True -def supplementary_difference_set(q, existence=False, check=True): +def supplementary_difference_set_from_rel_diff_set(q, existence=False, check=True): r"""Construct `4-\{2v; v, v+1, v, v; 2v\}` supplementary difference sets where `q=2v+1`. The sets are created from relative difference sets as detailed in Theorem 3.3 of [Spe1975]_. this construction @@ -1682,8 +1682,8 @@ def supplementary_difference_set(q, existence=False, check=True): EXAMPLES:: - sage: from sage.combinat.designs.difference_family import supplementary_difference_set - sage: supplementary_difference_set(17) #random + sage: from sage.combinat.designs.difference_family import supplementary_difference_set_from_rel_diff_set + sage: supplementary_difference_set_from_rel_diff_set(17) #random ([0, 2, 5, 6, 8, 10, 13, 14], [0, 1, 2, 6, 7, 9, 10, 14, 15], [0, 1, 2, 6, 11, 12, 13, 15], @@ -1691,31 +1691,31 @@ def supplementary_difference_set(q, existence=False, check=True): If existence is ``True``, the function returns a boolean:: - sage: supplementary_difference_set(7, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(7, existence=True) False - sage: supplementary_difference_set(17, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(17, existence=True) True TESTS:: sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set - sage: is_supplementary_difference_set(supplementary_difference_set(17), 16, 16) + sage: is_supplementary_difference_set(supplementary_difference_set_from_rel_diff_set(17), 16, 16) True - sage: is_supplementary_difference_set(supplementary_difference_set(9), 8, 8) + sage: is_supplementary_difference_set(supplementary_difference_set_from_rel_diff_set(9), 8, 8) True - sage: supplementary_difference_set(7) + sage: supplementary_difference_set_from_rel_diff_set(7) Traceback (most recent call last): ... ValueError: There is no s for which m-1 is an odd prime power - sage: supplementary_difference_set(8) + sage: supplementary_difference_set_from_rel_diff_set(8) Traceback (most recent call last): ... ValueError: q must be an odd prime power - sage: supplementary_difference_set(8, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(8, existence=True) False - sage: supplementary_difference_set(7, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(7, existence=True) False - sage: supplementary_difference_set(1, existence=True) + sage: supplementary_difference_set_from_rel_diff_set(1, existence=True) False .. SEEALSO:: diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 5f4c829684d..2a2681e98fb 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -1170,7 +1170,7 @@ def hadamard_matrix_spence_construction(n, existence=False, check=True): r"""Create an Hadamard matrix of order `n` using Spence construction. This construction (detailed in [Spe1975]_), uses supplementary difference sets implemented in - :func:`sage.combinat.designs.difference_family.supplementary_difference_set` to create the + :func:`sage.combinat.designs.difference_family.supplementary_difference_set_from_rel_diff_set` to create the desired matrix. INPUT: @@ -1217,19 +1217,19 @@ def hadamard_matrix_spence_construction(n, existence=False, check=True): ... AssertionError """ - from sage.combinat.designs.difference_family import supplementary_difference_set + from sage.combinat.designs.difference_family import supplementary_difference_set_from_rel_diff_set assert n % 4 == 0 and n > 0 q = n//4 if existence: - return supplementary_difference_set(q, existence=True) + return supplementary_difference_set_from_rel_diff_set(q, existence=True) - if not supplementary_difference_set(q, existence=True): + if not supplementary_difference_set_from_rel_diff_set(q, existence=True): raise ValueError(f'The order {n} is not covered by Spence construction.') - S1, S2, S3, S4 = supplementary_difference_set(q, check=False) + S1, S2, S3, S4 = supplementary_difference_set_from_rel_diff_set(q, check=False) A1 = matrix.circulant([1 if j in S1 else -1 for j in range(q-1)]) A2 = matrix.circulant([1 if j in S4 else -1 for j in range(q-1)]) From 2ee94365ac2cb77ed039409e5f0f31441af8dd99 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 12:10:26 +0000 Subject: [PATCH 317/392] Extract construction of sds --- .../combinat/designs/difference_family.py | 106 +++++++++++++++--- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 53e03023947..4a4e6c09156 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -1629,7 +1629,7 @@ def is_supplementary_difference_set(Ks, v, lmbda): .. SEEALSO:: - :func:`supplementary_difference_set` + :func:`supplementary_difference_set_from_rel_diff_set` """ from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroup @@ -1823,7 +1823,7 @@ def get_fixed_relative_difference_set(rel_diff_set, as_elements=False): `\{td | d\in R\}= R`. In addition, the set returned by this function will contain the element `0`. This is needed in the - construction of supplementary difference sets (see :func:`supplementary_difference_set`). + construction of supplementary difference sets (see :func:`supplementary_difference_set_from_rel_diff_set`). INPUT: @@ -2155,6 +2155,88 @@ def skew_supplementary_difference_set(n, existence=False, check=True): 267: [1, 4, 16, 64, 67, 91, 97, 121, 217, 223, 256], } + if existence: + return n in indices + + if n not in indices: + raise ValueError(f'Skew SDS of order {n} not yet implemented.') + + S1, S2, S3, S4 = _construction_supplementary_difference_set(n, H_db[n], indices[n], cosets_gens[n], check=False) + + if check: + lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n + assert is_supplementary_difference_set([S1, S2, S3, S4], n, lmbda) + assert _is_skew_set(S1, n) + + return S1, S2, S3, S4 + + +def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check=True): + r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. + + This construction is described in [Djo1994]_. + + Let H be a subgroup of Zmod(n) of order `t`. We construct the `2s = n/t` cosets + `\alpha_0, .., \alpha_{2s-1}` by using the elements contained in a sequence `A`: + `\alpha_{2i} = a_iH` and `\alpha_{2i+1} = -\alpha_{2i}`. + + Then, we use four indices sets `J_1, J_2, J_3, J_4` to construct the four + supplementary difference sets: `S_i = \bigcup_{j\in J__i} \alpha_i`. Note that + if `J_i` contains the value `-1`, this function will add `0` to the set `S_i`. + + To construct a coset `\alpha_{2i}` by listing it directly, replace the `2i`-th + element of the list `A` with the desired set. + + INPUT: + + - ``n`` -- integer, the parameter of the supplementary difference set. + + - ``H`` -- list of integers, the set `H` used to construct the cosets. + + - ``indices`` -- list containing four list of integers, which are the sets + `J_1, J_2, J_3, J_4` described above. + + - ``cosets_gen`` -- list containing integers or list of integers, is the set `A` + described above. + + - ``check`` -- boolean (default True). If true, check that the sets + are supplementary difference sets. Setting this parameter to False may speed + up the computation considerably. + + OUTPUT: + + The function returns the 4 sets (containing integers modulo `n`). + + TESTS:: + + sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set, _construction_supplementary_difference_set + sage: H = [1, 10, -11] + sage: cosets_gen = [1, 2, 3, 5, 6, 9] + sage: indices = [[0, 3, 5, 7, 9, 10], [0, 5, 6, 7, 8], [1, 2, 6, 7, 9], [2, 6, 8, 9, 10]] + sage: _construction_supplementary_difference_set(37, H, indices, cosets_gen) + ([1, 10, 26, 35, 17, 22, 34, 7, 33, 32, 24, 18, 31, 14, 29, 9, 16, 12], + [1, 10, 26, 34, 7, 33, 5, 13, 19, 32, 24, 18, 6, 23, 8], + [36, 27, 11, 2, 20, 15, 5, 13, 19, 32, 24, 18, 31, 14, 29], + [2, 20, 15, 5, 13, 19, 6, 23, 8, 31, 14, 29, 9, 16, 12]) + sage: H = [1, 16, 22] + sage: cosets_gen = [1, 2, 3, 4, 6, 8, [13]] + sage: indices = [[1, 3, 5, 6, 8, 10, 12], [0, 1, 5, 8, 12, 13], [1, 3, 4, 7, 9, 12, 13], [0, 1, 2, 3, 7, 8]] + sage: _construction_supplementary_difference_set(39, H, indices, cosets_gen) + ([38, 23, 17, 37, 7, 34, 36, 30, 12, 4, 25, 10, 6, 18, 15, 8, 11, 20, 13], + [1, 16, 22, 38, 23, 17, 36, 30, 12, 6, 18, 15, 13, 26], + [38, 23, 17, 37, 7, 34, 3, 9, 27, 35, 14, 29, 33, 21, 24, 13, 26], + [1, 16, 22, 38, 23, 17, 2, 32, 5, 37, 7, 34, 35, 14, 29, 6, 18, 15]) + sage: H = [1, 4, 11, 16, 21, -2, -8] + sage: cosets_gen = [1, 3, 7] + sage: indices = [[1, 2, 4], [1, 2, 4], [0, 2, 3], [3, 4, -1]] + sage: sets = _construction_supplementary_difference_set(43, H, indices, cosets_gen, check=False) + sage: is_supplementary_difference_set(sets, 43, 35) + True + + .. SEEALSO:: + + :func:`skew_supplementary_difference_set` + """ def generate_set(index_set, cosets): S = [] for idx in index_set: @@ -2164,17 +2246,11 @@ def generate_set(index_set, cosets): S += cosets[idx] return S - if existence: - return n in indices - - if n not in indices: - raise ValueError(f'Skew SDS of order {n} not yet implemented.') - Z = Zmod(n) - H = list(map(Z, H_db[n])) + H = list(map(Z, H)) cosets = [] - for el in cosets_gens[n]: + for el in cosets_gen: if isinstance(el, list): even_coset = [Z(x) for x in el] else: @@ -2183,18 +2259,18 @@ def generate_set(index_set, cosets): cosets.append(even_coset) cosets.append(odd_coset) - S1 = generate_set(indices[n][0], cosets) - S2 = generate_set(indices[n][1], cosets) - S3 = generate_set(indices[n][2], cosets) - S4 = generate_set(indices[n][3], cosets) + S1 = generate_set(indices[0], cosets) + S2 = generate_set(indices[1], cosets) + S3 = generate_set(indices[2], cosets) + S4 = generate_set(indices[3], cosets) if check: lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n assert is_supplementary_difference_set([S1, S2, S3, S4], n, lmbda) - assert _is_skew_set(S1, n) return S1, S2, S3, S4 + def _is_skew_set(S, n): r"""Check if `S` is a skew set over the set of integers modulo `n`. From 49ee5c7f820dd45e38c44c549c6e710b7d18643c Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 12:46:15 +0000 Subject: [PATCH 318/392] Add construction for SDS --- src/doc/en/reference/references/index.rst | 12 +- .../combinat/designs/difference_family.py | 106 +++++++++++++++++- src/sage/combinat/matrices/hadamard_matrix.py | 2 +- 3 files changed, 113 insertions(+), 7 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 1e218493aa9..c3e036bfe88 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2060,10 +2060,15 @@ REFERENCES: *Ten New Orders for Hadamard Matrices of Skew Type*, Publikacije Elektrotehničkog fakulteta. Serija Matematika 2 (1992): 47-59. -.. [Djo1994] \D. Đoković. +.. [Djo1994a] \D. Đoković. *Five New Orders for Hadamard Matrices of Skew Type*, Australasian Journal of Combinatorics 10 (1994): 259-264. +.. [Djo1994b] \D. Đoković. + *Two Hadamard matrices of order 956 of Goethals-Seidel type*, + Combinatorica 14(3) (1994): 375-377. + :doi:`10.1007/BF01212983` + .. [Djo2008a] \D. Đoković. *Skew-Hadamard matrices of orders 188 and 388 exist*, International Mathematical Forum 3 no.22 (2008): 1063-1068. @@ -2074,6 +2079,11 @@ REFERENCES: Journal of Combinatorial Designs 16 (2008): 493-498. :arxiv:`0706.1973` +.. [Djo2008c] \D. Đoković. + *Hadamard matrices of order 764 exist*, + Combinatorica 28(4) (2008): 487-489. + :doi:`10.1007/s00493-008-2384-z` + .. [Djo2023a] \D. Đoković. *Skew-Hadamard matrices of order 276*. :arxiv:`10.48550/ARXIV.2301.02751` diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 4a4e6c09156..2ff4d125650 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -1929,12 +1929,12 @@ def is_fixed_relative_difference_set(R, q): def skew_supplementary_difference_set(n, existence=False, check=True): r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `S_1` is skew and `n_1 + n_2 + n_3 + n_4 = n+\lambda`. - These sets are constructed from available data, as described in [Djo1994]_. The set `S_1 \subset G` is + These sets are constructed from available data, as described in [Djo1994a]_. The set `S_1 \subset G` is always skew, i.e. `S_1 \cap (-S_1) = \emptyset` and `S_1 \cup (-S_1) = G \setminus \{0\}`. The data is taken from: - * `n = 103, 151`: [Djo1994]_ + * `n = 103, 151`: [Djo1994a]_ * `n = 67, 113, 127, 157, 163, 181, 241`: [Djo1992a]_ * `n = 37, 43`: [Djo1992b]_ * `n = 39, 49, 65, 93, 121, 129, 133, 217, 219, 267`: [Djo1992c]_ @@ -2174,7 +2174,7 @@ def skew_supplementary_difference_set(n, existence=False, check=True): def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check=True): r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. - This construction is described in [Djo1994]_. + This construction is described in [Djo1994a]_. Let H be a subgroup of Zmod(n) of order `t`. We construct the `2s = n/t` cosets `\alpha_0, .., \alpha_{2s-1}` by using the elements contained in a sequence `A`: @@ -2184,7 +2184,7 @@ def _construction_supplementary_difference_set(n, H, indices, cosets_gen, check= supplementary difference sets: `S_i = \bigcup_{j\in J__i} \alpha_i`. Note that if `J_i` contains the value `-1`, this function will add `0` to the set `S_i`. - To construct a coset `\alpha_{2i}` by listing it directly, replace the `2i`-th + To construct a coset `\alpha_{2i}` by listing it directly, replace the `2i`-th element of the list `A` with the desired set. INPUT: @@ -2271,10 +2271,106 @@ def generate_set(index_set, cosets): return S1, S2, S3, S4 +def supplementary_difference_set(n, existence=False, check=True): + r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where skew and `n_1 + n_2 + n_3 + n_4 = n+\lambda`. + + These sets are constructed from available data, as described in [Djo1994a]_. + + The data for `n=191` is taken from [Djo2008c]_, and date for `n=239` is from [Djo1994b]_. + Additional SDS are constructed using :func:`skew_supplementary_difference_set`. + + INPUT: + + - ``n`` -- integer, the parameter of the supplementary difference set. + + - ``existence`` -- boolean (dafault False). If true, only check whether the + supplementary difference sets can be constructed. + + - ``check`` -- boolean (default True). If true, check that the sets are + supplementary difference sets before returning them. Setting this parameter + to False may speed up the computation considerably. + + OUTPUT: + + If ``existence`` is false, the function returns the 4 sets (containing integers + modulo `n`), or raises an error if data for the given ``n`` is not available. + If ``existence`` is true, the function returns a boolean representing whether + skew supplementary difference sets can be constructed. + + EXAMPLES:: + + sage: from sage.combinat.designs.difference_family import supplementary_difference_set + sage: S1, S2, S3, S4 = supplementary_difference_set(191) + + If existence is ``True``, the function returns a boolean :: + + sage: supplementary_difference_set(191, existence=True) + True + sage: supplementary_difference_set(17, existence=True) + False + + TESTS:: + + sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set + sage: S1, S2, S3, S4 = supplementary_difference_set(191, check=False) + sage: is_supplementary_difference_set([S1, S2, S3, S4], 191, len(S1)+len(S2)+len(S3)+len(S4)-191) + True + sage: S1, S2, S3, S4 = supplementary_difference_set(37, check=False) + sage: supplementary_difference_set(7) + Traceback (most recent call last): + ... + ValueError: SDS of order 7 not yet implemented. + sage: supplementary_difference_set(7, existence=True) + False + sage: supplementary_difference_set(127, existence=True) + True + """ + + indices = { + 191: [[1, 7, 9, 10, 11, 13, 17, 18, 25, 26, 30, 31, 33, 34, 35, 36, 37], + [1, 4, 7, 9, 11, 12, 13, 14, 19, 21, 22, 23, 24, 25, 26, 29, 36, 37], + [0, 3, 4, 5, 7, 8, 9, 16, 17, 19, 24, 25, 29, 30, 31, 33, 35, 37], + [1, 3, 4, 5, 8, 11, 14, 18, 19, 20, 21, 23, 24, 25, 28, 29, 30, 32, 34, 35]], + 239: [[0, 1, 2, 3, 4, 5, 6, 7, 14, 18, 19, 21, 24, 25, 29, 30], + [0, 1, 3, 7, 9, 12, 15, 18, 20, 22, 26, 28, 29, 30, 31, 32, 33], + [2, 3, 4, 5, 8, 9, 10, 11, 13, 17, 19, 21, 22, 24, 27, 31, 32], + [0, 1, 2, 3, 6, 7, 8, 11, 13, 15, 17, 18, 19, 22, 25, 26, 27, 32, 33]], + } + + cosets_gens = { + 191: [1, 2, 3, 4, 6, 8, 9, 11, 12, 13, 16, 17, 18, 19, 22, 32, 36, 38, 41], + 239: [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 14, 15, 18, 21, 28, 35, 42], + } + + H_db = { + 191: [1, 39, 184, 109, 49], + 239: [1, 10, 24, 44, 98, 100, 201], + } + + if existence: + return n in indices or skew_supplementary_difference_set(n, existence=True) + + sets = None + if n in indices: + sets = _construction_supplementary_difference_set(n, H_db[n], indices[n], cosets_gens[n], check=False) + elif skew_supplementary_difference_set(n, existence=True): + sets = skew_supplementary_difference_set(n, check=False) + + if sets is None: + raise ValueError(f'SDS of order {n} not yet implemented.') + + S1, S2, S3, S4 = sets + if check: + lmbda = len(S1) + len(S2) + len(S3) + len(S4) - n + assert is_supplementary_difference_set([S1, S2, S3, S4], n, lmbda) + + return S1, S2, S3, S4 + + def _is_skew_set(S, n): r"""Check if `S` is a skew set over the set of integers modulo `n`. - From [Djo1994]_, a set `S \subset G` (where `G` is a finite abelian group of order `n`) is of skew + From [Djo1994a]_, a set `S \subset G` (where `G` is a finite abelian group of order `n`) is of skew type if `S_1 \cap (-S_1) = \emptyset` and `S_1 \cup (-S_1) = G\setminus \{0\}`. INPUT: diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 2a2681e98fb..9bf4a94a843 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -2298,7 +2298,7 @@ def skew_hadamard_matrix_324(): r""" Construct a skew Hadamard matrix of order 324. - The construction is taken from [Djo1994]_. It uses four supplementary difference sets `S_1, S_2, S_3, S_4`, + The construction is taken from [Djo1994a]_. It uses four supplementary difference sets `S_1, S_2, S_3, S_4`, with `S_1` of skew type. These are then used to generate four matrices of order `81`, which are inserted into the Goethals-Seidel array. From 383a1b9c3c0533d967722d165b07f0ef671139f7 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 13:34:58 +0000 Subject: [PATCH 319/392] Add construction for hadamard matrix from sds --- src/sage/combinat/matrices/hadamard_matrix.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 9bf4a94a843..3e11e656ce9 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -775,6 +775,92 @@ def _construction_goethals_seidel_matrix(A, B, C, D): [-D*R, -C.T*R, B.T*R, A]]) +def hadamard_matrix_from_sds(n, existence=False, check=True): + r"""Construction of Hadamard matrices from supplementary difference sets. + + Given four SDS with parameters `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` with + `n_1 + n_2 + n_3 + n_4 = n+\lambda` we can construct four (-1,+1) sequences `a_i = (a_{i,0},...,a_{i,n-1})` + where `a_{i,j} = -1` iff `j \in S_i`. This will be the fist rows of four circulant + matrices `A_1, A_2, A_3, A_4` which, when plugged into the Goethals-Seidel array, create an + Hadamard matrix of order `4n` (see [Djo1994b]_). + + The supplementary difference sets are taken from + :func:`sage.combinat.designs.difference_family.supplementary_difference_set`. + + INPUT: + + - ``n`` -- integer, the order of the matrix to be constructed. + + - ``check`` -- boolean: if True (default), check the the matrix is a Hadamard + before returning. + + - ``existence`` -- boolean (default False): if True, only check if the matrix exists. + + OUTPUT: + + If ``existence`` is false, returns the Hadamard matrix of order `n`. It raises + an error if no data is available to construct the matrix of the given order, + or if `n` is not a multiple of `4`. + If ``existence`` is true, returns a boolean representing whether the matrix + can be constructed or not. + + EXAMPLES: + + By default The function returns the Hadamard matrix :: + + sage: from sage.combinat.matrices.hadamard_matrix import hadamard_matrix_from_sds + sage: hadamard_matrix_from_sds(148) + 148 x 148 dense matrix over Integer Ring... + + If ``existence`` is set to True, the function returns a boolean :: + + sage: hadamard_matrix_from_sds(764, existence=True) + True + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import hadamard_matrix_from_sds, is_hadamard_matrix + sage: is_hadamard_matrix(hadamard_matrix_from_sds(172)) + True + sage: hadamard_matrix_from_sds(64, existence=True) + False + sage: hadamard_matrix_from_sds(64) + Traceback (most recent call last): + ... + ValueError: SDS of order 16 not yet implemented. + sage: hadamard_matrix_from_sds(14) + Traceback (most recent call last): + ... + ValueError: n must be a positive multiple of four. + """ + from sage.combinat.designs.difference_family import supplementary_difference_set + + if n <= 0 or n % 4 != 0: + raise ValueError(f'n must be a positive multiple of four.') + t = n // 4 + + if existence: + return supplementary_difference_set(t, existence=True) + + S1, S2, S3, S4 = supplementary_difference_set(t, check=False) + a = [-1 if i in S1 else 1 for i in range(t)] + b = [-1 if i in S2 else 1 for i in range(t)] + c = [-1 if i in S3 else 1 for i in range(t)] + d = [-1 if i in S4 else 1 for i in range(t)] + + if n == 956: + a, b, c, d = [-el for el in d], a, b, c + + A, B, C, D = map(matrix.circulant, [a, b, c, d]) + if check: + assert A*A.T+B*B.T+C*C.T+D*D.T == 4*t*I(t) + + H = _construction_goethals_seidel_matrix(A, B, C, D) + if check: + assert is_hadamard_matrix(H) + return H + + def hadamard_matrix_cooper_wallis_construction(x1, x2, x3, x4, A, B, C, D, check=True): r""" Create an Hadamard matrix using the contruction detailed in [CW1972]_. @@ -1538,6 +1624,10 @@ def hadamard_matrix(n, existence=False, check=True): if existence: return True M = turyn_type_hadamard_matrix_smallcases(n, check=False) + elif hadamard_matrix_from_sds(n, existence=True): + if existence: + return True + M = hadamard_matrix_from_sds(n, check=False) elif hadamard_matrix_spence_construction(n, existence=True): if existence: return True From 8901eb6da0486ec99d1afebcd956fc8bc47be87a Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 18:27:59 +0000 Subject: [PATCH 320/392] Create function to construct symmetric conference matrices --- src/sage/combinat/matrices/hadamard_matrix.py | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 3e11e656ce9..6dcf34767ab 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -179,6 +179,56 @@ def hadamard_matrix_paleyI(n, normalize=True): return H +def symmetric_conference_matrix_paley(n): + r""" + Construct a symmetric conference matrix of order n. + + A conference matrix is an `n\times n` matrix `C` with 0s on the main diagonal + and 1s and -1s elsewhere, satisfying `CC^\top=(n-1)I`. This construction assumes + that `q = n-1` is a prime power, with `q \cong 1 \mod 4`. See [Hora]_ or [Lon2013]_. + + These matrices are used in the :func:`hadamard_matrix_paleyII`. + + INPUT: + + - ``n`` -- integer, the order of the symmetric conference matrix to consruct. + + EXAMPLES:: + + sage: from sage.combinat.matrices.hadamard_matrix import symmetric_conference_matrix_paley + sage: symmetric_conference_matrix_paley(6) + [ 0 1 1 1 1 1] + [ 1 0 1 -1 -1 1] + [ 1 1 0 1 -1 -1] + [ 1 -1 1 0 1 -1] + [ 1 -1 -1 1 0 1] + [ 1 1 -1 -1 1 0] + + TESTS:: + + sage: symmetric_conference_matrix_paley(5) + Traceback (most recent call last): + ... + ValueError: The order 5 is not covered by Paley construction of symmetric conference matrices. + """ + q = n - 1 + if not (is_prime_power(q) and (q % 4 == 1)): + raise ValueError("The order %s is not covered by Paley construction of symmetric conference matrices." % n) + + from sage.rings.finite_rings.finite_field_constructor import FiniteField + K = FiniteField(q, 'x') + K_list = list(K) + K_list.insert(0, K.zero()) + H = matrix(ZZ, [[(1 if (x-y).is_square() else -1) + for x in K_list] + for y in K_list]) + for i in range(n): + H[0, i] = 1 + H[i, 0] = 1 + H[i, i] = 0 + return H + + def hadamard_matrix_paleyII(n): r""" Implement the Paley type II construction. @@ -226,17 +276,7 @@ def hadamard_matrix_paleyII(n): if not (n % 2 == 0 and is_prime_power(q) and (q % 4 == 1)): raise ValueError("The order %s is not covered by the Paley type II construction." % n) - from sage.rings.finite_rings.finite_field_constructor import FiniteField - K = FiniteField(q, 'x') - K_list = list(K) - K_list.insert(0, K.zero()) - H = matrix(ZZ, [[(1 if (x-y).is_square() else -1) - for x in K_list] - for y in K_list]) - for i in range(q+1): - H[0, i] = 1 - H[i, 0] = 1 - H[i, i] = 0 + H = symmetric_conference_matrix_paley(q+1) tr = { 0: matrix(2, 2, [ 1, -1, -1, -1]), 1: matrix(2, 2, [ 1, 1, 1, -1]), From ed7a93933c24f8b2a1d6c9759fd62b9b4b9d521c Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Fri, 27 Jan 2023 19:23:12 +0000 Subject: [PATCH 321/392] Add miyamoto construction --- src/doc/en/reference/references/index.rst | 5 + src/sage/combinat/matrices/hadamard_matrix.py | 112 ++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index c3e036bfe88..70dda6c3940 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4508,6 +4508,11 @@ REFERENCES: .. [Mit2008] \A. Mitra. *On the construction of M-sequences via primitive polynomials with a fast identification method*, International Journal of Electronics and Communication Engineering 2(9) (2008): 1991-1996. +.. [Miy1991] \M. Miyamoto. + *A construction of Hadamard matrices*, + Journal of Combinatorial Theory, Series A 57(1) (1991): 86-108. + :doi:`10.1016/0097-3165(91)90008-5` + .. [MKO1998] Hans Munthe--Kaas and Brynjulf Owren. *Computations in a free Lie algebra*. (1998). `Downloadable from Munthe-Kaas's website diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 6dcf34767ab..8078f9cecae 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -287,6 +287,114 @@ def hadamard_matrix_paleyII(n): return normalise_hadamard(H) +def hadamard_matrix_miyamoto_construction(n, existence=False, check=True): + r""" + Construct Hadamard matrix using Miyamoto construction. + + If `q = n/4` is a prime power, and there exists an Hadamard matrix of order + `q-1`, then a Hadamard matrix of order `n` can be constructed (see [Miy1991]_). + + INPUT: + + - ``n`` -- integer, the order of the matrix to be constructed. + + - ``check`` -- boolean: if True (default), check the the matrix is a Hadamard + before returning. + + - ``existence`` -- boolean (default False): if True, only check if the matrix exists. + + OUTPUT: + + If ``existence`` is false, returns the Hadamard matrix of order `n`. It raises + an error if no data is available to construct the matrix of the given order, + or if `n` is not a multiple of `4`. + If ``existence`` is true, returns a boolean representing whether the matrix + can be constructed or not. + + EXAMPLES: + + By default the function returns the Hadamard matrix :: + + sage: from sage.combinat.matrices.hadamard_matrix import hadamard_matrix_miyamoto_construction + sage: hadamard_matrix_miyamoto_construction(20) + 20 x 20 dense matrix over Integer Ring... + + If ``existence`` is set to True, the function returns a boolean :: + + sage: hadamard_matrix_miyamoto_construction(36, existence=True) + True + + TESTS:: + + sage: from sage.combinat.matrices.hadamard_matrix import is_hadamard_matrix + sage: is_hadamard_matrix(hadamard_matrix_miyamoto_construction(68, check=False)) + True + sage: hadamard_matrix_miyamoto_construction(64, existence=True) + False + sage: hadamard_matrix_miyamoto_construction(64) + Traceback (most recent call last): + ... + ValueError: The order 64 is not covered by Miyamoto construction. + sage: hadamard_matrix_miyamoto_construction(14) + Traceback (most recent call last): + ... + ValueError: No Hadamard matrix of order 14 exists. + """ + if n < 0 or n % 4 != 0: + raise ValueError(f'No Hadamard matrix of order {n} exists.') + + q = n // 4 + if existence: + return is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True) + + if not (is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True)): + raise ValueError(f'The order {n} is not covered by Miyamoto construction.') + + m = (q-1) // 2 + + C = symmetric_conference_matrix_paley(q + 1) + + neg = [i for i in range(2, m+2) if C[1, i] == -1] + pos = [i for i in range(m+2, 2*m+2) if C[1, i] == 1] + + for i, j in zip(neg, pos): + C.swap_rows(i, j) + C.swap_columns(i, j) + + C1 = -C.submatrix(row=2, col=2, nrows=m, ncols=m) + C2 = C.submatrix(row=2, col=m+2, nrows=m, ncols=m) + C4 = C.submatrix(row=m+2, col=m+2, nrows=m, ncols=m) + + K = hadamard_matrix(q - 1) + K1 = K.submatrix(row=0, col=0, nrows=(q-1)//2, ncols=(q-1)//2) + K2 = K.submatrix(row=0, col=(q-1)//2, nrows=(q-1)//2, ncols=(q-1)//2) + K3 = -K.submatrix(row=(q-1)//2, col=0, nrows=(q-1)//2, ncols=(q-1)//2) + K4 = K.submatrix(row=(q-1)//2, col=(q-1)//2, nrows=(q-1)//2, ncols=(q-1)//2) + + Zr = zero_matrix(m) + Us = [[C1, C2, Zr, Zr], [C2.T, C4, Zr, Zr], [Zr, Zr, C1, C2], [Zr, Zr, C2.T, C4]] + Vs = [[I(m), Zr, K1, K2], [Zr, I(m), K3, K4], [K1.T, K3.T, I(m), Zr], [K2.T, K4.T, Zr, I(m)]] + + def T(i, j): + return block_matrix([[Us[i][j]+Vs[i][j], Us[i][j]-Vs[i][j]], + [Us[i][j]-Vs[i][j], Us[i][j]+Vs[i][j]]]) + + e = matrix([[1] * (2*m)]) + one = matrix([1]) + H = block_matrix([[ one, -e, one, e, one, e, one, e], + [-e.T, T(0, 0), e.T, T(0, 1), e.T, T(0, 2), e.T, T(0, 3)], + [-one, -e, one, -e, one, e, -one, -e], + [-e.T, -T(1, 0), -e.T, T(1, 1), e.T, T(1, 2), -e.T, -T(1, 3)], + [-one, -e, -one, -e, one, -e, one, e], + [-e.T, -T(2, 0), -e.T, -T(2, 1), -e.T, T(2, 2), e.T, T(2, 3)], + [-one, -e, one, e, -one, -e, one, -e], + [-e.T, -T(3, 0), e.T, T(3, 1), -e.T, -T(3, 2), -e.T, T(3, 3)]]) + + if check: + assert is_hadamard_matrix(H) + return H + + def hadamard_matrix_williamson_type(a, b, c, d, check=True): r""" Construction of Williamson type Hadamard matrix. @@ -1664,6 +1772,10 @@ def hadamard_matrix(n, existence=False, check=True): if existence: return True M = turyn_type_hadamard_matrix_smallcases(n, check=False) + elif hadamard_matrix_miyamoto_construction(n, existence=True): + if existence: + return True + M = hadamard_matrix_miyamoto_construction(n, check=False) elif hadamard_matrix_from_sds(n, existence=True): if existence: return True From da46f5813237f8b9c68b1cc1b8b86af15484f340 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Sun, 12 Feb 2023 17:39:59 +0000 Subject: [PATCH 322/392] tagged tests at long time --- src/sage/combinat/designs/difference_family.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 2ff4d125650..3afad208d7c 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -2300,7 +2300,7 @@ def supplementary_difference_set(n, existence=False, check=True): EXAMPLES:: sage: from sage.combinat.designs.difference_family import supplementary_difference_set - sage: S1, S2, S3, S4 = supplementary_difference_set(191) + sage: S1, S2, S3, S4 = supplementary_difference_set(191) # long time If existence is ``True``, the function returns a boolean :: @@ -2313,7 +2313,7 @@ def supplementary_difference_set(n, existence=False, check=True): sage: from sage.combinat.designs.difference_family import is_supplementary_difference_set sage: S1, S2, S3, S4 = supplementary_difference_set(191, check=False) - sage: is_supplementary_difference_set([S1, S2, S3, S4], 191, len(S1)+len(S2)+len(S3)+len(S4)-191) + sage: is_supplementary_difference_set([S1, S2, S3, S4], 191, len(S1)+len(S2)+len(S3)+len(S4)-191) # long time True sage: S1, S2, S3, S4 = supplementary_difference_set(37, check=False) sage: supplementary_difference_set(7) From f1f11cc4651291499a1d54e3f7271a2f1c24cad6 Mon Sep 17 00:00:00 2001 From: Matteo Cati Date: Mon, 13 Feb 2023 15:44:29 +0000 Subject: [PATCH 323/392] Fix docstrings --- src/sage/combinat/designs/difference_family.py | 6 +++--- src/sage/combinat/matrices/hadamard_matrix.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 3afad208d7c..90df86e3e41 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -2272,18 +2272,18 @@ def generate_set(index_set, cosets): def supplementary_difference_set(n, existence=False, check=True): - r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where skew and `n_1 + n_2 + n_3 + n_4 = n+\lambda`. + r"""Construct `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` supplementary difference sets where `n_1 + n_2 + n_3 + n_4 = n+\lambda`. These sets are constructed from available data, as described in [Djo1994a]_. - The data for `n=191` is taken from [Djo2008c]_, and date for `n=239` is from [Djo1994b]_. + The data for `n=191` is taken from [Djo2008c]_, and data for `n=239` is from [Djo1994b]_. Additional SDS are constructed using :func:`skew_supplementary_difference_set`. INPUT: - ``n`` -- integer, the parameter of the supplementary difference set. - - ``existence`` -- boolean (dafault False). If true, only check whether the + - ``existence`` -- boolean (default False). If true, only check whether the supplementary difference sets can be constructed. - ``check`` -- boolean (default True). If true, check that the sets are diff --git a/src/sage/combinat/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index 8078f9cecae..0434d20d8c2 100644 --- a/src/sage/combinat/matrices/hadamard_matrix.py +++ b/src/sage/combinat/matrices/hadamard_matrix.py @@ -187,7 +187,7 @@ def symmetric_conference_matrix_paley(n): and 1s and -1s elsewhere, satisfying `CC^\top=(n-1)I`. This construction assumes that `q = n-1` is a prime power, with `q \cong 1 \mod 4`. See [Hora]_ or [Lon2013]_. - These matrices are used in the :func:`hadamard_matrix_paleyII`. + These matrices are used in :func:`hadamard_matrix_paleyII`. INPUT: @@ -307,7 +307,7 @@ def hadamard_matrix_miyamoto_construction(n, existence=False, check=True): If ``existence`` is false, returns the Hadamard matrix of order `n`. It raises an error if no data is available to construct the matrix of the given order, - or if `n` is not a multiple of `4`. + or if `n` does not satisfies the constraints. If ``existence`` is true, returns a boolean representing whether the matrix can be constructed or not. @@ -345,7 +345,7 @@ def hadamard_matrix_miyamoto_construction(n, existence=False, check=True): q = n // 4 if existence: - return is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True) + return is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True) is True if not (is_prime_power(q) and q % 4 == 1 and hadamard_matrix(q-1, existence=True)): raise ValueError(f'The order {n} is not covered by Miyamoto construction.') @@ -928,7 +928,7 @@ def hadamard_matrix_from_sds(n, existence=False, check=True): Given four SDS with parameters `4-\{n; n_1, n_2, n_3, n_4; \lambda\}` with `n_1 + n_2 + n_3 + n_4 = n+\lambda` we can construct four (-1,+1) sequences `a_i = (a_{i,0},...,a_{i,n-1})` - where `a_{i,j} = -1` iff `j \in S_i`. This will be the fist rows of four circulant + where `a_{i,j} = -1` iff `j \in S_i`. These will be the fist rows of four circulant matrices `A_1, A_2, A_3, A_4` which, when plugged into the Goethals-Seidel array, create an Hadamard matrix of order `4n` (see [Djo1994b]_). From 150b9bad9def778b8de0f255a546c2a9b17fa16c Mon Sep 17 00:00:00 2001 From: Sandstorm831 Date: Sun, 19 Feb 2023 11:36:19 +0530 Subject: [PATCH 324/392] reformatting function --- src/sage/combinat/posets/linear_extensions.py | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 23d2688bd72..2533ad61b58 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -253,7 +253,7 @@ def is_greedy(self): return True def is_supergreedy(self): - r"""" + r""" Return ``True`` if the linear extension is supergreedy. A linear extension `[x_1 Date: Sun, 19 Feb 2023 19:27:28 +0100 Subject: [PATCH 325/392] Minor changes suggested by Travis Scrimshaw Co-authored-by: Travis Scrimshaw --- src/sage/rings/polynomial/multi_polynomial_libsingular.pyx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx index 4a004bfb9ef..cf471bb576c 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -2066,6 +2066,8 @@ cdef class MPolynomial_libsingular(MPolynomial): sage: f(y).parent() Multivariate Polynomial Ring in y over Finite Field in a of size 2^4 """ + cdef Element sage_res + if len(kwds) > 0: f = self.subs(**kwds) if len(x) > 0: @@ -2093,7 +2095,7 @@ cdef class MPolynomial_libsingular(MPolynomial): # give up, evaluate functional sage_res = parent.base_ring().zero() for (m,c) in self.dict().iteritems(): - sage_res += c*mul([ x[i]**m[i] for i in m.nonzero_positions()]) + sage_res += c * mul([x[i] ** m[i] for i in m.nonzero_positions()]) else: singular_polynomial_call(&res, self._poly, _ring, coerced_x, MPolynomial_libsingular_get_element) @@ -2105,7 +2107,7 @@ cdef class MPolynomial_libsingular(MPolynomial): else: sage_res = new_MP(parent, res) # pass on ownership of res to sage_res - if sage_res.parent() is not res_parent: + if sage_res._parent is not res_parent: sage_res = res_parent(sage_res) return sage_res From 632a5751d65c1bb8fa52bcae16ae35daf995d1a5 Mon Sep 17 00:00:00 2001 From: Marc Mezzarobba Date: Thu, 9 Feb 2023 19:26:37 +0100 Subject: [PATCH 326/392] fix code that relied on const_pol(x=a,y=b) landing in the wrong parent --- src/sage/modular/modform_hecketriangle/graded_ring_element.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/modular/modform_hecketriangle/graded_ring_element.py b/src/sage/modular/modform_hecketriangle/graded_ring_element.py index b6d01bf0c37..c244bc34f48 100644 --- a/src/sage/modular/modform_hecketriangle/graded_ring_element.py +++ b/src/sage/modular/modform_hecketriangle/graded_ring_element.py @@ -1566,7 +1566,9 @@ def _q_expansion_cached(self, prec, fix_d, subs_d, d_num_prec, fix_prec = False) Y = SC.f_i_ZZ().base_extend(formal_d.parent()) if (self.parent().is_modular()): - qexp = self._rat.subs(x=X, y=Y, d=formal_d) + # z does not appear in self._rat but we need to specialize it for + # the evaluation to land in the correct parent + qexp = self._rat.subs(x=X, y=Y, z=0, d=formal_d) else: Z = SC.E2_ZZ().base_extend(formal_d.parent()) qexp = self._rat.subs(x=X, y=Y, z=Z, d=formal_d) From 73fa44e0a682785ada92a844ff088b2a7096069d Mon Sep 17 00:00:00 2001 From: Marc Mezzarobba Date: Fri, 10 Feb 2023 11:11:40 +0100 Subject: [PATCH 327/392] =?UTF-8?q?fix=20more=20code=20that=20relied=20on?= =?UTF-8?q?=20pol(x,y...)=20=E2=88=88=20wrong=20parent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/schemes/riemann_surfaces/riemann_surface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index b219cb975e2..e7c401cd07b 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -2128,6 +2128,7 @@ def rigorous_line_integral(self, upstairs_edge, differentials, bounding_data): # CCzg is required to be known as we need to know the ring which the minpolys # lie in. CCzg, bounding_data_list = bounding_data + CCz = CCzg.univariate_ring(CCzg.gen(1)).base_ring() d_edge = tuple(u[0] for u in upstairs_edge) # Using a try-catch here allows us to retain a certain amount of back @@ -2193,7 +2194,7 @@ def local_N(ct, rt): z_1 = a0lc.abs() * prod((cz - r).abs() - rho_z for r in a0roots) n = minpoly.degree(CCzg.gen(1)) ai_new = [ - (minpoly.coefficient({CCzg.gen(1): i}))(z=cz + self._CCz.gen(0)) + CCz(minpoly.coefficient({CCzg.gen(1): i}))(z=cz + self._CCz.gen(0)) for i in range(n) ] ai_pos = [self._RRz([c.abs() for c in h.list()]) for h in ai_new] From 042fdc510d974275660c85a3710bab9317b5ee39 Mon Sep 17 00:00:00 2001 From: Marc Mezzarobba Date: Fri, 10 Feb 2023 11:19:06 +0100 Subject: [PATCH 328/392] =?UTF-8?q?fix=20more=20code=20that=20relied=20on?= =?UTF-8?q?=20pol(x,y,...)=20=E2=88=88=20wrong=20parent=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sage/schemes/toric/points.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/schemes/toric/points.py b/src/sage/schemes/toric/points.py index 5da8f26a9c5..4ebc076f0d7 100644 --- a/src/sage/schemes/toric/points.py +++ b/src/sage/schemes/toric/points.py @@ -849,7 +849,7 @@ def inhomogeneous_equations(self, ring, nonzero_coordinates, cokernel): z = [ring.zero()] * nrays for i, value in zip(nonzero_coordinates, z_nonzero): z[i] = value - return [poly(z) for poly in self.polynomials] + return [poly.change_ring(ring)(z) for poly in self.polynomials] def solutions_serial(self, inhomogeneous_equations, log_range): """ From ffb4647de92cd6d489fa47d3e013298b6e3fa50d Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Mon, 20 Feb 2023 09:29:53 -0800 Subject: [PATCH 329/392] src/sage/rings/polynomial/polynomial_element.pyx: Update doctest output (trac->github) --- src/sage/rings/polynomial/polynomial_element.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 2aae21a315e..0ca80d0ea49 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -155,7 +155,7 @@ cpdef is_Polynomial(f): sage: R. = ZZ[] sage: is_Polynomial(x^3 + x + 1) doctest:...: DeprecationWarning: the function is_Polynomial is deprecated; use isinstance(x, sage.structure.element.Polynomial) instead - See https://trac.sagemath.org/32709 for details. + See https://github.com/sagemath/sage/issues/32709 for details. True sage: S. = R[] sage: f = y^3 + x*y -3*x; f From 6ee79be1521d2da8d33352f73cbf64ee97a6878e Mon Sep 17 00:00:00 2001 From: MatteoCati <53347703+MatteoCati@users.noreply.github.com> Date: Tue, 21 Feb 2023 18:36:23 +0000 Subject: [PATCH 330/392] Merge updates from add_new_skew_hadamard_matrices (#2) * Improve camera positioning for threejs * Changes to sagedoc, stopgap and superseded; one mass replacement * Another mass replacement * Another mass replacement * Another replacement * Another replacement * Remove comment numbers * Fix a couple of strays * trac ticket -> github issue * Change checksums back, fix meta-ticket * Another mass replacement * Another mass replacement * Another mass replacement * Manual fixes * Small replacement * Small replacement * Fix typo * Add github role to tox * add explicit formulas to documentation of WeierstrassIsomorphism * lift documentation from .__init__() to class and tweak * add doctest for #20847 * add .torsion_basis() method to EllipticCurve_finite_field * Revert "Remove comment numbers" This reverts commit 27cdf3e9959fbbd8ee81c6b2abcd2af7b74d71bc. * Revert changes to git_trac.rst * Switch :github: to :issue: * Switch from sage-prod to sage in attachment links * Change trac_number to issue_number * cross linked matrix method * Add construction of strongly regular digraph * A few manual fixes * Another automatic change * Undo a bunch of changes in the doc directory * Another fix * Try to fix sphinx problem * Move comment tag inside * allowing iterables as input * Fix code style * generalized fix * converting iterables * added is_supergreedy() function * implemented diff algo, for borderline cases * correcting function for disjoint set of points * correcting function * documentation changes * minor chagnges * fixing_failing_tests * minor documentation correction * Update linear_extensions.py * corrected TeX maths in docstrings * further fixes to supergreedy docstring * Added Reference * adding reference * minor updates * correcting documentation errors * correct a wrong change to :trac: * remove spurrious blank line * another spurrious line removed * compute matrix kernels modulo composites * fix doctests * docstring style tweak * adding doctests * correct the import location (duh...) * adding colored permutation doctests * doctests fixes * replaced "Combining Caron"+c with one unicode character * Updated SageMath version to 10.0.beta1 --------- Co-authored-by: Kwankyu Lee Co-authored-by: David Roe Co-authored-by: Lorenz Panny Co-authored-by: Marc Mezzarobba Co-authored-by: Tobias Diez Co-authored-by: Sandstorm831 Co-authored-by: Rohan Garg <76916164+Sandstorm831@users.noreply.github.com> Co-authored-by: Dima Pasechnik Co-authored-by: Release Manager --- .devcontainer/portability-updateContent.sh | 4 +- .github/workflows/ci-macos.yml | 2 +- .github/workflows/ci-wsl.yml | 2 +- .gitignore | 2 +- .zenodo.json | 8 +- Makefile | 6 +- README.md | 2 +- VERSION.txt | 2 +- bootstrap | 6 +- build/bin/sage-bootstrap-python | 2 +- build/bin/sage-build-num-threads | 2 +- build/bin/sage-dist-helpers | 6 +- build/bin/sage-flock | 2 +- build/bin/sage-site | 2 +- build/bin/sage-spkg | 2 +- build/bin/sage-spkg-info | 4 +- build/bin/sage-venv | 2 +- build/make/Makefile.in | 2 +- build/pkgs/_prereq/distros/cygwin.txt | 4 +- build/pkgs/_prereq/distros/fedora.txt | 6 +- build/pkgs/cddlib/spkg-configure.m4 | 2 +- build/pkgs/cmake/spkg-check.in | 4 +- build/pkgs/configure/checksums.ini | 6 +- build/pkgs/configure/package-version.txt | 2 +- build/pkgs/curl/spkg-check.in | 4 +- build/pkgs/ecl/SPKG.rst | 2 +- build/pkgs/ecl/spkg-install.in | 2 +- build/pkgs/ecm/spkg-install.in | 4 +- build/pkgs/elliptic_curves/spkg-install.py | 2 +- build/pkgs/fflas_ffpack/spkg-install.in | 4 +- build/pkgs/gap/spkg-install.in | 4 +- build/pkgs/gc/spkg-install.in | 4 +- build/pkgs/gcc/dependencies | 2 +- build/pkgs/gcc/spkg-configure.m4 | 6 +- build/pkgs/gdb/distros/conda.txt | 2 +- .../patches/fix-int64-for-32bit-archs.patch | 2 +- build/pkgs/gfan/spkg-install.in | 2 +- build/pkgs/gfortran/spkg-configure.m4 | 2 +- build/pkgs/giac/patches/cygwin-icas.patch | 2 +- build/pkgs/giac/patches/isnan-conflict.patch | 2 +- build/pkgs/giac/patches/pari_2_11.patch | 2 +- build/pkgs/glpk/patches/error_recovery.patch | 2 +- build/pkgs/glpk/spkg-install.in | 4 +- build/pkgs/glucose/spkg-install.in | 2 +- build/pkgs/iconv/spkg-check.in | 2 +- build/pkgs/iconv/spkg-install.in | 12 +- build/pkgs/jupyterlab/requirements.txt | 2 +- build/pkgs/libgd/spkg-install.in | 2 +- build/pkgs/linbox/spkg-install.in | 6 +- build/pkgs/matplotlib/make-setup-config.py | 2 +- build/pkgs/maxima/spkg-install.in | 2 +- build/pkgs/meataxe/spkg-install.in | 2 +- .../pkgs/notebook/jupyter_notebook_config.py | 2 +- .../don-t-rely-on-pandoc-for-the-readme.patch | 2 +- build/pkgs/numpy/spkg-install.in | 2 +- build/pkgs/openblas/spkg-install.in | 4 +- build/pkgs/pcre/patches/8.39-cygwin-jit.patch | 2 +- build/pkgs/pcre/spkg-install.in | 2 +- .../ppl/patches/cygwin-weak-symbols.patch | 2 +- build/pkgs/ppl/spkg-install.in | 2 +- .../pkgs/prompt_toolkit/install-requires.txt | 2 +- build/pkgs/ptyprocess/install-requires.txt | 2 +- build/pkgs/python3/spkg-build.in | 6 +- build/pkgs/python3/spkg-configure.m4 | 4 +- build/pkgs/python3/spkg-install.in | 2 +- .../patches/0002-WIP-Don-t-add-lrt.patch | 2 +- build/pkgs/qepcad/spkg-install.in | 6 +- build/pkgs/sage_conf/install-requires.txt | 2 +- build/pkgs/sage_docbuild/install-requires.txt | 2 +- build/pkgs/sage_setup/install-requires.txt | 2 +- build/pkgs/sage_sws2rst/install-requires.txt | 2 +- build/pkgs/sagelib/install-requires.txt | 2 +- build/pkgs/sagelib/spkg-install | 2 +- .../sagemath_categories/install-requires.txt | 2 +- .../sagemath_environment/install-requires.txt | 2 +- .../sagemath_objects/install-requires.txt | 2 +- build/pkgs/sagemath_repl/install-requires.txt | 2 +- build/pkgs/setuptools/distros/conda.txt | 2 +- build/pkgs/snappy/dependencies | 2 +- build/pkgs/snappy/requirements.txt | 4 +- build/pkgs/sympow/SPKG.rst | 4 +- build/pkgs/wheel/install-requires.txt | 2 +- build/sage_bootstrap/flock.py | 2 +- build/sage_bootstrap/uncompress/action.py | 4 +- build/sage_bootstrap/uncompress/tar_file.py | 4 +- build/sage_bootstrap/uninstall.py | 2 +- docker/Dockerfile | 2 +- docker/README.md | 2 +- m4/sage_spkg_collect.m4 | 2 +- pkgs/sage-conf/README.rst | 4 +- pkgs/sage-conf/VERSION.txt | 2 +- pkgs/sage-conf_pypi/VERSION.txt | 2 +- pkgs/sage-docbuild/VERSION.txt | 2 +- pkgs/sage-setup/VERSION.txt | 2 +- pkgs/sage-sws2rst/README.rst | 2 +- pkgs/sage-sws2rst/VERSION.txt | 2 +- pkgs/sagemath-categories/MANIFEST.in.m4 | 2 +- pkgs/sagemath-categories/VERSION.txt | 2 +- pkgs/sagemath-environment/VERSION.txt | 2 +- pkgs/sagemath-objects/VERSION.txt | 2 +- pkgs/sagemath-repl/VERSION.txt | 2 +- pkgs/sagemath-standard/README.rst | 2 +- src/.relint.yml | 2 +- src/VERSION.txt | 2 +- src/bin/sage | 12 +- src/bin/sage-cleaner | 2 +- src/bin/sage-coverage | 2 +- src/bin/sage-cython | 2 +- src/bin/sage-env | 16 +-- src/bin/sage-num-threads.py | 2 +- src/bin/sage-runtests | 10 +- src/bin/sage-version.sh | 6 +- src/conftest.py | 2 +- src/doc/bootstrap | 4 +- src/doc/en/constructions/contributions.rst | 2 +- src/doc/en/prep/Advanced-2DPlotting.rst | 2 +- src/doc/en/reference/references/index.rst | 13 +- .../nf_galois_groups.rst | 2 +- src/sage/algebras/free_algebra_element.py | 2 +- src/sage/algebras/fusion_rings/fusion_ring.py | 2 +- .../free_algebra_element_letterplace.pyx | 2 +- .../algebras/lie_algebras/free_lie_algebra.py | 2 +- src/sage/algebras/lie_algebras/lie_algebra.py | 2 +- .../lie_algebras/lie_algebra_element.pyx | 2 +- src/sage/algebras/lie_algebras/morphism.py | 2 +- .../algebras/quatalg/quaternion_algebra.py | 4 +- src/sage/arith/constants.pxd | 2 +- src/sage/arith/long.pxd | 2 +- src/sage/calculus/wester.py | 2 +- src/sage/categories/category_with_axiom.py | 2 +- .../finite_complex_reflection_groups.py | 2 +- src/sage/categories/homset.py | 4 +- src/sage/categories/magmas.py | 2 +- src/sage/categories/modules.py | 2 +- src/sage/categories/morphism.pyx | 2 +- src/sage/categories/primer.py | 4 +- src/sage/categories/pushout.py | 4 +- src/sage/categories/unital_algebras.py | 4 +- src/sage/coding/gabidulin_code.py | 2 +- src/sage/combinat/colored_permutations.py | 114 +++++++++------- src/sage/combinat/combinat.py | 2 +- .../combinat/designs/difference_family.py | 2 +- src/sage/combinat/finite_state_machine.py | 2 +- src/sage/combinat/free_module.py | 2 +- src/sage/combinat/k_regular_sequence.py | 2 +- src/sage/combinat/permutation.py | 6 +- src/sage/combinat/posets/hasse_diagram.py | 4 +- src/sage/combinat/posets/lattices.py | 2 +- src/sage/combinat/posets/linear_extensions.py | 70 ++++++++++ src/sage/combinat/posets/posets.py | 4 +- src/sage/combinat/recognizable_series.py | 4 +- src/sage/combinat/root_system/type_relabel.py | 2 +- src/sage/combinat/set_partition.py | 4 +- src/sage/combinat/sf/dual.py | 2 +- src/sage/combinat/sf/hall_littlewood.py | 2 +- src/sage/combinat/tiling.py | 2 +- src/sage/combinat/words/finite_word.py | 10 +- src/sage/combinat/words/morphic.py | 2 +- src/sage/combinat/words/morphism.py | 10 +- src/sage/cpython/__init__.py | 8 +- src/sage/cpython/atexit.pyx | 2 +- src/sage/crypto/boolean_function.pyx | 2 +- src/sage/databases/findstat.py | 2 +- src/sage/databases/oeis.py | 4 +- src/sage/doctest/forker.py | 16 +-- .../dynamics/surface_dynamics_deprecation.py | 2 +- src/sage/ext/memory_allocator.pxd | 4 +- src/sage/ext/memory_allocator.pyx | 4 +- src/sage/ext/mod_int.h | 2 +- .../ext_data/threejs/threejs_template.html | 14 +- src/sage/features/__init__.py | 2 +- src/sage/features/join_feature.py | 2 +- src/sage/functions/exp_integral.py | 2 +- src/sage/functions/other.py | 2 +- src/sage/game_theory/normal_form_game.py | 4 +- src/sage/game_theory/parser.py | 2 +- src/sage/geometry/cone.py | 6 +- .../hyperplane_arrangement/arrangement.py | 2 +- src/sage/geometry/lattice_polytope.py | 2 +- src/sage/geometry/polyhedron/base3.py | 2 +- .../combinatorial_polyhedron/base.pyx | 4 +- src/sage/geometry/polyhedron/parent.py | 2 +- .../polyhedron/ppl_lattice_polytope.py | 6 +- src/sage/graphs/base/c_graph.pyx | 2 +- .../graphs/base/static_sparse_backend.pyx | 2 +- src/sage/graphs/bipartite_graph.py | 4 +- src/sage/graphs/connectivity.pyx | 4 +- src/sage/graphs/digraph.py | 2 +- src/sage/graphs/digraph_generators.py | 47 +++++++ src/sage/graphs/generic_graph.py | 16 +-- src/sage/graphs/graph.py | 4 +- src/sage/graphs/graph_coloring.pyx | 4 +- src/sage/graphs/graph_latex.py | 2 +- src/sage/graphs/graph_plot.py | 4 +- src/sage/graphs/spanning_tree.pyx | 8 +- src/sage/groups/abelian_gps/abelian_group.py | 2 +- .../additive_abelian_wrapper.py | 2 +- src/sage/groups/braid.py | 2 +- src/sage/groups/class_function.py | 2 +- src/sage/groups/galois_group.py | 2 +- .../groups/matrix_gps/finitely_generated.py | 2 +- src/sage/groups/matrix_gps/homset.py | 2 +- src/sage/groups/matrix_gps/morphism.py | 2 +- src/sage/groups/perm_gps/permgroup.py | 8 +- src/sage/homology/chain_complex.py | 4 +- src/sage/homology/tests.py | 8 +- src/sage/interacts/library.py | 2 +- src/sage/interfaces/axiom.py | 2 +- src/sage/interfaces/chomp.py | 18 +-- src/sage/interfaces/expect.py | 8 +- src/sage/interfaces/fricas.py | 2 +- src/sage/interfaces/gap.py | 2 +- src/sage/interfaces/gfan.py | 2 +- src/sage/interfaces/gp.py | 2 +- src/sage/interfaces/interface.py | 2 +- src/sage/interfaces/kash.py | 2 +- src/sage/interfaces/lie.py | 2 +- src/sage/interfaces/lisp.py | 2 +- src/sage/interfaces/macaulay2.py | 4 +- src/sage/interfaces/magma.py | 2 +- src/sage/interfaces/maxima.py | 4 +- src/sage/interfaces/maxima_lib.py | 4 +- src/sage/interfaces/qepcad.py | 2 +- src/sage/interfaces/r.py | 2 +- src/sage/interfaces/sagespawn.pyx | 2 +- src/sage/interfaces/singular.py | 4 +- src/sage/interfaces/tachyon.py | 2 +- src/sage/interfaces/tides.py | 4 +- src/sage/libs/coxeter3/coxeter_group.py | 2 +- src/sage/libs/linbox/fflas.pxd | 2 +- src/sage/libs/ntl/ntl_lzz_pContext.pyx | 2 +- src/sage/libs/singular/function.pyx | 2 +- src/sage/libs/singular/groebner_strategy.pyx | 2 +- src/sage/libs/singular/singular.pyx | 9 +- src/sage/manifolds/chart.py | 4 +- src/sage/manifolds/chart_func.py | 2 +- .../differentiable/examples/euclidean.py | 2 +- .../manifolds/differentiable/mixed_form.py | 2 +- .../differentiable/vectorfield_module.py | 2 +- src/sage/manifolds/scalarfield.py | 2 +- src/sage/matrix/matrix0.pyx | 4 +- src/sage/matrix/matrix1.pyx | 4 +- src/sage/matrix/matrix2.pyx | 90 ++++++++++--- src/sage/matrix/matrix_complex_ball_dense.pyx | 6 +- src/sage/matrix/matrix_double_dense.pyx | 4 +- src/sage/matrix/matrix_double_sparse.pyx | 4 +- src/sage/matrix/matrix_gfpn_dense.pyx | 2 +- .../matrix/matrix_modn_dense_template.pxi | 11 +- src/sage/matrix/matrix_modn_sparse.pyx | 2 +- src/sage/matrix/matrix_space.py | 2 +- src/sage/media/wav.py | 2 +- src/sage/misc/cachefunc.pyx | 4 +- src/sage/misc/compat.py | 2 +- src/sage/misc/cython.py | 8 +- src/sage/misc/decorators.py | 12 +- src/sage/misc/dev_tools.py | 6 +- src/sage/misc/dist.py | 2 +- src/sage/misc/functional.py | 6 +- src/sage/misc/latex_standalone.py | 12 +- src/sage/misc/lazy_import.pyx | 22 +-- src/sage/misc/misc.py | 12 +- src/sage/misc/package.py | 4 +- src/sage/misc/persist.pyx | 2 +- src/sage/misc/rest_index_of_methods.py | 2 +- src/sage/misc/sagedoc.py | 17 +-- src/sage/misc/sagedoc_conf.py | 2 +- src/sage/misc/sageinspect.py | 2 +- src/sage/misc/stopgap.pyx | 14 +- src/sage/misc/superseded.py | 127 +++++++++--------- src/sage/misc/weak_dict.pyx | 4 +- src/sage/modular/dirichlet.py | 2 +- src/sage/modular/modform/find_generators.py | 8 +- src/sage/modular/modsym/p1list.pyx | 2 +- src/sage/modules/matrix_morphism.py | 2 +- src/sage/modules/vector_mod2_dense.pyx | 2 +- .../modules/with_basis/indexed_element.pyx | 2 +- .../numerical/backends/generic_backend.pyx | 2 +- .../numerical/interactive_simplex_method.py | 4 +- src/sage/numerical/optimize.py | 2 +- src/sage/parallel/map_reduce.py | 6 +- src/sage/plot/graphics.py | 2 +- src/sage/plot/histogram.py | 2 +- src/sage/plot/matrix_plot.py | 2 +- src/sage/plot/plot.py | 4 +- src/sage/plot/plot3d/implicit_surface.pyx | 2 +- src/sage/plot/plot3d/list_plot3d.py | 2 +- src/sage/plot/plot3d/texture.py | 2 +- ...uadratic_form__local_density_congruence.py | 2 +- src/sage/quivers/representation.py | 2 +- src/sage/repl/configuration.py | 6 +- src/sage/repl/load.py | 2 +- src/sage/repl/preparse.py | 2 +- src/sage/rings/all.py | 6 +- .../asymptotic_expansion_generators.py | 4 +- src/sage/rings/asymptotic/asymptotic_ring.py | 2 +- ...otics_multivariate_generating_functions.py | 2 +- src/sage/rings/asymptotic/term_monoid.py | 6 +- src/sage/rings/bernmm/README.txt | 2 +- src/sage/rings/complex_double.pyx | 2 +- src/sage/rings/complex_field.py | 2 +- src/sage/rings/complex_interval_field.py | 2 +- src/sage/rings/complex_mpfr.pyx | 2 +- src/sage/rings/complex_number.pyx | 2 +- .../rings/finite_rings/integer_mod_ring.py | 2 +- src/sage/rings/fraction_field_FpT.pyx | 2 +- src/sage/rings/function_field/order.py | 2 +- src/sage/rings/ideal.py | 2 +- src/sage/rings/integer.pyx | 2 +- .../rings/laurent_series_ring_element.pyx | 2 +- src/sage/rings/lazy_series.py | 6 +- src/sage/rings/morphism.pyx | 6 +- src/sage/rings/number_field/galois_group.py | 22 +-- src/sage/rings/number_field/number_field.py | 16 +-- .../number_field/number_field_element.pyx | 8 +- .../number_field_element_quadratic.pyx | 2 +- src/sage/rings/padics/factory.py | 2 +- src/sage/rings/padics/generic_nodes.py | 6 +- src/sage/rings/padics/lattice_precision.py | 2 +- src/sage/rings/padics/local_generic.py | 2 +- .../rings/padics/local_generic_element.pyx | 2 +- src/sage/rings/padics/padic_base_generic.py | 2 +- src/sage/rings/padics/padic_base_leaves.py | 4 +- src/sage/rings/padics/padic_generic.py | 10 +- .../rings/padics/padic_generic_element.pyx | 2 +- .../rings/padics/padic_lattice_element.py | 2 +- .../polynomial/infinite_polynomial_element.py | 2 +- .../rings/polynomial/laurent_polynomial.pyx | 4 +- .../multi_polynomial_libsingular.pyx | 6 +- .../polynomial/ore_polynomial_element.pyx | 2 +- .../polynomial_padic_capped_relative_dense.py | 2 +- src/sage/rings/polynomial/pbori/pbori.pyx | 4 +- .../rings/polynomial/polynomial_element.pyx | 6 +- .../polynomial/polynomial_element_generic.py | 4 +- .../polynomial/polynomial_rational_flint.pyx | 2 +- src/sage/rings/polynomial/polynomial_ring.py | 6 +- .../polynomial/skew_polynomial_element.pyx | 4 +- .../rings/polynomial/skew_polynomial_ring.py | 2 +- src/sage/rings/polynomial/term_order.py | 2 +- src/sage/rings/power_series_poly.pyx | 9 +- src/sage/rings/power_series_ring.py | 2 +- src/sage/rings/qqbar.py | 24 ++-- src/sage/rings/rational.pyx | 6 +- src/sage/rings/real_double.pyx | 2 +- src/sage/rings/real_interval_field.py | 2 +- src/sage/rings/real_mpfi.pyx | 8 +- src/sage/rings/real_mpfr.pyx | 16 +-- src/sage/rings/ring_extension.pyx | 4 +- src/sage/sat/solvers/satsolver.pyx | 2 +- src/sage/schemes/elliptic_curves/ell_field.py | 10 +- .../elliptic_curves/ell_finite_field.py | 61 +++++++++ .../schemes/elliptic_curves/ell_local_data.py | 2 +- .../elliptic_curves/ell_number_field.py | 5 + .../schemes/elliptic_curves/ell_torsion.py | 2 +- .../schemes/elliptic_curves/formal_group.py | 2 +- src/sage/schemes/elliptic_curves/hom.py | 4 +- src/sage/schemes/elliptic_curves/padics.py | 2 +- .../elliptic_curves/weierstrass_morphism.py | 95 +++++++------ src/sage/schemes/generic/algebraic_scheme.py | 4 +- src/sage/schemes/hyperelliptic_curves/all.py | 8 +- .../hyperelliptic_finite_field.py | 10 +- .../hyperelliptic_curves/monsky_washnitzer.py | 6 +- .../schemes/projective/proj_bdd_height.py | 2 +- .../schemes/projective/projective_morphism.py | 6 +- .../schemes/projective/projective_point.py | 2 +- src/sage/schemes/toric/variety.py | 6 +- src/sage/sets/recursively_enumerated_set.pyx | 2 +- src/sage/sets/set.py | 2 +- src/sage/stats/basic_stats.py | 16 +-- src/sage/stats/time_series.pyx | 2 +- src/sage/structure/coerce_actions.pyx | 2 +- src/sage/structure/coerce_maps.pyx | 2 +- src/sage/structure/dynamic_class.py | 2 +- src/sage/structure/element.pyx | 2 +- src/sage/structure/global_options.py | 2 +- src/sage/structure/graphics_file.py | 2 +- src/sage/structure/list_clone.pxd | 2 +- src/sage/structure/parent.pyx | 2 +- src/sage/structure/support_view.py | 4 +- src/sage/symbolic/callable.py | 4 +- src/sage/symbolic/expression.pyx | 12 +- src/sage/symbolic/expression_conversions.py | 2 +- src/sage/symbolic/function.pyx | 2 +- src/sage/symbolic/ginac/README.md | 2 +- src/sage/symbolic/ginac/templates.cpp | 2 +- src/sage/symbolic/pynac_impl.pxi | 2 +- src/sage/symbolic/ring.pyx | 2 +- src/sage/tensor/modules/comp.py | 2 +- .../tensor/modules/ext_pow_free_module.py | 4 +- .../tensor/modules/finite_rank_free_module.py | 4 +- src/sage/tensor/modules/reflexive_module.py | 4 +- .../tensor/modules/tensor_free_submodule.py | 2 +- .../graphique_doctest.py | 6 +- src/sage/tests/lazy_imports.py | 4 +- src/sage/tests/test_deprecation.py | 4 +- src/sage/topology/cubical_complex.py | 2 +- src/sage/topology/simplicial_complex.py | 2 +- .../topology/simplicial_complex_examples.py | 2 +- src/sage/typeset/character_art.py | 2 +- src/sage/version.py | 6 +- src/sage_docbuild/__main__.py | 4 +- src/sage_docbuild/builders.py | 2 +- src/sage_docbuild/conf.py | 4 +- src/sage_docbuild/ext/sage_autodoc.py | 16 +-- src/sage_docbuild/sphinxbuild.py | 2 +- src/sage_setup/command/sage_build_cython.py | 2 +- src/sage_setup/run_parallel.py | 2 +- src/sage_setup/setenv.py | 2 +- src/tox.ini | 1 + 408 files changed, 1212 insertions(+), 889 deletions(-) diff --git a/.devcontainer/portability-updateContent.sh b/.devcontainer/portability-updateContent.sh index a923fc0d106..41033933911 100755 --- a/.devcontainer/portability-updateContent.sh +++ b/.devcontainer/portability-updateContent.sh @@ -5,14 +5,14 @@ # The script assumes that it is run from SAGE_ROOT. # # If "config.log" or "logs" are symlinks (for example, created by 'tox -e local-...', -# or after https://trac.sagemath.org/ticket/33262), they might point outside of +# or after https://github.com/sagemath/sage/issues/33262), they might point outside of # the dev container, so remove them. Likewise for upstream. for f in config.log logs upstream; do if [ -L $f ]; then rm -f $f fi done -# If possible (ensured after https://trac.sagemath.org/ticket/33262), keep the +# If possible (ensured after https://github.com/sagemath/sage/issues/33262), keep the # logs in the container. if [ ! -d logs ]; then ln -s /sage/logs logs diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 015f1c8fb4f..c07c6968095 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -38,7 +38,7 @@ jobs: matrix: stage: ["1", "2", "2-optional-0-o", "2-optional-p-z", "2-experimental-0-o", "2-experimental-p-z"] # python3_xcode is only accepted if enough packages are available from the system - # --> to test "minimal", we would need https://trac.sagemath.org/ticket/30949 + # --> to test "minimal", we would need https://github.com/sagemath/sage/issues/30949 tox_env: [homebrew-macos-usrlocal-minimal, homebrew-macos-usrlocal-standard, homebrew-macos-usrlocal-maximal, homebrew-macos-usrlocal-python3_xcode-standard, conda-forge-macos-minimal, conda-forge-macos-standard, conda-forge-macos-maximal] xcode_version_factor: [default] os: [ macos-11, macos-12 ] diff --git a/.github/workflows/ci-wsl.yml b/.github/workflows/ci-wsl.yml index 4093af802b0..e6d4d30cfc3 100644 --- a/.github/workflows/ci-wsl.yml +++ b/.github/workflows/ci-wsl.yml @@ -13,7 +13,7 @@ jobs: windows: runs-on: windows-latest name: Ubuntu 20.04 - # Following https://trac.sagemath.org/ticket/25206#comment:63 + # Following https://github.com/sagemath/sage/issues/25206#comment:63 steps: - name: Configure git run: git config --global core.symlinks true diff --git a/.gitignore b/.gitignore index b93c5dbd12b..2faf325a44c 100644 --- a/.gitignore +++ b/.gitignore @@ -187,7 +187,7 @@ build/bin/sage-build-env-config /pkgs/sagemath-repl/requirements.txt /pkgs/sagemath-categories/MANIFEST.in -# same for old locations - before Trac #31577 +# same for old locations - before Issue #31577 /build/pkgs/*/src/build /build/pkgs/*/src/dist /build/pkgs/*/src/MANIFEST diff --git a/.zenodo.json b/.zenodo.json index da76244bfa7..45c935970a7 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,10 +1,10 @@ { "description": "Mirror of the Sage https://sagemath.org/ source tree", "license": "other-open", - "title": "sagemath/sage: 10.0.beta0", - "version": "10.0.beta0", + "title": "sagemath/sage: 10.0.beta1", + "version": "10.0.beta1", "upload_type": "software", - "publication_date": "2023-02-12", + "publication_date": "2023-02-19", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/10.0.beta0", + "identifier": "https://github.com/sagemath/sage/tree/10.0.beta1", "relation": "isSupplementTo" }, { diff --git a/Makefile b/Makefile index 414398ddf0d..008d6ff2221 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ SAGE_ROOT_LOGS = logs # The --stop flag below is just a random flag to induce graceful # breakage with non-GNU versions of make. -# See https://trac.sagemath.org/ticket/24617 +# See https://github.com/sagemath/sage/issues/24617 # Defer unknown targets to build/make/Makefile %:: @@ -259,8 +259,8 @@ TEST_FILES = --all TEST_FLAGS = # When the documentation is installed, "optional" also includes all tests marked 'sagemath_doc_html', -# see https://trac.sagemath.org/ticket/25345, https://trac.sagemath.org/ticket/26110, and -# https://trac.sagemath.org/ticket/32759 +# see https://github.com/sagemath/sage/issues/25345, https://github.com/sagemath/sage/issues/26110, and +# https://github.com/sagemath/sage/issues/32759 TEST_OPTIONAL = sage,optional # Keep track of the top-level *test* Makefile target for logging. diff --git a/README.md b/README.md index 1a0582d87c5..1233f17de73 100644 --- a/README.md +++ b/README.md @@ -329,7 +329,7 @@ in the Installation Guide. manager. For a large [list of Sage - packages](https://trac.sagemath.org/ticket/27330), Sage is able to + packages](https://github.com/sagemath/sage/issues/27330), Sage is able to detect whether an installed system package is suitable for use with Sage; in that case, Sage will not build another copy from source. diff --git a/VERSION.txt b/VERSION.txt index ec675a255a2..8b731a891fa 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.0.beta0, Release Date: 2023-02-12 +SageMath version 10.0.beta1, Release Date: 2023-02-19 diff --git a/bootstrap b/bootstrap index 75bd9c78b9d..48c4801d4b5 100755 --- a/bootstrap +++ b/bootstrap @@ -47,17 +47,17 @@ AS_VAR_SET_IF([SAGE_ENABLE_$pkgname], [], [AS_VAR_SET([SAGE_ENABLE_$pkgname], [y done # --enable-SPKG options for pkgname in $(sage-package list :optional: :experimental: | sort); do - # Trac #29629: Temporary solution for Sage 9.1: Do not provide + # Issue #29629: Temporary solution for Sage 9.1: Do not provide # --enable-SPKG options for installing pip packages if [ ! -f build/pkgs/$pkgname/requirements.txt ]; then pkgtype="$(cat build/pkgs/$pkgname/type)" - # Trac #29124: Do not provide --enable-_recommended and similar + # Issue #29124: Do not provide --enable-_recommended and similar case "$pkgname" in _*) ;; *) spkg_configures="$spkg_configures AC_SUBST(SAGE_ENABLE_$pkgname, [if_installed])" if [ -f build/pkgs/$pkgname/spkg-install -o -f build/pkgs/$pkgname/spkg-install.in ]; then - # Trac #31163: Not just an optional dummy package + # Issue #31163: Not just an optional dummy package spkg_configures="$spkg_configures SAGE_SPKG_ENABLE([$pkgname], [$pkgtype], [$(grep -v ^= build/pkgs/$pkgname/SPKG.rst | head -n1 2>/dev/null || echo $pkgname)])" fi diff --git a/build/bin/sage-bootstrap-python b/build/bin/sage-bootstrap-python index db4cea35dfd..1fa3ea565b1 100755 --- a/build/bin/sage-bootstrap-python +++ b/build/bin/sage-bootstrap-python @@ -30,7 +30,7 @@ SAGE_ORIG_PATH=${NEW_PATH%%':'} # Also, Trac #20023 removed the vendored argparse library from sage_bootstrap, # so we test that python is new enough (>= 2.7) to run it. # -# See https://trac.sagemath.org/ticket/29090 +# See https://github.com/sagemath/sage/issues/29090 # Trac #29890: Our first choice is "python", not "python3". This is to avoid # a defect of sage_bootstrap on macOS regarding SSL URLs. diff --git a/build/bin/sage-build-num-threads b/build/bin/sage-build-num-threads index 5dbdc5d5029..1ba165b1a21 100755 --- a/build/bin/sage-build-num-threads +++ b/build/bin/sage-build-num-threads @@ -10,7 +10,7 @@ # 3) The number of CPU cores in the system, as determined by # multiprocessing.cpu_count() # -# AUTHOR: Jeroen Demeyer (2011-12-08): Trac ticket #12016 +# AUTHOR: Jeroen Demeyer (2011-12-08): Github issue #12016 # from __future__ import print_function diff --git a/build/bin/sage-dist-helpers b/build/bin/sage-dist-helpers index 24769ebfffc..4eac9626ffa 100644 --- a/build/bin/sage-dist-helpers +++ b/build/bin/sage-dist-helpers @@ -98,7 +98,7 @@ # (Linux only--no-op on other platforms.) Check shared libraries loaded by # EXECUTABLE (may be a program or another library) for a library starting # with SONAME, and if found appends SONAME to the LD_PRELOAD environment -# variable. See https://trac.sagemath.org/24885. +# variable. See https://github.com/sagemath/sage/issues/24885. set -o allexport @@ -166,7 +166,7 @@ sdh_configure() { echo "Configuring $PKG_NAME" # Run all configure scripts with bash to work around bugs with # non-portable scripts. - # See https://trac.sagemath.org/ticket/24491 + # See https://github.com/sagemath/sage/issues/24491 if [ -z "$CONFIG_SHELL" ]; then export CONFIG_SHELL=`command -v bash` fi @@ -290,7 +290,7 @@ sdh_pip_install() { sdh_pip_editable_install() { echo "Installing $PKG_NAME (editable mode)" - # Until https://trac.sagemath.org/ticket/34209 switches us to PEP 660 editable wheels + # Until https://github.com/sagemath/sage/issues/34209 switches us to PEP 660 editable wheels export SETUPTOOLS_ENABLE_FEATURES=legacy-editable python3 -m pip install --verbose --no-deps --no-index --no-build-isolation --isolated --editable "$@" || \ sdh_die "Error installing $PKG_NAME" diff --git a/build/bin/sage-flock b/build/bin/sage-flock index d10eaf55091..9611cfde80b 100755 --- a/build/bin/sage-flock +++ b/build/bin/sage-flock @@ -11,7 +11,7 @@ # This is originally motivated by pip, but has since been generalized. We # should avoid running pip while uninstalling a package because that is prone # to race conditions. This script can be used to run pip under a lock. For -# details, see https://trac.sagemath.org/ticket/21672 +# details, see https://github.com/sagemath/sage/issues/21672 try: import sage_bootstrap diff --git a/build/bin/sage-site b/build/bin/sage-site index f9d13d53047..f36eb4d415d 100755 --- a/build/bin/sage-site +++ b/build/bin/sage-site @@ -184,7 +184,7 @@ if [ "$1" = '-i' ]; then for PKG in $PACKAGES; do echo # Check that $PKG is actually a Makefile target - # See https://trac.sagemath.org/ticket/25078 + # See https://github.com/sagemath/sage/issues/25078 if ! echo "$ALL_TARGETS" | grep "^${PKG}$" >/dev/null; then echo >&2 "Error: package '$PKG' not found" echo >&2 "Note: if it is an old-style package, installing these is no longer supported" diff --git a/build/bin/sage-spkg b/build/bin/sage-spkg index 53835babf27..9b38ed6afc7 100755 --- a/build/bin/sage-spkg +++ b/build/bin/sage-spkg @@ -686,7 +686,7 @@ echo "Copying package files from temporary location $SAGE_DESTDIR to $SAGE_INST_ if [ -d "$SAGE_DESTDIR" ]; then # Some `find` implementations will put superfluous slashes in the # output if we give them a directory name with a slash; so make sure - # any trailing slash is removed; https://trac.sagemath.org/ticket/26013 + # any trailing slash is removed; https://github.com/sagemath/sage/issues/26013 PREFIX="${SAGE_DESTDIR_LOCAL%/}" rm -f "$PREFIX"/lib/*.la diff --git a/build/bin/sage-spkg-info b/build/bin/sage-spkg-info index ffc864320ed..4e53139fa7e 100755 --- a/build/bin/sage-spkg-info +++ b/build/bin/sage-spkg-info @@ -116,10 +116,10 @@ else echo "However, these system packages will not be used for building Sage" if [ -f "$PKG_SCRIPTS"/install-requires.txt ]; then echo "because using Python site-packages is not supported by the Sage distribution;" - echo "see https://trac.sagemath.org/ticket/29023" + echo "see https://github.com/sagemath/sage/issues/29023" else echo "because spkg-configure.m4 has not been written for this package;" - echo "see https://trac.sagemath.org/ticket/27330" + echo "see https://github.com/sagemath/sage/issues/27330" fi fi fi diff --git a/build/bin/sage-venv b/build/bin/sage-venv index 9ee9fc794be..c2728ba1143 100755 --- a/build/bin/sage-venv +++ b/build/bin/sage-venv @@ -38,7 +38,7 @@ if options.upgrade and options.clear: raise ValueError('you cannot supply --upgrade and --clear together.') if sys.platform == 'cygwin': - # default for Cygwin; see https://trac.sagemath.org/ticket/30149 + # default for Cygwin; see https://github.com/sagemath/sage/issues/30149 use_symlinks = False else: # default for posix diff --git a/build/make/Makefile.in b/build/make/Makefile.in index e971def2416..84f6f877e3d 100644 --- a/build/make/Makefile.in +++ b/build/make/Makefile.in @@ -78,7 +78,7 @@ DUMMY_PACKAGES = @SAGE_DUMMY_PACKAGES@ # Set to the path to Sage's GCC (if GCC is installed) to force rebuilds # of packages if GCC changed. -# See m4/sage_spkg_collect.m4 and https://trac.sagemath.org/ticket/24703 +# See m4/sage_spkg_collect.m4 and https://github.com/sagemath/sage/issues/24703 GCC_DEP = @SAGE_GCC_DEP@ # Versions of all the packages, in the format diff --git a/build/pkgs/_prereq/distros/cygwin.txt b/build/pkgs/_prereq/distros/cygwin.txt index 115d2555745..2bf3164005b 100644 --- a/build/pkgs/_prereq/distros/cygwin.txt +++ b/build/pkgs/_prereq/distros/cygwin.txt @@ -12,14 +12,14 @@ binutils make m4 -# a system python is needed for downloading the sage packages, https://trac.sagemath.org/ticket/29090 +# a system python is needed for downloading the sage packages, https://github.com/sagemath/sage/issues/29090 python39-urllib3 python39 perl perl-ExtUtils-MakeMaker tar gcc-core gcc-g++ -# Needed according to embray at https://trac.sagemath.org/ticket/26964: +# Needed according to embray at https://github.com/sagemath/sage/issues/26964: # The need for which comes [...] from MPIR's configure script findutils which diff --git a/build/pkgs/_prereq/distros/fedora.txt b/build/pkgs/_prereq/distros/fedora.txt index a82e29abc30..79919eef51b 100644 --- a/build/pkgs/_prereq/distros/fedora.txt +++ b/build/pkgs/_prereq/distros/fedora.txt @@ -15,7 +15,7 @@ binutils make m4 -# a system python is needed for downloading the sage packages, https://trac.sagemath.org/ticket/29090 +# a system python is needed for downloading the sage packages, https://github.com/sagemath/sage/issues/29090 python3 perl perl-ExtUtils-MakeMaker @@ -26,11 +26,11 @@ gcc # configure: error: in `/sage': # configure: error: C++ preprocessor "/lib/cpp" fails sanity check gcc-c++ -# Needed according to embray at https://trac.sagemath.org/ticket/26964: +# Needed according to embray at https://github.com/sagemath/sage/issues/26964: # The need for which comes [...] from MPIR's configure script findutils which -# Needed for pcre configure, see https://trac.sagemath.org/ticket/29129: +# Needed for pcre configure, see https://github.com/sagemath/sage/issues/29129: diffutils # Needed for openssl 3.0 perl-IPC-Cmd diff --git a/build/pkgs/cddlib/spkg-configure.m4 b/build/pkgs/cddlib/spkg-configure.m4 index 8508f28512d..b18cd879b00 100644 --- a/build/pkgs/cddlib/spkg-configure.m4 +++ b/build/pkgs/cddlib/spkg-configure.m4 @@ -16,7 +16,7 @@ SAGE_SPKG_CONFIGURE([cddlib], [ AC_CHECK_PROGS(SCDD, [scdd_gmp scdd]) AS_IF([test x$SCDD = x], [sage_spkg_install_cddlib=yes]) - dnl https://trac.sagemath.org/ticket/30319 + dnl https://github.com/sagemath/sage/issues/30319 AS_IF([test -n "$CDDEXEC"], [ AC_MSG_CHECKING([whether $CDDEXEC --redcheck works correctly for real input]) cat > conftest.ine <&2 "Warning: Disabling debug symbols on MacOS X 10.5" \ "PowerPC because of a linker (?) bug." - echo >&2 "See http://trac.sagemath.org/sage_trac/ticket/5847#comment:35" \ + echo >&2 "See https://github.com/sagemath/sage/issues/5847#comment:35" \ "ff. for details." echo >&2 CFLAGS="-O3 $ORIGINAL_CFLAGS" diff --git a/build/pkgs/elliptic_curves/spkg-install.py b/build/pkgs/elliptic_curves/spkg-install.py index 79b6c423446..c6e9bbcffa9 100644 --- a/build/pkgs/elliptic_curves/spkg-install.py +++ b/build/pkgs/elliptic_curves/spkg-install.py @@ -60,7 +60,7 @@ def install_ellcurves(): ellcurves_root = os.path.join(SAGE_SHARE, 'ellcurves') # Remove previous installs (possibly with bad permissions, see - # https://trac.sagemath.org/ticket/21641) + # https://github.com/sagemath/sage/issues/21641) import shutil try: shutil.rmtree(ellcurves_root) diff --git a/build/pkgs/fflas_ffpack/spkg-install.in b/build/pkgs/fflas_ffpack/spkg-install.in index 55684dd8a5f..b9dd851875f 100644 --- a/build/pkgs/fflas_ffpack/spkg-install.in +++ b/build/pkgs/fflas_ffpack/spkg-install.in @@ -24,13 +24,13 @@ if [ "$SAGE_FAT_BINARY" = yes ]; then fi # Need to use 'bash' for configure, see -# https://trac.sagemath.org/ticket/23451 +# https://github.com/sagemath/sage/issues/23451 if [ -z "$CONFIG_SHELL" ]; then export CONFIG_SHELL=`command -v bash` fi # We disable openmp because of build failures, see -# http://trac.sagemath.org/ticket/17635#comment:67 +# https://github.com/sagemath/sage/issues/17635#comment:67 sdh_configure --with-default="$SAGE_LOCAL" --with-blas-libs="$LINBOX_BLAS" \ "$LINBOX_BLAS_CFLAGS" --disable-static \ --enable-precompilation $FFLAS_FFPACK_CONFIGURE diff --git a/build/pkgs/gap/spkg-install.in b/build/pkgs/gap/spkg-install.in index 2ceadf99db4..30320027274 100644 --- a/build/pkgs/gap/spkg-install.in +++ b/build/pkgs/gap/spkg-install.in @@ -53,7 +53,7 @@ sdh_install pkg/GAPDoc-* pkg/primgrp-* pkg/SmallGrp-* pkg/transgrp "$GAP_ROOT"/p # typically "expected" to be loaded: These are the default packages that are # autoloaded at GAP startup (via the PackagesToLoad UserPreference) with an # out-of-the-box GAP installation; see -# https://trac.sagemath.org/ticket/22626#comment:393 for discussion on this +# https://github.com/sagemath/sage/issues/22626#comment:393 for discussion on this # # Also include atlasrep which is a dependency of tomlib sdh_install \ @@ -96,7 +96,7 @@ done # be going away. This breaks the build toolchain for some compiled GAP # packages. We need to replace these paths with the final GAP_ROOT path. The # below will work so long as neither of these paths contain '|', and if they do -# then god help you. https://trac.sagemath.org/ticket/27218 +# then god help you. https://github.com/sagemath/sage/issues/27218 sed -i -e "s|$GAP_BUILD_ROOT|$GAP_ROOT|g" \ "$SAGE_DESTDIR$SAGE_BIN/gac" "$DESTDIR_GAP_ROOT/sysinfo.gap" \ "$DESTDIR_GAP_ROOT/bin/gap.sh" "$DESTDIR_GAP_ROOT/doc/make_doc" || \ diff --git a/build/pkgs/gc/spkg-install.in b/build/pkgs/gc/spkg-install.in index e5d254c86fe..4acf5cf8b19 100644 --- a/build/pkgs/gc/spkg-install.in +++ b/build/pkgs/gc/spkg-install.in @@ -3,9 +3,9 @@ cd src GC_CONFIGURE="--enable-large-config" if [ "$UNAME" = "CYGWIN" ]; then - # See https://trac.sagemath.org/ticket/22694 + # See https://github.com/sagemath/sage/issues/22694 GC_CONFIGURE="$GC_CONFIGURE --enable-threads=posix --enable-handle-fork --enable-shared --disable-static" - # Force use of mmap on Cygwin https://trac.sagemath.org/ticket/23973 + # Force use of mmap on Cygwin https://github.com/sagemath/sage/issues/23973 export CFLAGS="$CFLAGS -DUSE_MMAP -DUSE_MUNMAP" fi diff --git a/build/pkgs/gcc/dependencies b/build/pkgs/gcc/dependencies index 9af2c9534bb..34a0cdf783d 100644 --- a/build/pkgs/gcc/dependencies +++ b/build/pkgs/gcc/dependencies @@ -5,5 +5,5 @@ NOTE: all dependencies of GCC must be order-only dependencies (appearing after the | symbol). This is to prevent rebuilds of GCC even if the dependencies are updated. There is logic in the top-level configure file to ensure that GCC is rebuilt anyway if required. See -https://trac.sagemath.org/ticket/24907 +https://github.com/sagemath/sage/issues/24907 ------------------------------------------------------------------------ diff --git a/build/pkgs/gcc/spkg-configure.m4 b/build/pkgs/gcc/spkg-configure.m4 index 63335eb7357..fe4d220a189 100644 --- a/build/pkgs/gcc/spkg-configure.m4 +++ b/build/pkgs/gcc/spkg-configure.m4 @@ -78,7 +78,7 @@ SAGE_SPKG_CONFIGURE_BASE([gcc], [ SAGE_MUST_INSTALL_GCC([gcc is already installed in SAGE_LOCAL]) # Check whether it actually works... - # See https://trac.sagemath.org/ticket/24599 + # See https://github.com/sagemath/sage/issues/24599 SAGE_CHECK_BROKEN_GCC() if test x$SAGE_BROKEN_GCC = xyes; then # Prentend that GCC is not installed. @@ -167,7 +167,7 @@ SAGE_SPKG_CONFIGURE_BASE([gcc], [ ], [1[[3-9]].*], [ # Install our own GCC if the system-provided one is newer than 12.x. - # See https://trac.sagemath.org/ticket/29456 + # See https://github.com/sagemath/sage/issues/29456 SAGE_SHOULD_INSTALL_GCC([$CXX is g++ version $GXX_VERSION, which is too recent for this version of Sage]) ]) fi @@ -181,7 +181,7 @@ SAGE_SPKG_CONFIGURE_BASE([gcc], [ fi # Check that the assembler and linker used by $CXX match $AS and $LD. - # See http://trac.sagemath.org/sage_trac/ticket/14296 + # See https://github.com/sagemath/sage/issues/14296 if test -n "$AS"; then CXX_as=`$CXX -print-prog-name=as 2>/dev/null` CXX_as=`command -v $CXX_as 2>/dev/null` diff --git a/build/pkgs/gdb/distros/conda.txt b/build/pkgs/gdb/distros/conda.txt index 5d13af10681..b7a56f9afb3 100644 --- a/build/pkgs/gdb/distros/conda.txt +++ b/build/pkgs/gdb/distros/conda.txt @@ -1,2 +1,2 @@ -# Disabled for now because of https://trac.sagemath.org/ticket/30845#comment:269 +# Disabled for now because of https://github.com/sagemath/sage/issues/30845#comment:269 # gdb diff --git a/build/pkgs/gfan/patches/fix-int64-for-32bit-archs.patch b/build/pkgs/gfan/patches/fix-int64-for-32bit-archs.patch index 0d1baf45cec..773b71d08c0 100644 --- a/build/pkgs/gfan/patches/fix-int64-for-32bit-archs.patch +++ b/build/pkgs/gfan/patches/fix-int64-for-32bit-archs.patch @@ -3,7 +3,7 @@ On 32-bit architectures, longs are only 32 bits. The resulting overflow was causing an infinite loop in the 0602ResultantFanProjection test. References: -https://trac.sagemath.org/ticket/32088 +https://github.com/sagemath/sage/issues/32088 https://salsa.debian.org/math-team/gfan/-/commit/acaaa70 https://github.com/void-linux/void-packages/pull/34182 diff --git a/build/pkgs/gfan/spkg-install.in b/build/pkgs/gfan/spkg-install.in index f3224735e4f..5c31e18ec54 100644 --- a/build/pkgs/gfan/spkg-install.in +++ b/build/pkgs/gfan/spkg-install.in @@ -8,7 +8,7 @@ cd src find src -type f -print0 | xargs -0 sed -i.bak "s/log2/logger2/g" # To let testsuite/0009RenderStairCase pass on 32bit machines -# See https://trac.sagemath.org/ticket/32088 +# See https://github.com/sagemath/sage/issues/32088 case "$($CC -dumpmachine)" in i[3456]86*) CXXFLAGS+=" -ffloat-store" diff --git a/build/pkgs/gfortran/spkg-configure.m4 b/build/pkgs/gfortran/spkg-configure.m4 index db7c4e7bc14..1a2d6ba767c 100644 --- a/build/pkgs/gfortran/spkg-configure.m4 +++ b/build/pkgs/gfortran/spkg-configure.m4 @@ -88,7 +88,7 @@ SAGE_SPKG_CONFIGURE([gfortran], [ ], [1[[3-9]].*], [ # Install our own gfortran if the system-provided one is newer than 12.x. - # See https://trac.sagemath.org/ticket/29456, https://trac.sagemath.org/ticket/31838 + # See https://github.com/sagemath/sage/issues/29456, https://github.com/sagemath/sage/issues/31838 SAGE_MUST_INSTALL_GFORTRAN([$FC is version $GFORTRAN_VERSION, which is too recent for this version of Sage]) ]) ]) diff --git a/build/pkgs/giac/patches/cygwin-icas.patch b/build/pkgs/giac/patches/cygwin-icas.patch index 88669806647..56321e78b26 100644 --- a/build/pkgs/giac/patches/cygwin-icas.patch +++ b/build/pkgs/giac/patches/cygwin-icas.patch @@ -1,6 +1,6 @@ Disable threaded eval function on Cygwin since it's currently buggy, and not strictly needed in the first place since we don't -build giac with FLTK support; see https://trac.sagemath.org/ticket/27385 +build giac with FLTK support; see https://github.com/sagemath/sage/issues/27385 --- a/src/icas.cc 2018-12-22 17:08:24.000000000 +0100 +++ b/src/icas.cc 2019-03-06 14:38:19.814030200 +0100 @@ -160,7 +160,7 @@ diff --git a/build/pkgs/giac/patches/isnan-conflict.patch b/build/pkgs/giac/patches/isnan-conflict.patch index 88ca5f715a6..20af63aa806 100644 --- a/build/pkgs/giac/patches/isnan-conflict.patch +++ b/build/pkgs/giac/patches/isnan-conflict.patch @@ -1,7 +1,7 @@ Just always use std::isnan and std::isinf so there is no risk of conflicting with the libc math.h equivalents thereof. -See https://trac.sagemath.org/ticket/27263 +See https://github.com/sagemath/sage/issues/27263 --- a/src/global.cc 2019-02-12 15:49:03.082594000 +0000 +++ b/src/global.cc 2019-02-12 15:49:43.438594000 +0000 @@ -4139,11 +4139,7 @@ diff --git a/build/pkgs/giac/patches/pari_2_11.patch b/build/pkgs/giac/patches/pari_2_11.patch index 24142257a34..27de3f12fac 100644 --- a/build/pkgs/giac/patches/pari_2_11.patch +++ b/build/pkgs/giac/patches/pari_2_11.patch @@ -1,6 +1,6 @@ Change test output for PARI 2.11 -See https://trac.sagemath.org/ticket/25567 +See https://github.com/sagemath/sage/issues/25567 and https://xcas.univ-grenoble-alpes.fr/forum/viewtopic.php?f=4&t=2102 diff -ru a/check/TP11-sol.cas.out1 b/check/TP11-sol.cas.out1 diff --git a/build/pkgs/glpk/patches/error_recovery.patch b/build/pkgs/glpk/patches/error_recovery.patch index f040f4b4a19..a383e25769b 100644 --- a/build/pkgs/glpk/patches/error_recovery.patch +++ b/build/pkgs/glpk/patches/error_recovery.patch @@ -1,6 +1,6 @@ From: Jeroen Demeyer Allow error recovery. See discussion at -http://trac.sagemath.org/ticket/20710#comment:18 +https://github.com/sagemath/sage/issues/20710#comment:18 diff --git a/src/env/error.c b/src/env/error.c index a898b76..154de0f 100644 diff --git a/build/pkgs/glpk/spkg-install.in b/build/pkgs/glpk/spkg-install.in index cdb4b4a168e..a911c46f024 100644 --- a/build/pkgs/glpk/spkg-install.in +++ b/build/pkgs/glpk/spkg-install.in @@ -1,6 +1,6 @@ cd src/ -# Use newer version of config.guess and config.sub (see Trac #19713) +# Use newer version of config.guess and config.sub (see Github issue #19713) cp "$SAGE_ROOT"/config/config.* . # Note: The following doesn't work with spaces in `$SAGE_LOCAL`, but we don't @@ -26,7 +26,7 @@ sdh_configure --with-gmp --disable-static sdh_make # Remove old libraries to make sure we can downgrade it if needed. -# See https://trac.sagemath.org/ticket/23596#comment:4 and later. +# See https://github.com/sagemath/sage/issues/23596#comment:4 and later. rm -f "$SAGE_LOCAL"/lib/libglpk.* sdh_make_install diff --git a/build/pkgs/glucose/spkg-install.in b/build/pkgs/glucose/spkg-install.in index 8dabc6458fb..f99cca1ae18 100644 --- a/build/pkgs/glucose/spkg-install.in +++ b/build/pkgs/glucose/spkg-install.in @@ -8,7 +8,7 @@ sdh_make sdh_install glucose ${SAGE_LOCAL}/bin/ cd .. -# Possible license issue, see warning below and discussion on https://trac.sagemath.org/ticket/26361 +# Possible license issue, see warning below and discussion on https://github.com/sagemath/sage/issues/26361 cd parallel sdh_make sdh_install glucose-syrup ${SAGE_LOCAL}/bin/ diff --git a/build/pkgs/iconv/spkg-check.in b/build/pkgs/iconv/spkg-check.in index 4ae5a6c622d..c0e91eff164 100644 --- a/build/pkgs/iconv/spkg-check.in +++ b/build/pkgs/iconv/spkg-check.in @@ -7,7 +7,7 @@ SunOS) # We must test iconv, but on Solaris some tests will always fail. echo "If you see 3 core dumps, don't be too alarmed." echo "This is a known Solaris bug and can safely be ignored. See" - echo " http://trac.sagemath.org/sage_trac/ticket/8270" + echo " https://github.com/sagemath/sage/issues/8270" echo "It will probably be fixed in later releases of Solaris 10," echo "and was fixed in build 66 of OpenSolaris:" echo " http://bugs.opensolaris.org/bugdatabase/view_bug.do?bug_id=6550204" diff --git a/build/pkgs/iconv/spkg-install.in b/build/pkgs/iconv/spkg-install.in index 9dc19c7bdab..89b401c70e9 100644 --- a/build/pkgs/iconv/spkg-install.in +++ b/build/pkgs/iconv/spkg-install.in @@ -1,6 +1,6 @@ # Only build iconv on Solaris, HP-UX and Cygwin. See -# http://trac.sagemath.org/sage_trac/ticket/8567 and -# http://trac.sagemath.org/sage_trac/ticket/9603 +# https://github.com/sagemath/sage/issues/8567 and +# https://github.com/sagemath/sage/issues/9603 # for details. case "$UNAME" in @@ -8,8 +8,8 @@ CYGWIN|HP-UX|SunOS) echo "iconv will be installed as the operating system is Cygwin, HP-UX or Solaris." echo "These systems either lack iconv, or do not have a sufficiently capable" echo "version of iconv. See:" - echo " http://trac.sagemath.org/sage_trac/ticket/8567" - echo " http://trac.sagemath.org/sage_trac/ticket/9603" + echo " https://github.com/sagemath/sage/issues/8567" + echo " https://github.com/sagemath/sage/issues/9603" # Disable NLS on Cygwin to be able to build libiconv without the Cygwin # libiconv package. @@ -29,8 +29,8 @@ CYGWIN|HP-UX|SunOS) echo "Solaris, HP-UX and Cygwin, as the system's iconv will be used" echo "on other platforms, rather than the one shipped with Sage." echo "See:" - echo " http://trac.sagemath.org/sage_trac/ticket/8567" - echo " http://trac.sagemath.org/sage_trac/ticket/9603" + echo " https://github.com/sagemath/sage/issues/8567" + echo " https://github.com/sagemath/sage/issues/9603" exit 0 esac diff --git a/build/pkgs/jupyterlab/requirements.txt b/build/pkgs/jupyterlab/requirements.txt index 802d33d22f1..f03a26674fd 100644 --- a/build/pkgs/jupyterlab/requirements.txt +++ b/build/pkgs/jupyterlab/requirements.txt @@ -1,3 +1,3 @@ jupyterlab ~= 3.3 -# See https://trac.sagemath.org/ticket/33607 +# See https://github.com/sagemath/sage/issues/33607 jupyterlab-server < 2.11 diff --git a/build/pkgs/libgd/spkg-install.in b/build/pkgs/libgd/spkg-install.in index dc73d868fa5..d291869a923 100644 --- a/build/pkgs/libgd/spkg-install.in +++ b/build/pkgs/libgd/spkg-install.in @@ -6,7 +6,7 @@ if [ "$UNAME" = "CYGWIN" ]; then # Compiling with vpx support creates a broken library in some cases # because the vpx package itself is broken on some older Cygwin versions; # we don't need this feature so safer to just disable - # https://trac.sagemath.org/ticket/27970 + # https://github.com/sagemath/sage/issues/27970 LIBGD_CONFIGURE="--without-vpx $LIBGD_CONFIGURE" fi if [ -n "$SAGE_FREETYPE_PREFIX" ]; then diff --git a/build/pkgs/linbox/spkg-install.in b/build/pkgs/linbox/spkg-install.in index a2ad194837a..8d415e81fd8 100644 --- a/build/pkgs/linbox/spkg-install.in +++ b/build/pkgs/linbox/spkg-install.in @@ -20,13 +20,13 @@ fi # Disable fplll as version 5.x is not supported by linbox <= 1.5.0. # This is harmless as no functionality using fplll is exposed in Sage. -# See trac ticket #21221. +# See github issue #21221. LINBOX_CONFIGURE="--without-fplll $LINBOX_CONFIGURE" # We disable openmp because of build failures, see -# http://trac.sagemath.org/ticket/17635#comment:67 +# https://github.com/sagemath/sage/issues/17635#comment:67 # We disable ocl because of build failures, see -# https://trac.sagemath.org/ticket/32076 +# https://github.com/sagemath/sage/issues/32076 sdh_configure --with-default="$SAGE_LOCAL" \ --disable-static --disable-openmp --without-ocl \ $LINBOX_CONFIGURE diff --git a/build/pkgs/matplotlib/make-setup-config.py b/build/pkgs/matplotlib/make-setup-config.py index 4f9acf1f04c..61f61806810 100644 --- a/build/pkgs/matplotlib/make-setup-config.py +++ b/build/pkgs/matplotlib/make-setup-config.py @@ -11,7 +11,7 @@ config.set('libs', 'system_qhull', 'True') # lto is problematic if we mix libraries from the OS with our own libraries, # which are not necessarily compiled with the same gcc version -# https://trac.sagemath.org/ticket/27754 +# https://github.com/sagemath/sage/issues/27754 config.set('libs', 'enable_lto', 'False') ##################################################################### diff --git a/build/pkgs/maxima/spkg-install.in b/build/pkgs/maxima/spkg-install.in index 3516e1b3146..3ae6382f9ba 100644 --- a/build/pkgs/maxima/spkg-install.in +++ b/build/pkgs/maxima/spkg-install.in @@ -1,6 +1,6 @@ # Sometimes, ECL gives interactive prompts when something goes wrong # during the build. Avoid this by redirecting stdin from /dev/null. -# See http://trac.sagemath.org/sage_trac/ticket/11884#comment:34 +# See https://github.com/sagemath/sage/issues/11884#comment:34 exec Date: Tue, 6 Mar 2018 00:18:41 +0100 Don't rely on pandoc for the readme -See https://trac.sagemath.org/ticket/24901 +See https://github.com/sagemath/sage/issues/24901 and https://github.com/aaren/notedown/issues/76 diff --git a/setup.py b/setup.py diff --git a/build/pkgs/numpy/spkg-install.in b/build/pkgs/numpy/spkg-install.in index 2b555d8b871..e28660f393f 100644 --- a/build/pkgs/numpy/spkg-install.in +++ b/build/pkgs/numpy/spkg-install.in @@ -7,7 +7,7 @@ if [ `uname` = "Darwin" ]; then unset ATLAS unset BLAS unset LAPACK - # https://trac.sagemath.org/ticket/34110#comment:35 + # https://github.com/sagemath/sage/issues/34110#comment:35 # The fix for "reciprocal" (affected by a clang compiler bug) in # https://github.com/numpy/numpy/pull/19926 relies on -ftrapping-math # being used when Apple clang v12+ is used. diff --git a/build/pkgs/openblas/spkg-install.in b/build/pkgs/openblas/spkg-install.in index f6ac7753838..00413ca517d 100644 --- a/build/pkgs/openblas/spkg-install.in +++ b/build/pkgs/openblas/spkg-install.in @@ -34,7 +34,7 @@ fi echo "Building OpenBLAS: $MAKE $OPENBLAS_CONFIGURE" -# Ensure USE_TLS=1 ; see https://trac.sagemath.org/ticket/27256 +# Ensure USE_TLS=1 ; see https://github.com/sagemath/sage/issues/27256 OPENBLAS_CONFIGURE="$OPENBLAS_CONFIGURE USE_TLS=1" if ! (sdh_make libs netlib shared $OPENBLAS_CONFIGURE); then @@ -51,7 +51,7 @@ if ! (sdh_make libs netlib shared $OPENBLAS_CONFIGURE); then fi fi -# See https://trac.sagemath.org/ticket/30335 +# See https://github.com/sagemath/sage/issues/30335 rm -f "$SAGE_LOCAL/lib/pkgconfig/cblas.pc" "$SAGE_LOCAL/lib/pkgconfig/blas.pc" "$SAGE_LOCAL/lib/pkgconfig/lapack.pc" sdh_make_install PREFIX="$SAGE_LOCAL" NO_STATIC=1 $OPENBLAS_CONFIGURE diff --git a/build/pkgs/pcre/patches/8.39-cygwin-jit.patch b/build/pkgs/pcre/patches/8.39-cygwin-jit.patch index e581623b0c5..ca1ea43c435 100644 --- a/build/pkgs/pcre/patches/8.39-cygwin-jit.patch +++ b/build/pkgs/pcre/patches/8.39-cygwin-jit.patch @@ -1,5 +1,5 @@ Patch from Cygwin to fix segfault when using the JIT compiler; see -https://trac.sagemath.org/ticket/23291 +https://github.com/sagemath/sage/issues/23291 --- a/sljit/sljitConfigInternal.h 2016-04-06 03:05:43.000000000 -0500 +++ b/sljit/sljitConfigInternal.h 2016-08-11 14:33:58.201820000 -0500 @@ -564,7 +564,7 @@ SLJIT_API_FUNC_ATTRIBUTE void sljit_free diff --git a/build/pkgs/pcre/spkg-install.in b/build/pkgs/pcre/spkg-install.in index 892e1aa84f7..fb0c49272fe 100644 --- a/build/pkgs/pcre/spkg-install.in +++ b/build/pkgs/pcre/spkg-install.in @@ -8,7 +8,7 @@ sdh_make # The JIT feature of pcre is known to be broken on some systems, in # particular on Solaris. We run the testsuite of pcre (this takes only # a few seconds). It the testsuite fails, we rebuild pcre without JIT -# support. See https://trac.sagemath.org/ticket/24628 +# support. See https://github.com/sagemath/sage/issues/24628 if ! $MAKE check; then echo >&2 "*** Rebuilding pcre without JIT support ***" $MAKE clean diff --git a/build/pkgs/ppl/patches/cygwin-weak-symbols.patch b/build/pkgs/ppl/patches/cygwin-weak-symbols.patch index 81215ea432c..535a009f82d 100644 --- a/build/pkgs/ppl/patches/cygwin-weak-symbols.patch +++ b/build/pkgs/ppl/patches/cygwin-weak-symbols.patch @@ -1,6 +1,6 @@ weak symbols don't work properly in 64-bit Cygwin and should not be used (even though it will compile with __attribute__((weak)), -it will not linke; see https://trac.sagemath.org/ticket/16152 +it will not linke; see https://github.com/sagemath/sage/issues/16152 --- a/src/assertions.hh 2016-07-26 16:21:22.591434100 +0200 +++ b/src/assertions.hh 2016-07-26 16:35:09.500888200 +0200 @@ -98,7 +98,7 @@ diff --git a/build/pkgs/ppl/spkg-install.in b/build/pkgs/ppl/spkg-install.in index bc570b95cfc..1f3162a6269 100644 --- a/build/pkgs/ppl/spkg-install.in +++ b/build/pkgs/ppl/spkg-install.in @@ -19,7 +19,7 @@ fi # Workaround to disable PPL's "watchdog timer", preventing it from clobbering # Cysignals' SIGALRM handler on Cygwin; see -# https://trac.sagemath.org/ticket/21190 +# https://github.com/sagemath/sage/issues/21190 if [ "$UNAME" = "CYGWIN" ]; then sed -i 's/#define HAVE_DECL_SETITIMER 1/#define HAVE_DECL_SETITIMER 0/' config.h fi diff --git a/build/pkgs/prompt_toolkit/install-requires.txt b/build/pkgs/prompt_toolkit/install-requires.txt index fe90872577d..30d49fc8454 100644 --- a/build/pkgs/prompt_toolkit/install-requires.txt +++ b/build/pkgs/prompt_toolkit/install-requires.txt @@ -1,2 +1,2 @@ -# https://trac.sagemath.org/ticket/33428 - prompt_toolkit 3.0.25+ breaks Ctrl-C +# https://github.com/sagemath/sage/issues/33428 - prompt_toolkit 3.0.25+ breaks Ctrl-C prompt_toolkit >=3.0.5, <3.0.25 diff --git a/build/pkgs/ptyprocess/install-requires.txt b/build/pkgs/ptyprocess/install-requires.txt index de89260872b..a89be777b4a 100644 --- a/build/pkgs/ptyprocess/install-requires.txt +++ b/build/pkgs/ptyprocess/install-requires.txt @@ -1,3 +1,3 @@ ptyprocess ==0.5.1 -# https://trac.sagemath.org/ticket/31280#comment:42 and following +# https://github.com/sagemath/sage/issues/31280#comment:42 and following # sagelib is not compatible with ptyprocess 0.5.2, 0.6, and 0.7 diff --git a/build/pkgs/python3/spkg-build.in b/build/pkgs/python3/spkg-build.in index 3cb837b3be6..0c08814a242 100644 --- a/build/pkgs/python3/spkg-build.in +++ b/build/pkgs/python3/spkg-build.in @@ -52,7 +52,7 @@ elif [ "$UNAME" = SunOS ]; then # Enable some C99 features on Solaris. This in particular enables # the isinf() and isfinite() functions. It works both for C and # C++ code (which is not true for -std=c99). See - # http://trac.sagemath.org/sage_trac/ticket/14265 + # https://github.com/sagemath/sage/issues/14265 export CFLAGS="-D__C99FEATURES__ $CFLAGS" fi @@ -69,14 +69,14 @@ rm -f "$SAGE_LOCAL/lib/python" # Remove old libraries. We really need to do this before building Python # since Python tries to import some modules (e.g. ctypes) at build-time. # We need to make sure that the old installed libraries in local/lib are -# not used for that. See https://trac.sagemath.org/ticket/24605 +# not used for that. See https://github.com/sagemath/sage/issues/24605 rm -f "$SAGE_LOCAL"/lib/lib"$PKG_BASE"* # Note: --without-ensurepip ensures that setuptools+pip are *not* installed # automatically when installing python3. They will be installed instead by # the separate setuptools and pip packages; see - # https://trac.sagemath.org/ticket/23398 + # https://github.com/sagemath/sage/issues/23398 PYTHON_CONFIGURE="$PYTHON_CONFIGURE --without-ensurepip" sdh_configure --enable-shared $PYTHON_CONFIGURE diff --git a/build/pkgs/python3/spkg-configure.m4 b/build/pkgs/python3/spkg-configure.m4 index 19642b59dd9..d8ece39683a 100644 --- a/build/pkgs/python3/spkg-configure.m4 +++ b/build/pkgs/python3/spkg-configure.m4 @@ -33,7 +33,7 @@ SAGE_SPKG_CONFIGURE([python3], [ MIN_VERSION, LT_VERSION, $check_modules, [ AS_IF([[conftest_venv/bin/python3 -m sysconfig | grep '^\sw*\(C\|LD\)FLAGS *=.*[" ]-[IL] *[^.]' ]] [>& AS_MESSAGE_LOG_FD 2>&1 ], [ - AC_MSG_WARN([this is a misconfigured Python whose sysconfig compiler/linker flags contain -I or -L options, which may cause wrong versions of libraries to leak into the build of Python packages - see https://trac.sagemath.org/ticket/31132]) + AC_MSG_WARN([this is a misconfigured Python whose sysconfig compiler/linker flags contain -I or -L options, which may cause wrong versions of libraries to leak into the build of Python packages - see https://github.com/sagemath/sage/issues/31132]) ]) dnl It is good ac_cv_path_PYTHON3="$ac_path_PYTHON3" @@ -52,7 +52,7 @@ SAGE_SPKG_CONFIGURE([python3], [ MIN_VERSION, LT_VERSION, $check_modules, [ AS_IF([[conftest_venv/bin/python3 -m sysconfig | grep '^\sw*\(C\|LD\)FLAGS *=.*[" ]-[IL] *[^.]' ]] [>& AS_MESSAGE_LOG_FD 2>&1 ], [ - AC_MSG_RESULT([no, this is a misconfigured Python whose sysconfig compiler/linker flags contain -I or -L options, which may cause wrong versions of libraries to leak into the build of Python packages - see https://trac.sagemath.org/ticket/31132; to use it anyway, use ./configure --with-python=$ac_path_PYTHON3]) + AC_MSG_RESULT([no, this is a misconfigured Python whose sysconfig compiler/linker flags contain -I or -L options, which may cause wrong versions of libraries to leak into the build of Python packages - see https://github.com/sagemath/sage/issues/31132; to use it anyway, use ./configure --with-python=$ac_path_PYTHON3]) ], [ dnl It is good ac_cv_path_PYTHON3="$ac_path_PYTHON3" diff --git a/build/pkgs/python3/spkg-install.in b/build/pkgs/python3/spkg-install.in index 20c039cc81a..eae0f39647a 100644 --- a/build/pkgs/python3/spkg-install.in +++ b/build/pkgs/python3/spkg-install.in @@ -60,7 +60,7 @@ if [ "$UNAME" = "Darwin" ] && \ [ `uname -r | cut '-d.' -f1` -gt 9 ]; then rm -f "${SAGE_DESTDIR}${PYTHON_CONFIG_DIR}/libpython${PYTHON_LDVERSION}.a" elif [ "$UNAME" = "CYGWIN" ]; then - # See http://trac.sagemath.org/ticket/20437 + # See https://github.com/sagemath/sage/issues/20437 ln -sf "${PYTHON_CONFIG_DIR}/libpython${PYTHON_LDVERSION}.dll.a" \ "${SAGE_DESTDIR_LOCAL}/lib/libpython${PYTHON_LDVERSION}.dll.a" fi diff --git a/build/pkgs/qepcad/patches/0002-WIP-Don-t-add-lrt.patch b/build/pkgs/qepcad/patches/0002-WIP-Don-t-add-lrt.patch index 54282fbf8f0..08e2d8644df 100644 --- a/build/pkgs/qepcad/patches/0002-WIP-Don-t-add-lrt.patch +++ b/build/pkgs/qepcad/patches/0002-WIP-Don-t-add-lrt.patch @@ -18,7 +18,7 @@ index d184650..f13113b 100644 +LIBS = -lreadline + +# On macOS, librt is not available, -+# see https://trac.sagemath.org/ticket/28388 ++# see https://github.com/sagemath/sage/issues/28388 +#ifeq ($(findstring darwin,${OSTYPE}),) +#LIBS += -lrt +#endif diff --git a/build/pkgs/qepcad/spkg-install.in b/build/pkgs/qepcad/spkg-install.in index 11fbea86b16..4287c3e4f1a 100644 --- a/build/pkgs/qepcad/spkg-install.in +++ b/build/pkgs/qepcad/spkg-install.in @@ -5,11 +5,11 @@ export saclib="$SAGE_LOCAL/lib/saclib" export qe=$(pwd -P) # * Override SHELL: use /bin/sh instead of /bin/csh, see -# https://trac.sagemath.org/ticket/10224 +# https://github.com/sagemath/sage/issues/10224 # * Add rpath to compiler flags, see -# https://trac.sagemath.org/ticket/22653 +# https://github.com/sagemath/sage/issues/22653 # * Use ARFLAGS that also work on macOS, avoiding the U option, see -# https://trac.sagemath.org/ticket/28388 +# https://github.com/sagemath/sage/issues/28388 LIBS=-lreadline if [ "$UNAME" = "Linux" ]; then LIBS="$LIBS -lrt" diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index 819432d90ff..87a8f39f3f0 100644 --- a/build/pkgs/sage_conf/install-requires.txt +++ b/build/pkgs/sage_conf/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 10.0b0 +sage-conf ~= 10.0b1 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index 73f499affef..dc1652133a1 100644 --- a/build/pkgs/sage_docbuild/install-requires.txt +++ b/build/pkgs/sage_docbuild/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 10.0b0 +sage-docbuild ~= 10.0b1 diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt index 169a4c5d9cc..f418d8648ec 100644 --- a/build/pkgs/sage_setup/install-requires.txt +++ b/build/pkgs/sage_setup/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 10.0b0 +sage-setup ~= 10.0b1 diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index e2b85444aca..d62c0ef4662 100644 --- a/build/pkgs/sage_sws2rst/install-requires.txt +++ b/build/pkgs/sage_sws2rst/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 10.0b0 +sage-sws2rst ~= 10.0b1 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index 565dfde8395..c231384faae 100644 --- a/build/pkgs/sagelib/install-requires.txt +++ b/build/pkgs/sagelib/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagelib ~= 10.0b0 +sagelib ~= 10.0b1 diff --git a/build/pkgs/sagelib/spkg-install b/build/pkgs/sagelib/spkg-install index ad8b2ed43fc..ed6bb969f31 100755 --- a/build/pkgs/sagelib/spkg-install +++ b/build/pkgs/sagelib/spkg-install @@ -29,7 +29,7 @@ fi ## Building takes places in the build/ subdirectory. ## ## As a special exception, we feed SAGE_PKGS. -## They are needed by src/sage/misc/package.py. See meta-ticket #28815 for planned changes to this. +## They are needed by src/sage/misc/package.py. See github issue #28815 for planned changes to this. export SAGE_PKGS="$SAGE_ROOT"/build/pkgs export SAGE_ROOT=/doesnotexist diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index 15c1dfbcc42..1ddff9fc8f8 100644 --- a/build/pkgs/sagemath_categories/install-requires.txt +++ b/build/pkgs/sagemath_categories/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 10.0b0 +sagemath-categories ~= 10.0b1 diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index 73ef51c4e92..760c1dcf2e9 100644 --- a/build/pkgs/sagemath_environment/install-requires.txt +++ b/build/pkgs/sagemath_environment/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 10.0b0 +sagemath-environment ~= 10.0b1 diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index a03b35802fe..51e5c5a346d 100644 --- a/build/pkgs/sagemath_objects/install-requires.txt +++ b/build/pkgs/sagemath_objects/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 10.0b0 +sagemath-objects ~= 10.0b1 diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt index 306890d9db9..ca64cd6b39f 100644 --- a/build/pkgs/sagemath_repl/install-requires.txt +++ b/build/pkgs/sagemath_repl/install-requires.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 10.0b0 +sagemath-repl ~= 10.0b1 diff --git a/build/pkgs/setuptools/distros/conda.txt b/build/pkgs/setuptools/distros/conda.txt index 0a486bd850f..2602d0f6344 100644 --- a/build/pkgs/setuptools/distros/conda.txt +++ b/build/pkgs/setuptools/distros/conda.txt @@ -1,4 +1,4 @@ -# Set this bound until https://trac.sagemath.org/ticket/34209 adds support for PEP660 editable builds +# Set this bound until https://github.com/sagemath/sage/issues/34209 adds support for PEP660 editable builds # By setting this version bound, we avoid having to include the following in our installation instructions. # export SETUPTOOLS_ENABLE_FEATURES=legacy-editable "setuptools<64" diff --git a/build/pkgs/snappy/dependencies b/build/pkgs/snappy/dependencies index 8e4fca87caa..db96ccc42e0 100644 --- a/build/pkgs/snappy/dependencies +++ b/build/pkgs/snappy/dependencies @@ -7,4 +7,4 @@ The dependency cypari above is actually cypari2. An installed sagelib is needed when snappy is installed from source (instead of a wheel) because snappy's setup.py tests its presence to adjust dependencies. -https://trac.sagemath.org/ticket/31180 +https://github.com/sagemath/sage/issues/31180 diff --git a/build/pkgs/snappy/requirements.txt b/build/pkgs/snappy/requirements.txt index 33e93e66a1a..0799f0eac11 100644 --- a/build/pkgs/snappy/requirements.txt +++ b/build/pkgs/snappy/requirements.txt @@ -2,9 +2,9 @@ # if installed as a wheel but will actually use Sage's cypari2. # cypari contains a statically linked copy of pari and other libraries # and will remain completely unused (wastes 30M). Snappy is about 165M. -# See https://trac.sagemath.org/ticket/31180 +# See https://github.com/sagemath/sage/issues/31180 snappy -# cypari 2.4.0 has a broken sdist, https://trac.sagemath.org/ticket/31180 +# cypari 2.4.0 has a broken sdist, https://github.com/sagemath/sage/issues/31180 cypari !=2.4.0 # An optional database (110M uncompressed) snappy_15_knots diff --git a/build/pkgs/sympow/SPKG.rst b/build/pkgs/sympow/SPKG.rst index c1f33b6a00c..1a436e3b7ba 100644 --- a/build/pkgs/sympow/SPKG.rst +++ b/build/pkgs/sympow/SPKG.rst @@ -43,7 +43,7 @@ Special Update/Build Instructions that might show up. I (David Kirkby) would personally not trust this code much at all. -- This is a difficult package to maintain. A trac ticket (#9758) has +- This is a difficult package to maintain. A github issue (#9758) has been opened to implement Watkins-Delaunay's algorithm for computing modular @@ -58,7 +58,7 @@ Special Update/Build Instructions This has been fixed in the Gentoo Linux distribution. Some information from Christopher can be seen on - http://trac.sagemath.org/sage_trac/ticket/9703 + https://github.com/sagemath/sage/issues/9703 This package will generate binary versions of all shipped datafiles, so these will work. However, creating totally new datafiles from scratch diff --git a/build/pkgs/wheel/install-requires.txt b/build/pkgs/wheel/install-requires.txt index fb07166d42c..43f74ab0144 100644 --- a/build/pkgs/wheel/install-requires.txt +++ b/build/pkgs/wheel/install-requires.txt @@ -1,2 +1,2 @@ -# https://trac.sagemath.org/ticket/31050 - version constraint for macOS Big Sur support +# https://github.com/sagemath/sage/issues/31050 - version constraint for macOS Big Sur support wheel >=0.36.2 diff --git a/build/sage_bootstrap/flock.py b/build/sage_bootstrap/flock.py index ee723113b5e..483482a6edf 100644 --- a/build/sage_bootstrap/flock.py +++ b/build/sage_bootstrap/flock.py @@ -8,7 +8,7 @@ # This is originally motivated by pip, but has since been generalized. We # should avoid running pip while uninstalling a package because that is prone # to race conditions. This script runs pip under a lock. For details, see -# https://trac.sagemath.org/ticket/21672 +# https://github.com/sagemath/sage/issues/21672 import fcntl import os diff --git a/build/sage_bootstrap/uncompress/action.py b/build/sage_bootstrap/uncompress/action.py index 9655f76860c..8d8af71da5e 100644 --- a/build/sage_bootstrap/uncompress/action.py +++ b/build/sage_bootstrap/uncompress/action.py @@ -74,8 +74,8 @@ def unpack_archive(archive, dirname=None): retry(rename, OSError, tries=len(archive.names)) # Apply typical umask to the top-level directory in case it wasn't - # already; see https://trac.sagemath.org/ticket/24567 - # and later https://trac.sagemath.org/ticket/28596 + # already; see https://github.com/sagemath/sage/issues/24567 + # and later https://github.com/sagemath/sage/issues/28596 os.chmod(dirname, os.stat(dirname).st_mode & ~0o022) finally: os.chdir(prev_cwd) diff --git a/build/sage_bootstrap/uncompress/tar_file.py b/build/sage_bootstrap/uncompress/tar_file.py index 9777d265b4d..fdfa8d240e8 100644 --- a/build/sage_bootstrap/uncompress/tar_file.py +++ b/build/sage_bootstrap/uncompress/tar_file.py @@ -41,8 +41,8 @@ class SageBaseTarFile(tarfile.TarFile): time (the current time), not the timestamps stored in the tarball. This is meant to work around https://bugs.python.org/issue32773 - See http://trac.sagemath.org/ticket/20218#comment:16 and - https://trac.sagemath.org/ticket/24567 for more background. + See https://github.com/sagemath/sage/issues/20218#comment:16 and + https://github.com/sagemath/sage/issues/24567 for more background. """ umask = 0o022 diff --git a/build/sage_bootstrap/uninstall.py b/build/sage_bootstrap/uninstall.py index 1ce039921fc..7cd808d0fe7 100644 --- a/build/sage_bootstrap/uninstall.py +++ b/build/sage_bootstrap/uninstall.py @@ -186,7 +186,7 @@ def rmdir(dirname): # from it, remove the directory too. for filename in files: # Just in case: use lstrip to remove leading "/" from - # filename. See https://trac.sagemath.org/ticket/26013. + # filename. See https://github.com/sagemath/sage/issues/26013. filename = pth.join(sage_local, filename.lstrip(os.sep)) dirname = pth.dirname(filename) if pth.lexists(filename): diff --git a/docker/Dockerfile b/docker/Dockerfile index bf14547cd38..9d56b10b9c5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -196,7 +196,7 @@ ENV MAKEFLAGS $MAKEFLAGS ARG SAGE_NUM_THREADS="2" ENV SAGE_NUM_THREADS $SAGE_NUM_THREADS RUN make configure -# Old default before https://trac.sagemath.org/ticket/32406 +# Old default before https://github.com/sagemath/sage/issues/32406 RUN ./configure --disable-editable RUN make build diff --git a/docker/README.md b/docker/README.md index eb0f1ba344d..14c49b5d4fe 100644 --- a/docker/README.md +++ b/docker/README.md @@ -26,7 +26,7 @@ There are several flavours of this image. ``` docker run -p8888:8888 sagemath/sagemath:latest sage-jupyter ``` -* [`sagemath/sagemath-dev`![image size](https://img.shields.io/microbadger/image-size/sagemath/sagemath-dev.svg)](https://hub.docker.com/r/sagemath/sagemath-dev) contains all the build artifacts to rebuild Sage quickly (currently, this is broken, see [#34241](https://trac.sagemath.org/ticket/34241).) This version is probably only relevant for Sage developers. Run this image with: +* [`sagemath/sagemath-dev`![image size](https://img.shields.io/microbadger/image-size/sagemath/sagemath-dev.svg)](https://hub.docker.com/r/sagemath/sagemath-dev) contains all the build artifacts to rebuild Sage quickly (currently, this is broken, see [#34241](https://github.com/sagemath/sage/issues/34241).) This version is probably only relevant for Sage developers. Run this image with: ``` docker run -it sagemath/sagemath-dev:develop ``` diff --git a/m4/sage_spkg_collect.m4 b/m4/sage_spkg_collect.m4 index 75903603c49..d52dc6205ee 100644 --- a/m4/sage_spkg_collect.m4 +++ b/m4/sage_spkg_collect.m4 @@ -81,7 +81,7 @@ AC_DEFUN([SAGE_SPKG_COLLECT_INIT], [ dnl Intialize the collection variables. # To deal with ABI incompatibilities when gcc is upgraded, every package # (except gcc) should depend on gcc if gcc is already installed. -# See https://trac.sagemath.org/ticket/24703 +# See https://github.com/sagemath/sage/issues/24703 if test x$SAGE_INSTALL_GCC = xexists; then SAGE_GCC_DEP='$(SAGE_LOCAL)/bin/gcc' else diff --git a/pkgs/sage-conf/README.rst b/pkgs/sage-conf/README.rst index ec4ba0dd631..c968612aef2 100644 --- a/pkgs/sage-conf/README.rst +++ b/pkgs/sage-conf/README.rst @@ -22,7 +22,7 @@ sage_conf sdist on PyPI ----------------------- This implementation of the ``sage_conf`` distribution package comes from -https://trac.sagemath.org/ticket/29039, which added the directory +https://github.com/sagemath/sage/issues/29039, which added the directory `pkgs/sage-conf_pypi `_. To install, use ``pip install -v sage_conf``. Using ``-v`` ensures that diagnostic @@ -48,7 +48,7 @@ sage_conf wheels Prebuilt binary wheels of the ``sage_conf`` distribution package are available at https://github.com/sagemath/sage-wheels/releases/ -This implementation of ``sage_conf`` comes from https://trac.sagemath.org/ticket/31396, +This implementation of ``sage_conf`` comes from https://github.com/sagemath/sage/issues/31396, which adds the directory ``pkgs/sage-conf_relocatable/``. On building a wheel, it invokes ``sage_bootstrap`` to establish a diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/pkgs/sage-sws2rst/README.rst b/pkgs/sage-sws2rst/README.rst index 8098507fff4..f20d3e636cf 100644 --- a/pkgs/sage-sws2rst/README.rst +++ b/pkgs/sage-sws2rst/README.rst @@ -8,4 +8,4 @@ Provides a script `sage-sws2rst`, which translates a Sage worksheet file (.sws) Sage worksheet files (.sws) are a file format that was used by the now-obsolete Sage notebook (https://github.com/sagemath/sagenb), superseded by the Jupyter notebook. SageNB was dropped in the course of the transition of SageMath to Python 3. -This package was extracted from the SageNB sources in https://trac.sagemath.org/ticket/28838 to provide a way to convert pedagogical material written available in Sage worksheet format. +This package was extracted from the SageNB sources in https://github.com/sagemath/sage/issues/28838 to provide a way to convert pedagogical material written available in Sage worksheet format. diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/pkgs/sagemath-categories/MANIFEST.in.m4 b/pkgs/sagemath-categories/MANIFEST.in.m4 index 98d10a91fef..361132e0cfa 100644 --- a/pkgs/sagemath-categories/MANIFEST.in.m4 +++ b/pkgs/sagemath-categories/MANIFEST.in.m4 @@ -35,7 +35,7 @@ exclude sage/categories/__init__.py include sage/rings/ideal.* include sage/rings/ring.* graft sage/typeset # dep of sage.categories.tensor -# include sage/rings/integer*.* # depends on cypari, flint - https://trac.sagemath.org/ticket/30022 +# include sage/rings/integer*.* # depends on cypari, flint - https://github.com/sagemath/sage/issues/30022 # include sage/rings/rational*.* # include sage/rings/infinity.* diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/pkgs/sagemath-standard/README.rst b/pkgs/sagemath-standard/README.rst index 5709c22249b..f624d1414a7 100644 --- a/pkgs/sagemath-standard/README.rst +++ b/pkgs/sagemath-standard/README.rst @@ -28,4 +28,4 @@ for partial lists for various systems. The connection to the system environment is facilitated through the https://pypi.org/project/sage-conf/ distribution package. -A modularization effort is in progress with the goal of making it possible to install parts of the Sage Library with fewer prerequisites. https://trac.sagemath.org/ticket/29705 +A modularization effort is in progress with the goal of making it possible to install parts of the Sage Library with fewer prerequisites. https://github.com/sagemath/sage/issues/29705 diff --git a/src/.relint.yml b/src/.relint.yml index 2b49c758dc0..86684ad1040 100644 --- a/src/.relint.yml +++ b/src/.relint.yml @@ -35,7 +35,7 @@ # From various typo tickets -# https://trac.sagemath.org/ticket/30585 +# https://github.com/sagemath/sage/issues/30585 - name: 'typo "homogenous" detected' hint: | in mathematics it should be "homogeneous" diff --git a/src/VERSION.txt b/src/VERSION.txt index 39516ebfda0..4ab501a9912 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.0.beta0 +10.0.beta1 diff --git a/src/bin/sage b/src/bin/sage index 03aec9cfbfc..97f6b2d540b 100755 --- a/src/bin/sage +++ b/src/bin/sage @@ -77,7 +77,7 @@ resolvelinks() { } # Resolve the links in $0 so that local/bin/sage can be executed from -# a symlink (Trac #30888). +# a symlink (Issue #30888). SELF=$(resolvelinks "${0}") # Display the current version of Sage @@ -173,7 +173,7 @@ if [ -z "$SAGE_VENV" -a -x "${SELF}-venv-config" ]; then export SAGE_VENV=$("${SELF}-venv-config" SAGE_VENV) fi if [ -f "${SELF}-env-config" ]; then - # As of Trac #22731, sage-env-config is optional. + # As of Issue #22731, sage-env-config is optional. . "${SELF}-env-config" >&2 fi @@ -577,7 +577,7 @@ fi if [ "$1" = '-c' ]; then shift sage_setup - unset TERM # See Trac #12263 + unset TERM # See Issue #12263 exec sage-eval "$@" fi @@ -1028,7 +1028,7 @@ fi if [ "$1" = "-docbuild" -o "$1" = "--docbuild" ]; then shift - # Trac #30002: ensure an English locale so that it is possible to + # Issue #30002: ensure an English locale so that it is possible to # scrape out warnings by pattern matching. export LANG=C export LANGUAGE=C @@ -1039,7 +1039,7 @@ if [ "$1" = "-docbuild" -o "$1" = "--docbuild" ]; then export OMP_NUM_THREADS=1 fi - # Trac #33650: Make sure that user configuration of Jupyter does not + # Issue #33650: Make sure that user configuration of Jupyter does not # shadow our sagemath kernel when jupyter-sphinx is invoked export JUPYTER_CONFIG_DIR=/doesnotexist export JUPYTER_CONFIG_PATH=/doesnotexist @@ -1153,7 +1153,7 @@ if [ $# -ge 1 ]; then exit 1 fi sage_setup - unset TERM # See Trac #12263 + unset TERM # See Issue #12263 # sage-run rejects all command line options as the first argument. exec sage-run "$@" fi diff --git a/src/bin/sage-cleaner b/src/bin/sage-cleaner index 7fe6784ee26..e2e578eec60 100755 --- a/src/bin/sage-cleaner +++ b/src/bin/sage-cleaner @@ -169,7 +169,7 @@ def fix_old_mistakes(): """ Experience is simply the name we give our mistakes. """ - # inconsistently escaped hyphens with underscores (http://trac.sagemath.org/14055) + # inconsistently escaped hyphens with underscores (https://github.com/sagemath/sage/issues/14055) wrong_hostname = HOSTNAME.replace('-','_').replace('/','_').replace('\\','_') wrong_sage_tmp_root = os.path.join(DOT_SAGE, 'temp', wrong_hostname) if wrong_sage_tmp_root != SAGE_TMP_ROOT and os.path.exists(wrong_sage_tmp_root): diff --git a/src/bin/sage-coverage b/src/bin/sage-coverage index 45c4073a18c..f4920600528 100755 --- a/src/bin/sage-coverage +++ b/src/bin/sage-coverage @@ -37,7 +37,7 @@ def coverage_all(directory): print(''.join(s)) - # Trac #5859: Don't crash if there isn't anything to test. + # Issue #5859: Don't crash if there isn't anything to test. score = 100.0 if total != 0: score = (float(scr) / total) diff --git a/src/bin/sage-cython b/src/bin/sage-cython index 157cf76a23f..a52a729dd02 100755 --- a/src/bin/sage-cython +++ b/src/bin/sage-cython @@ -1,7 +1,7 @@ #!/usr/bin/env sage-python # This script is a deprecated wrapper around the "cython" program. -# It is deprecated since Trac #27041 (Sage 8.7) because one should +# It is deprecated since Issue #27041 (Sage 8.7) because one should # simply use "cython" directly. We display deprecation messages whenever # "sage-cython" does something different from plain "cython". # diff --git a/src/bin/sage-env b/src/bin/sage-env index 3a02640bc97..28f86bc52ef 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -31,7 +31,7 @@ # absolute path (a relative path remains relative), nor does it treat # "." or ".." specially. # -# AUTHOR: Jeroen Demeyer (2011-08-23): Trac tickets #5852 and #11704 +# AUTHOR: Jeroen Demeyer (2011-08-23): Github issues #5852 and #11704 # resolvelinks() { # $in is what still needs to be converted (normally has no starting slash) @@ -278,7 +278,7 @@ if [ "$UNAME" = "Darwin" ]; then export MACOSX_VERSION=`uname -r | awk -F. '{print $1}'` # Work around problems on recent OS X crashing with an error message # "... may have been in progress in another thread when fork() was called" - # when objective-C functions are called after fork(). See Trac #25921. + # when objective-C functions are called after fork(). See Issue #25921. # Most likely, these errors are false positives, so we disable them: export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES fi @@ -355,12 +355,12 @@ if [ "$PYTHON_EGG_CACHE" = "" ]; then fi # Set PYTHONUSERBASE to avoid picking up non-Sage versions of -# Matplotlib, numpy, etc. See http://trac.sagemath.org/ticket/19612. +# Matplotlib, numpy, etc. See https://github.com/sagemath/sage/issues/19612. # # For more history (it used to be PYTHONNOUSERSITE=yes which killed # the ability to do "sage -pip install PACKAGE --user"), see -# http://trac.sagemath.org/ticket/14243 and -# http://trac.sagemath.org/ticket/18955. +# https://github.com/sagemath/sage/issues/14243 and +# https://github.com/sagemath/sage/issues/18955. if [ "$PYTHONUSERBASE" = "" ]; then PYTHONUSERBASE="$DOT_SAGE/local" @@ -449,7 +449,7 @@ if [ -n "$SAGE_LOCAL" ]; then PERL5LIB="$SAGE_LOCAL/lib/perl5:$PERL5LIB" && export PERL5LIB fi -# Allow SAGE_BROWSER to override BROWSER (Trac #22449) +# Allow SAGE_BROWSER to override BROWSER (Issue #22449) if [ -n "$SAGE_BROWSER" ]; then export BROWSER="$SAGE_BROWSER" fi @@ -558,7 +558,7 @@ case "$SAGE_NUM_THREADS,$SAGE_NUM_THREADS_PARALLEL" in # sage-num-threads.py would just recompute them ;; *) - # See Trac Ticket #12016 + # See Issue Ticket #12016 # First, figure out the right values for SAGE_NUM_THREADS (default # number of threads) and SAGE_NUM_THREADS_PARALLEL (default number of # threads when parallel execution is asked explicitly). @@ -615,7 +615,7 @@ fi if [ -n "$SAGE_LOCAL" ]; then # If we move the Sage tree then ncurses cannot find terminfo, hence, we - # tell it where to find it. See Trac Ticket #15091 + # tell it where to find it. See Issue Ticket #15091 export TERMINFO="$SAGE_LOCAL/share/terminfo" # If nodejs is installed, activate the nodeenv containing it. diff --git a/src/bin/sage-num-threads.py b/src/bin/sage-num-threads.py index 1b920182690..7c70a3cbe3a 100755 --- a/src/bin/sage-num-threads.py +++ b/src/bin/sage-num-threads.py @@ -11,7 +11,7 @@ # 3) The number of CPU cores in the system, as determined by # multiprocessing.cpu_count() # -# AUTHOR: Jeroen Demeyer (2011-12-08): Trac ticket #12016 +# AUTHOR: Jeroen Demeyer (2011-12-08): Github issue #12016 # import os import multiprocessing diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index 81dff8d5bf3..ed1e56953ff 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -8,7 +8,7 @@ import sys DOT_SAGE = os.environ.get('DOT_SAGE', os.path.join(os.environ.get('HOME'), '.sage')) -# Override to not pick up user configuration, see Trac #20270 +# Override to not pick up user configuration, see Issue #20270 os.environ['SAGE_STARTUP_FILE'] = os.path.join(DOT_SAGE, 'init-doctests.sage') @@ -41,8 +41,8 @@ if __name__ == "__main__": type=float, default=-1.0, const=1.0, metavar="SECONDS", help="warn if tests take more time than SECONDS") # By default, include all tests marked 'sagemath_doc_html' -- see - # https://trac.sagemath.org/ticket/25345 and - # https://trac.sagemath.org/ticket/26110: + # https://github.com/sagemath/sage/issues/25345 and + # https://github.com/sagemath/sage/issues/26110: parser.add_argument("--optional", metavar="FEATURES", default=_get_optional_defaults(), help='only run tests including one of the "# optional" tags listed in FEATURES (separated by commas); ' 'if "sage" is listed, will also run the standard doctests; ' @@ -141,7 +141,7 @@ if __name__ == "__main__": sys.exit(2) # Limit the number of threads to 2 to save system resources. - # See Trac #23713, #23892, #30351 + # See Issue #23713, #23892, #30351 if sys.platform == 'darwin': os.environ["OMP_NUM_THREADS"] = "1" else: @@ -153,7 +153,7 @@ if __name__ == "__main__": DC = DocTestController(args, args.filenames) err = DC.run() - # Trac #33521: Do not run pytest if the pytest configuration is not available. + # Issue #33521: Do not run pytest if the pytest configuration is not available. # This happens when the source tree is not available and SAGE_SRC falls back # to SAGE_LIB. from sage.env import SAGE_SRC diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 127b827342d..d8c6b161134 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -4,6 +4,6 @@ # which stops "setup.py develop" from rewriting it as a Python file. : # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='10.0.beta0' -SAGE_RELEASE_DATE='2023-02-12' -SAGE_VERSION_BANNER='SageMath version 10.0.beta0, Release Date: 2023-02-12' +SAGE_VERSION='10.0.beta1' +SAGE_RELEASE_DATE='2023-02-19' +SAGE_VERSION_BANNER='SageMath version 10.0.beta1, Release Date: 2023-02-19' diff --git a/src/conftest.py b/src/conftest.py index f12f0aa0cbc..9448087cc7a 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -25,7 +25,7 @@ from _pytest.pathlib import import_path, ImportMode # Import sage.all is necessary to: -# - avoid cyclic import errors, see Trac #33580 +# - avoid cyclic import errors, see Issue #33580 # - inject it into globals namespace for doctests import sage.all from sage.doctest.parsing import SageDocTestParser, SageOutputChecker diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 6b44bc828d8..6dd50fd5cec 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -155,8 +155,8 @@ for PKG_BASE in $(sage-package list --has-file SPKG.rst | sort); do PKG_SCRIPTS=build/pkgs/$PKG_BASE # Instead of just copying, we may want to call # a version of sage-spkg-info to format extra information. - # for sphinx 4.4 we need to replace all direct links by some "extlink" (ticket 33272) + # for sphinx 4.4 we need to replace all direct links by some "extlink" (issue 33272) - (echo ".. _spkg_$PKG_BASE:" && echo && OUTPUT_RST=1 sage-spkg-info $PKG_BASE) | sed -e "s|https://trac.sagemath.org/ticket/\([0-9]*\)|:trac:\`\1\`|g" -e "s|https://arxiv.org/abs/cs/\([0-9]*\)|:arxiv:\`cs/\1\`|g" > "$OUTPUT_DIR"/$PKG_BASE.rst + (echo ".. _spkg_$PKG_BASE:" && echo && OUTPUT_RST=1 sage-spkg-info $PKG_BASE) | sed -e "s|https://github.com/sagemath/sage/issues/\([0-9]*\)|:issue:\`\1\`|g" -e "s|https://arxiv.org/abs/cs/\([0-9]*\)|:arxiv:\`cs/\1\`|g" > "$OUTPUT_DIR"/$PKG_BASE.rst echo >> "$OUTPUT_INDEX" " $PKG_BASE" done diff --git a/src/doc/en/constructions/contributions.rst b/src/doc/en/constructions/contributions.rst index 66bff2b74f5..c6324e3459e 100644 --- a/src/doc/en/constructions/contributions.rst +++ b/src/doc/en/constructions/contributions.rst @@ -36,4 +36,4 @@ documentation were made by Gary Zablackis. [SAGE] {SJ} William Stein, David Joyner, SAGE: System for Algebra and Geometry Experimentation, Comm. Computer Algebra 39(2005)61-64. (SIGSAM Bull. June 2005) https://sagemath.org/ - https://trac.sagemath.org/ + https://github.com/sagemath/sage/issues/ diff --git a/src/doc/en/prep/Advanced-2DPlotting.rst b/src/doc/en/prep/Advanced-2DPlotting.rst index 633e33d68b3..4ac7465ccf7 100644 --- a/src/doc/en/prep/Advanced-2DPlotting.rst +++ b/src/doc/en/prep/Advanced-2DPlotting.rst @@ -239,7 +239,7 @@ to put together. ....: graph += arrow( [0,0], [360, 0], color = "#000" ) ....: # let's set tics ....: # or http://aghitza.org/posts/tweak_labels_and_ticks_in_2d_plots_using_matplotlib/ - ....: # or wayt for http://trac.sagemath.org/sage_trac/ticket/1431 + ....: # or wayt for https://github.com/sagemath/sage/issues/1431 ....: # ['$-\pi/3$', '$2\pi/3$', '$5\pi/3$'] ....: for x in range(0, 361, 30): ....: graph += point( [x, 0] ) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 70dda6c3940..04b7919bc80 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -624,7 +624,7 @@ REFERENCES: and variants*. :arxiv:`1810.00789` -.. [BDKR2013] \D. Best, D.Z. Dokovic, H. Kharaghani and H. Ramp. +.. [BDKR2013] \D. Best, D.Ž. Đoković, H. Kharaghani and H. Ramp. *Turyn-Type Sequences: Classification, Enumeration, and Construction*, Journal of Combinatorial Designs 21(1) (2013): 24-35. :doi:`10.1002/jcd.21318` @@ -2058,7 +2058,7 @@ REFERENCES: .. [Djo1992c] \D. Đoković. *Ten New Orders for Hadamard Matrices of Skew Type*, - Publikacije Elektrotehničkog fakulteta. Serija Matematika 2 (1992): 47-59. + Publikacije Elektrotehničkog fakulteta. Serija Matematika 2 (1992): 47-59. .. [Djo1994a] \D. Đoković. *Five New Orders for Hadamard Matrices of Skew Type*, @@ -2223,6 +2223,11 @@ REFERENCES: .. [Duv1983] J.-P. Duval, Factorizing words over an ordered alphabet, J. Algorithms 4 (1983) 363--381. +.. [Duv1988] \A. Duval. + *A directed graph version of strongly regular graphs*, + Journal of Combinatorial Theory, Series A 47(1) (1988): 71-100. + :doi:`10.1016/0097-3165(88)90043-X` + .. [DW1995] Andreas W.M. Dress and Walter Wenzel, *A Simple Proof of an Identity Concerning Pfaffians of Skew Symmetric Matrices*, Advances in Mathematics, volume 112, Issue 1, @@ -3866,6 +3871,10 @@ REFERENCES: *Bethe ansatz and inverse scattering transform in a periodic box-ball system*, Nuclear Phys. B **747**, no. 3 (2006), 354--397. +.. [KTZ1987] Kierstead, H.A., Trotter, W.T. & Zhou, B. Representing an ordered + set as the intersection of super greedy linear extensions. Order 4, + 293-311 (1987). + :doi:`10.1007/BF00337892` .. [Kuh1987] \W. Kühnel, "Minimal triangulations of Kummer varieties", Abh. Math. Sem. Univ. Hamburg 57 (1987), 7-20. diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst index 4832272f1cb..fb7697587c5 100644 --- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst +++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst @@ -41,7 +41,7 @@ Some more advanced number-theoretical tools are available via G: sage: P = K.primes_above(2)[0] sage: G.inertia_group(P) Subgroup generated by [(1,4,6)(2,5,3)] of (Galois group 6T2 ([3]2) with order 6 of x^6 + 40*x^3 + 1372) - sage: sorted([G.artin_symbol(Q) for Q in K.primes_above(5)]) # random order, see Trac #18308 + sage: sorted([G.artin_symbol(Q) for Q in K.primes_above(5)]) # random order, see Issue #18308 [(1,3)(2,6)(4,5), (1,2)(3,4)(5,6), (1,5)(2,4)(3,6)] If the number field is not Galois over `\QQ`, then the ``galois_group`` diff --git a/src/sage/algebras/free_algebra_element.py b/src/sage/algebras/free_algebra_element.py index 378f06479db..559df673fbf 100644 --- a/src/sage/algebras/free_algebra_element.py +++ b/src/sage/algebras/free_algebra_element.py @@ -92,7 +92,7 @@ def _repr_(self): sage: repr(-x+3*y*z) # indirect doctest '-x + 3*y*z' - Trac ticket :trac:`11068` enables the use of local variable names:: + Github issue :trac:`11068` enables the use of local variable names:: sage: from sage.structure.parent_gens import localvars sage: with localvars(A, ['a','b','c']): diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index d36faae2b34..915ca62e73b 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -1349,7 +1349,7 @@ def get_braid_generators(self, set_start_method('fork') except RuntimeError: pass - # Turn off multiprocessing when field is QQbar due to pickling issues introduced by PARI upgrade in trac ticket #30537 + # Turn off multiprocessing when field is QQbar due to pickling issues introduced by PARI upgrade in github issue #30537 pool = Pool() if use_mp and self.fvars_field() != QQbar else None # Set up computational basis and compute generators one at a time diff --git a/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx b/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx index 41444812c49..80ac48b202c 100644 --- a/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx +++ b/src/sage/algebras/letterplace/free_algebra_element_letterplace.pyx @@ -3,7 +3,7 @@ Weighted homogeneous elements of free algebras, in letterplace implementation AUTHOR: -- Simon King (2011-03-23): Trac ticket :trac:`7797` +- Simon King (2011-03-23): Github issue :trac:`7797` """ diff --git a/src/sage/algebras/lie_algebras/free_lie_algebra.py b/src/sage/algebras/lie_algebras/free_lie_algebra.py index 8a02e5265c6..af001d0fa57 100644 --- a/src/sage/algebras/lie_algebras/free_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/free_lie_algebra.py @@ -59,7 +59,7 @@ def __init__(self, lie, basis_name): doctest:warning ... FutureWarning: The Hall basis has not been fully proven correct, but currently no bugs are known - See http://trac.sagemath.org/16823 for details. + See https://github.com/sagemath/sage/issues/16823 for details. Free Lie algebra generated by (x, y) over Rational Field in the Hall basis """ self._basis_name = basis_name diff --git a/src/sage/algebras/lie_algebras/lie_algebra.py b/src/sage/algebras/lie_algebras/lie_algebra.py index 0aa08d2a823..87b1a8cee37 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra.py +++ b/src/sage/algebras/lie_algebras/lie_algebra.py @@ -273,7 +273,7 @@ class LieAlgebra(Parent, UniqueRepresentation): # IndexedGenerators): sage: H = L.Hall() doctest:warning...: FutureWarning: The Hall basis has not been fully proven correct, but currently no bugs are known - See http://trac.sagemath.org/16823 for details. + See https://github.com/sagemath/sage/issues/16823 for details. sage: H Free Lie algebra generated by (x, y, z) over Rational Field in the Hall basis sage: Lyn = L.Lyndon() diff --git a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx index ffe27c69797..d0a1f70510a 100644 --- a/src/sage/algebras/lie_algebras/lie_algebra_element.pyx +++ b/src/sage/algebras/lie_algebras/lie_algebra_element.pyx @@ -95,7 +95,7 @@ cdef class LieAlgebraElement(IndexedFreeModuleElement): sage: H = L.Hall() doctest:warning...: FutureWarning: The Hall basis has not been fully proven correct, but currently no bugs are known - See http://trac.sagemath.org/16823 for details. + See https://github.com/sagemath/sage/issues/16823 for details. sage: elt = Lyn.an_element() sage: elt._im_gens_(H, H.gens()) x + y + z diff --git a/src/sage/algebras/lie_algebras/morphism.py b/src/sage/algebras/lie_algebras/morphism.py index ca65bb162aa..f71001ce98f 100644 --- a/src/sage/algebras/lie_algebras/morphism.py +++ b/src/sage/algebras/lie_algebras/morphism.py @@ -54,7 +54,7 @@ class LieAlgebraHomomorphism_im_gens(Morphism): sage: H = L.Hall() doctest:warning...: FutureWarning: The Hall basis has not been fully proven correct, but currently no bugs are known - See http://trac.sagemath.org/16823 for details. + See https://github.com/sagemath/sage/issues/16823 for details. sage: phi = Lyn.coerce_map_from(H); phi Lie algebra morphism: From: Free Lie algebra generated by (x, y, z) over Rational Field in the Hall basis diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 45a4d512c0a..4b7af26d31c 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -2197,7 +2197,7 @@ def quaternion_order(self): sage: R = QuaternionAlgebra(-11,-1).maximal_order() sage: R.unit_ideal().quaternion_order() is R doctest:...: DeprecationWarning: quaternion_order() is deprecated, please use left_order() or right_order() - See https://trac.sagemath.org/31583 for details. + See https://github.com/sagemath/sage/issues/31583 for details. True """ from sage.misc.superseded import deprecation @@ -2229,7 +2229,7 @@ def ring(self): sage: R = QuaternionAlgebra(-11,-1).maximal_order() sage: R.unit_ideal().ring() is R doctest:...: DeprecationWarning: ring() will return the quaternion algebra in the future, please use left_order() or right_order() - See https://trac.sagemath.org/31583 for details. + See https://github.com/sagemath/sage/issues/31583 for details. True """ from sage.misc.superseded import deprecation diff --git a/src/sage/arith/constants.pxd b/src/sage/arith/constants.pxd index ec6f59074ce..6e5e713707f 100644 --- a/src/sage/arith/constants.pxd +++ b/src/sage/arith/constants.pxd @@ -3,7 +3,7 @@ # float. We use these instead of decimal constants like 3.1415... # because the hex floats are exactly representable as "double", so there # shouldn't be any rounding issues in the compiler. -# See https://trac.sagemath.org/ticket/23919#comment:15 +# See https://github.com/sagemath/sage/issues/23919#comment:15 # # Hex floats are standardized in C99, but GCC accepts them # unconditionally, also for C++ code. diff --git a/src/sage/arith/long.pxd b/src/sage/arith/long.pxd index 1c9a53387a0..4addc8cfc89 100644 --- a/src/sage/arith/long.pxd +++ b/src/sage/arith/long.pxd @@ -296,7 +296,7 @@ cdef inline bint integer_check_long_py(x, long* value, int* err): # - BITS_IN_LONG = 63, PyLong_SHIFT = 30 # - BITS_IN_LONG = 31, PyLong_SHIFT = 15 (python <= 3.10) # - BITS_IN_LONG = 31, PyLong_SHIFT = 30 (new in python 3.11) - # cf. https://trac.sagemath.org/ticket/33842#comment:130 + # cf. https://github.com/sagemath/sage/issues/33842#comment:130 # # This way, we know that 1 digit certainly fits in a C long # and 4 or more digits never fit. diff --git a/src/sage/calculus/wester.py b/src/sage/calculus/wester.py index 10a64d25471..0a2a374cd5f 100644 --- a/src/sage/calculus/wester.py +++ b/src/sage/calculus/wester.py @@ -390,7 +390,7 @@ sage: # domain. sage: # To stick with the behaviour of previous versions, the domain is set sage: # to 'real' in the following. - sage: # See Trac #10682 for further details. + sage: # See Issue #10682 for further details. sage: n = var('n') sage: f = x^(1/n)*y^(1/n)-(x*y)^(1/n) sage: assume(real(x) > 0, real(y) > 0) diff --git a/src/sage/categories/category_with_axiom.py b/src/sage/categories/category_with_axiom.py index e8811e9f25e..9850f4d4c85 100644 --- a/src/sage/categories/category_with_axiom.py +++ b/src/sage/categories/category_with_axiom.py @@ -927,7 +927,7 @@ def _(): return LazyImport('sage.categories.rngs', 'Rngs', at_startup=True) Supporting similar deduction rules will be an important feature in the future, with quite a few occurrences already implemented in - upcoming tickets. For the time being though there is a single + upcoming issues. For the time being though there is a single occurrence of this idiom outside of the tests. So this would be an easy thing to refactor after :trac:`10963` if a better idiom is found. diff --git a/src/sage/categories/finite_complex_reflection_groups.py b/src/sage/categories/finite_complex_reflection_groups.py index bb3df827890..9c91c2af549 100644 --- a/src/sage/categories/finite_complex_reflection_groups.py +++ b/src/sage/categories/finite_complex_reflection_groups.py @@ -795,7 +795,7 @@ def elements_below_coxeter_element(self, c=None): sage: W = CoxeterGroup(['A', 3]) sage: len(list(W.elements_below_coxeter_element())) doctest:...: DeprecationWarning: The method elements_below_coxeter_element is deprecated. Please use absolute_order_ideal instead. - See https://trac.sagemath.org/27924 for details. + See https://github.com/sagemath/sage/issues/27924 for details. 14 """ from sage.misc.superseded import deprecation diff --git a/src/sage/categories/homset.py b/src/sage/categories/homset.py index 1b488a03b69..88b162c5845 100644 --- a/src/sage/categories/homset.py +++ b/src/sage/categories/homset.py @@ -74,9 +74,9 @@ ################################### # Use the weak "triple" dictionary -# introduced in trac ticket #715 +# introduced in github issue #715 # with weak values, as introduced in -# trac ticket #14159 +# github issue #14159 from sage.structure.coerce_dict import TripleDict _cache = TripleDict(weak_values=True) diff --git a/src/sage/categories/magmas.py b/src/sage/categories/magmas.py index 975ea0491b3..ff1a7f955c5 100644 --- a/src/sage/categories/magmas.py +++ b/src/sage/categories/magmas.py @@ -785,7 +785,7 @@ def __init_extra__(self): # This should instead register the multiplication to the coercion model # But this is not yet implemented in the coercion model # - # Trac ticket #11900: The following used to test whether + # Github issue #11900: The following used to test whether # self.product != self.product_from_element_class_mul. But # that is, of course, a bug. Namely otherwise, if the parent # has an optimized `product` then its elements will *always* use diff --git a/src/sage/categories/modules.py b/src/sage/categories/modules.py index 2e3eb65dc3d..cb878f1c6f2 100644 --- a/src/sage/categories/modules.py +++ b/src/sage/categories/modules.py @@ -973,6 +973,6 @@ def tensor_factors(self): sage: M.construction() doctest:warning... DeprecationWarning: implementations of Modules().TensorProducts() now must define the method tensor_factors - See https://trac.sagemath.org/34393 for details. + See https://github.com/sagemath/sage/issues/34393 for details. (VectorFunctor, Integer Ring) """ diff --git a/src/sage/categories/morphism.pyx b/src/sage/categories/morphism.pyx index 121dc3bbd15..59a73a43c62 100644 --- a/src/sage/categories/morphism.pyx +++ b/src/sage/categories/morphism.pyx @@ -393,7 +393,7 @@ cdef class Morphism(Map): # multiplying it with the gens of the scalar ring. # # It is known that this way of comparing morphisms may give - # a mathematically wrong answer. See Trac #28617 and #31783. + # a mathematically wrong answer. See Issue #28617 and #31783. if e is not None and isinstance(e, ModuleElement): B = (e)._parent._base gens = [e * B.coerce(x) for x in gens] diff --git a/src/sage/categories/primer.py b/src/sage/categories/primer.py index b3624ad4dc1..96dff8e2631 100644 --- a/src/sage/categories/primer.py +++ b/src/sage/categories/primer.py @@ -440,12 +440,12 @@ class implements: For plain Python methods, one can also just ask in which module they are implemented:: - sage: i._pow_.__module__ # not tested (Trac #24275) + sage: i._pow_.__module__ # not tested (Issue #24275) 'sage.categories.semigroups' sage: pQ._mul_.__module__ 'sage.rings.polynomial.polynomial_element_generic' - sage: pQ._pow_.__module__ # not tested (Trac #24275) + sage: pQ._pow_.__module__ # not tested (Issue #24275) 'sage.categories.semigroups' We see that integers and polynomials have each their own diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index 86e33e45b1e..6c5b08e003a 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -2878,8 +2878,8 @@ def _apply_functor(self, R): Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x^2 + 2, y^2 + 3*x) Note that the ``quo()`` method of a field used to return the - integer zero. That strange behaviour was removed in trac - ticket :trac:`9138`. It now returns a trivial quotient ring + integer zero. That strange behaviour was removed in github + issue :trac:`9138`. It now returns a trivial quotient ring when applied to a field:: sage: F = ZZ.quo([5]*ZZ).construction()[0] diff --git a/src/sage/categories/unital_algebras.py b/src/sage/categories/unital_algebras.py index 43ffa76d47c..d245b4de8c1 100644 --- a/src/sage/categories/unital_algebras.py +++ b/src/sage/categories/unital_algebras.py @@ -109,7 +109,7 @@ def __init_extra__(self): sage: Bar(category=Algebras(QQ)) doctest:warning...: DeprecationWarning: the attribute _no_generic_basering_coercion is deprecated, implement _coerce_map_from_base_ring() instead - See http://trac.sagemath.org/19225 for details. + See https://github.com/sagemath/sage/issues/19225 for details. <__main__.Bar_with_category object at 0x...> """ if getattr(self, '_no_generic_basering_coercion', False): @@ -227,7 +227,7 @@ def _coerce_map_from_base_ring(self): elif isinstance(generic_from_base_ring, lazy_attribute): # If the category implements from_base_ring() as lazy # attribute, then we always use it. - # This is for backwards compatibility, see Trac #25181 + # This is for backwards compatibility, see Issue #25181 use_from_base_ring = True else: try: diff --git a/src/sage/coding/gabidulin_code.py b/src/sage/coding/gabidulin_code.py index b29b85d6359..b6d2e2bac4d 100644 --- a/src/sage/coding/gabidulin_code.py +++ b/src/sage/coding/gabidulin_code.py @@ -544,7 +544,7 @@ class GabidulinPolynomialEvaluationEncoder(Encoder): sage: p = (z9^6 + z9^2 + z9 + 1)*x + z9^7 + z9^5 + z9^4 + z9^2 sage: vector(p.multi_point_evaluation(C.evaluation_points())) doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. - See http://trac.sagemath.org/13215 for details. + See https://github.com/sagemath/sage/issues/13215 for details. (z9^7 + z9^6 + z9^5 + z9^4 + z9 + 1, z9^6 + z9^5 + z9^3 + z9) EXAMPLES:: diff --git a/src/sage/combinat/colored_permutations.py b/src/sage/combinat/colored_permutations.py index 4ace496fbdb..28b20e502af 100644 --- a/src/sage/combinat/colored_permutations.py +++ b/src/sage/combinat/colored_permutations.py @@ -436,7 +436,7 @@ class ColoredPermutations(Parent, UniqueRepresentation): [[0, 1, 0], [1, 2, 3]] We can also create a colored permutation by passing - either a list of tuples consisting of ``(color, element)``:: + an iterable consisting of tuples consisting of ``(color, element)``:: sage: x = C([(2,1), (3,3), (3,2)]); x [[2, 3, 3], [1, 3, 2]] @@ -445,6 +445,8 @@ class ColoredPermutations(Parent, UniqueRepresentation): sage: C([[3,3,1], [1,3,2]]) [[3, 3, 1], [1, 3, 2]] + sage: C(([3,3,1], [1,3,2])) + [[3, 3, 1], [1, 3, 2]] There is also the natural lift from permutations:: @@ -452,6 +454,12 @@ class ColoredPermutations(Parent, UniqueRepresentation): sage: C(P.an_element()) [[0, 0, 0], [3, 1, 2]] + + A colored permutation:: + + sage: C(C.an_element()) == C.an_element() + True + REFERENCES: - :wikipedia:`Generalized_symmetric_group` @@ -688,20 +696,22 @@ def _element_constructor_(self, x): sage: x == C([[2,3,3], [1,3,2]]) True """ - if isinstance(x, list): - if isinstance(x[0], tuple): - c = [] - p = [] - for k in x: - if len(k) != 2: - raise ValueError("input must be pairs (color, element)") - c.append(self._C(k[0])) - p.append(k[1]) - return self.element_class(self, c, self._P(p)) - - if len(x) != 2: - raise ValueError("input must be a pair of a list of colors and a permutation") - return self.element_class(self, [self._C(v) for v in x[0]], self._P(x[1])) + if isinstance(x, self.element_class) and x.parent() is self: + return self + x = list(x) + if isinstance(x[0], tuple): + c = [] + p = [] + for k in x: + if len(k) != 2: + raise ValueError("input must be pairs (color, element)") + c.append(self._C(k[0])) + p.append(k[1]) + return self.element_class(self, c, self._P(p)) + + if len(x) != 2: + raise ValueError("input must be a pair of a list of colors and a permutation") + return self.element_class(self, [self._C(v) for v in x[0]], self._P(x[1])) def _coerce_map_from_(self, C): """ @@ -989,6 +999,11 @@ def __classcall_private__(cls, pi): sage: SignedPermutation([2, 1, -3]) [2, 1, -3] + sage: SignedPermutation((2,1,-3)) + [2, 1, -3] + + sage: SignedPermutation(range(1,4)) + [1, 2, 3] """ return SignedPermutations(len(list(pi)))(pi) @@ -1360,38 +1375,43 @@ def _element_constructor_(self, x): sage: S([]) == list(S)[0] True - """ - if isinstance(x, list): - if x and isinstance(x[0], tuple): - c = [] - p = [] - for k in x: - if len(k) != 2: - raise ValueError("input must be pairs (sign, element)") - if k[0] != 1 and k[0] != -1: - raise ValueError("the sign must be +1 or -1") - c.append(ZZ(k[0])) - p.append(k[1]) - return self.element_class(self, c, self._P(p)) - - if len(x) == self._n: - c = [] - p = [] - one = ZZ.one() - for v in x: - if v > 0: - c.append(one) - p.append(v) - else: - c.append(-one) - p.append(-v) - return self.element_class(self, c, self._P(p)) - - if len(x) != 2: - raise ValueError("input must be a pair of a list of signs and a permutation") - if any(s != 1 and s != -1 for s in x[0]): - raise ValueError("the sign must be +1 or -1") - return self.element_class(self, [ZZ(v) for v in x[0]], self._P(x[1])) + sage: T = SignedPermutation(range(1,4)) + sage: SignedPermutations(3)(T) + [1, 2, 3] + """ + if isinstance(x, self.element_class) and x.parent() is self: + return self + x = list(x) + if x and isinstance(x[0], tuple): + c = [] + p = [] + for k in x: + if len(k) != 2: + raise ValueError("input must be pairs (sign, element)") + if k[0] != 1 and k[0] != -1: + raise ValueError("the sign must be +1 or -1") + c.append(ZZ(k[0])) + p.append(k[1]) + return self.element_class(self, c, self._P(p)) + + if len(x) == self._n: + c = [] + p = [] + one = ZZ.one() + for v in x: + if v > 0: + c.append(one) + p.append(v) + else: + c.append(-one) + p.append(-v) + return self.element_class(self, c, self._P(p)) + + if len(x) != 2: + raise ValueError("input must be a pair of a list of signs and a permutation") + if any(s != 1 and s != -1 for s in x[0]): + raise ValueError("the sign must be +1 or -1") + return self.element_class(self, [ZZ(v) for v in x[0]], self._P(x[1])) def __iter__(self): """ diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index af2bc0da8c8..a4ad56b0170 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2528,7 +2528,7 @@ def cardinality(self) -> Integer | infinity: sage: R = InfiniteAbstractCombinatorialClass() doctest:warning... DeprecationWarning: this class is deprecated, do not use - See https://trac.sagemath.org/31545 for details. + See https://github.com/sagemath/sage/issues/31545 for details. sage: R.cardinality() +Infinity diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 90df86e3e41..95c9104e4b3 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -1050,7 +1050,7 @@ def are_mcfarland_1973_parameters(v, k, lmbda, return_parameters=False): # NOTE: below we compute the value of s so that qs = q^s. If the method # is_power_of of integers would be able to return the exponent, we could use # that... but currently this is not the case - # see trac ticket #19792 + # see github issue #19792 p1,a1 = qs.is_prime_power(get_data=True) p2,a2 = q.is_prime_power(get_data=True) diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 6be4e0e2683..29671fb28ed 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -4887,7 +4887,7 @@ def key_function(s): return (s.from_state, s.to_state) # We use an OrderedDict instead of a dict in order to have a # defined ordering of the transitions in the output. See - # http://trac.sagemath.org/ticket/16580#comment:3 . As the + # https://github.com/sagemath/sage/issues/16580#comment:3 . As the # transitions have to be sorted anyway, the performance # penalty should be bearable; nevertheless, this is only # required for doctests. diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index 3432e18b27d..c8269e5f96f 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -256,7 +256,7 @@ class CombinatorialFreeModule(UniqueRepresentation, Module, IndexedGenerators): sage: XQ == XQ True - We check that ticket :trac:`28681` is fixed:: + We check that issue :trac:`28681` is fixed:: sage: F = CombinatorialFreeModule(ZZ, ZZ); F.rename("F") sage: FF = tensor((F,F)) diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py index 95a9283f7f0..50ae558aaad 100644 --- a/src/sage/combinat/k_regular_sequence.py +++ b/src/sage/combinat/k_regular_sequence.py @@ -18,7 +18,7 @@ doctest:...: FutureWarning: This class/method/function is marked as experimental. It, its functionality or its interface might change without a formal deprecation. - See http://trac.sagemath.org/21202 for details. + See https://github.com/sagemath/sage/issues/21202 for details. Examples diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index a237f2398a9..ec1958ee313 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7248,7 +7248,7 @@ def has_left_descent(self, i, mult=None): doctest:warning ... DeprecationWarning: The mult option is deprecated and ignored. - See https://trac.sagemath.org/27467 for details. + See https://github.com/sagemath/sage/issues/27467 for details. True sage: x.has_left_descent(2, mult='r2l') True @@ -7302,7 +7302,7 @@ def has_right_descent(self, i, mult=None): doctest:warning ... DeprecationWarning: The mult option is deprecated and ignored. - See https://trac.sagemath.org/27467 for details. + See https://github.com/sagemath/sage/issues/27467 for details. True sage: x.has_right_descent(3, mult='r2l') True @@ -9063,7 +9063,7 @@ def a(self): sage: P = Permutations(3, avoiding=[[2,1,3],[1,2,3]]) sage: P.a doctest:...: DeprecationWarning: The attribute a for the list of patterns to avoid is deprecated, use the method patterns instead. - See https://trac.sagemath.org/26810 for details. + See https://github.com/sagemath/sage/issues/26810 for details. ([2, 1, 3], [1, 2, 3]) """ from sage.misc.superseded import deprecation diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index d857092f9c7..f59f4fd951f 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -745,7 +745,7 @@ def rank_function(self): sage: Q.rank_function() is None True - test for ticket :trac:`14006`:: + test for issue :trac:`14006`:: sage: H = Poset()._hasse_diagram sage: s = dumps(H) @@ -2051,7 +2051,7 @@ def orthocomplementations_iterator(self): mt = self.meet_matrix() jn = self.join_matrix() for e in range(n): - # Fix following after ticket #20727 + # Fix following after issue #20727 comps[e] = [x for x in range(n) if mt[e, x] == 0 and jn[e, x] == n - 1 and x in orbits[orbit_number[dual_isomorphism[e]]]] diff --git a/src/sage/combinat/posets/lattices.py b/src/sage/combinat/posets/lattices.py index 43bd9f83fdc..985b61cd6ca 100644 --- a/src/sage/combinat/posets/lattices.py +++ b/src/sage/combinat/posets/lattices.py @@ -3010,7 +3010,7 @@ def vertical_composition(self, other, labels='pairs'): # Todo: This and ordinal_sum() of posets could keep # distinguished linear extension, if it is defined # for both posets/lattices. That can be done after - # trac ticket #21607. + # github issue #21607. if labels not in ['integers', 'pairs']: raise ValueError("labels must be either 'pairs' or 'integers'") diff --git a/src/sage/combinat/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 5d9aae755d8..48ca0af51d5 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -252,6 +252,73 @@ def is_greedy(self): return False return True + def is_supergreedy(self): + r""" + Return ``True`` if the linear extension is supergreedy. + + A linear extension `[x_1 @@ -793,7 +793,7 @@ def __call__(self, w, order=1, datatype=None): doctest:warning ... DeprecationWarning: the "datatype" argument is deprecated - See https://trac.sagemath.org/26307 for details. + See https://github.com/sagemath/sage/issues/26307 for details. sage: type(w) sage: w = m([0],4,datatype='str') @@ -1907,7 +1907,7 @@ def fixed_points(self): sage: for w in f.fixed_points(): print(w) abcabbccabcabcabbccbccabcabbccabcabbccab... - This shows that ticket :trac:`13668` has been resolved:: + This shows that issue :trac:`13668` has been resolved:: sage: d = {1:[1,2],2:[2,3],3:[4],4:[5],5:[6],6:[7],7:[8],8:[9],9:[10],10:[1]} sage: s = WordMorphism(d) @@ -1918,7 +1918,7 @@ def fixed_points(self): sage: s7r.periodic_point(2) word: 2,1,1,10,9,8,7,6,5,4,3,2,1,10,9,8,7,6,5,4,3,2,10,9,8,7,6,5,4,3,2,9,8,7,6,5,4,3,2,8,... - This shows that ticket :trac:`13668` has been resolved:: + This shows that issue :trac:`13668` has been resolved:: sage: s = "1->321331332133133,2->133321331332133133,3->2133133133321331332133133" sage: s = WordMorphism(s) @@ -1994,7 +1994,7 @@ def periodic_points(self): sage: f.fixed_points() [] - This shows that ticket :trac:`13668` has been resolved:: + This shows that issue :trac:`13668` has been resolved:: sage: d = {1:[1,2],2:[2,3],3:[4],4:[5],5:[6],6:[7],7:[8],8:[9],9:[10],10:[1]} sage: s = WordMorphism(d) diff --git a/src/sage/cpython/__init__.py b/src/sage/cpython/__init__.py index 19c8d87a3be..51974f1e438 100644 --- a/src/sage/cpython/__init__.py +++ b/src/sage/cpython/__init__.py @@ -5,18 +5,18 @@ # Make sure that the correct zlib library is loaded. This is needed # to prevent the system zlib to be loaded instead of the Sage one. -# See https://trac.sagemath.org/ticket/23122 +# See https://github.com/sagemath/sage/issues/23122 import zlib as _zlib del _zlib # Monkey-patch ExtensionFileLoader to allow IPython to find the sources -# of Cython files. See https://trac.sagemath.org/ticket/24681 +# of Cython files. See https://github.com/sagemath/sage/issues/24681 from importlib.machinery import ExtensionFileLoader as _ExtensionFileLoader del _ExtensionFileLoader.get_source del _ExtensionFileLoader # Work around a Cygwin-specific bug caused by sqlite3; see -# https://trac.sagemath.org/ticket/30157 and the docstring for +# https://github.com/sagemath/sage/issues/30157 and the docstring for # fix_for_ticket_30157 # Here we monkey-patch the sqlite3 module to ensure the fix is # applied the very first time a connection is made to a sqlite3 @@ -39,7 +39,7 @@ def _patch_sqlite3(): def connect(*args, **kwargs): if fix_for_ticket_30157(): raise RuntimeError( - 'patch for Trac ticket #30157 failed; please report this ' + 'patch for Github issue #30157 failed; please report this ' 'bug to https://trac.sagemath.org') # Undo the monkey-patch diff --git a/src/sage/cpython/atexit.pyx b/src/sage/cpython/atexit.pyx index 3ba391b4cdd..baa3d7a2c0e 100644 --- a/src/sage/cpython/atexit.pyx +++ b/src/sage/cpython/atexit.pyx @@ -51,7 +51,7 @@ cdef class restore_atexit: sage: import atexit sage: from sage.cpython.atexit import restore_atexit sage: def handler(*args, **kwargs): - ....: import sys # see https://trac.sagemath.org/ticket/25270#comment:56 + ....: import sys # see https://github.com/sagemath/sage/issues/25270#comment:56 ....: sys.stdout.write(str((args, kwargs))) ....: sys.stdout.write('\n') sage: atexit.register(handler, 1, 2, c=3) diff --git a/src/sage/crypto/boolean_function.pyx b/src/sage/crypto/boolean_function.pyx index a9ea665475c..d10c0c11546 100644 --- a/src/sage/crypto/boolean_function.pyx +++ b/src/sage/crypto/boolean_function.pyx @@ -942,7 +942,7 @@ cdef class BooleanFunction(SageObject): doctest:warning ... DeprecationWarning: absolut_indicator is deprecated. Please use absolute_indicator instead. - See https://trac.sagemath.org/28001 for details. + See https://github.com/sagemath/sage/issues/28001 for details. 32 """ cdef long a diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py index 218a2dff3ff..452abbcce8e 100644 --- a/src/sage/databases/findstat.py +++ b/src/sage/databases/findstat.py @@ -4733,7 +4733,7 @@ def __init__(self): print(" %s: %s" % (id, data["NamePlural"])) print("To use it with this interface, it has to be added to the dictionary") print(" _SupportedFindStatCollections in src/sage/databases/findstat.py") - print("of the SageMath distribution. Please open a ticket on trac!") + print("of the SageMath distribution. Please open an issue on github!") # print("Very likely, the following code would work:") # fields = "SageCodeElementToString,SageCodeElementsOnLevel,SageCodeStringToElement" # url = FINDSTAT_API_COLLECTIONS + id + "?fields=" + fields diff --git a/src/sage/databases/oeis.py b/src/sage/databases/oeis.py index a800db53df2..509f0c10d46 100644 --- a/src/sage/databases/oeis.py +++ b/src/sage/databases/oeis.py @@ -592,7 +592,7 @@ def _imaginary_entry(self, ident='A999999', keywords=''): '%D ' + ident + ' Lewis Carroll, The Hunting of the Snark.\n' '%D ' + ident + ' Deep Thought, The Answer to the Ultimate Question of Life, The Universe, and Everything.\n' '%H ' + ident + ' Wikipedia, 42 (number)\n' - '%H ' + ident + ' See. also trac ticket #42\n' + '%H ' + ident + ' See. also github issue #42\n' '%H ' + ident + ' Do not confuse with the sequence A000042 or the sequence A000024\n' '%H ' + ident + ' The string http://42.com is not a link.\n' '%F ' + ident + ' For n big enough, s(n+1) - s(n) = 0.\n' @@ -1556,7 +1556,7 @@ def links(self, browse=None, format='guess'): sage: HTML = s.links(format="html"); HTML 0: Wikipedia, 42 (number) - 1: See. also trac ticket #42 + 1: See. also github issue #42 ... sage: type(HTML) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index adbf012ab2e..fe58e2bde3e 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -152,10 +152,10 @@ def init_sage(controller=None): """ try: # We need to ensure that the Matplotlib font cache is built to - # avoid spurious warnings (see Trac #20222). + # avoid spurious warnings (see Issue #20222). import matplotlib.font_manager except ImportError: - # Do not require matplotlib for running doctests (Trac #25106). + # Do not require matplotlib for running doctests (Issue #25106). pass else: # Make sure that the agg backend is selected during doctesting. @@ -170,7 +170,7 @@ def init_sage(controller=None): # Set the Python PRNG class to the Python 2 implementation for consistency # of 'random' test results that use it; see - # https://trac.sagemath.org/ticket/24508 + # https://github.com/sagemath/sage/issues/24508 # We use the baked in copy of the random module for both Python 2 and 3 # since, although the upstream copy is unlikely to change, this further # ensures consistency of test results @@ -214,17 +214,17 @@ def init_sage(controller=None): debug.refine_category_hash_check = True # We import readline before forking, otherwise Pdb doesn't work - # on OS X: http://trac.sagemath.org/14289 + # on OS X: https://github.com/sagemath/sage/issues/14289 try: import readline except ModuleNotFoundError: - # Do not require readline for running doctests (Trac #31160). + # Do not require readline for running doctests (Issue #31160). pass try: import sympy except ImportError: - # Do not require sympy for running doctests (Trac #25106). + # Do not require sympy for running doctests (Issue #25106). pass else: # Disable SymPy terminal width detection @@ -731,7 +731,7 @@ def compiler(example): # exceptions), whereas on Python 2 does not, so we # normalize Python 3 exceptions to match tests written to # Python 2 - # See https://trac.sagemath.org/ticket/24271 + # See https://github.com/sagemath/sage/issues/24271 exc_cls = exception[0] exc_name = exc_cls.__name__ if exc_cls.__module__: @@ -2144,7 +2144,7 @@ def run(self): sys.stdin = os.fdopen(0, "r") except OSError: # We failed to open stdin for reading, this might happen - # for example when running under "nohup" (Trac #14307). + # for example when running under "nohup" (Issue #14307). # Simply redirect stdin from /dev/null and try again. with open(os.devnull) as f: os.dup2(f.fileno(), 0) diff --git a/src/sage/dynamics/surface_dynamics_deprecation.py b/src/sage/dynamics/surface_dynamics_deprecation.py index 9a2a1b4312c..9da8f1d0be3 100644 --- a/src/sage/dynamics/surface_dynamics_deprecation.py +++ b/src/sage/dynamics/surface_dynamics_deprecation.py @@ -25,7 +25,7 @@ def surface_dynamics_deprecation(name): computation that are currently available in Sage. See more information at http://www.labri.fr/perso/vdelecro/surface-dynamics/latest/ - See http://trac.sagemath.org/20695 for details. + See https://github.com/sagemath/sage/issues/20695 for details. """ from sage.misc.superseded import deprecation deprecation(20695, deprecation_msg.format(name)) diff --git a/src/sage/ext/memory_allocator.pxd b/src/sage/ext/memory_allocator.pxd index b612df2110c..5ada54f9535 100644 --- a/src/sage/ext/memory_allocator.pxd +++ b/src/sage/ext/memory_allocator.pxd @@ -65,7 +65,7 @@ cdef class MemoryAllocator: ....: assert ptr == ( ptr) & ~(2**i-1) ....: ''') doctest:...: DeprecationWarning: this class is deprecated; use the class from the python package `memory_allocator` - See https://trac.sagemath.org/31591 for details. + See https://github.com/sagemath/sage/issues/31591 for details. """ cdef size_t extra = alignment - 1 return align(self.malloc(size + extra), alignment) @@ -96,7 +96,7 @@ cdef class MemoryAllocator: ....: ''') sage: foo() doctest:...: DeprecationWarning: this class is deprecated; use the class from the python package `memory_allocator` - See https://trac.sagemath.org/31591 for details. + See https://github.com/sagemath/sage/issues/31591 for details. """ # Find extra such that (nmemb + extra) * size >= nmemb * size + alignment - 1 # ⇔ extra * size >= alignment - 1 diff --git a/src/sage/ext/memory_allocator.pyx b/src/sage/ext/memory_allocator.pyx index cae760c9752..1f271a3e82e 100644 --- a/src/sage/ext/memory_allocator.pyx +++ b/src/sage/ext/memory_allocator.pyx @@ -27,7 +27,7 @@ cdef class MemoryAllocator: ....: mem.aligned_allocarray(8, n, 8) ....: ''') doctest:...: DeprecationWarning: this class is deprecated; use the class from the python package `memory_allocator` - See https://trac.sagemath.org/31591 for details. + See https://github.com/sagemath/sage/issues/31591 for details. """ def __cinit__(self): """ @@ -44,7 +44,7 @@ cdef class MemoryAllocator: ....: ''') sage: foo() doctest:...: DeprecationWarning: this class is deprecated; use the class from the python package `memory_allocator` - See https://trac.sagemath.org/31591 for details. + See https://github.com/sagemath/sage/issues/31591 for details. 1 16 """ diff --git a/src/sage/ext/mod_int.h b/src/sage/ext/mod_int.h index bb18adf3c11..9bb5043ec71 100644 --- a/src/sage/ext/mod_int.h +++ b/src/sage/ext/mod_int.h @@ -14,7 +14,7 @@ * Hence, we use signed 64-bit ints. This gives us fast conversion * to/from Python on 64-bit Linux and OSX, and a large number of * available (but not quite as fast) primes on 64-bit Windows and all - * 32-bit platforms (see Trac #10281) + * 32-bit platforms (see Issue #10281) */ typedef int64_t mod_int; diff --git a/src/sage/ext_data/threejs/threejs_template.html b/src/sage/ext_data/threejs/threejs_template.html index 38800560c47..172d0cec4aa 100644 --- a/src/sage/ext_data/threejs/threejs_template.html +++ b/src/sage/ext_data/threejs/threejs_template.html @@ -214,7 +214,17 @@ camera.up.set( 0, 0, 1 ); camera.position.set( a[0]*xMid, a[1]*yMid, a[2]*zMid ); - var offset = new THREE.Vector3( a[0]*xRange, a[1]*yRange, a[2]*zRange ); + // camera is positioned so that the line from the camera to the center + // of the bounding sphere of the objects makes an angle of 60 degrees with x-axis + // and an angle of 30 degrees with z-axis and the field of view of the camera looking + // at the center has an angle of 45 degrees. + const sin8 = Math.sin(Math.PI / 8); + const sin5 = Math.sin(Math.PI / 5); + const cos5 = Math.cos(Math.PI / 5); + const sin3 = Math.sin(Math.PI / 3); + const cos3 = Math.cos(Math.PI / 3); + var r = midToCorner / sin8; + var offset = new THREE.Vector3( r * sin3 * cos5, r * sin3 * sin5, r * cos3 ); if ( options.viewpoint ) { @@ -569,7 +579,7 @@ setTimeout( function() { m.style.display = 'none'; }, 2000 ); } - +