diff --git a/.zenodo.json b/.zenodo.json index 91f308b7b75..a08f77e55a5 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.beta3", - "version": "10.0.beta3", + "title": "sagemath/sage: 10.0.beta4", + "version": "10.0.beta4", "upload_type": "software", - "publication_date": "2023-03-02", + "publication_date": "2023-03-12", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/10.0.beta3", + "identifier": "https://github.com/sagemath/sage/tree/10.0.beta4", "relation": "isSupplementTo" }, { diff --git a/README.md b/README.md index 1233f17de73..6b8dc835480 100644 --- a/README.md +++ b/README.md @@ -394,6 +394,20 @@ 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 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: + + $ 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 `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`. Troubleshooting --------------- diff --git a/VERSION.txt b/VERSION.txt index d3cf4ce44c4..74eaefdc8e1 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.0.beta3, Release Date: 2023-03-02 +SageMath version 10.0.beta4, Release Date: 2023-03-12 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index c08ada17ba4..9486757bd30 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=b9a6c30f4f1c828ca611527d104643d8c0b89ed9 -md5=37a7a99a528efa8a4eea1beff33985db -cksum=1112286999 +sha1=d8ef4b8b0d227b2530cff20c65911eb9587828a3 +md5=f58322981d5c5e26b1d9eadf3c2a9132 +cksum=3821522607 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 82496e60715..cd1ba3b2549 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -a822a2132aa0e85069ec8b6efa48ad6db08cc044 +d6c38e59d84f0dcb7c08110445050c1c370f7440 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], [ diff --git a/build/pkgs/sage_conf/install-requires.txt b/build/pkgs/sage_conf/install-requires.txt index c1acbf5a6b3..67a9d5738d6 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.0b3 +sage-conf ~= 10.0b4 diff --git a/build/pkgs/sage_docbuild/install-requires.txt b/build/pkgs/sage_docbuild/install-requires.txt index 4c0503af358..a585b2f9b8b 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.0b3 +sage-docbuild ~= 10.0b4 diff --git a/build/pkgs/sage_setup/install-requires.txt b/build/pkgs/sage_setup/install-requires.txt index be67a744301..d45d5e65a6a 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.0b3 +sage-setup ~= 10.0b4 diff --git a/build/pkgs/sage_sws2rst/install-requires.txt b/build/pkgs/sage_sws2rst/install-requires.txt index b30f48ea38a..24127fdeca8 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.0b3 +sage-sws2rst ~= 10.0b4 diff --git a/build/pkgs/sagelib/install-requires.txt b/build/pkgs/sagelib/install-requires.txt index 70aefd13369..dcc880b47ba 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.0b3 +sagelib ~= 10.0b4 diff --git a/build/pkgs/sagemath_categories/install-requires.txt b/build/pkgs/sagemath_categories/install-requires.txt index 31553eaef51..b1f2619afe2 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.0b3 +sagemath-categories ~= 10.0b4 diff --git a/build/pkgs/sagemath_environment/install-requires.txt b/build/pkgs/sagemath_environment/install-requires.txt index bcd541eea0b..04a1bfeb648 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.0b3 +sagemath-environment ~= 10.0b4 diff --git a/build/pkgs/sagemath_objects/install-requires.txt b/build/pkgs/sagemath_objects/install-requires.txt index 1da2fc997e0..2be3fb2dd0c 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.0b3 +sagemath-objects ~= 10.0b4 diff --git a/build/pkgs/sagemath_repl/install-requires.txt b/build/pkgs/sagemath_repl/install-requires.txt index 798c25aee10..eb8ae0f1c24 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.0b3 +sagemath-repl ~= 10.0b4 diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/src/VERSION.txt b/src/VERSION.txt index b3001ec9281..fe05847be08 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.0.beta3 +10.0.beta4 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 9aab0c0fcc8..05ade37401c 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.beta3' -SAGE_RELEASE_DATE='2023-03-02' -SAGE_VERSION_BANNER='SageMath version 10.0.beta3, Release Date: 2023-03-02' +SAGE_VERSION='10.0.beta4' +SAGE_RELEASE_DATE='2023-03-12' +SAGE_VERSION_BANNER='SageMath version 10.0.beta4, Release Date: 2023-03-12' diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index b26404da549..610b9546525 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/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..d7485c9762e --- /dev/null +++ b/src/doc/en/reference/drinfeld_modules/index.rst @@ -0,0 +1,42 @@ +Drinfeld modules +==================================== + +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`. + +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/index.rst b/src/doc/en/reference/index.rst index c91bbc02de9..2b1c95b1988 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -115,6 +115,7 @@ Number Fields, Function Fields, and Valuations * :doc:`Number Fields ` * :doc:`Function Fields ` * :doc:`Discrete Valuations ` +* :doc:`Drinfeld Modules ` Number Theory ------------- diff --git a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst index 414c04bb611..97cff1bb835 100644 --- a/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst +++ b/src/doc/en/reference/polynomial_rings/polynomial_rings_univar.rst @@ -43,3 +43,5 @@ whereas others have multiple bases. sage/rings/polynomial/polynomial_compiled sage/rings/polynomial/polynomial_fateman + + sage/rings/polynomial/integer_valued_polynomials diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 04164b1b0b1..85f225ab5c7 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2069,10 +2069,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. @@ -2083,6 +2088,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` @@ -2608,6 +2618,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: @@ -2771,6 +2784,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) @@ -3879,6 +3895,7 @@ REFERENCES: 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. @@ -4141,6 +4158,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. @@ -4516,6 +4538,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 @@ -4641,6 +4668,11 @@ REFERENCES: Int. Math. Res. Not. (2015). :doi:`10.1093/imrn/rnv194`, :arxiv:`1408.0320`. +.. [MS2019] \Y. Musleh and \'E. 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. @@ -5221,6 +5253,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 @@ -6092,6 +6126,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\"auser, 2006. + .. [VW1994] Leonard Van Wyk. *Graph groups are biautomatic*. J. Pure Appl. Alg. **94** (1994). no. 3, 341-352. 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..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, Element, Vector, parent +from sage.structure.element cimport AlgebraElement, Element, Vector from sage.matrix.matrix cimport Matrix cdef class FiniteDimensionalAlgebraElement(AlgebraElement): 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/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/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 4b7af26d31c..58bc0da3d24 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/drinfeld_modules.py b/src/sage/categories/drinfeld_modules.py new file mode 100644 index 00000000000..08613d2d14a --- /dev/null +++ b/src/sage/categories/drinfeld_modules.py @@ -0,0 +1,767 @@ +r""" +Drinfeld modules over a base + +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 +# +# Distributed under the terms of the GNU General Public License (GPL) +# 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 +from sage.misc.latex import latex +from sage.rings.integer import Integer +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_ring): + r""" + 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 `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`. + + 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 + 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. + + .. RUBRIC:: Construction + + 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: 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: 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. + + .. RUBRIC:: Properties of the category + + The base field is retrieved using the method :meth:`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: 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 + + The so-called constant coefficient --- which is the same for all + Drinfeld modules in the category --- is simply the image of `T` by + the base morphism:: + + sage: C.constant_coefficient() + z^3 + 7*z^2 + 6*z + 10 + 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: C.characteristic() + T^2 + 7*T + 2 + 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: C.base() is phi.base() + True + sage: C.base_morphism() is phi.base_morphism() + True + + sage: C.function_ring() + Univariate Polynomial Ring in T over Finite Field of size 11 + sage: C.function_ring() is phi.function_ring() + True + + 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 = 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 C + True + + Of course, the constant coefficient of the input must be the same as + the category:: + + sage: C.object([z, 1]) + Traceback (most recent call last): + ... + 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:: + + 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 C + True + + TESTS:: + + sage: Fq = GF(11) + sage: A. = Fq[] + sage: K. = Fq.extension(4) + sage: from sage.categories.drinfeld_modules import DrinfeldModules + sage: base = Hom(A, K)(0) + sage: C = DrinfeldModules(base) + Traceback (most recent call last): + ... + TypeError: base field must be a ring extension + + :: + + sage: C.base().defining_morphism() == C.base_morphism() + True + + :: + + sage: base = Hom(A, A)(1) + sage: C = DrinfeldModules(base) + Traceback (most recent call last): + ... + TypeError: base field must be a ring extension + + :: + + sage: base = 'I hate Rostropovitch' + sage: C = DrinfeldModules(base) # known bug (blankline) + + Traceback (most recent call last): + ... + TypeError: input must be a ring morphism + + :: + + sage: ZZT. = ZZ[] + sage: base = Hom(ZZT, K)(1) + sage: C = DrinfeldModules(base) # known bug (blankline) + + Traceback (most recent call last): + ... + TypeError: function ring base must be a finite field + """ + + def __init__(self, base_field, name='t'): + r""" + Initialize `self`. + + INPUT: + + - ``base_field`` -- the base field, which is a ring extension + over a base + + - ``name`` (default: ``'t'``) -- the name of the Ore polynomial + variable + + TESTS:: + + 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: C = phi.category() + sage: ore_polring. = OrePolynomialRing(phi.base(), phi.base().frobenius_endomorphism()) + 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: C.base() == K.over(base_morphism) + True + sage: C._base_morphism == i * base_morphism + True + sage: C._function_ring is A + True + sage: C._constant_coefficient == base_morphism(T) + True + sage: C._characteristic(C._constant_coefficient) + 0 + """ + # 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[T] + function_ring = self._function_ring + if not isinstance(function_ring, PolynomialRing_general): + 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('function ring base must be a finite field') + # Shortcuts + Fq = function_ring_base + A = function_ring + T = A.gen() + 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_morphism(T) + # Create characteristic + self._characteristic = None + if K.is_finite(): + self._characteristic = A(K.over(Fq)(base_morphism(T)).minpoly()) + 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 + self._base_over_constants_field = base_field.over(Fq_to_K) + super().__init__(base=base_field) + + def _latex_(self): + r""" + Return a latex representation of the category. + + OUTPUT: a string + + 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: C = phi.category() + sage: latex(C) + \text{Category{ }of{ }Drinfeld{ }modules{ }over{ }\Bold{F}_{11^{4}} + """ + return f'\\text{{Category{{ }}of{{ }}Drinfeld{{ }}modules{{ }}' \ + f'over{{ }}{latex(self._base_field)}' + + def _repr_(self): + r""" + Return a string representation of the category. + + OUTPUT: a string + + 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: 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 over {self._base_field}' + + def Homsets(self): + r""" + Return the category of homsets. + + 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: C = phi.category() + + sage: from sage.categories.homsets import Homsets + sage: C.Homsets() is Homsets() + True + """ + return Homsets() + + def Endsets(self): + r""" + Return the category of endsets. + + 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: C = phi.category() + + sage: from sage.categories.homsets import Homsets + sage: C.Endsets() is Homsets().Endsets() + True + """ + return Homsets().Endsets() + + def base_morphism(self): + r""" + Return the base morphism of the category. + + 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: 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: C.constant_coefficient() == C.base_morphism()(T) + True + """ + 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`. + + 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: 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 + + def characteristic(self): + r""" + Return the function ring-characteristic. + + 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: C = phi.category() + sage: C.characteristic() + T^2 + 7*T + 2 + + :: + + sage: psi = DrinfeldModule(A, [Frac(A).gen(), 1]) + sage: C = psi.category() + sage: C.characteristic() + 0 + """ + if self._characteristic is None: + raise NotImplementedError('function ring characteristic not ' \ + 'implemented in this case') + return self._characteristic + + def constant_coefficient(self): + r""" + Return the constant coefficient of the category. + + 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: C = phi.category() + sage: C.constant_coefficient() + z^3 + 7*z^2 + 6*z + 10 + sage: C.constant_coefficient() == C.base()(T) + True + """ + return self._constant_coefficient + + def function_ring(self): + r""" + Return the function ring of the category. + + 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: C = phi.category() + sage: C.function_ring() + Univariate Polynomial Ring in T over Finite Field of size 11 + sage: C.function_ring() is A + True + """ + return self._function_ring + + def object(self, gen): + r""" + Return a Drinfeld module object in the category whose generator + is the input. + + INPUT: + + - ``gen`` -- the generator of the Drinfeld module, given as an Ore + polynomial or a list of coefficients + + 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: 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: 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 + # If gen is not in the Ore polring, an exception is raised + gen = self._ore_polring(gen) + 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) + + def ore_polring(self): + r""" + Return the Ore polynomial ring of the category + + 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: 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 + """ + return self._ore_polring + + def random_object(self, rank): + r""" + Return a random Drinfeld module in the category with given rank. + + INPUT: + + - ``rank`` -- an integer, the rank of the Drinfeld module + + 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: 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 + """ + 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_field + 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.object(coeffs) + + def super_categories(self): + """ + 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: C = phi.category() + sage: C.super_categories() + [Category of objects] + """ + return [Objects()] + + class ParentMethods: + + def base(self): + r""" + Return the base field of this Drinfeld module, viewed as + an algebra over the function ring. + + This is an instance of the class + :class:`sage.rings.ring_extension.RingExtension`. + + 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() + Finite Field in z12 of size 5^12 over its base + + The base can be infinite:: + + 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() + + def base_morphism(self): + r""" + Return the base morphism of this Drinfeld module. + + 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_morphism() + Ring morphism: + 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: 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 field can be infinite:: + + 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() + + def base_over_constants_field(self): + r""" + Return the base field, seen as an extension over the constants + field `\mathbb{F}_q`. + + This is an instance of the class + :class:`sage.rings.ring_extension.RingExtension`. + + 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. + + 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() + Traceback (most recent call last): + ... + NotImplementedError: function ring characteristic not implemented in this case + """ + return self.category().characteristic() + + def function_ring(self): + r""" + Return the function ring of this Drinfeld module. + + 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() + Univariate Polynomial Ring in T over Finite Field in z2 of size 5^2 + sage: phi.function_ring() is A + True + """ + return self.category().function_ring() + + def constant_coefficient(self): + r""" + Return the constant coefficient of the generator + of this Drinfeld module. + + 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` be + the base of the Drinfeld module. The constant coefficient is + `\gamma(T)`:: + + sage: C = phi.category() + sage: base = C.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 = 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 + + Reciprocally, it is impossible to create two Drinfeld modules in + this category if they do not share the same constant + coefficient:: + + sage: rho = C.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 this Drinfeld module. + + 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: 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: S is phi.category().ore_polring() + True + + The generator of the Drinfeld module is in the Ore polynomial + ring:: + + sage: phi(T) in S + True + """ + return self.category().ore_polring() diff --git a/src/sage/categories/examples/sets_cat.py b/src/sage/categories/examples/sets_cat.py index c4df13b1357..a69af2a6118 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 ff1a7f955c5..33dc2bb973a 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..af150b460c6 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/all.py b/src/sage/combinat/all.py index 47cd721e0a5..a378e86b5d3 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 --------- @@ -299,3 +300,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..0c7d50a7659 --- /dev/null +++ b/src/sage/combinat/bijectionist.py @@ -0,0 +1,3229 @@ +r""" +A bijectionist's toolkit + +AUTHORS: + +- Alexander Grosz, Tobias Kietreiber, Stephan Pfannerer and Martin + Rubey (2020-2022): Initial version + +Quick reference +=============== + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :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_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. + + + :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 +============= + +Consider the following combinatorial statistics on a permutation: + + * `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)] + 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))) + ....: 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} + +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: 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]) + +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: + ....: 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()) + [] + +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)] + 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: 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) + ....: 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, defaultdict +from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException +from sage.rings.integer_ring import ZZ +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 +from sage.misc.verbose import get_verbose + + +class Bijectionist(SageObject): + r""" + A toolbox to list all possible bijections between two finite sets + under various constraints. + + INPUT: + + - ``A``, ``B`` -- sets of equal size, given as a list + + - ``tau`` -- (optional) a function from ``B`` to ``Z``, in case of + ``None``, the identity map ``lambda x: x`` is used + + - ``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 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`` + + - ``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. + + 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=None, + pi_rho=tuple(), phi_psi=tuple(), Q=None, + elements_distributions=tuple(), + value_restrictions=tuple(), solver=None, key=None): + """ + Initialize the bijectionist. + + TESTS: + + Check that large input sets are handled well:: + + sage: A = B = range(20000) + sage: bij = Bijectionist(A, B) # long time + """ + # glossary of standard letters: + # A, B, Z, W ... finite sets + # 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 + # 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 + 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._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} + # 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"]) + self._Z = self._sorter["Z"](self._Z) + else: + try: + self._Z = sorted(self._Z) + self._sorter["Z"] = lambda x: sorted(x) + 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) + self.set_value_restrictions(*value_restrictions) + self.set_distributions(*elements_distributions) + self.set_quadratic_relation(*phi_psi) + self.set_homomesic(Q) + 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 = '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: 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}] + 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): + ... + StopIteration + + """ + self._bmilp = None + 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. + + 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 + 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: 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()))): + ....: 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: 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(): + ....: 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} + + """ + 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) + 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))) + tau_beta_inverse = {} + self._statistics_possible_values = {} + for a in self._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""" + 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: 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()]) + (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`` -- (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: 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() + 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, *value_restrictions): + 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: + + - ``value_restrictions`` -- 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(bij.solutions_iterator(), key=lambda d: 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. + + 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]] + + 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 + + """ + # 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 + self._bmilp = None + 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" + self._restrictions_possible_values[a] = self._restrictions_possible_values[a].intersection(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. + + 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(): + 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}") + 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, \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. + + 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)] + 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, 3, 1]], [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: 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) + {[]: 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`. + + """ + self._bmilp = None + for tA, tZ in 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") + if z not in self._Z: + raise ValueError(f"value {z} was not found in tau(A)") + self._elements_distributions = tuple(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 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 + of the second permutation by the length of the first + permutation:: + + 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 + 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()) + [] + + """ + self._bmilp = None + 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)) + + set_semi_conjugacy = set_intertwining_relations + + def set_quadratic_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(sorted(bij.minimal_subdistributions_iterator())) + [ ( [ /\ ] ) + [ ( [ / \ ] ) ( [ /\ /\ ] [ /\ /\/\ ] ) + [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \, / \/\ ], [ / \/\, / \ ] ), + + ( [ /\ ] ) ] + ( [ /\/\ / \ ] [ /\ ] ) ] + ( [ / \, / \ ], [ /\/\/\, /\/ \ ] ) ] + sage: bij.set_quadratic_relation((lambda D: D, lambda D: D)) + sage: ascii_art(sorted(bij.minimal_subdistributions_iterator())) + [ ( [ /\ ] ) + [ ( [ / \ ] ) ( [ /\ ] [ /\/\ ] ) + [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \ ], [ / \ ] ), + + + ( [ /\ ] [ /\ ] ) ( [ /\/\ ] [ /\ ] ) + ( [ / \/\ ], [ / \/\ ] ), ( [ / \ ], [ /\/ \ ] ), + + ( [ /\ ] ) ] + ( [ / \ ] ) ] + ( [ / \ ], [ /\/\/\ ] ) ] + + """ + 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 + 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) # indirect doctest + {{[], [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: 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) + 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: 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:: + + 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: 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))) + ....: 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: P = bij.constant_blocks() + 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, 3, 2], [2, 1, 3], [3, 2, 1]] + [[1, 4, 3, 2], [3, 2, 1, 4]] + [[2, 1, 4, 3], [4, 3, 2, 1]] + [[1, 2, 4, 3], [1, 3, 2, 4], [2, 1, 3, 4], [4, 2, 3, 1]] + [[1, 3, 4, 2], [2, 3, 1, 4], [2, 4, 3, 1], [3, 2, 4, 1]] + [[1, 4, 2, 3], [3, 1, 2, 4], [4, 1, 3, 2], [4, 2, 1, 3]] + [[2, 4, 1, 3], [3, 1, 4, 2], [3, 4, 2, 1], [4, 3, 1, 2]] + + 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, 3, 2], [2, 1, 3], [3, 2, 1], + [1, 2, 4, 3], [1, 3, 2, 4], [1, 3, 4, 2], [1, 4, 3, 2], + [2, 1, 3, 4], [2, 1, 4, 3], [2, 3, 1, 4], [2, 3, 4, 1], + [2, 4, 3, 1], [3, 2, 1, 4], [3, 2, 4, 1], [4, 2, 3, 1], + [4, 3, 2, 1]] + [[1, 4, 2, 3], [2, 4, 1, 3], [3, 1, 2, 4], [3, 1, 4, 2], + [3, 4, 2, 1], [4, 1, 3, 2], [4, 2, 1, 3], [4, 3, 1, 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'}} + + """ + if self._bmilp is None: + self._bmilp = _BijectionistMILP(self) + + 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 + for z, tP in _invert_dict(solution).items() + if len(tP) > 1} + + # _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 = copy(self._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]] + 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 + # 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): + 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] + 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._P = tmp_P + # recreate the MILP + self._bmilp = _BijectionistMILP(self, + self._bmilp._solution_cache) + 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""" + Return for each block the values of `s` compatible with the + imposed restrictions. + + INPUT: + + - ``p`` -- (optional) a block of `P`, or an element of a + block of `P`, or a list of these + + - ``optimal`` -- (default: ``False``) whether or 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"] + 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 an unfeasible problem:: + + 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") + {'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() + if p in self._A: + blocks.add(self._P.find(p)) + 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)) + elif type(p1) is list: + for p2 in p1: + blocks.add(self._P.find(p2)) + + if optimal: + if self._bmilp is None: + self._bmilp = _BijectionistMILP(self) + bmilp = self._bmilp + solutions = defaultdict(set) + 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 = [bmilp._x[p, z] == 0 for z in solutions[p]] + while True: + 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(bmilp._x[p, solution[p]] == 0) + + # create dictionary to return + possible_values = {} + for p in blocks: + for a in self._P.root_to_elements_dict()[p]: + if optimal: + possible_values[a] = solutions[p] + else: + possible_values[a] = self._possible_block_values[p] + + return possible_values + + def minimal_subdistributions_iterator(self): + r""" + Return all minimal subsets `\tilde A` of `A` + together with submultisets `\tilde Z` with `s(\tilde A) = + \tilde Z` as multisets. + + 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_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: 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()))): + ....: 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) + + if self._bmilp is None: + self._bmilp = _BijectionistMILP(self) + 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]) + try: + minimal_subdistribution.solve() + except MIPSolverException: + return + d = minimal_subdistribution.get_values(D, convert=bool, tolerance=0.1) # a dict from A to {0, 1} + new_s = self._find_counterexample(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) + + # get all variables with value 1 + active_vars = [D[a] for a in self._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 + minimal_subdistribution.add_constraint(sum(active_vars) <= len(active_vars) - 1, + name="veto") + else: + s = new_s + + def _find_counterexample(self, P, s0, d, on_blocks): + r""" + Return a solution `s` such that ``d`` is not a subdistribution of + `s0`. + + INPUT: + + - ``P`` -- the representatives of the blocks, or `A` if + ``on_blocks`` is ``False`` + + - ``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 counterexample 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_counterexample(bij._A, s0, d, False) + {'a': 2, 'b': 2, 'c': 1, 'd': 3, 'e': 1} + + """ + bmilp = self._bmilp + 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 + z_in_d = sum(d[p] * bmilp._x[self._P.find(p), z] + for p in P + if z in self._possible_block_values[self._P.find(p)]) + + # it is sufficient to require that z occurs less often as + # a value among {a | d[a] == 1} than it does in + # 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: + solution = next(bmilp.solutions_iterator(on_blocks, tmp_constraints)) + return solution + except StopIteration: + pass + + def minimal_subdistributions_blocks_iterator(self): + r""" + Return all representatives of minimal subsets `\tilde P` + of `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. + + 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: 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()))): + ....: 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()) + [(['b', 'b', '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()) # 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]), + (['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]) + + if self._bmilp is None: + self._bmilp = _BijectionistMILP(self) + + s = next(self._bmilp.solutions_iterator(True, [])) + add_counter_example_constraint(s) + while True: + try: + minimal_subdistribution.solve() + except MIPSolverException: + return + d = minimal_subdistribution.get_values(D, convert=ZZ, tolerance=0.1) # a dict from P to multiplicities + new_s = self._find_counterexample(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] + 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 _preprocess_intertwining_relations(self): + r""" + 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`. + + 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 + + .. TODO:: + + it is not clear whether this method makes sense + + EXAMPLES:: + + sage: A = B = 'abcd' + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 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 + {{'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} + for composition_index, pi_rho in enumerate(self._pi_rho): + 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: + images[a_tuple].add(a) + + # 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 = defaultdict(set) # (p_1,...,p_k) to {a_1,....} + for a_tuple, image_set in images.items(): + 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: + P.union(image, image_set.pop()) + something_changed = True + # we keep a representative + image_set.add(image) + + images = updated_images + + 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 + `\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:: + + \sum_p r_p x_{p, z} = d_z. + + EXAMPLES:: + + 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} + + 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} + + Changing or re-setting problem parameters clears the internal + cache. Setting the verbosity prints the MILP which is solved.:: + + sage: set_verbose(2) + sage: bij.set_constant_blocks(P) + 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: 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 + 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: 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 + 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 + + 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()) + [] + + 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: 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: 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 + + """ + if self._bmilp is None: + self._bmilp = _BijectionistMILP(self) + yield from self._bmilp.solutions_iterator(False, []) + + +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. + """ + def __init__(self, bijectionist: Bijectionist, solutions=None): + r""" + Initialize the mixed integer linear program. + + INPUT: + + - ``bijectionist`` -- an instance of :class:`Bijectionist`. + + - ``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:: + + 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 + # _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. + bijectionist._preprocess_intertwining_relations() + bijectionist._compute_possible_block_values() + + self.milp = MixedIntegerLinearProgram(solver=bijectionist._solver) + self.milp.set_objective(None) + 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) + + 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() + self.add_quadratic_relation_constraints() + self.add_homomesic_constraints() + self.add_intertwining_relation_constraints() + 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 + 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 _prepare_solution(self, on_blocks, solution): + r""" + Return the solution as a dictionary from `A` (or `P`) to + `Z`. + + INPUT: + + - ``on_blocks`` -- whether to return the solution on blocks + or on all elements + + TESTS:: + + 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} + + """ + 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 + + def solutions_iterator(self, on_blocks, additional_constraints): + r""" + Iterate over all solutions satisfying the additional constraints. + + INPUT: + + - ``additional_constraints`` -- a list of constraints for the + underlying MILP + + - ``on_blocks``, whether to return the solution on blocks or + on all elements + + TESTS:: + + 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} + + """ + 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 ``solution`` to the cache and an appropriate + veto constraint to the MILP. + + INPUT: + + - ``solution`` -- a dictionary from the indices of the MILP to + a 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) + for z in self._bijectionist._possible_block_values[p] + if solution[(p, z)]] + self.milp.add_constraint(sum(active_vars) <= len(active_vars) - 1, + name="veto") + self._solution_cache.append(solution) + + def _is_solution(self, constraint, values): + r""" + Evaluate the given function at the given values. + + INPUT: + + - ``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`, + 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: 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 + """ + index_block_value_dict = {} + for (p, z), v in self._x.items(): + variable_index = next(iter(v.dict())) + index_block_value_dict[variable_index] = (p, z) + + 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): + return False + for lhs, rhs in constraint.inequalities(): + if evaluate(lhs - rhs) > 0: + return False + return True + + 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. + + 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: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) # indirect doctest + sage: next(bmilp.solutions_iterator(False, [])) + {[]: 0, [1]: 1, [1, 2]: 2, [2, 1]: 2} + """ + 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))] + + 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 + + for w in range(len(W)): + for z in range(len(Z)): + self.milp.add_constraint(AZ_matrix[z][w] == B_matrix[z][w], + name="statistics") + + def add_distribution_constraints(self): + r""" + Add constraints so the distributions given by + :meth:`set_distributions` are fulfilled. + + To accomplish this 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. + + 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: from sage.combinat.bijectionist import _BijectionistMILP + sage: bmilp = _BijectionistMILP(bij) # indirect doctest + sage: next(bmilp.solutions_iterator(False, [])) + {[1, 2, 3]: 3, + [1, 3, 2]: 1, + [2, 1, 3]: 2, + [2, 3, 1]: 2, + [3, 1, 2]: 2, + [3, 2, 1]: 2} + + """ + 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 = [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]: + tA_sum[Z_dict[z]] += self._x[p, z] + for z in tZ: + tZ_sum[Z_dict[z]] += 1 + + 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): + 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:`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}. + + 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 = 'abcd' + sage: bij = Bijectionist(A, B, lambda x: B.index(x) % 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 + sage: next(bmilp.solutions_iterator(False, [])) + {'a': 0, 'b': 1, 'c': 0, 'd': 1} + """ + 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, 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) + 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_quadratic_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(sorted(bij.minimal_subdistributions_iterator())) + [ ( [ /\ ] ) + [ ( [ / \ ] ) ( [ /\ /\ ] [ /\ /\/\ ] ) + [ ( [ /\/\/\ ], [ / \ ] ), ( [ /\/ \, / \/\ ], [ / \/\, / \ ] ), + + ( [ /\ ] ) ] + ( [ /\/\ / \ ] [ /\ ] ) ] + ( [ / \, / \ ], [ /\/\/\, /\/ \ ] ) ] + sage: bij.set_quadratic_relation((lambda D: D, lambda D: D)) # indirect doctest + sage: ascii_art(sorted(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 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): + """ + 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(): + preimages[v] = preimages.get(v, []) + [k] + return preimages + + +def _disjoint_set_roots(d): + """ + Return the representatives of the blocks of the disjoint set. + + INPUT: + + - ``d`` -- a :class:`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() + + +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 + + 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) + for s in sets: + N = len(s) + if n < N: + return result + if s == result: + return s + + +""" +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]]] + +Note that adding ``[(2,-2,-1), (2,2,-1), (2,-2,1), (2,2,1)]`` makes +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]) + + 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 -- (2.7 seconds with SCIP on AMD Ryzen 5 PRO 3500U w/ Radeon Vega Mobile Gfx) + 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)] # 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 + +Our benchmark example:: + + sage: from sage.combinat.cyclic_sieving_phenomenon import orbit_decomposition + 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))) + ....: return Permutation([cycle.inverse()(p(cycle(i))) for i in range(1, len(p)+1)]) + + 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] + 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, 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 + + sage: for a, d in bij.minimal_subdistributions_iterator(): # not tested + ....: print(sorted(a), sorted(d)) +""" diff --git a/src/sage/combinat/colored_permutations.py b/src/sage/combinat/colored_permutations.py index 28b20e502af..c7f134efbe0 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/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 609cdf6542e..95c9104e4b3 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) @@ -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 @@ -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:: @@ -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: @@ -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]_ @@ -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 [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`: + `\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,22 +2259,118 @@ 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 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 `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 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 (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 + 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) # long time + + 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) # long time + 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/kazhdan_lusztig.py b/src/sage/combinat/kazhdan_lusztig.py index e5a1456b7de..0c213ea9013 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.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 @@ -76,7 +76,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/matrices/hadamard_matrix.py b/src/sage/combinat/matrices/hadamard_matrix.py index bf915399d20..0434d20d8c2 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 :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]), @@ -247,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` does not satisfies the constraints. + 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) 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.') + + 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. @@ -319,7 +467,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=29 and n=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: @@ -354,31 +503,67 @@ 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: ('+', '+', '+', '+'), + 3: ('+++', '+--', '+--', '+--'), + 5: ('+-++-', '++--+', '+----', '+----'), + 7: ('+--++--', '+-+--+-', '++----+', '+------'), + 9: ('+---++---', '+--+--+--', '+-+----+-', '++------+'), + 11: ('++--------+', '++-+-++-+-+', '++-++--++-+', '+-++----++-'), + 13: ('++++-+--+-+++', '+---+-++-+---', '++---+--+---+', '++---+--+---+'), + 15: ('+-+---++++---+-', '++-++------++-+', + '++-++++--++++-+', '++-++-+--+-++-+'), + 17: ('+---+++----+++---', '++-+---+--+---+-+', + '+--+-++++++++-+--', '+-++-+++--+++-++-'), + 19: ('++--+++-+--+-+++--+', '++-++--+-++-+--++-+', + '+-+---++++++++---+-', '++--+-++++++++-+--+'), + 21: ('+--++++---++---++++--', '++++-+---+--+---+-+++', + '++--+-+-++--++-+-+--+', '++-+++++-+--+-+++++-+'), + 23: ('++---+---+-++-+---+---+', '+-++-++--++++++--++-++-', + '+++---++-+-++-+-++---++', '+++-+++-+------+-+++-++'), + 25: ('++++-+-+-+--++--+-+-+-+++', '++--+--+-++++++++-+--+--+', + '+++--+--++++--++++--+--++', '+-+--+++--++++++--+++--+-'), + 27: ('+--+--+-+++--++--+++-+--+--', '+++-++-+---++--++---+-++-++', + '+---+++++-+-++++-+-+++++---', '+---+++++-+-++++-+-+++++---'), + 29: ('+++---++--+-+----+-+--++---++', '+-+---++--+-++++++-+--++---+-', + '++++-++-+---++++++---+-++-+++', '++--+--+-+++-++++-+++-+--+--+'), + 31: ('++++++-+--+---++++---+--+-+++++', '+--++---+-+-++----++-+-+---++--', + '+--++---+-+-++----++-+-+---++--', '+-----+-++-+++----+++-++-+-----'), + 33: ('++++++-+-+-+++------+++-+-+-+++++', '++-+-++-+----+++--+++----+-++-+-+', + '++--++-+++-+--+-++-+--+-+++-++--+', '+--++--+++++-++----++-+++++--++--'), + 37: ('+--+-+-+-++---+--++++--+---++-+-+-+--', '+---++-++--+-+-++----++-+-+--++-++---', + '+++++-+-----++----++----++-----+-++++', '+--+++-+-----+----++----+-----+-+++--'), + 39: ('+++--+-+-----+--++----++--+-----+-+--++', '+++--++-+---+-+--+----+--+-+---+-++--++', + '++++---+--++----+-+--+-+----++--+---+++', '+---++-+-+-----+++-++-+++-----+-+-++---'), + 41: ('++++--+-++++-++--++----++--++-++++-+--+++', '++++--+-++++-++--++----++--++-++++-+--+++', + '+++-++-+-+-+-----+++--+++-----+-+-+-++-++', '+--+--+-+-+-+++++---++---+++++-+-+-+--+--'), + 43: ('++---++++-+--+--++--------++--+--+-++++---+', '+++-+-++--+-+-++++-+----+-++++-+-+--++-+-++', + '++-++++++----+-+--++-++-++--+-+----++++++-+', '+---++--++++-+-+++-++--++-+++-+-++++--++---'), + 45: ('+++++-++----+-++--++-++-++--++-+----++-++++', '+++---++--+-+-+-++--------++-+-+-+--++---++', + '++-+-++++-+--+--+++--++--+++--+--+-++++-+-+', '+-++-----++++-+-+++-++++-+++-+-++++-----++-'), + 49: ('++++-++-+---++-+++---++-++-++---+++-++---+-++-+++', '++++-++-+---++-+++---++-++-++---+++-++---+-++-+++', + '+----+-++++--+-+++-+-+++--+++-+-+++-+--++++-+----', '+++++-+----++-+---+-+---++---+-+---+-++----+-++++'), + 51: ('+---+++-++-+-+++--+++++--++--+++++--+++-+-++-+++---', '----+++-++-+-+++--+++++--++--+++++--+++-+-++-+++---', + '-+--+----+-+++-+-+++++--+--+--+++++-+-+++-+----+--+', '-+--+----+-+++-+-+++++--+--+--+++++-+-+++-+----+--+'), + 55: ('+-+--+-+-++--+-+++++-+++--++++--+++-+++++-+--++-+-+--+-', '--+--+-+-++--+-+++++-+++--++++--+++-+++++-+--++-+-+--+-', + '+++----++-++--++----+-+-++++++++-+-+----++--++-++----++', '+++----++-++--++----+-+-++++++++-+-+----++--++-++----++'), + 57: ('+---++-+--++++-+++-++---+-++++++-+---++-+++-++++--+-++---', '----++-+--++++-+++-++---+-++++++-+---++-+++-++++--+-++---', + '--+-+-+++--+--+-++---+++++-++++-+++++---++-+--+--+++-+-+-', '--+-+-+++--+--+-++---+++++-++++-+++++---++-+--+--+++-+-+-'), + 61: ('++--+--++--+-+-++++--+-----+------+-----+--++++-+-+--++--+--+', '++--+--++--+-+-++++--+-----+------+-----+--++++-+-+--++--+--+', + '+---+-+-++++---++--+-++-+---++++++---+-++-+--++---++++-+-+---', '++++-+-+----+++--++-+--+-+++------+++-+--+-++--+++----+-+-+++'), + 63: ('++-+++--++-++--+--+-++-+-+++--------+++-+-++-+--+--++-++--+++-+', '-+-+++--++-++--+--+-++-+-+++--------+++-+-++-+--+--++-++--+++-+', + '++++-++-+-++++-+---+---+++---++++++---+++---+---+-++++-+-++-+++', '++++-++-+-++++-+---+---+++---++++++---+++---+---+-++++-+-++-+++'), } + 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 @@ -406,10 +591,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 @@ -738,6 +923,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`. 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]_). + + 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]_. @@ -1133,7 +1404,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: @@ -1180,19 +1451,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)]) @@ -1501,6 +1772,14 @@ 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 + M = hadamard_matrix_from_sds(n, check=False) elif hadamard_matrix_spence_construction(n, existence=True): if existence: return True @@ -2261,7 +2540,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. 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 59fd68b252c..b503cf5e21c 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -4293,7 +4293,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]) @@ -4409,7 +4409,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 f59f4fd951f..93990ee7cbc 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/posets/linear_extensions.py b/src/sage/combinat/posets/linear_extensions.py index 48ca0af51d5..e14be386228 100644 --- a/src/sage/combinat/posets/linear_extensions.py +++ b/src/sage/combinat/posets/linear_extensions.py @@ -255,15 +255,15 @@ def is_greedy(self): def is_supergreedy(self): r""" Return ``True`` if the linear extension is supergreedy. - - A linear extension `[x_1= 0 for c in x.coefficients(sparse=False)) else: return x >= 0 diff --git a/src/sage/combinat/subset.py b/src/sage/combinat/subset.py index 5cae6958510..9505b1d4ced 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..ae5e93682cc 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/crypto/boolean_function.pyx b/src/sage/crypto/boolean_function.pyx index d10c0c11546..13b8a1c82a1 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.rings.polynomial.polynomial_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< +# +# 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 ListOfPairs: + 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: + """ + Return a pointer to a pair of the list corresponding to the ``index``. + """ + 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.list_of_pairs import ListOfPairs + sage: l = ListOfPairs() + 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) + 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.list_of_pairs import ListOfPairs + sage: l = ListOfPairs() + 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.first = first + pair_pt.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.first = first + last.second = second 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/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 a669cb153af..5068c8e10e3 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 865fa9299f2..1967d71cc2d 100644 --- a/src/sage/geometry/cone.py +++ b/src/sage/geometry/cone.py @@ -6370,7 +6370,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 3a043815a38..8829815c84f 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..42f0edb9a4c 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/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 55bd007f234..34fd41a184c 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 @@ -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/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/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/geometry/polyhedron/combinatorial_polyhedron/base.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd index 7c2a14192da..494213512ff 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.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): @@ -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 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) 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 + ListOfPairs 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 de5f26d951f..833455f1371 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 @@ -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 @@ -103,7 +103,7 @@ from .conversions cimport Vrep_list_to_bit_rep from sage.misc.cachefunc import cached_method 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 +347,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 +569,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""" @@ -1243,15 +1231,10 @@ cdef class CombinatorialPolyhedron(SageObject): def f(size_t i): 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)) - - def vertex_two(size_t i): - return f(self._get_edge(self._edges, i, 1)) - cdef size_t j - return tuple((vertex_one(j), vertex_two(j)) for j in range(self._n_edges)) + 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""" @@ -1339,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, a, b + cdef size_t i, first, second 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): + 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 @@ -1459,7 +1442,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 +1452,16 @@ 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 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)) + ((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((facet_one(i), facet_two(i)) - for i in range(n_ridges)) + return tuple( + (f(self._ridges.get(i).first), + f(self._ridges.get(i).second)) + for i in range (n_ridges)) @cached_method def facet_adjacency_matrix(self, algorithm=None): @@ -1529,14 +1505,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 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): + 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 @@ -2123,7 +2099,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. @@ -2234,7 +2210,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. @@ -2911,23 +2887,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 +3526,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 +3564,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 ListOfPairs edges = ListOfPairs() 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 +3594,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 +3620,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: + ListOfPairs 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 +3668,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 +3680,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,135 +3703,49 @@ 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 ListOfPairs incidences = ListOfPairs() if all_faces is None: raise ValueError("could not determine a list of all faces") 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 - 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) - - sig_check() - - # Increase dimensions. - dimension_one += 1 - dimension_two = dimension_one - 1 + second += already_seen_next + first += already_seen + incidences.add(first, second) - # 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) + sig_check() - 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""" 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 38e1f8d5751..5c43247dd66 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 @@ -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 - Issue :trac:`22003` is fixed: + Issue :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/affine_gps/group_element.py b/src/sage/groups/affine_gps/group_element.py index cb5fd945239..127c1d33cb5 100644 --- a/src/sage/groups/affine_gps/group_element.py +++ b/src/sage/groups/affine_gps/group_element.py @@ -414,13 +414,13 @@ def __call__(self, v): if v in parent.vector_space(): return self._A*v + self._b - from sage.rings.polynomial.polynomial_element import is_Polynomial - if is_Polynomial(v) and parent.degree() == 1: + 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.rings.polynomial.multi_polynomial import is_MPolynomial - if is_MPolynomial(v) and parent.degree() == v.parent().ngens(): + 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 image_coords = self._A * vector(ring, ring.gens()) + self._b diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index a7372d3d77e..be5e3372d6f 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_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index c08442d60af..d81fe55f5e7 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -122,8 +122,8 @@ from cypari2.gen cimport Gen from sage.ext.stdsage cimport HAS_DICTIONARY from sage.rings.all import ZZ, Integer -from sage.rings.polynomial.polynomial_element import is_Polynomial -from sage.rings.polynomial.multi_polynomial import is_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 @@ -1233,12 +1233,12 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): """ if not self_on_left: left = x - if is_Polynomial(left): + if isinstance(left, Polynomial): if self != 1: 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/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 0381e1912a1..4fb4a094244 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -635,7 +635,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 0f322f76e91..e992bea8e3a 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 ae1b656b77f..bf6142f1150 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 e195936bc2d..92a67285d9c 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 3e8541a14ad..47e2c0c6561 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/knots/knotinfo.py b/src/sage/knots/knotinfo.py index cefd33aebb4..8233b55077f 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:: diff --git a/src/sage/libs/symmetrica/sb.pxi b/src/sage/libs/symmetrica/sb.pxi index 9bde08effa7..b884d33dafd 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/libs/symmetrica/symmetrica.pxi b/src/sage/libs/symmetrica/symmetrica.pxi index 7244b7e9655..95f9e52fbda 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 @@ -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 - is_MPolynomial = sage.rings.polynomial.multi_polynomial_element.is_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 689760625da..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.rings.polynomial.polynomial_element import is_Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing @@ -795,7 +795,7 @@ def _element_constructor_(self, x, **kwargs): Characteristic cohomology class pontr(TM) of the Tangent bundle TM over the 8-dimensional differentiable manifold M """ - if isinstance(x, (str, Expression)) or is_Polynomial(x): + if isinstance(x, (str, Expression)) or isinstance(x, Polynomial): return self._build_element(x, **kwargs) R = self.base_ring() @@ -983,7 +983,7 @@ def _build_element(self, *args, **kwargs): val = P(val.taylor(x, 0, pow_range)) # turn polynomial into a characteristic cohomology class via sequences - if is_Polynomial(val): + if isinstance(val, Polynomial): if class_type is None: raise TypeError(f'class_type must be stated if {val} ' f'is a polynomial') 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 = [] 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/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 7fd2108e4d2..3f022f421ee 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -105,6 +105,13 @@ from sage.matrix.matrix_misc import permanental_minor_polynomial # used to deprecate only adjoint method from sage.misc.superseded import deprecated_function_alias +# temporary hack to silence the warnings from #34806 +from sage.rings.number_field.order import Order as NumberFieldOrder +def ideal_or_fractional(R, *args): + if isinstance(R, NumberFieldOrder): + R = R.number_field() + return R.ideal(*args) + _Fields = Fields() cdef class Matrix(Matrix1): @@ -15993,7 +16000,7 @@ cdef class Matrix(Matrix1): try: for i in xrange(1, len(pivs)): y = a[i][pivs[i]] - I = R.ideal(y) + I = ideal_or_fractional(R, y) s = a[0][pivs[i]] t = I.small_residue(s) v = R( (s-t) / y) @@ -17730,9 +17737,9 @@ def _smith_diag(d, transformation=True): else: left = right = None for i in xrange(n): - I = R.ideal(dp[i,i]) + I = ideal_or_fractional(R, dp[i,i]) - if I == R.unit_ideal(): + if I == ideal_or_fractional(R, 1): if dp[i,i] != 1: if transformation: left.add_multiple_of_row(i,i,R(R(1)/(dp[i,i])) - 1) @@ -17741,12 +17748,12 @@ def _smith_diag(d, transformation=True): for j in xrange(i+1,n): if dp[j,j] not in I: - t = R.ideal([dp[i,i], dp[j,j]]).gens_reduced() + t = ideal_or_fractional(R, [dp[i,i], dp[j,j]]).gens_reduced() if len(t) > 1: raise ArithmeticError t = t[0] # find lambda, mu such that lambda*d[i,i] + mu*d[j,j] = t - lamb = R(dp[i,i]/t).inverse_mod( R.ideal(dp[j,j]/t)) + lamb = R(dp[i,i]/t).inverse_mod( ideal_or_fractional(R, dp[j,j]/t)) mu = R((t - lamb*dp[i,i]) / dp[j,j]) newlmat = dp.new_matrix(dp.nrows(), dp.nrows(), 1) @@ -17821,18 +17828,15 @@ def _generic_clear_column(m): # [e,f] # is invertible over R - if a[0,0] != 0: - I = R.ideal(a[0, 0]) # need to make sure we change this when a[0,0] changes - else: - I = R.zero_ideal() + I = ideal_or_fractional(R, a[0, 0]) # need to make sure we change this when a[0,0] changes for k in xrange(1, a.nrows()): if a[k,0] not in I: try: - v = R.ideal(a[0,0], a[k,0]).gens_reduced() + v = ideal_or_fractional(R, a[0,0], a[k,0]).gens_reduced() except Exception as msg: raise ArithmeticError("%s\nCan't create ideal on %s and %s" % (msg, a[0,0], a[k,0])) if len(v) > 1: - raise ArithmeticError("Ideal %s not principal" % R.ideal(a[0,0], a[k,0])) + raise ArithmeticError("Ideal %s not principal" % ideal_or_fractional(R, a[0,0], a[k,0])) B = v[0] # now we find c,d, using the fact that c * (a_{0,0}/B) - d * @@ -17841,7 +17845,7 @@ def _generic_clear_column(m): # need to handle carefully the case when a_{k,0}/B is a unit, i.e. a_{k,0} divides # a_{0,0}. - c = R(a[0,0] / B).inverse_mod(R.ideal(a[k,0] / B)) + c = R(a[0,0] / B).inverse_mod(ideal_or_fractional(R, a[k,0] / B)) d = R( (c*a[0,0] - B)/(a[k,0]) ) # sanity check @@ -17850,7 +17854,7 @@ def _generic_clear_column(m): # now we find e,f such that e*d + c*f = 1 in the same way if c != 0: - e = d.inverse_mod( R.ideal(c) ) + e = d.inverse_mod( ideal_or_fractional(R, c) ) f = R((1 - d*e)/c) else: e = R(-a[k,0]/B) # here d is a unit and this is just 1/d @@ -17866,7 +17870,7 @@ def _generic_clear_column(m): if newlmat.det() != 1: raise ArithmeticError a = newlmat*a - I = R.ideal(a[0,0]) + I = ideal_or_fractional(R, a[0,0]) left_mat = newlmat*left_mat if left_mat * m != a: raise ArithmeticError 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 ebfa844586b..ef22aebb242 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 9419c145ad8..6e0e2abde37 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 6bebd478e60..f263f53bad3 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/dirichlet.py b/src/sage/modular/dirichlet.py index 7efa9d8553f..47f116abdd6 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -2339,6 +2339,7 @@ class DirichletGroupFactory(UniqueFactory): sage: parent(val) Gaussian Integers in Cyclotomic Field of order 4 and degree 2 sage: r4.residue_field(r4.ideal(29).factor()[0][0])(val) + doctest:warning ... DeprecationWarning: ... 17 sage: r4.residue_field(r4.ideal(29).factor()[0][0])(val) * GF(29)(3) 22 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/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) diff --git a/src/sage/modular/modsym/ambient.py b/src/sage/modular/modsym/ambient.py index 627f1c567e7..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.rings.polynomial.multi_polynomial import is_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 @@ -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/modules/fg_pid/fgp_element.py b/src/sage/modules/fg_pid/fgp_element.py index faf255b2ded..dbaeaf8c3e0 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 45a6699a0cc..aba3df2211c 100644 --- a/src/sage/modules/torsion_quadratic_module.py +++ b/src/sage/modules/torsion_quadratic_module.py @@ -852,7 +852,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..ff24627e81e 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/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py index e7cd7086f09..6eebe6f0c46 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.rings.polynomial.multi_polynomial 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/quadratic_forms/constructions.py b/src/sage/quadratic_forms/constructions.py index dd9f83216d3..89fca1bed6e 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.rings.polynomial.polynomial_element import is_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 @@ -46,7 +46,7 @@ def BezoutianQuadraticForm(f, g): """ # Check that f and g are polynomials with a common base ring - if not is_Polynomial(f) or not is_Polynomial(g): + if not isinstance(f, Polynomial) or not isinstance(g, Polynomial): raise TypeError("one of your inputs is not a polynomial") if f.base_ring() != g.base_ring(): # TO DO: Change this to allow coercion! raise TypeError("these polynomials are not defined over the same coefficient ring") 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/quadratic_forms/quadratic_form.py b/src/sage/quadratic_forms/quadratic_form.py index f53602f49f3..47f3d8be23e 100644 --- a/src/sage/quadratic_forms/quadratic_form.py +++ b/src/sage/quadratic_forms/quadratic_form.py @@ -32,8 +32,8 @@ from sage.rings.ring import is_Ring, PrincipalIdealDomain from sage.structure.element import is_Vector from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.polynomial.polynomial_element import is_Polynomial -from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.rings.polynomial.polynomial_element import Polynomial +from sage.rings.polynomial.multi_polynomial import MPolynomial from sage.modules.free_module_element import vector from sage.quadratic_forms.genera.genus import genera from sage.quadratic_forms.quadratic_form__evaluate import QFEvaluateVector, QFEvaluateMatrix @@ -576,9 +576,9 @@ def __init__(self, R, n=None, entries=None, unsafe_initialization=False, number_ M_ring = M.base_ring() matrix_init_flag = True - elif is_Polynomial(R) or is_MPolynomial(R): + elif isinstance(R, (Polynomial, MPolynomial)): p = R - + if not p.is_zero() and not (p.is_homogeneous() and p.degree() == 2): raise ValueError("polynomial is neither zero nor homogeneous of degree 2") diff --git a/src/sage/quivers/morphism.py b/src/sage/quivers/morphism.py index b8bb25657eb..fc08e35ba60 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/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index 109ce513b61..16065f47966 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -1004,8 +1004,8 @@ 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.rings.polynomial.polynomial_element import is_Polynomial - sage: is_Polynomial(e.polynomial()) + sage: from sage.rings.polynomial.polynomial_element import Polynomial + sage: isinstance(e.polynomial(), Polynomial) True sage: e.polynomial('x') diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index 285412aa5bd..8a2d0ce94fa 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.rings.polynomial.polynomial_element import is_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 @@ -1532,7 +1532,7 @@ cdef class FiniteField(Field): E = GF((self.characteristic(), modulus), name=name, **kwds) elif isinstance(modulus, (list, tuple)): E = GF((self.characteristic(), len(modulus) - 1), name=name, modulus=modulus, **kwds) - elif is_Polynomial(modulus): + elif isinstance(modulus, Polynomial): if modulus.change_ring(self).is_irreducible(): E = GF((self.characteristic(), modulus.degree()), name=name, modulus=modulus, **kwds) else: diff --git a/src/sage/rings/finite_rings/finite_field_constructor.py b/src/sage/rings/finite_rings/finite_field_constructor.py index e42b2bed2dd..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.rings.polynomial.polynomial_element import Polynomial from sage.rings.integer import Integer # the import below is just a redirection @@ -641,7 +641,7 @@ def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, else: self._modulus_cache[order][modulus] = modulus = R.irreducible_element(n, algorithm=modulus) else: - if sage.rings.polynomial.polynomial_element.is_Polynomial(modulus): + if isinstance(modulus, Polynomial): modulus = modulus.change_variable_name('x') modulus = R(modulus).monic() diff --git a/src/sage/rings/finite_rings/finite_field_givaro.py b/src/sage/rings/finite_rings/finite_field_givaro.py index b67df86bb30..0a0aa9a8d67 100644 --- a/src/sage/rings/finite_rings/finite_field_givaro.py +++ b/src/sage/rings/finite_rings/finite_field_givaro.py @@ -137,8 +137,8 @@ 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.rings.polynomial.polynomial_element import is_Polynomial - if not is_Polynomial(modulus): + from sage.rings.polynomial.polynomial_element import Polynomial + if not isinstance(modulus, Polynomial): raise TypeError("modulus must be a polynomial") self._cache = Cache_givaro(self, p, k, modulus, repr, cache) 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 82004502674..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,8 +138,8 @@ 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.rings.polynomial.polynomial_element import is_Polynomial - if not is_Polynomial(modulus): + from sage.rings.polynomial.polynomial_element import Polynomial + if not isinstance(modulus, Polynomial): raise TypeError("modulus must be a polynomial") self._cache = Cache_ntl_gf2e(self, k, modulus) diff --git a/src/sage/rings/finite_rings/residue_field.pyx b/src/sage/rings/finite_rings/residue_field.pyx index 83436bd6582..53ec78e057a 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.rings.polynomial.polynomial_element import is_Polynomial +from sage.rings.polynomial.polynomial_element import Polynomial from sage.structure.factory import UniqueFactory from sage.structure.element cimport parent @@ -300,7 +300,7 @@ class ResidueFieldFactory(UniqueFactory): p = p.parent().ring_of_integers().ideal(p) else: p = p.parent().ideal(p) - elif is_Polynomial(p): + elif isinstance(p, Polynomial): p = p.parent().ideal(p) #elif isinstance(p.parent(), FractionField_1poly_field): # p = p.parent().ring_of_integers().ideal(p) diff --git a/src/sage/rings/function_field/all.py b/src/sage/rings/function_field/all.py index b6bba3369c3..5557bbb349f 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.drinfeld_module import DrinfeldModule diff --git a/src/sage/rings/polynomial/__init__.py b/src/sage/rings/function_field/drinfeld_modules/__init__.py similarity index 100% rename from src/sage/rings/polynomial/__init__.py rename to src/sage/rings/function_field/drinfeld_modules/__init__.py 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..32a00f9ea71 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/action.py @@ -0,0 +1,194 @@ +r""" +The 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.misc.latex import latex +from sage.rings.function_field.drinfeld_modules.drinfeld_module import DrinfeldModule + + +class DrinfeldModuleAction(Action): + r""" + This class implements the module action induced by a Drinfeld + `\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 + function ring element; the action is defined as `(a, x) \mapsto + \phi_a(x)`. + + .. NOTE:: + + In this implementation, `L` is `K`. + + .. 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 + + EXAMPLES:: + + sage: Fq. = GF(11) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + 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 + + The action on elements is computed as follows:: + + sage: P = T + 1 + sage: a = z + sage: action(P, a) + ... + 4*z + 2 + sage: action(0, K.random_element()) + 0 + sage: action(A.random_element(), 0) + 0 + + 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): + """ + Initialize ``self``. + + INPUT: the Drinfeld module + + TESTS:: + + sage: Fq. = GF(11) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) + sage: action = phi.action() + sage: action._drinfeld_module is phi + True + 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 = drinfeld_module.base() + super().__init__(drinfeld_module.function_ring(), self._base) + + def _act_(self, pol, x): + r""" + Return the action of ``pol`` on ``x``. + + INPUT: + + - ``pol`` -- a function ring element + - ``x`` -- an element in the field acted upon + + OUTPUT: an element in the base codomain + + EXAMPLES:: + + sage: Fq. = GF(11) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) + sage: action = phi.action() + sage: P = T + 1 + sage: a = z + sage: action(P, a) + 4*z + 2 + sage: action(0, K.random_element()) + 0 + sage: action(A.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._base: + raise TypeError('second input must be in the field acted upon') + 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: A. = Fq[] + sage: K. = Fq.extension(2) + 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{ }}\phi: T \mapsto t^{3} + z + """ + return f'\\text{{Action{{ }}on{{ }}}}' \ + f'{latex(self._base)}\\text{{{{ }}' \ + f'induced{{ }}by{{ }}}}{latex(self._drinfeld_module)}' + + def _repr_(self): + r""" + Return a string representation of the action. + + OUTPUT: a string + + EXAMPLES:: + + sage: Fq. = GF(11) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + 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 + """ + return f'Action on {self._base} induced by ' \ + f'{self._drinfeld_module}' + + def drinfeld_module(self): + r""" + Return the Drinfeld module defining the action. + + OUTPUT: a Drinfeld module + + EXAMPLES:: + + sage: Fq. = GF(11) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [z, 0, 0, 1]) + sage: action = phi.action() + sage: action.drinfeld_module() is phi + True + """ + return 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 new file mode 100644 index 00000000000..1a24f09d66d --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/drinfeld_module.py @@ -0,0 +1,1261 @@ +r""" +Drinfeld modules + +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`. + +AUTHORS: + +- Antoine Leudière (2022-04) +- Xavier Caruso (2022-06) +""" + +# ***************************************************************************** +# 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.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 +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 + + +class DrinfeldModule(Parent, UniqueRepresentation): + r""" + 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 `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 `\mathrm{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. + + .. 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*. 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 + extension of `\mathbb{F}_q`:: + + sage: Fq = GF(25) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z, 4, 1]) + sage: phi + Drinfeld module defined by T |--> t^2 + 4*t + z + + :: + + sage: Fq = GF(49) + sage: A. = Fq[] + sage: K = Frac(A) + sage: psi = DrinfeldModule(A, [K(T), T+1]) + sage: psi + Drinfeld module defined by T |--> (T + 1)*t + T + + .. NOTE:: + + Finite Drinfeld modules are implemented in the class + :class:`sage.rings.function_field.drinfeld_modules.finite_drinfeld_module`. + + Classical references on Drinfeld modules include [Gos1998]_, + [Rosen2002]_, [VS06]_ and [Gek1991]_. + + .. NOTE:: + + Drinfeld modules are defined in a larger setting, in which the + 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 + fixed place of `k`. This is out of the scope of this + implementation. + + INPUT: + + - ``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 + + .. RUBRIC:: Construction + + A Drinfeld module object is constructed by giving the function ring + and the generator:: + + sage: Fq. = GF(3^2) + sage: A. = Fq[] + sage: K. = Fq.extension(6) + sage: phi = DrinfeldModule(A, [z, 1, 1]) + sage: phi + Drinfeld module defined by T |--> t^2 + t + z + + .. 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:: + + 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 + + :: + + 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_T = z + t + t^2`. One can also use + regular Ore polynomials:: + + sage: ore_polring = phi.ore_polring() + sage: t = ore_polring.gen() + sage: rho_T = z + t^3 + sage: rho = DrinfeldModule(A, rho_T) + sage: rho + Drinfeld module defined by T |--> t^3 + z + sage: rho(T) == rho_T + True + + Images under the Drinfeld module are computed by calling the object:: + + 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) + 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 + + Drinfeld modules have their own category (see class + :class:`sage.categories.drinfeld_modules.DrinfeldModules`):: + + sage: phi.category() + 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() + True + + One can use the category to directly create new objects:: + + sage: cat = phi.category() + sage: cat.object([z, 0, 0, 1]) + Drinfeld module defined by T |--> t^3 + z + + .. RUBRIC:: The base field of a Drinfeld module + + The base field of the Drinfeld module is retrieved using :meth:`base`:: + + sage: phi.base() + Finite Field in z of size 3^12 over its base + + The base morphism is retrieved using :meth:`base_morphism`:: + + sage: phi.base_morphism() + Ring morphism: + 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: T |--> z + + 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() is K + False + + .. RUBRIC:: Getters + + 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 + To: Finite Field in z of size 3^12 over its base + Defn: T |--> z + + :: + + sage: phi.ore_polring() # K{t} + Ore Polynomial Ring in t over Finite Field in z of size 3^12 over its base twisted by Frob^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_T + t^2 + t + z + sage: phi.gen() == phi(T) + True + + :: + + 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 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: T |--> t^2 + t + z + + One can compute the rank and height:: + + sage: phi.rank() + 2 + sage: phi.height() + 1 + + As well as the j-invariant if the rank is two:: + + 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:: + + 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 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(T) 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, rho) + False + sage: 1 in Hom(phi, phi) + True + sage: 0 in Hom(phi, rho) + True + + To create a SageMath object representing the morphism, call the + homset (``hom``):: + + sage: hom = Hom(phi, phi) + sage: frobenius_endomorphism = hom(t^6) + sage: identity_morphism = hom(1) + sage: zero_morphism = hom(0) + sage: frobenius_endomorphism + Endomorphism of Drinfeld module defined by T |--> t^2 + t + z + Defn: t^6 + sage: identity_morphism + Identity morphism of Drinfeld module defined by T |--> t^2 + t + z + sage: zero_morphism + 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`:: + + sage: frobenius_endomorphism.ore_polynomial() + t^6 + sage: identity_morphism.ore_polynomial() + 1 + + One checks if a morphism is an isogeny, endomorphism or + isomorphism:: + + sage: frobenius_endomorphism.is_isogeny() + True + sage: identity_morphism.is_isogeny() + True + sage: zero_morphism.is_isogeny() + False + sage: frobenius_endomorphism.is_isomorphism() + False + sage: identity_morphism.is_isomorphism() + True + sage: zero_morphism.is_isomorphism() + False + + .. RUBRIC:: The Vélu formula + + 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: 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: P in Hom(phi, psi) + True + sage: P * phi(T) == psi(T) * P + 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 + + 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 a + :class:`sage.rings.function_field.drinfeld_modules.action.Action` + object representing the Drinfeld module action. + + .. NOTE:: + + In this implementation, `L` is `K`:: + + 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 + + The action on elements is computed by calling the action object:: + + 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(A.random_element(), 0) + 0 + + .. WARNING:: + + The class ``DrinfeldModuleAction`` may be replaced later on. See + issues #34833 and #34834. + + TESTS: + + The generator must have positive degree:: + + sage: Fq = GF(2) + sage: K. = Fq.extension(2) + sage: A. = Fq[] + sage: DrinfeldModule(A, [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: 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[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: A. = Fq[] + sage: DrinfeldModule(A, [z, QQ(1)]) + Traceback (most recent call last): + ... + ValueError: function ring base must coerce into base field + + :: + + sage: Fq = GF(2) + sage: K. = Fq.extension(2) + sage: A. = Fq[] + sage: DrinfeldModule(A, [1, QQ(1)]) + Traceback (most recent call last): + ... + ValueError: function ring base must coerce into base field + + 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: A. = 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: 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 + + 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: A. = Fq[] + sage: phi = DrinfeldModule(A, [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: A. = Fq[] + sage: phi = DrinfeldModule(A, [1, 1]) + Traceback (most recent call last): + ... + ValueError: function ring base must coerce into base field + + :: + + sage: Fq = K = GF(2) + sage: A. = Fq[] + sage: phi = DrinfeldModule(A, [K(1), 1]) + sage: isinstance(phi.ore_polring(), OrePolynomialRing) + True + """ + + @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`` (default: ``'t'``) -- 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: 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: isinstance(phi, FiniteDrinfeldModule) + True + + :: + + sage: K = Frac(A) + sage: phi = DrinfeldModule(A, [K(T), 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 + # 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[T] + if not isinstance(function_ring, PolynomialRing_general): + 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('function ring base must be a finite field') + + # Check all possible input types for gen + # `gen` is an Ore polynomial: + if isinstance(gen, OrePolynomial): + ore_polring = gen.parent() + # Base ring without morphism structure: + 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_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 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_ring())): + raise ValueError('function ring base must coerce into base field') + + # 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]) + 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 + ore_polring = category.ore_polring() # Sanity cast + gen = ore_polring(gen) + if gen.degree() <= 0: + raise ValueError('generator must have positive degree') + + # 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) + + def __init__(self, gen, category): + """ + Initialize ``self``. + + 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 + + TESTS:: + + 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: gen = [p_root, z12^3, z12^5] + sage: phi = DrinfeldModule(A, gen) + sage: ore_polring = phi.ore_polring() + sage: phi._base == phi.category().base() + True + sage: phi._function_ring == A + True + sage: phi._gen == ore_polring(gen) + True + sage: phi._ore_polring == ore_polring + True + sage: phi._morphism == Hom(A, ore_polring)(phi._gen) + True + + :: + + sage: TestSuite(phi).run() + """ + 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""" + 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`. + + INPUT: + + - ``a`` -- a function ring element + + OUTPUT: an element in the base codomain + + TESTS:: + + 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 = 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 + + :: + + sage: phi(0) + 0 + sage: phi(1) + 1 + sage: phi(T) == phi._gen + True + + :: + + sage: a = A.random_element(5) + sage: phi(a)[0] == phi.category().base()(a) + True + """ + 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()`` + + 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: 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()) + 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. + + TESTS:: + + 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._check_rank_two() + sage: phi = DrinfeldModule(A, [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') + + def _latex_(self): + r""" + Return a LaTeX representation of the Drinfeld module. + + If a representation name is given with meth:`rename`, it is + taken into account for LaTeX representation. + + 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: latex(phi) + \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} + + :: + + sage: phi.rename('phi') + sage: latex(phi) + \phi + sage: phi.reset_name() + """ + 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)}' + + def _repr_(self): + r""" + Return a string representation of this Drinfeld module. + + 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 + 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}' + + 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 __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 + (:class:`sage.rings.function_field.drinfeld_modules.action.Action`) + that represents the module action, on the base codomain, that is + induced by the Drinfeld module. + + OUTPUT: a Drinfeld module action object + + 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: 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 + + The action on elements is computed as follows:: + + 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 + sage: action(0, a) + 0 + sage: action(P, 0) + 0 + """ + from sage.rings.function_field.drinfeld_modules.action import DrinfeldModuleAction + return DrinfeldModuleAction(self) + + def coefficient(self, n): + r""" + Return the `n`-th coefficient of the generator. + + INPUT: + + - ``n`` -- a nonnegative integer + + OUTPUT: an element in the base codomain + + 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.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 flag ``sparse`` is ``True`` (default), only return the + nonzero coefficients; otherwise, return all of them. + + INPUT: + + - ``sparse`` -- a boolean + + 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.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 nonzero coefficients, + unless otherwise specified:: + + 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] + 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.coefficients(sparse=sparse) + + def gen(self): + r""" + Return the generator of the Drinfeld module. + + 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.gen() == phi(T) + True + """ + return self._gen + + def height(self): + r""" + Return the height of the Drinfeld module if the function field + characteristic is a prime ideal; raise ValueError otherwise. + + 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. + + 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.height() == 1 + True + sage: phi.is_ordinary() + True + + :: + + sage: B. = Fq[] + sage: L = Frac(B) + sage: phi = DrinfeldModule(A, [L(2), L(1)]) + sage: phi.height() + Traceback (most recent call last): + ... + NotImplementedError: height not implemented in this case + + :: + + sage: Fq = GF(343) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [1, 0, z6]) + sage: phi.height() + 2 + sage: phi.is_supersingular() + True + + """ + 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 is_finite(self): + r""" + Return ``True`` if this Drinfeld module is finite, + ``False`` otherwise. + + 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.is_finite() + True + sage: B. = Fq[] + sage: L = Frac(B) + sage: psi = DrinfeldModule(A, [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_invariant(self): + r""" + 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_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 field + of the function ring. In our case, this field is always finite. + + OUTPUT: an element in the base codomain + + 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.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(A, [p_root, 1, 1]) + sage: psi.j_invariant() + 1 + sage: rho = DrinfeldModule(A, [p_root, 0, 1]) + sage: rho.j_invariant() + 0 + + The rank must be two:: + + sage: sigma = DrinfeldModule(A, [p_root, 1, 0]) + sage: sigma.j_invariant() + Traceback (most recent call last): + ... + NotImplementedError: rank must be 2 + """ + self._check_rank_two() + g = self.coefficient(1) + delta = self.coefficient(2) + 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: 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.morphism() + Ring morphism: + 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: 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 + + Actually, the ``DrinfeldModule`` method :meth:`__call__` simply + class the ``__call__`` method of this morphism:: + + sage: phi.morphism()(T) == phi(T) + True + sage: a = A.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(T) == m.im_gens()[0] + True + """ + return self._morphism + + def rank(self): + r""" + Return the rank of the Drinfeld module. + + In our case, the rank is the degree of the generator. + + OUTPUT: an integer + + 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.rank() + 2 + sage: psi = DrinfeldModule(A, [p_root, 2]) + sage: psi.rank() + 1 + sage: rho = DrinfeldModule(A, [p_root, 0, 0, 0, 1]) + sage: rho.rank() + 4 + """ + return self._gen.degree() + + def velu(self, isog): + r""" + Return a new Drinfeld module such that input is an + isogeny to this module with domain ``self``; if no such isogeny + exists, raise an exception. + + INPUT: + + - ``isog`` -- the Ore polynomial that defines the isogeny + + 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 `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. + + 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. + + 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: 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 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 + + This method works for endomorphisms as well:: + + sage: phi.velu(phi(T)) 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) + 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 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: + raise e + quo, rem = (isog * self.gen()).right_quo_rem(isog) + char_deg = self.characteristic().degree() + if not char_deg.divides(isog.valuation()) \ + or rem != 0: + raise e + else: + return self.category().object(quo) 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..114d1730b9b --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/finite_drinfeld_module.py @@ -0,0 +1,541 @@ +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.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 + + +class FiniteDrinfeldModule(DrinfeldModule): + r""" + 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 + ideal. + + For general definitions and help on Drinfeld modules, see class + :class:`sage.rings.function_fields.drinfeld_module.drinfeld_module.DrinfeldModule`. + + .. RUBRIC:: Construction: + + The user does not ever need to directly call + ``FiniteDrinfeldModule`` --- the metaclass ``DrinfeldModule`` is + responsible for instantiating ``DrinfeldModule`` or + ``FiniteDrinfeldModule`` depending on the input:: + + sage: Fq = GF(343) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [z6, 0, 5]) + sage: phi + Drinfeld module defined by T |--> 5*t^2 + z6 + + :: + + sage: isinstance(phi, DrinfeldModule) + True + sage: from sage.rings.function_field.drinfeld_modules.finite_drinfeld_module import FiniteDrinfeldModule + sage: isinstance(phi, FiniteDrinfeldModule) + True + + 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 + Endomorphism of Drinfeld module defined by T |--> 5*t^2 + z6 + Defn: t^2 + + Its characteristic polynomial can be computed:: + + sage: chi = phi.frobenius_charpoly() + sage: chi + 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(T)) + 0 + + as well as its trace and norm:: + + sage: phi.frobenius_trace() + 6*T + 5*z3^2 + 5*z3 + 6 + sage: phi.frobenius_trace() == -chi[1] + True + sage: phi.frobenius_norm() + 2*T^2 + (z3^2 + z3 + 4)*T + 2*z3 + + We can decide if a Drinfeld module is ordinary or supersingular:: + + sage: phi.is_ordinary() + 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): + """ + 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`` (default: `'t'`) -- the name of the Ore polynomial + ring gen + + TESTS:: + + 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: gen = [p_root, z12^3, z12^5] + sage: phi = DrinfeldModule(A, 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 + # have _frobenius_norm and _frobenius_trace attributes. + super().__init__(gen, category) + self._frobenius_norm = None + self._frobenius_trace = None + + def frobenius_endomorphism(self): + r""" + 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 + defining Ore polynomial is `t^q`. + + EXAMPLES:: + + sage: Fq = GF(343) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [1, 0, z6]) + sage: phi.frobenius_endomorphism() + Endomorphism of Drinfeld module defined by T |--> 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 + """ + t = self.ore_polring().gen() + Fq = self._function_ring.base() + deg = self._base.over(Fq).degree(Fq) + return self._Hom_(self, category=self.category())(t**deg) + + def frobenius_charpoly(self, var='X'): + r""" + Return the characteristic polynomial of the Frobenius + endomorphism if the rank is two. Raise a NotImplementedError + otherwise. + + 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 + 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 = 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 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)`. + + INPUT: + + - ``var`` (default: ``'X'``) -- the name of the second variable + + EXAMPLES:: + + sage: Fq = GF(343) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [1, 0, z6]) + sage: chi = phi.frobenius_charpoly() + sage: chi + 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 + + :: + + sage: frob_pol = phi.frobenius_endomorphism().ore_polynomial() + sage: chi(frob_pol, phi(T)) + 0 + + :: + + 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 of the base field over Fq + sage: trace.degree() <= n/2 + True + sage: norm.degree() == n + True + + ALGORITHM: + + We compute the Frobenius norm, and with it the Frobenius + trace. This gives the Frobenius characteristic polynomial. + See [MS2019]_, Section 4. + + See docstrings of methods :meth:`frobenius_norm` and + :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][X] + # Does not work when Fq is not a prime field... + # chi = self._gen.reduced_charpoly() + # return -chi(A.gen(), S.gen()) + 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. + + 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(T) \in \mathbb{F}_q[T]`. + + Let `n` be the degree of the base field over `\mathbb{F}_q` Then the + Frobenius norm has degree `n`. + + EXAMPLES:: + + sage: Fq = GF(343) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [1, 0, z6]) + sage: B = phi.frobenius_norm() + sage: B + (5*z3^2 + 2*z3)*T^2 + (4*z3^2 + 3*z3)*T + 5*z3^2 + 2*z3 + + :: + + sage: n = 2 # Degree of the base field 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 [MS2019]_, Section 4, Proposition 3. + """ + self._check_rank_two() + L = self._base.over(self._Fq) + # Notations from Schost-Musleh: + if self._frobenius_norm is None: + n = L.degree_over(self._Fq) + d = self.characteristic().degree() + m = n // d + delta = self._gen[2] + norm = L(delta).norm() + char = self.characteristic() + self._frobenius_norm = ((-1)**n) * (char**m) / norm + 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. + + 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 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 at most `\frac{n}{2}`. + + EXAMPLES:: + + sage: Fq = GF(343) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [1, 0, z6]) + sage: A = phi.frobenius_trace() + sage: A + (4*z3^2 + 6*z3 + 3)*T + 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 + + 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: + if self._frobenius_trace is None: + 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)) + 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 + + 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_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 + 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 + NotImplementedError if the rank is not two. + + A rank two finite Drinfeld module is *ordinary* if and only if + 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. + + A rank two Drinfeld module is *ordinary* if and only if it is + not supersingular; see :meth:`is_supersingular`. + + EXAMPLES:: + + sage: Fq = GF(343) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [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 + `\mathbb{F}_q[T]` characteristic divides it. + + We could also test if the image of the + `\mathbb{F}_q[T]`-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`` 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 function ring-characteristic divides the Frobenius + trace. An *ordinary* rank two finite Drinfeld module is a + Drinfeld module that is not supersingular. + + EXAMPLES:: + + sage: Fq = GF(343) + sage: A. = Fq[] + sage: K. = Fq.extension(2) + sage: phi = DrinfeldModule(A, [1, 0, z6]) + sage: phi.is_supersingular() + True + sage: phi(phi.characteristic()) # Purely inseparable + z6*t^2 + + ALGORITHM: + + Compute the Frobenius trace and test if the function + ring-characteristic divides it. + + 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() + return self.characteristic().divides(self.frobenius_trace()) diff --git a/src/sage/rings/function_field/drinfeld_modules/homset.py b/src/sage/rings/function_field/drinfeld_modules/homset.py new file mode 100644 index 00000000000..84fdc4c6e14 --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/homset.py @@ -0,0 +1,307 @@ +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.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 implements the set of morphisms between two Drinfeld + `\mathbb{F}_q[T]`-modules. + + INPUT: + + - ``X`` -- the domain + + - ``Y`` -- the codomain + + EXAMPLES:: + + sage: Fq = GF(27) + sage: A. = Fq[] + 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: 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(H, DrinfeldModuleHomset) + True + + There is a simpler syntax for endomorphisms sets:: + + 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: E is Hom(phi, phi) + True + + The domain and codomain must have the same Drinfeld modules + category:: + + sage: rho = DrinfeldModule(A, [Frac(A)(T), 1]) + sage: Hom(phi, rho) + Traceback (most recent call last): + ... + ValueError: Drinfeld modules must be in the same category + + :: + + sage: sigma = DrinfeldModule(A, [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: identity_morphism = E(1) + sage: identity_morphism + 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 + 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: 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:: + + sage: 1 in H + False + sage: t^6 in H + False + sage: t + 1 in H + True + sage: 1 in E + True + sage: t^6 in E + True + sage: t + 1 in E + False + + This also works if the candidate is a morphism object:: + + sage: isogeny in H + True + sage: E(0) in E + True + sage: identity_morphism in H + False + sage: frobenius_endomorphism in H + False + """ + + Element = DrinfeldModuleMorphism + __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: A. = Fq[] + 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: H = Hom(phi, psi) + sage: H.domain() is phi + True + sage: H.codomain() is psi + True + """ + if category is None: + category = X.category() + if check: + if X.category() != Y.category() \ + or not isinstance(X.category(), DrinfeldModules): + raise ValueError('Drinfeld modules must be in the same ' + 'category') + if category != X.category(): + raise ValueError('category should be DrinfeldModules') + base = category.base() + super().__init__(X, Y, category=category, base=base, check=check) + + def _latex_(self): + r""" + Return a LaTeX representation of the homset. + + EXAMPLES:: + + sage: Fq = GF(27) + sage: A. = Fq[] + 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: 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' \ + f'{{ }}from{{ }}(gen){{ }}}}{latex(self.domain().gen())}' \ + f'\\text{{{{ }}to{{ }}(gen){{ }}}}'\ + f'{latex(self.codomain().gen())}' + + def _repr_(self): + r""" + Return a string representation of the homset. + + EXAMPLES:: + + sage: Fq = GF(27) + sage: A. = Fq[] + 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: 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) '\ + f'{self.domain().gen()} to (gen) {self.codomain().gen()}' + + def __contains__(self, x): + r""" + Return ``True`` if the input defines a morphism in the homset. + + INPUT: + + - ``x`` -- an Ore polynomial or a Drinfeld module morphism + + EXAMPLES: + + In the next examples, the input is an Ore polynomial:: + + sage: Fq = GF(27) + sage: A. = Fq[] + 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: H = Hom(phi, psi) + sage: E = End(phi) + sage: t = phi.ore_polring().gen() + sage: 1 in H + False + sage: t^6 in H + False + sage: t + 1 in H + True + sage: 1 in E + True + sage: t^6 in E + True + sage: t + 1 in E + False + + Whereas the input is now a Drinfeld module morphism:: + + sage: isogeny = H(t + 1) + sage: isogeny in H + True + sage: E(0) in E + True + sage: identity_morphism = E(1) + sage: identity_morphism in H + False + sage: frobenius_endomorphism = phi.frobenius_endomorphism() + sage: frobenius_endomorphism in H + False + """ + try: + x = self(x) + return True + except (AttributeError, ValueError, TypeError): + return False + + def _element_constructor_(self, *args, **kwds): + r""" + Return the Drinfeld module morphism defined by the given Ore + polynomial. + + INPUT: an Ore polynomial + + EXAMPLES:: + + sage: Fq = GF(27) + sage: A. = Fq[] + 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: H = Hom(phi, psi) + sage: E = End(phi) + sage: t = phi.ore_polring().gen() + sage: identity_morphism = E(1) + sage: identity_morphism + Identity morphism of Drinfeld module defined by T |--> 2*t^2 + z6*t + z6 + + :: + + sage: frobenius_endomorphism = E(t^6) + sage: frobenius_endomorphism + 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: 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 + # 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 new file mode 100644 index 00000000000..dab86c43efa --- /dev/null +++ b/src/sage/rings/function_field/drinfeld_modules/morphism.py @@ -0,0 +1,406 @@ +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 +# +# 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 sage.categories.morphism import Morphism +from sage.structure.unique_representation import UniqueRepresentation + + +class DrinfeldModuleMorphism(Morphism, UniqueRepresentation, + metaclass=InheritComparisonClasscallMetaclass): + r""" + 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 + 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 homset with the defining Ore polynomial:: + + sage: Fq = GF(4) + sage: A. = Fq[] + 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 + 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: 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:: + + sage: morphism = Hom(phi, psi)(1) + Traceback (most recent call last): + ... + ValueError: Ore polynomial does not define a morphism + + One can get basic data on the morphism:: + + sage: morphism.domain() + 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 |--> (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 + z^5 + z^3 + z + 1 + sage: morphism.ore_polynomial() is ore_pol + True + + One can check various properties:: + + sage: morphism.is_zero() + False + sage: morphism.is_isogeny() + True + sage: morphism.is_endomorphism() + False + 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 + + 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: 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 + """ + + @staticmethod + def __classcall_private__(cls, parent, x): + """ + Create the morphism. + + INPUT: + + - ``cls`` -- the class ``DrinfeldModuleMorphism`` + + - ``parent`` -- the Drinfeld module homset + + - ``x`` -- the Ore polynomial defining the morphism or a + DrinfeldModuleMorphism + + TESTS:: + + 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: 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') + domain = parent.domain() + codomain = parent.codomain() + # 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('Ore polynomial does not define a morphism') + 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: 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: 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() + self._ore_polynomial = ore_pol + + def _latex_(self): + r""" + Return a LaTeX representation of the morphism. + + 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: latex(morphism) + t + z_{6}^{5} + z_{6}^{2} + 1 + """ + return f'{latex(self._ore_polynomial)}' + + def _repr_(self): + r""" + Return a string representation of the morphism. + + 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: morphism + Drinfeld Module morphism: + 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 + """ + 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 __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. + + 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: morphism.is_zero() + False + + :: + + sage: zero_morphism = End(phi)(0) + sage: zero_morphism.is_zero() + True + """ + 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. + + 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: 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`` whether the morphism is an isomorphism. + + 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: 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: 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: ore_pol = morphism.ore_polynomial() + sage: ore_pol + t + z6^5 + z6^2 + 1 + + :: + + sage: ore_pol * phi(T) == psi(T) * ore_pol + True + """ + return self._ore_polynomial diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index 782aa99cc42..60b834221ba 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1307,8 +1307,8 @@ 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.rings.polynomial.polynomial_element import is_Polynomial - if polynomial.parent().ngens()>1 or not is_Polynomial(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: names = (polynomial.variable_name(), ) diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index 9cad038840c..be921f22154 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -466,8 +466,8 @@ 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.rings.polynomial.multi_polynomial_element import is_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 @@ -500,7 +500,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/lazy_series.py b/src/sage/rings/lazy_series.py index 51eeaf5d065..2e614e225ea 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/bdd_height.py b/src/sage/rings/number_field/bdd_height.py index b7c8c33d0be..d0f37a2b192 100644 --- a/src/sage/rings/number_field/bdd_height.py +++ b/src/sage/rings/number_field/bdd_height.py @@ -430,7 +430,6 @@ def bdd_height(K, height_bound, tolerance=1e-2, precision=53): if B < 1: return embeddings = K.places(prec=precision) - O_K = K.ring_of_integers() r1, r2 = K.signature() r = r1 + r2 - 1 RF = RealField(precision) @@ -486,7 +485,7 @@ def log_height_for_generators_approx(alpha, beta, Lambda): Return a lambda approximation h_K(alpha/beta) """ delta = Lambda / (r + 2) - norm_log = delta_approximation(RR(O_K.ideal(alpha, beta).norm()).log(), delta) + norm_log = delta_approximation(RR(K.ideal(alpha, beta).norm()).log(), delta) log_ga = vector_delta_approximation(log_map(alpha), delta) log_gb = vector_delta_approximation(log_map(beta), delta) arch_sum = sum([max(log_ga[k], log_gb[k]) for k in range(r + 1)]) diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 0d2271844ca..d157026ddf8 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.rings.polynomial.polynomial_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 @@ -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 @@ -12534,7 +12534,7 @@ def map_Zmstar_to_Zm(h): p = u.lift() while not p.is_prime(): p += m - f = R.ideal(p).prime_factors()[0].residue_class_degree() + f = K.fractional_ideal(p).prime_factors()[0].residue_class_degree() h = g**f if h not in H: Hgens += [h] diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 09a51a1264a..1d296d46928 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -169,9 +169,8 @@ def _inverse_mod_generic(elt, I): """ from sage.matrix.constructor import matrix R = elt.parent() - try: - I = R.ideal(I) - except ValueError: + I = R.number_field().fractional_ideal(I) + if not I.is_integral(): raise ValueError("inverse is only defined modulo integral ideals") if I == 0: raise ValueError("inverse is not defined modulo the zero ideal") @@ -1987,6 +1986,9 @@ cdef class NumberFieldElement(FieldElement): raise ArithmeticError("factorization of 0 is not defined") K = self.parent() + from .order import is_NumberFieldOrder + if is_NumberFieldOrder(K): + K = K.number_field() fac = K.ideal(self).factor() # Check whether all prime ideals in `fac` are principal for P,e in fac: @@ -1999,6 +2001,29 @@ cdef class NumberFieldElement(FieldElement): from sage.structure.all import Factorization return Factorization(element_fac, unit=self/element_product) + def is_prime(self): + r""" + Test whether this number-field element is prime as + an algebraic integer. + + Note that the behavior of this method differs from the behavior + of :meth:`~sage.structure.element.RingElement.is_prime` in a + general ring, according to which (number) fields would have no + nonzero prime elements. + + EXAMPLES:: + + sage: K. = NumberField(x^2+1) + sage: (1+i).is_prime() + True + sage: ((1+i)/2).is_prime() + False + """ + if not self or not self.is_integral(): + return False + I = self.number_field().fractional_ideal(self) + return I.is_prime() + @coerce_binop def gcd(self, other): """ @@ -2068,7 +2093,8 @@ cdef class NumberFieldElement(FieldElement): if not is_NumberFieldOrder(R) or not R.is_maximal(): raise NotImplementedError("gcd() for %r is not implemented" % R) - g = R.ideal(self, other).gens_reduced() + K = R.number_field() + g = K.fractional_ideal(self, other).gens_reduced() if len(g) > 1: raise ArithmeticError("ideal (%r, %r) is not principal, gcd is not defined" % (self, other) ) diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index d5f7157217f..8ca1f958039 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -996,16 +996,38 @@ def is_prime(self): False sage: K.ideal(17).is_prime() # ramified False + + TESTS: + + Check that we do not factor the norm of the ideal, this used + to take half an hour, see :trac:`33360`:: + + sage: K. = NumberField([x^2-2,x^2-3,x^2-5]) + sage: t = (((-2611940*c + 1925290/7653)*b - 1537130/7653*c + ....: + 10130950)*a + (1343014/7653*c - 8349770)*b + ....: + 6477058*c - 2801449990/4002519) + sage: t.is_prime() + False """ try: return self._pari_prime is not None except AttributeError: - F = self.factor() # factorization with caching - if len(F) != 1 or F[0][1] != 1: - self._pari_prime = None - else: - self._pari_prime = F[0][0]._pari_prime - return self._pari_prime is not None + pass + + K = self.number_field().pari_nf() + I = self.pari_hnf() + + candidate = K.idealismaximal(I) or None + + # PARI uses probabilistic primality testing inside idealismaximal(). + if get_flag(None, 'arithmetic'): + # proof required, check using isprime() + if candidate and not candidate[0].isprime(): + candidate = None + + self._pari_prime = candidate + + return self._pari_prime is not None def pari_prime(self): r""" 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/order.py b/src/sage/rings/number_field/order.py index f0b0c0f9656..ea8c6d6cfe1 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 @@ -491,6 +491,14 @@ def ideal(self, *args, **kwds): """ if not self.is_maximal(): raise NotImplementedError("ideals of non-maximal orders not yet supported.") + from sage.misc.superseded import deprecation + deprecation(34806, 'In the future, constructing an ideal of the ring of ' + 'integers of a number field will use an implementation ' + 'compatible with ideals of other (non-maximal) orders, ' + 'rather than returning an integral fractional ideal of ' + 'its containing number field. Use .fractional_ideal(), ' + 'together with an .is_integral() check if desired, to ' + 'avoid your code breaking with future changes to Sage.') I = self.number_field().ideal(*args, **kwds) if not I.is_integral(): raise ValueError("ideal must be integral; use fractional_ideal to create a non-integral ideal.") 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 3e396efa8ac..fae673de284 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.rings.polynomial.polynomial_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) @@ -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() @@ -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/lattice_precision.py b/src/sage/rings/padics/lattice_precision.py index 654ba06bfe7..e9966629fdf 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 3930103df75..7963a442f97 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..b93c404f660 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 3518ce86c67..141af863ef5 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_template_element.pxi b/src/sage/rings/padics/padic_template_element.pxi index 712f7dc9eeb..ce9e7029002 100644 --- a/src/sage/rings/padics/padic_template_element.pxi +++ b/src/sage/rings/padics/padic_template_element.pxi @@ -35,6 +35,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.rings.polynomial.polynomial_element import Polynomial from sage.structure.element import canonical_coercion import itertools @@ -155,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/padics/padic_valuation.py b/src/sage/rings/padics/padic_valuation.py index 2d444d0e676..a495435493d 100644 --- a/src/sage/rings/padics/padic_valuation.py +++ b/src/sage/rings/padics/padic_valuation.py @@ -283,7 +283,7 @@ def create_key_and_extra_args_for_number_field_from_ideal(self, R, I, prime): EXAMPLES:: - sage: GaussianIntegers().valuation(GaussianIntegers().ideal(2)) # indirect doctest + sage: GaussianIntegers().valuation(GaussianIntegers().number_field().fractional_ideal(2)) # indirect doctest 2-adic valuation TESTS: @@ -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/all.py b/src/sage/rings/polynomial/all.py index 816db5efe2a..853f422bdc7 100644 --- a/src/sage/rings/polynomial/all.py +++ b/src/sage/rings/polynomial/all.py @@ -1,8 +1,7 @@ """ Polynomials """ - -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2005 William Stein # # Distributed under the terms of the GNU General Public License (GPL) @@ -14,8 +13,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.misc.lazy_import import lazy_import @@ -51,3 +50,7 @@ # Evaluation of cyclotomic polynomials from sage.rings.polynomial.cyclotomic import cyclotomic_value + +# Integer-valued Univariate Polynomial Ring +lazy_import('sage.rings.polynomial.integer_valued_polynomials', + 'IntegerValuedPolynomialRing') 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..dc9f2cab8b7 --- /dev/null +++ b/src/sage/rings/polynomial/commutative_polynomial.pyx @@ -0,0 +1,23 @@ +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`. + + 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 diff --git a/src/sage/rings/polynomial/infinite_polynomial_element.py b/src/sage/rings/polynomial/infinite_polynomial_element.py index 5ac9bd00147..c7d43e31d1b 100644 --- a/src/sage/rings/polynomial/infinite_polynomial_element.py +++ b/src/sage/rings/polynomial/infinite_polynomial_element.py @@ -103,14 +103,16 @@ 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.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass +from sage.structure.element import RingElement +from .commutative_polynomial import CommutativePolynomial +from .multi_polynomial import MPolynomial import copy -def InfinitePolynomial(A, p): +class InfinitePolynomial(CommutativePolynomial, metaclass=InheritComparisonClasscallMetaclass): """ Create an element of a Polynomial Ring with a Countably Infinite Number of Variables. @@ -176,73 +178,72 @@ 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(RingElement): - """ - 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:: + @staticmethod + def __classcall_private__(cls, A, p): + r""" + TESTS:: - 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 + 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))): + 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. @@ -261,7 +262,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) @@ -312,60 +313,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 - - sage: M = matrix([[1,1],[2,0]]) - sage: a(x_1=M) - [x_0 + 1 1] - [ 2 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`. @@ -649,79 +596,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:: @@ -731,7 +605,7 @@ def _rmul_(self, left): 4 """ - return InfinitePolynomial_sparse(self.parent(), left * self._p) + return type(self)(self.parent(), left * self._p) def _lmul_(self, right): """ @@ -742,7 +616,7 @@ def _lmul_(self, right): 4*alpha_3 """ - return InfinitePolynomial_sparse(self.parent(), self._p * right) + return type(self)(self.parent(), self._p * right) def _div_(self, x): r""" @@ -818,235 +692,72 @@ 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 + The coefficient of the leading term of ``self``. - NOTE: + EXAMPLES:: - 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. + 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``. 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.lt() + 3*x_10*x_1^2*y_1^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, 'lt'): + return InfinitePolynomial(self.parent(), self._p.lt()) + if self._p == 0: + return self + if hasattr(self._p, 'variable_name'): # if it is univariate + return InfinitePolynomial(self.parent(), self._p.leading_coefficient()*self._p.parent().gen()**max(self._p.exponents())) + return self # if it is scalar - 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``. - - EXAMPLES:: - - sage: X. = InfinitePolynomialRing(QQ) - sage: p = 2*x[10]*y[30]+3*x[10]*y[1]^3*x[1]^2 - sage: p.lt() - 3*x_10*x_1^2*y_1^3 - - """ - if hasattr(self._p, 'lt'): - return InfinitePolynomial(self.parent(), self._p.lt()) - if self._p == 0: - return self - if hasattr(self._p, 'variable_name'): # if it is univariate - return InfinitePolynomial(self.parent(), self._p.leading_coefficient()*self._p.parent().gen()**max(self._p.exponents())) - return self # if it is scalar - - def tail(self): - """ - The tail of ``self`` (this is ``self`` minus its leading term). + def tail(self): + """ + The tail of ``self`` (this is ``self`` minus its leading term). EXAMPLES:: @@ -1126,7 +837,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'): @@ -1482,8 +1193,325 @@ 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 + + sage: M = matrix([[1,1],[2,0]]) + sage: a(x_1=M) + [x_0 + 1 1] + [ 2 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): + 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): + 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 _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. @@ -1499,8 +1527,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): """ @@ -1524,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) @@ -1608,28 +1634,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:: diff --git a/src/sage/rings/polynomial/infinite_polynomial_ring.py b/src/sage/rings/polynomial/infinite_polynomial_ring.py index ff237743f4a..8e3ba4156e9 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 @@ -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/integer_valued_polynomials.py b/src/sage/rings/polynomial/integer_valued_polynomials.py new file mode 100644 index 00000000000..603cdbf2e38 --- /dev/null +++ b/src/sage/rings/polynomial/integer_valued_polynomials.py @@ -0,0 +1,1133 @@ +# -*- coding: utf-8 -*- +r""" +Integer-valued polynomial rings + +AUTHORS: + +- Frédéric Chapoton (2023-03): Initial version +""" +# *************************************************************************** +# Copyright (C) 2013 Frédéric Chapoton +# +# Distributed under the terms of the GNU General Public License (GPL) +# https://www.gnu.org/licenses/ +# *************************************************************************** +from sage.arith.misc import (binomial, factorial) +from sage.categories.rings import Rings +from sage.categories.all import Algebras +from sage.categories.realizations import Category_realization_of_parent +from sage.combinat.free_module import CombinatorialFreeModule +from sage.matrix.constructor import matrix +from sage.misc.cachefunc import cached_method +from sage.modules.free_module_element import vector +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring import polygen +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ +from sage.sets.non_negative_integers import NonNegativeIntegers +from sage.sets.family import Family +from sage.misc.bindable_class import BindableClass +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent + + +class IntegerValuedPolynomialRing(UniqueRepresentation, Parent): + r""" + The integer-valued polynomial ring over a base ring `R`. + + Integer-valued polynomial rings are commutative and associative + algebras, with a basis indexed by non-negative integers. + + There are two natural bases, made of the sequence + `\binom{x}{n}` for `n \geq 0` (the *binomial basis*) and of + the other sequence `\binom{x+n}{n}` for `n \geq 0` (the *shifted basis*). + + These two bases are available as follows:: + + sage: B = IntegerValuedPolynomialRing(QQ).Binomial() + sage: S = IntegerValuedPolynomialRing(QQ).Shifted() + + or by using the shortcuts:: + + sage: B = IntegerValuedPolynomialRing(QQ).B() + sage: S = IntegerValuedPolynomialRing(QQ).S() + + There is a conversion formula between the two bases: + + .. MATH:: + + \binom{x}{i} = \sum_{k=0}^{i} (-1)^{i-k} \binom{i}{k} \binom{x+k}{k} + + with inverse: + + .. MATH:: + + \binom{x+i}{i} = \sum_{k=0}^{i} \binom{i}{k} \binom{x}{k}. + + REFERENCES: + + - :wikipedia:`Integer-valued polynomial` + + TESTS:: + + sage: IntegerValuedPolynomialRing(24) + Traceback (most recent call last): + ... + TypeError: argument R must be a commutative ring + """ + def __init__(self, R): + """ + TESTS:: + + sage: IV = IntegerValuedPolynomialRing(ZZ) + sage: TestSuite(IV).run() + """ + if R not in Rings().Commutative(): + raise TypeError("argument R must be a commutative ring") + self._base = R + cat = Algebras(R).Commutative().WithBasis() + Parent.__init__(self, base=R, category=cat.WithRealizations()) + + _shorthands = ["B", "S"] + + def _repr_(self) -> str: + r""" + Return the string representation. + + EXAMPLES:: + + sage: IntegerValuedPolynomialRing(QQ) + Integer-Valued Polynomial Ring over Rational Field + """ + br = self.base_ring() + return f"Integer-Valued Polynomial Ring over {br}" + + def a_realization(self): + """ + Return a default realization. + + The Binomial realization is chosen. + + EXAMPLES:: + + sage: IntegerValuedPolynomialRing(QQ).a_realization() + Integer-Valued Polynomial Ring over Rational Field + in the binomial basis + """ + return self.Binomial() + + class Bases(Category_realization_of_parent): + def super_categories(self) -> list: + r""" + Return the super-categories of ``self``. + + EXAMPLES:: + + sage: A = IntegerValuedPolynomialRing(QQ); A + Integer-Valued Polynomial Ring over Rational Field + sage: C = A.Bases(); C + Category of bases of Integer-Valued Polynomial Ring + over Rational Field + sage: C.super_categories() + [Category of realizations of Integer-Valued Polynomial Ring + over Rational Field, + Join of Category of algebras with basis over Rational Field and + Category of filtered algebras over Rational Field and + Category of commutative algebras over Rational Field and + Category of realizations of unital magmas] + """ + A = self.base() + category = Algebras(A.base_ring()).Commutative().Filtered() + return [A.Realizations(), + category.Realizations().WithBasis()] + + class ParentMethods: + def _repr_(self) -> str: + r""" + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(QQ).S() + sage: F # indirect doctest + Integer-Valued Polynomial Ring over Rational Field + in the shifted basis + sage: F = IntegerValuedPolynomialRing(QQ).B() + sage: F # indirect doctest + Integer-Valued Polynomial Ring over Rational Field + in the binomial basis + """ + real = self.realization_of() + return f"{real} in the {self._realization_name()} basis" + + @cached_method + def one_basis(self): + r""" + Return the number 0, which index the unit of this algebra. + + EXAMPLES:: + + sage: A = IntegerValuedPolynomialRing(QQ).S() + sage: A.one_basis() + 0 + sage: A.one() + S[0] + """ + return self.basis().keys()(0) + + def degree_on_basis(self, m): + r""" + Return the degree of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: A = IntegerValuedPolynomialRing(QQ).S() + sage: A.degree_on_basis(4) + 4 + """ + return ZZ(m) + + def from_polynomial(self, p): + """ + Convert a polynomial into the ring of integer-valued polynomials. + + This raises a ``ValueError`` if this is not possible. + + INPUT: + + - ``p`` -- a polynomial in one variable + + EXAMPLES:: + + sage: A = IntegerValuedPolynomialRing(ZZ).S() + sage: S = A.basis() + sage: S[5].polynomial() + 1/120*x^5 + 1/8*x^4 + 17/24*x^3 + 15/8*x^2 + 137/60*x + 1 + sage: A.from_polynomial(_) + S[5] + sage: x = polygen(QQ, 'x') + sage: A.from_polynomial(x) + -S[0] + S[1] + + sage: A = IntegerValuedPolynomialRing(ZZ).B() + sage: B = A.basis() + sage: B[5].polynomial() + 1/120*x^5 - 1/12*x^4 + 7/24*x^3 - 5/12*x^2 + 1/5*x + sage: A.from_polynomial(_) + B[5] + sage: x = polygen(QQ, 'x') + sage: A.from_polynomial(x) + B[1] + + TESTS:: + + sage: x = polygen(QQ,'x') + sage: A.from_polynomial(x+1/3) + Traceback (most recent call last): + ... + ValueError: not a polynomial with integer values: 1/3 + """ + B = self.basis() + poly = self._poly + remain = p + result = self.zero() + while remain: + N = remain.degree() + top_coeff = remain.leading_coefficient() * factorial(N) + try: + top_coeff = self.base_ring()(top_coeff) + except TypeError as exc: + msg = 'not a polynomial with integer' + msg += f' values: {top_coeff}' + raise ValueError(msg) from exc + remain += -top_coeff * poly(N) + result += top_coeff * B[N] + return result + + def gen(self, i=0): + r""" + Return the generator of this algebra. + + The optional argument is ignored. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).B() + sage: F.gen() + B[1] + """ + return self.algebra_generators()[0] + + @cached_method + def algebra_generators(self): + r""" + Return the generators of this algebra. + + EXAMPLES:: + + sage: A = IntegerValuedPolynomialRing(ZZ).S(); A + Integer-Valued Polynomial Ring over Integer Ring + in the shifted basis + sage: A.algebra_generators() + Family (S[1],) + """ + NonNeg = self.basis().keys() + return Family([self.monomial(NonNeg(1))]) + + gens = algebra_generators + + class ElementMethods: + def __call__(self, v): + """ + Evaluation at some value ``v`` + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).S() + sage: B = F.gen() + sage: f = B**2+4*B+6 + sage: f(1/3) + 118/9 + + sage: F = IntegerValuedPolynomialRing(ZZ).B() + sage: B = F.gen() + sage: f = B**2+4*B+6 + sage: f(1/3) + 67/9 + """ + return self.polynomial()(v) + + def polynomial(self): + """ + Convert to a polynomial in `x`. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).S() + sage: B = F.gen() + sage: (B+1).polynomial() + x + 2 + + sage: F = IntegerValuedPolynomialRing(ZZ).B() + sage: B = F.gen() + sage: (B+1).polynomial() + x + 1 + + TESTS:: + + sage: F.zero().polynomial().parent() + Univariate Polynomial Ring in x over Rational Field + """ + R = PolynomialRing(QQ, 'x') + p = self.parent()._poly + return R.sum(c * p(i) for i, c in self) + + def shift(self, j=1): + """ + Shift all indices by `j`. + + INPUT: + + - `j` -- integer (default: 1) + + In the binomial basis, the shift by 1 corresponds to + a summation operator from `0` to `x`. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).B() + sage: B = F.gen() + sage: (B+1).shift() + B[1] + B[2] + sage: (B+1).shift(3) + B[3] + B[4] + """ + A = self.parent() + return A._from_dict({A._indices(i + j): c for i, c in self}) + + def sum_of_coefficients(self): + """ + Return the sum of coefficients. + + In the shifted basis, this is the evaluation at `x=0`. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).S() + sage: B = F.basis() + sage: (B[2]*B[4]).sum_of_coefficients() + 1 + + TESTS:: + + sage: (0*B[2]).sum_of_coefficients().parent() + Integer Ring + """ + R = self.parent().base_ring() + return R.sum(self._monomial_coefficients.values()) + + def content(self): + """ + Return the content of ``self``. + + This is the gcd of the coefficients. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).S() + sage: B = F.basis() + sage: (3*B[4]+6*B[7]).content() + 3 + + TESTS:: + + sage: (0*B[2]).content() + 0 + """ + from sage.arith.misc import gcd + return gcd(self._monomial_coefficients.values()) + + class Shifted(CombinatorialFreeModule, BindableClass): + r""" + The integer-valued polynomial ring in the shifted basis. + + The basis used here is given by `S[i] = \binom{i+x}{i}` for `i \in \NN`. + + Assuming `n_1 \leq n_2`, the product of two monomials `S[n_1] \cdot S[n_2]` + is given by the sum + + .. MATH:: + + \sum_{k=0}^{n_1} (-1)^k \binom{n_1}{k}\binom{n_1+n_2-k}{n_1} S[n_1 + n_2 - k]. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(QQ).S(); F + Integer-Valued Polynomial Ring over Rational Field + in the shifted basis + + sage: F.gen() + S[1] + + sage: S = IntegerValuedPolynomialRing(ZZ).S(); S + Integer-Valued Polynomial Ring over Integer Ring + in the shifted basis + sage: S.base_ring() + Integer Ring + + sage: G = IntegerValuedPolynomialRing(S).S(); G + Integer-Valued Polynomial Ring over Integer-Valued Polynomial + Ring over Integer Ring in the shifted basis in the shifted basis + sage: G.base_ring() + Integer-Valued Polynomial Ring over Integer Ring + in the shifted basis + + Integer-valued polynomial rings commute with their base ring:: + + sage: K = IntegerValuedPolynomialRing(QQ).S() + sage: a = K.gen() + sage: K.is_commutative() + True + sage: L = IntegerValuedPolynomialRing(K).S() + sage: c = L.gen() + sage: L.is_commutative() + True + sage: s = a * c^3; s + S[1]*S[1] + (-6*S[1])*S[2] + 6*S[1]*S[3] + sage: parent(s) + Integer-Valued Polynomial Ring over Integer-Valued Polynomial + Ring over Rational Field in the shifted basis in the shifted basis + + Integer-valued polynomial rings are commutative:: + + sage: c^3 * a == c * a * c * c + True + + We can also manipulate elements in the basis and + coerce elements from our base field:: + + sage: F = IntegerValuedPolynomialRing(QQ).S() + sage: S = F.basis() + sage: S[2] * S[3] + 3*S[3] - 12*S[4] + 10*S[5] + sage: 1 - S[2] * S[2] / 2 + S[0] - 1/2*S[2] + 3*S[3] - 3*S[4] + """ + def __init__(self, A): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(QQ).S(); F + Integer-Valued Polynomial Ring over Rational Field + in the shifted basis + sage: TestSuite(F).run() + """ + CombinatorialFreeModule.__init__(self, A.base_ring(), + NonNegativeIntegers(), + category=A.Bases(), + prefix="S", + latex_prefix=r"\mathbb{S}") + + def _realization_name(self) -> str: + r""" + TESTS:: + + sage: F = IntegerValuedPolynomialRing(QQ).S() + sage: F._realization_name() + 'shifted' + """ + return "shifted" + + def product_on_basis(self, n1, n2): + r""" + Return the product of basis elements ``n1`` and ``n2``. + + INPUT: + + - ``n1``, ``n2`` -- integers + + EXAMPLES:: + + sage: A = IntegerValuedPolynomialRing(QQ).S() + sage: A.product_on_basis(0, 1) + S[1] + sage: A.product_on_basis(1, 2) + -2*S[2] + 3*S[3] + """ + i = ZZ(n1) + j = ZZ(n2) + if j < i: + j, i = i, j + + R = self.base_ring() + return self._from_dict({i + j - k: R((-1)**k * i.binomial(k) * (i + j - k).binomial(i)) + for k in range(i + 1)}) + + def _from_binomial_basis(self, i): + """ + Convert from the ``binomial(x,k)`` basis. + + INPUT: + + - ``i`` -- an integer + + EXAMPLES:: + + sage: S = IntegerValuedPolynomialRing(ZZ).S() + sage: B = IntegerValuedPolynomialRing(ZZ).B() + sage: b = B.basis() + sage: S(b[3]+1) # indirect doctest + 3*S[1] - 3*S[2] + S[3] + sage: B(_) + B[0] + B[3] + """ + i = ZZ(i) + R = self.base_ring() + return self._from_dict({k: R((-1)**(i - k) * i.binomial(k)) + for k in range(i + 1)}) + + def from_h_vector(self, h): + """ + Convert from some `h`-vector. + + INPUT: + + - ``h`` -- a tuple or vector + + .. SEEALSO:: :meth:`Element.h_vector` + + EXAMPLES:: + + sage: A = IntegerValuedPolynomialRing(ZZ).S() + sage: S = A.basis() + sage: ex = S[2]+S[4] + sage: A.from_h_vector(ex.h_vector()) + S[2] + S[4] + """ + d = len(h) - 1 + m = matrix(QQ, d + 1, d + 1, + lambda j, i: (-1)**(d - j) * binomial(d - i, d - j)) + v = vector(QQ, [h[i] for i in range(d + 1)]) + R = self.base_ring() + return self._from_dict({i: R(c) + for i, c in enumerate(m * v)}) + + def _element_constructor_(self, x): + r""" + Convert ``x`` into ``self``. + + INPUT: + + - ``x`` -- an element of the base ring or something convertible + + EXAMPLES:: + + sage: R = IntegerValuedPolynomialRing(QQ).S() + sage: x = R.gen() + sage: R(3) + 3*S[0] + sage: R(x) + S[1] + """ + P = x.parent() + if isinstance(P, IntegerValuedPolynomialRing.Shifted): + if P is self: + return x + if P is not self.base_ring(): + return self.element_class(self, x.monomial_coefficients()) + + # ok, not a integer-valued polynomial ring element + R = self.base_ring() + # coercion via base ring + x = R(x) + if x == 0: + return self.element_class(self, {}) + return self.from_base_ring_from_one_basis(x) + + def _coerce_map_from_(self, R): + r""" + Return ``True`` if there is a coercion from ``R`` into ``self`` + and ``False`` otherwise. + + INPUT: + + - ``R`` -- a commutative ring + + The things that coerce into ``self`` are + + - Integer-Valued Polynomial Rings over a base + with a coercion map into ``self.base_ring()``. + + - Anything with a coercion into ``self.base_ring()``. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(GF(7)).S(); F + Integer-Valued Polynomial Ring over Finite Field of size 7 + in the shifted basis + + Elements of the integer-valued polynomial ring canonically + coerce in:: + + sage: x = F.gen() + sage: F.coerce(x*x) # indirect doctest + 6*S[1] + 2*S[2] + + Elements of the integers coerce in, since there is a coerce map + from `\ZZ` to `\GF(7)`:: + + sage: F.coerce(1) # indirect doctest + S[0] + + There is no coerce map from `\QQ` to `\GF{7}`:: + + sage: F.coerce(2/3) # indirect doctest + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Rational Field to + Integer-Valued Polynomial Ring over Finite Field of size 7 + in the shifted basis + + Elements of the base ring coerce in:: + + sage: F.coerce(GF(7)(5)) + 5*S[0] + + The integer-valued polynomial ring over `\ZZ` on `x` coerces in, + since `\ZZ` coerces to `\GF{7}`:: + + sage: G = IntegerValuedPolynomialRing(ZZ).S() + sage: Gx = G.gen() + sage: z = F.coerce(Gx**2); z + -S[1] + 2*S[2] + sage: z.parent() is F + True + + However, `\GF{7}` does not coerce to `\ZZ`, so the + integer-valued polynomial algebra over `\GF{7}` does not + coerce to the one over `\ZZ`:: + + sage: G.coerce(x^3+x) + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Integer-Valued Polynomial + Ring over Finite Field of size 7 in the shifted basis + to Integer-Valued Polynomial + Ring over Integer Ring in the shifted basis + + TESTS:: + + sage: F = IntegerValuedPolynomialRing(ZZ).S() + sage: G = IntegerValuedPolynomialRing(QQ).S() + sage: H = IntegerValuedPolynomialRing(ZZ).S() + sage: F._coerce_map_from_(G) + False + sage: G._coerce_map_from_(F) + True + sage: F._coerce_map_from_(H) + True + sage: F._coerce_map_from_(QQ) + False + sage: G._coerce_map_from_(QQ) + True + sage: F.has_coerce_map_from(PolynomialRing(ZZ,'x')) + False + """ + # integer-valued polynomial rings over any base + # that coerces in: + if isinstance(R, IntegerValuedPolynomialRing.Shifted): + return self.base_ring().has_coerce_map_from(R.base_ring()) + if isinstance(R, IntegerValuedPolynomialRing.Binomial): + return R.module_morphism(self._from_binomial_basis, + codomain=self) + return self.base_ring().has_coerce_map_from(R) + + def _poly(self, i): + """ + Convert the basis element `S[i]` to a polynomial. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).S() + sage: F._poly(4) + 1/24*x^4 + 5/12*x^3 + 35/24*x^2 + 25/12*x + 1 + """ + x = polygen(QQ, 'x') + return binomial(x + i, i) + + class Element(CombinatorialFreeModule.Element): + + def umbra(self): + """ + Return the Bernoulli umbra. + + This is the derivative at `-1` of the shift by one. + + .. SEEALSO:: :meth:`derivative_at_minus_one` + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).S() + sage: B = F.gen() + sage: (B+1).umbra() + 3/2 + + TESTS:: + + sage: [(B**n).umbra() for n in range(1, 11)] + [1/2, 1/6, 0, -1/30, 0, 1/42, 0, -1/30, 0, 5/66] + """ + return self.shift().derivative_at_minus_one() + + def delta(self): + r""" + Return the image by the difference operator `\Delta`. + + The operator `\Delta` is defined on polynomials by + + .. MATH:: + + f \mapsto f(x+1)-f(x). + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).S() + sage: S = F.basis() + sage: S[5].delta() + S[0] + S[1] + S[2] + S[3] + S[4] + """ + return self.variable_shift() - self + + def variable_shift(self, k=1): + r""" + Return the image by the shift of variables. + + On polynomials, the action is the shift + on variables `x \mapsto x + k`. + + INPUT: + + - `k` -- integer (default: 1) + + EXAMPLES:: + + sage: A = IntegerValuedPolynomialRing(ZZ).S() + sage: S = A.basis() + sage: S[5].variable_shift() + S[0] + S[1] + S[2] + S[3] + S[4] + S[5] + + sage: S[5].variable_shift(-1) + -S[4] + S[5] + + TESTS:: + + sage: S[5].variable_shift(0) + S[5] + sage: S[5].variable_shift().variable_shift(-1) + S[5] + """ + if k == 0: + return self + + A = self.parent() + + if k > 0: + B = A.basis() + resu = A.linear_combination((B[j], c) for i, c in self + for j in range(i + 1)) + if k == 1: + return resu + return resu.variable_shift(k - 1) + + resu = self - A._from_dict({i - 1: c for i, c in self if i}) + if k == -1: + return resu + return resu.variable_shift(k + 1) + + def derivative_at_minus_one(self): + """ + Return the derivative at `-1`. + + This is sometimes useful when `-1` is a root. + + .. SEEALSO:: :meth:`umbra` + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).S() + sage: B = F.gen() + sage: (B+1).derivative_at_minus_one() + 1 + """ + return QQ.sum(c / QQ(i) for i, c in self if i) + + def h_vector(self): + """ + Return the numerator of the generating series of values. + + If ``self`` is an Ehrhart polynomial, this is the `h`-vector. + + .. SEEALSO:: :meth:`h_polynomial` + + EXAMPLES:: + + sage: x = polygen(QQ,'x') + sage: A = IntegerValuedPolynomialRing(ZZ).S() + sage: ex = A.from_polynomial((1+x)**3) + sage: ex.h_vector() + (0, 1, 4, 1) + """ + d = max(self.support(), default=-1) + m = matrix(QQ, d + 1, d + 1, + lambda j, i: (-1)**(d - j) * (d - i).binomial(d - j)) + v = vector(QQ, [self.coefficient(i) for i in range(d + 1)]) + return m * v + + def h_polynomial(self): + """ + Return the `h`-vector as a polynomial. + + .. SEEALSO:: :meth:`h_vector` + + EXAMPLES:: + + sage: x = polygen(QQ,'x') + sage: A = IntegerValuedPolynomialRing(ZZ).S() + sage: ex = A.from_polynomial((1+x)**3) + sage: ex.h_polynomial() + z^3 + 4*z^2 + z + """ + anneau = PolynomialRing(self.parent().base_ring(), 'z') + return anneau(list(self.h_vector())) + + S = Shifted + + # ===== Another basis for the same algebra ===== + + class Binomial(CombinatorialFreeModule, BindableClass): + r""" + The integer-valued polynomial ring in the binomial basis. + + The basis used here is given by `B[i] = \binom{x}{i}` for `i \in \NN`. + + Assuming `n_1 \leq n_2`, the product of two monomials `B[n_1] \cdot B[n_2]` + is given by the sum + + .. MATH:: + + \sum_{k=0}^{n_1} \binom{n_1}{k}\binom{n_1+n_2-k}{n_1} B[n_1 + n_2 - k]. + + The product of two monomials is therefore a positive linear combination + of monomials. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(QQ).B(); F + Integer-Valued Polynomial Ring over Rational Field + in the binomial basis + + sage: F.gen() + B[1] + + sage: S = IntegerValuedPolynomialRing(ZZ).B(); S + Integer-Valued Polynomial Ring over Integer Ring + in the binomial basis + sage: S.base_ring() + Integer Ring + + sage: G = IntegerValuedPolynomialRing(S).B(); G + Integer-Valued Polynomial Ring over Integer-Valued Polynomial Ring + over Integer Ring in the binomial basis in the binomial basis + sage: G.base_ring() + Integer-Valued Polynomial Ring over Integer Ring + in the binomial basis + + Integer-valued polynomial rings commute with their base ring:: + + sage: K = IntegerValuedPolynomialRing(QQ).B() + sage: a = K.gen() + sage: K.is_commutative() + True + sage: L = IntegerValuedPolynomialRing(K).B() + sage: c = L.gen() + sage: L.is_commutative() + True + sage: s = a * c^3; s + B[1]*B[1] + 6*B[1]*B[2] + 6*B[1]*B[3] + sage: parent(s) + Integer-Valued Polynomial Ring over Integer-Valued Polynomial + Ring over Rational Field in the binomial basis in the binomial basis + + Integer-valued polynomial rings are commutative:: + + sage: c^3 * a == c * a * c * c + True + + We can also manipulate elements in the basis:: + + sage: F = IntegerValuedPolynomialRing(QQ).B() + sage: B = F.basis() + sage: B[2] * B[3] + 3*B[3] + 12*B[4] + 10*B[5] + sage: 1 - B[2] * B[2] / 2 + B[0] - 1/2*B[2] - 3*B[3] - 3*B[4] + + and coerce elements from our base field:: + + sage: F(4/3) + 4/3*B[0] + """ + def __init__(self, A): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(QQ).B(); F + Integer-Valued Polynomial Ring over Rational Field + in the binomial basis + sage: TestSuite(F).run() + """ + CombinatorialFreeModule.__init__(self, A.base_ring(), + NonNegativeIntegers(), + latex_prefix="", + category=A.Bases()) + + def _realization_name(self) -> str: + r""" + TESTS:: + + sage: F = IntegerValuedPolynomialRing(QQ).B() + sage: F._realization_name() + 'binomial' + """ + return "binomial" + + def product_on_basis(self, n1, n2): + r""" + Return the product of basis elements ``n1`` and ``n2``. + + INPUT: + + - ``n1``, ``n2`` -- integers + + EXAMPLES:: + + sage: A = IntegerValuedPolynomialRing(QQ).B() + sage: A.product_on_basis(0, 1) + B[1] + sage: A.product_on_basis(1, 2) + 2*B[2] + 3*B[3] + """ + i = ZZ(n1) + j = ZZ(n2) + if j < i: + j, i = i, j + + R = self.base_ring() + return self._from_dict({i + j - k: + R(binomial(i, k) * binomial(i + j - k, i)) + for k in range(i + 1)}) + + def _from_shifted_basis(self, i): + """ + Convert from the shifted binomial(x+k,k) basis. + + INPUT: + + - ``i`` -- an integer + + EXAMPLES:: + + sage: S = IntegerValuedPolynomialRing(ZZ).S() + sage: B = IntegerValuedPolynomialRing(ZZ).B() + sage: s = S.basis() + sage: B(s[3]+1) # indirect doctest + 2*B[0] + 3*B[1] + 3*B[2] + B[3] + sage: S(_) + S[0] + S[3] + """ + i = ZZ(i) + R = self.base_ring() + return self._from_dict({k: R(i.binomial(k)) + for k in range(i + 1)}) + + def _element_constructor_(self, x): + r""" + Convert ``x`` into ``self``. + + EXAMPLES:: + + sage: R = IntegerValuedPolynomialRing(QQ).B() + sage: x = R.gen() + sage: R(3) + 3*B[0] + sage: R(x) + B[1] + """ + P = x.parent() + if isinstance(P, IntegerValuedPolynomialRing.Binomial): + if P is self: + return x + if P is not self.base_ring(): + return self.element_class(self, x.monomial_coefficients()) + + # ok, not a integer-valued polynomial ring element + R = self.base_ring() + # coercion via base ring + x = R(x) + if x == 0: + return self.element_class(self, {}) + return self.from_base_ring_from_one_basis(x) + + def _coerce_map_from_(self, R): + r""" + Return ``True`` if there is a coercion from ``R`` into ``self`` + and ``False`` otherwise. + + The things that coerce into ``self`` are + + - Integer-Valued Polynomial Rings over a base + with a coercion map into ``self.base_ring()``. + + - Anything with a coercion into ``self.base_ring()``. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(GF(7)).B(); F + Integer-Valued Polynomial Ring over Finite Field of size 7 + in the binomial basis + + Elements of the integer-valued polynomial ring canonically coerce + in:: + + sage: x = F.gen() + sage: F.coerce(x*x) # indirect doctest + B[1] + 2*B[2] + + Elements of the integers coerce in, since there is a coerce map + from `\ZZ` to `\GF(7)`:: + + sage: F.coerce(1) # indirect doctest + B[0] + + There is no coerce map from `\QQ` to `\GF{7}`:: + + sage: F.coerce(2/3) # indirect doctest + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Rational Field to + Integer-Valued Polynomial Ring over Finite Field of size 7 + in the binomial basis + + Elements of the base ring coerce in:: + + sage: F.coerce(GF(7)(5)) + 5*B[0] + + The integer-valued polynomial ring over `\ZZ` on `x` coerces in, + since `\ZZ` coerces to `\GF{7}`:: + + sage: G = IntegerValuedPolynomialRing(ZZ).B() + sage: Gx = G.gen() + sage: z = F.coerce(Gx**2); z + B[1] + 2*B[2] + sage: z.parent() is F + True + + However, `\GF{7}` does not coerce to `\ZZ`, so the + integer-valued polynomial algebra over `\GF{7}` does not + coerce to the one over `\ZZ`:: + + sage: G.coerce(x^3+x) + Traceback (most recent call last): + ... + TypeError: no canonical coercion from Integer-Valued Polynomial + Ring over Finite Field of size 7 in the binomial basis to + Integer-Valued Polynomial Ring over Integer Ring + in the binomial basis + + TESTS:: + + sage: F = IntegerValuedPolynomialRing(ZZ).B() + sage: G = IntegerValuedPolynomialRing(QQ).B() + sage: H = IntegerValuedPolynomialRing(ZZ).B() + sage: F._coerce_map_from_(G) + False + sage: G._coerce_map_from_(F) + True + sage: F._coerce_map_from_(H) + True + sage: F._coerce_map_from_(QQ) + False + sage: G._coerce_map_from_(QQ) + True + sage: F.has_coerce_map_from(PolynomialRing(ZZ,'x')) + False + """ + # integer-valued polynomial rings over any base + # that coerces in: + if isinstance(R, IntegerValuedPolynomialRing.Binomial): + return self.base_ring().has_coerce_map_from(R.base_ring()) + if isinstance(R, IntegerValuedPolynomialRing.Shifted): + return R.module_morphism(self._from_shifted_basis, + codomain=self) + return self.base_ring().has_coerce_map_from(R) + + def _poly(self, i): + """ + Convert the basis element `B[i]` to a polynomial. + + EXAMPLES:: + + sage: F = IntegerValuedPolynomialRing(ZZ).B() + sage: F._poly(4) + 1/24*x^4 - 1/4*x^3 + 11/24*x^2 - 1/4*x + """ + x = polygen(QQ, 'x') + return binomial(x, i) + + class Element(CombinatorialFreeModule.Element): + pass + + B = Binomial 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/multi_polynomial.pxd b/src/sage/rings/polynomial/multi_polynomial.pxd index 44fdf3edfc4..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 CommutativeRingElement +from .commutative_polynomial cimport CommutativePolynomial -cdef class MPolynomial(CommutativeRingElement): + +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 4f8e47049a2..4dc633296cb 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -20,6 +20,9 @@ from itertools import chain, islice 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 @@ -32,7 +35,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(CommutativePolynomial): #################### # Some standard conversions @@ -2852,3 +2855,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_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/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 c8d4ad59156..51d9a73aabd 100644 --- a/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_libsingular.pyx @@ -1897,7 +1897,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. """ @@ -2057,7 +2057,18 @@ 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 """ + cdef Element sage_res + if len(kwds) > 0: f = self.subs(**kwds) if len(x) > 0: @@ -2076,29 +2087,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 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() diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index 6e8ca561e2a..20e0a0353b2 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -1379,7 +1379,16 @@ 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) + [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:: @@ -1387,8 +1396,11 @@ 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 + 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""" diff --git a/src/sage/rings/polynomial/ore_function_element.py b/src/sage/rings/polynomial/ore_function_element.py index d21ed8d1ed4..fe87008cf2c 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/padics/polynomial_padic_capped_relative_dense.py b/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py index d41eaee7dd1..6aa21db4af7 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/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_element.pxd b/src/sage/rings/polynomial/polynomial_element.pxd index 1ba103329c3..083f506b222 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.parent cimport Parent from sage.rings.integer cimport Integer +from .commutative_polynomial cimport CommutativePolynomial from .polynomial_compiled cimport CompiledPolynomialFunction -cdef class Polynomial(CommutativeAlgebraElement): +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 ceb36976126..88a25eae183 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -135,7 +135,7 @@ from sage.categories.morphism cimport Morphism from sage.misc.superseded import deprecation_cython as deprecation, deprecated_function_alias from sage.misc.cachefunc import cached_method -from sage.rings.number_field.order import is_NumberFieldOrder +from sage.rings.number_field.order import is_NumberFieldOrder, Order as NumberFieldOrder from sage.categories.number_fields import NumberFields @@ -143,6 +143,8 @@ cpdef is_Polynomial(f): """ Return True if f is of type univariate polynomial. + This function is deprecated. + INPUT: - ``f`` -- an object @@ -152,6 +154,8 @@ cpdef is_Polynomial(f): sage: from sage.rings.polynomial.polynomial_element import is_Polynomial 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://github.com/sagemath/sage/issues/32709 for details. True sage: S. = R[] sage: f = y^3 + x*y -3*x; f @@ -175,6 +179,9 @@ cpdef is_Polynomial(f): 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 @@ -182,7 +189,7 @@ from .polynomial_compiled cimport CompiledPolynomialFunction from sage.rings.polynomial.polydict cimport ETuple -cdef class Polynomial(CommutativeAlgebraElement): +cdef class Polynomial(CommutativePolynomial): """ A polynomial. @@ -5105,8 +5112,11 @@ cdef class Polynomial(CommutativeAlgebraElement): y = self._parent.quo(self).gen() from sage.groups.generic import order_from_multiple return n == order_from_multiple(y, n, n_prime_divs, operation="*") + elif isinstance(R, NumberFieldOrder): + K = R.number_field() + return K.fractional_ideal(self.coefficients()) == K.fractional_ideal(1) else: - return R.ideal(self.coefficients())==R.ideal(1) + return R.ideal(self.coefficients()) == R.ideal(1) def is_constant(self): """ diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx index c2db35e0bf7..38e2470f1f1 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/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index c7e9a7f9dbf..74e6b061a09 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 212f5cb0d73..bda1ec75a10 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/rings/qqbar.py b/src/sage/rings/qqbar.py index 1e21659d504..da8b8d71b91 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.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 @@ -6724,7 +6724,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/rings/quotient_ring.py b/src/sage/rings/quotient_ring.py index 8f9bf928cc5..33df6cf2e37 100644 --- a/src/sage/rings/quotient_ring.py +++ b/src/sage/rings/quotient_ring.py @@ -290,7 +290,13 @@ def QuotientRing(R, I, names=None, **kwds): from sage.rings.polynomial.polynomial_ring_constructor import BooleanPolynomialRing_constructor as BooleanPolynomialRing kwds.pop('implementation') return BooleanPolynomialRing(R.ngens(), names=names, **kwds) - if not isinstance(I, ideal.Ideal_generic) or I.ring() != R: + # workaround to silence warning from #34806 + from sage.rings.number_field.order import Order + if isinstance(R, Order): + if not R.is_maximal(): + raise NotImplementedError('only implemented for maximal orders') + I = R.number_field().ideal(I) + elif not isinstance(I, ideal.Ideal_generic) or I.ring() != R: I = R.ideal(I) if I.is_zero(): return R @@ -444,7 +450,13 @@ def __init__(self, R, I, names, category=None): """ if R not in _Rings: raise TypeError("The first argument must be a ring, but %s is not"%R) - if I not in R.ideal_monoid(): + # workaround to silence warning from #34806 + from sage.rings.number_field.order import Order + if isinstance(R, Order): + M = R.number_field().ideal_monoid() + else: + M = R.ideal_monoid() + if I not in M: raise TypeError("The second argument must be an ideal of the given ring, but %s is not"%I) self.__R = R self.__I = I diff --git a/src/sage/rings/ring_extension.pyx b/src/sage/rings/ring_extension.pyx index fa8c836fb80..c72c5316a90 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: diff --git a/src/sage/schemes/affine/affine_morphism.py b/src/sage/schemes/affine/affine_morphism.py index 9efef46cda3..38b71556ba2 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/berkovich/berkovich_cp_element.py b/src/sage/schemes/berkovich/berkovich_cp_element.py index b8cc1887957..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.rings.polynomial.polynomial_element import is_Polynomial + from sage.rings.polynomial.polynomial_element import Polynomial from sage.rings.fraction_field_element import FractionFieldElement_1poly_field self._type = None @@ -109,17 +109,17 @@ 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.rings.polynomial.multi_polynomial import MPolynomial + if isinstance(center, MPolynomial): try: center = center.univariate_polynomial() except AttributeError: 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/curves/constructor.py b/src/sage/schemes/curves/constructor.py index fc3d174de59..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.rings.polynomial.multi_polynomial_element import is_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 @@ -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/cyclic_covers/constructor.py b/src/sage/schemes/cyclic_covers/constructor.py index 0966b0793d5..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.rings.polynomial.polynomial_element import is_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 @@ -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/constructor.py b/src/sage/schemes/elliptic_curves/constructor.py index 1ecdc082cd5..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.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.rings.polynomial.multi_polynomial 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/ell_curve_isogeny.py b/src/sage/schemes/elliptic_curves/ell_curve_isogeny.py index 441f45c2e12..213ac83d73b 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.rings.polynomial.polynomial_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/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index a379bc469e8..9da8cc3acfd 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -1131,7 +1131,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/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index acb392a0773..6848b4b34ea 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -1499,10 +1499,11 @@ def conductor(self): # Note: for number fields other than QQ we could initialize # N=K.ideal(1) or N=OK.ideal(1), which are the same, but for # K == QQ it has to be ZZ.ideal(1). - OK = self.base_ring().ring_of_integers() + K = self.base_field() + N = ZZ.ideal(1) if K is QQ else K.fractional_ideal(1) self._conductor = prod([d.prime()**d.conductor_valuation() for d in self.local_data()], - OK.ideal(1)) + N) return self._conductor def minimal_discriminant_ideal(self): diff --git a/src/sage/schemes/elliptic_curves/heegner.py b/src/sage/schemes/elliptic_curves/heegner.py index f9b752f9533..bd38ff19428 100644 --- a/src/sage/schemes/elliptic_curves/heegner.py +++ b/src/sage/schemes/elliptic_curves/heegner.py @@ -1661,7 +1661,7 @@ def ideal(self): (A,B,C) = f if A%c == 0: A, C = C, A - return K.maximal_order().ideal([A, (-B+c*sqrtD)/2]) + return K.fractional_ideal([A, (-B+c*sqrtD)/2]) ## def __call__(self, z): ## """ diff --git a/src/sage/schemes/elliptic_curves/hom_velusqrt.py b/src/sage/schemes/elliptic_curves/hom_velusqrt.py index 0f1101e57d2..15863173c4f 100644 --- a/src/sage/schemes/elliptic_curves/hom_velusqrt.py +++ b/src/sage/schemes/elliptic_curves/hom_velusqrt.py @@ -511,7 +511,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/schemes/elliptic_curves/jacobian.py b/src/sage/schemes/elliptic_curves/jacobian.py index b6983ab75f7..6cf48d0760a 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.rings.polynomial.multi_polynomial 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..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.rings.polynomial.multi_polynomial_element import is_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 @@ -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/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index 54556e08755..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.rings.polynomial.polynomial_element import is_Polynomial +from sage.rings.polynomial.polynomial_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..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.rings.polynomial.polynomial_element import is_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 @@ -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 6d0d2403ba8..687ab8606df 100644 --- a/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py +++ b/src/sage/schemes/hyperelliptic_curves/monsky_washnitzer.py @@ -71,7 +71,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.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 @@ -496,7 +496,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(): @@ -2396,7 +2396,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/schemes/plane_conics/constructor.py b/src/sage/schemes/plane_conics/constructor.py index a2a14190087..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.rings.polynomial.multi_polynomial_element import is_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 @@ -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..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.rings.polynomial.multi_polynomial_element import is_MPolynomial +from sage.rings.polynomial.multi_polynomial 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: diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index 84e8741f739..d7441f0f463 100644 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -36,7 +36,7 @@ from sage.categories.number_fields import NumberFields _NumberFields = NumberFields() from sage.rings.fraction_field import FractionField -from sage.rings.number_field.order import is_NumberFieldOrder +from sage.rings.number_field.order import is_NumberFieldOrder, Order as NumberFieldOrder from sage.rings.qqbar import number_field_elements_from_algebraics from sage.rings.quotient_ring import QuotientRing_generic from sage.rings.rational_field import QQ @@ -751,6 +751,8 @@ def global_height(self, prec=None): raise TypeError("must be defined over an algebraic field") else: K = P.codomain().base_ring() + if isinstance(K, NumberFieldOrder): + K = K.number_field() # first get rid of the denominators denom = lcm([xi.denominator() for xi in P]) x = [xi * denom for xi in P] 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] diff --git a/src/sage/schemes/toric/points.py b/src/sage/schemes/toric/points.py index 1815c4fa6f0..a0e0d586563 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): """ diff --git a/src/sage/sets/finite_set_maps.py b/src/sage/sets/finite_set_maps.py index ce5029d8032..ffe885e2977 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 b988525a880..e0c640040ca 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/structure/element.pyx b/src/sage/structure/element.pyx index ad84ab939ac..4b4ed0a3ab6 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -3361,6 +3361,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 @@ -4301,6 +4314,8 @@ def is_CommutativeAlgebraElement(x): cdef class CommutativeAlgebraElement(CommutativeRingElement): pass + ############################################## + def is_InfinityElement(x): """ Return ``True`` if x is of type InfinityElement. 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') diff --git a/src/sage/version.py b/src/sage/version.py index 6c86cab5b29..829e4e5aaf2 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,5 +1,5 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '10.0.beta3' -date = '2023-03-02' -banner = 'SageMath version 10.0.beta3, Release Date: 2023-03-02' +version = '10.0.beta4' +date = '2023-03-12' +banner = 'SageMath version 10.0.beta4, Release Date: 2023-03-12' diff --git a/tox.ini b/tox.ini index 1d17c7d175f..49de42daa14 100644 --- a/tox.ini +++ b/tox.ini @@ -646,7 +646,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); \