diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3d69721d33b..c15714b3e22 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -46,9 +46,8 @@ body: - **OS**: Ubuntu 20.04 - Sage Version: 9.2 value: | - - **OS**: - - **Sage Version**: - render: markdown + - **OS**: + - **Sage Version**: validations: required: true - type: checkboxes diff --git a/.github/ISSUE_TEMPLATE/failure_building_from_source.yml b/.github/ISSUE_TEMPLATE/failure_building_from_source.yml index 2575f5c9a10..59becde362e 100644 --- a/.github/ISSUE_TEMPLATE/failure_building_from_source.yml +++ b/.github/ISSUE_TEMPLATE/failure_building_from_source.yml @@ -17,9 +17,8 @@ body: - **OS**: Ubuntu 20.04 - Sage Version: 9.2 value: | - - **OS**: + - **OS**: - **Sage Version**: - render: markdown validations: required: true - type: textarea diff --git a/CITATION.cff b/CITATION.cff index 983e656e9b4..0576a00c2be 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -4,8 +4,8 @@ title: SageMath abstract: SageMath is a free open-source mathematics software system. authors: - name: "The SageMath Developers" -version: 10.5.beta3 +version: 10.5.beta4 doi: 10.5281/zenodo.8042260 -date-released: 2024-09-03 +date-released: 2024-09-15 repository-code: "https://github.com/sagemath/sage" url: "https://www.sagemath.org/" diff --git a/README.md b/README.md index 7a318f53b79..af91374fe19 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ virtualization). Detailed information on supported platforms for a specific version of Sage can be found in the section _Availability and installation help_ of the -[release tour](https://wiki.sagemath.org/ReleaseTours) for this version. +[release tour for this version](https://github.com/sagemath/sage/releases). We highly appreciate contributions to Sage that fix portability bugs and help port Sage to new platforms; let us know at the [sage-devel @@ -489,8 +489,8 @@ Troubleshooting --------------- If you have problems building Sage, check the Sage Installation Guide, -as well as the version-specific Sage Installation FAQ in the [Sage Release -Tour](https://wiki.sagemath.org/ReleaseTours) corresponding to the +as well as the version-specific installation help in the [release +tour](https://github.com/sagemath/sage/releases) corresponding to the version that you are installing. Please do not hesitate to ask for help in the [SageMath forum diff --git a/VERSION.txt b/VERSION.txt index e6ed441beea..76a54840a85 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.5.beta3, Release Date: 2024-09-03 +SageMath version 10.5.beta4, Release Date: 2024-09-15 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 9882f48fdce..3f4c1eecd6d 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,3 +1,3 @@ tarball=configure-VERSION.tar.gz -sha1=0f6355fc136bb6619585863b9e4bc954cc6e0e3d -sha256=5b618581d51997afa78b5e6647584f7ef4e6d5844823dd44e607f2accd7abba5 +sha1=d2f8c0bc6c40a0e3e8f7cb0deebee5e7bc7da501 +sha256=cde422cec1dc104f4f4b1369167a17cc77519186180bfc14322d078881581c50 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index f215e057b9e..d3929445739 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -b9cb7bc2559cde857d84d77f0b37a3616ce1eb6c +27fce1faa78ef19b8c43287016f0acbdf0fa169a diff --git a/build/pkgs/cython/checksums.ini b/build/pkgs/cython/checksums.ini index 9393fe60b7f..e463df31628 100644 --- a/build/pkgs/cython/checksums.ini +++ b/build/pkgs/cython/checksums.ini @@ -1,4 +1,4 @@ -tarball=Cython-VERSION.tar.gz -sha1=83d6428e3bb7869f44f92ed75d7dff867c2a38ce -sha256=dcc96739331fb854dcf503f94607576cfe8488066c61ca50dfd55836f132de99 -upstream_url=https://pypi.io/packages/source/C/Cython/Cython-VERSION.tar.gz +tarball=cython-VERSION.tar.gz +sha1=f692b0c6f209b75b6bbd69bdbd57fac23785c23e +sha256=7146dd2af8682b4ca61331851e6aebce9fe5158e75300343f80c07ca80b1faff +upstream_url=https://pypi.io/packages/source/c/cython/cython-VERSION.tar.gz diff --git a/build/pkgs/cython/package-version.txt b/build/pkgs/cython/package-version.txt index a909317fe5a..778bf95c00f 100644 --- a/build/pkgs/cython/package-version.txt +++ b/build/pkgs/cython/package-version.txt @@ -1 +1 @@ -3.0.10 +3.0.11 diff --git a/build/pkgs/hatchling/checksums.ini b/build/pkgs/hatchling/checksums.ini index ce647e83c52..1d456db2d53 100644 --- a/build/pkgs/hatchling/checksums.ini +++ b/build/pkgs/hatchling/checksums.ini @@ -1,4 +1,4 @@ tarball=hatchling-VERSION-py3-none-any.whl -sha1=2212af13a26dbaea72c7a4ecbdb950c05f6e7c00 -sha256=30ec7ee09f6e17b73257eedfd7f5bb5a9b028a6cf6d144d9faad1d826fa203b8 +sha1=cae79d374a238c7674687fb4fff0b15cf1af9be4 +sha256=b47948e45d4d973034584dd4cb39c14b6a70227cf287ab7ec0ad7983408a882c upstream_url=https://pypi.io/packages/py3/h/hatchling/hatchling-VERSION-py3-none-any.whl diff --git a/build/pkgs/hatchling/package-version.txt b/build/pkgs/hatchling/package-version.txt index da9594fd66f..ad2191947f7 100644 --- a/build/pkgs/hatchling/package-version.txt +++ b/build/pkgs/hatchling/package-version.txt @@ -1 +1 @@ -1.22.5 +1.25.0 diff --git a/build/pkgs/jmol/type b/build/pkgs/jmol/type index a6a7b9cd726..134d9bc32d5 100644 --- a/build/pkgs/jmol/type +++ b/build/pkgs/jmol/type @@ -1 +1 @@ -standard +optional diff --git a/build/pkgs/jupyter_jsmol/type b/build/pkgs/jupyter_jsmol/type index a6a7b9cd726..134d9bc32d5 100644 --- a/build/pkgs/jupyter_jsmol/type +++ b/build/pkgs/jupyter_jsmol/type @@ -1 +1 @@ -standard +optional diff --git a/build/pkgs/nauty/checksums.ini b/build/pkgs/nauty/checksums.ini index 9663569c7c4..41ff91d3186 100644 --- a/build/pkgs/nauty/checksums.ini +++ b/build/pkgs/nauty/checksums.ini @@ -1,4 +1,4 @@ tarball=nauty${VERSION}.tar.gz -sha1=672e9fc9dfd07201af37ee65807a9b493331ed92 -sha256=159d2156810a6bb240410cd61eb641add85088d9f15c888cdaa37b8681f929ce +sha1=23504eeae95a1a8a9abfd47029b4ff9da886471f +sha256=c97ab42bf48796a86a598bce3e9269047ca2b32c14fc23e07208a244fe52c4ee upstream_url=https://pallini.di.uniroma1.it/nauty${VERSION_MAJOR}_${VERSION_MINOR}_${VERSION_MICRO}.tar.gz diff --git a/build/pkgs/nauty/package-version.txt b/build/pkgs/nauty/package-version.txt index b8635c72de3..d578041c4b8 100644 --- a/build/pkgs/nauty/package-version.txt +++ b/build/pkgs/nauty/package-version.txt @@ -1 +1 @@ -2.8.8.p0 +2.8.9 diff --git a/build/pkgs/nauty/spkg-install.in b/build/pkgs/nauty/spkg-install.in index a2557c3cd8e..8f83cf3b841 100644 --- a/build/pkgs/nauty/spkg-install.in +++ b/build/pkgs/nauty/spkg-install.in @@ -8,7 +8,7 @@ fi # Nauty doesn't have an install target; passing a prefix to configure is # useless (but harmless) -sdh_configure CC="$CC -fPIC" $NAUTY_CONFIGURE +sdh_configure CC="$CC -fPIC" $NAUTY_CONFIGURE --enable-static --disable-shared sdh_make # No install target so we resort to manual copy @@ -18,7 +18,7 @@ countg countneg cubhamg deledgeg delptg dimacs2g directg dreadnaut dretodot dretog edgetransg genbg genbgL geng gengL genposetg genquarticg genrang genspecialg gentourng gentreeg genktreeg hamheuristic labelg linegraphg listg multig nbrhoodg newedgeg pickg planarg productg ranlabg ransubg shortg showg -subdivideg twohamg underlyingg vcolg watercluster2 NRswitchg" +subdivideg twohamg underlyingg uniqg vcolg watercluster2 NRswitchg" sdh_install $PROGRAMS "$SAGE_LOCAL/bin" sdh_install nauty.h "$SAGE_LOCAL/include/nauty" diff --git a/build/pkgs/packaging/checksums.ini b/build/pkgs/packaging/checksums.ini index 4b1b2974bde..560bd2a20a4 100644 --- a/build/pkgs/packaging/checksums.ini +++ b/build/pkgs/packaging/checksums.ini @@ -1,4 +1,4 @@ tarball=packaging-VERSION-py3-none-any.whl -sha1=21573cef174a05ac2794b34f3841d6f9ea9fa507 -sha256=2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 +sha1=a050029d1e0c1b95b3ddcd566be4ad352cd42666 +sha256=5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 upstream_url=https://pypi.io/packages/py3/p/packaging/packaging-VERSION-py3-none-any.whl diff --git a/build/pkgs/packaging/package-version.txt b/build/pkgs/packaging/package-version.txt index d9133a54b63..0dad123924d 100644 --- a/build/pkgs/packaging/package-version.txt +++ b/build/pkgs/packaging/package-version.txt @@ -1 +1 @@ -24.0 +24.1 diff --git a/build/pkgs/pip/checksums.ini b/build/pkgs/pip/checksums.ini index 62ecf000278..0afec1866ed 100644 --- a/build/pkgs/pip/checksums.ini +++ b/build/pkgs/pip/checksums.ini @@ -1,4 +1,4 @@ tarball=pip-VERSION-py3-none-any.whl -sha1=e44313ae1e6af3c2bd3b60ab2fa8c34308d00555 -sha256=ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc +sha1=044a04440eef697c8ec9e03544117345c57aa683 +sha256=2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2 upstream_url=https://pypi.io/packages/py3/p/pip/pip-VERSION-py3-none-any.whl diff --git a/build/pkgs/pip/package-version.txt b/build/pkgs/pip/package-version.txt index d9133a54b63..9dc0ade5023 100644 --- a/build/pkgs/pip/package-version.txt +++ b/build/pkgs/pip/package-version.txt @@ -1 +1 @@ -24.0 +24.2 diff --git a/build/pkgs/primecount/checksums.ini b/build/pkgs/primecount/checksums.ini index c3f0b9ce77d..4e9965b9965 100644 --- a/build/pkgs/primecount/checksums.ini +++ b/build/pkgs/primecount/checksums.ini @@ -1,4 +1,4 @@ tarball=primecount-VERSION.tar.gz -sha1=3854ef6c7f454086f31aa80d68f628c5b685d702 -sha256=e9a1fa2c41b9a7b84f2bead21b53cc9f7e2a5a0a34ddd818431a4e789aa44230 +sha1=dac5db8fb6aadd8b96fcb190073becd420a2cc31 +sha256=d867ac18cc52c0f7014682169988a76f39e4cd56f8ce78fb56e064499b1d66bb upstream_url=https://github.com/kimwalisch/primecount/archive/refs/tags/vVERSION.tar.gz diff --git a/build/pkgs/primecount/package-version.txt b/build/pkgs/primecount/package-version.txt index 38abeb202c0..9ad4d4f295e 100644 --- a/build/pkgs/primecount/package-version.txt +++ b/build/pkgs/primecount/package-version.txt @@ -1 +1 @@ -7.6 +7.14 diff --git a/build/pkgs/primesieve/checksums.ini b/build/pkgs/primesieve/checksums.ini index faae4fa7982..5bfc159aae0 100644 --- a/build/pkgs/primesieve/checksums.ini +++ b/build/pkgs/primesieve/checksums.ini @@ -1,4 +1,4 @@ tarball=primesieve-VERSION.tar.gz -sha1=cb0a7c49b37b51980fc610d3041b9591c67a460c -sha256=b29a7ec855764ce7474d00be03e1d83209bd097faa3778382dfb73a06866097e +sha1=19941abdd52bc44a8714ead8da4af0a7ddd92ec5 +sha256=eb7081adebe8030e93b3675c74ac603438d10a36792246b274c79f11d8a987ce upstream_url=https://github.com/kimwalisch/primesieve/archive/refs/tags/vVERSION.tar.gz diff --git a/build/pkgs/primesieve/package-version.txt b/build/pkgs/primesieve/package-version.txt index 2dbc24b32d3..800fd35ee86 100644 --- a/build/pkgs/primesieve/package-version.txt +++ b/build/pkgs/primesieve/package-version.txt @@ -1 +1 @@ -11.0 +12.4 diff --git a/build/pkgs/pyproject_api/checksums.ini b/build/pkgs/pyproject_api/checksums.ini index cb551b5f7dd..dc2899fad91 100644 --- a/build/pkgs/pyproject_api/checksums.ini +++ b/build/pkgs/pyproject_api/checksums.ini @@ -1,4 +1,4 @@ tarball=pyproject_api-VERSION-py3-none-any.whl -sha1=5ea24c784a68fd0ef0228c332dc078ce64387eb8 -sha256=4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675 +sha1=3723eb52bd6844f30f30f6f5b3723ce0f57a3cbf +sha256=2dc1654062c2b27733d8fd4cdda672b22fe8741ef1dde8e3a998a9547b071eeb upstream_url=https://pypi.io/packages/py3/p/pyproject_api/pyproject_api-VERSION-py3-none-any.whl diff --git a/build/pkgs/pyproject_api/package-version.txt b/build/pkgs/pyproject_api/package-version.txt index 9c6d6293b1a..943f9cbc4ec 100644 --- a/build/pkgs/pyproject_api/package-version.txt +++ b/build/pkgs/pyproject_api/package-version.txt @@ -1 +1 @@ -1.6.1 +1.7.1 diff --git a/build/pkgs/pyproject_hooks/checksums.ini b/build/pkgs/pyproject_hooks/checksums.ini index 4688d10283b..fbb1f71394e 100644 --- a/build/pkgs/pyproject_hooks/checksums.ini +++ b/build/pkgs/pyproject_hooks/checksums.ini @@ -1,4 +1,4 @@ tarball=pyproject_hooks-VERSION-py3-none-any.whl -sha1=6c99163c52786fb97eac8b4e38cc13fa3af141a9 -sha256=283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 +sha1=f8c16752e1deea6a3e9a261c6725c1af408d04e7 +sha256=7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2 upstream_url=https://pypi.io/packages/py3/p/pyproject_hooks/pyproject_hooks-VERSION-py3-none-any.whl diff --git a/build/pkgs/pyproject_hooks/package-version.txt b/build/pkgs/pyproject_hooks/package-version.txt index 3eefcb9dd5b..9084fa2f716 100644 --- a/build/pkgs/pyproject_hooks/package-version.txt +++ b/build/pkgs/pyproject_hooks/package-version.txt @@ -1 +1 @@ -1.0.0 +1.1.0 diff --git a/build/pkgs/pyproject_metadata/checksums.ini b/build/pkgs/pyproject_metadata/checksums.ini index 446d55ef265..91d3eb258c8 100644 --- a/build/pkgs/pyproject_metadata/checksums.ini +++ b/build/pkgs/pyproject_metadata/checksums.ini @@ -1,4 +1,4 @@ -tarball=pyproject-metadata-VERSION.tar.gz -sha1=41fba5c33917d77b9364fadb76e590e86789634d -sha256=0a94f18b108b9b21f3a26a3d541f056c34edcb17dc872a144a15618fed7aef67 -upstream_url=https://pypi.io/packages/source/p/pyproject_metadata/pyproject-metadata-VERSION.tar.gz +tarball=pyproject_metadata-VERSION-py3-none-any.whl +sha1=59998bbcd31cc63f3e95f3ad8120ff71326595a0 +sha256=ad858d448e1d3a1fb408ac5bac9ea7743e7a8bbb472f2693aaa334d2db42f526 +upstream_url=https://pypi.io/packages/py3/p/pyproject_metadata/pyproject_metadata-VERSION-py3-none-any.whl diff --git a/build/pkgs/pyproject_metadata/dependencies b/build/pkgs/pyproject_metadata/dependencies index 3df264eee42..a0e3d35a70e 100644 --- a/build/pkgs/pyproject_metadata/dependencies +++ b/build/pkgs/pyproject_metadata/dependencies @@ -1,4 +1,4 @@ - packaging pyparsing | $(PYTHON_TOOLCHAIN) $(PYTHON) +packaging | pip $(PYTHON) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/pyproject_metadata/package-version.txt b/build/pkgs/pyproject_metadata/package-version.txt index 39e898a4f95..a3df0a6959e 100644 --- a/build/pkgs/pyproject_metadata/package-version.txt +++ b/build/pkgs/pyproject_metadata/package-version.txt @@ -1 +1 @@ -0.7.1 +0.8.0 diff --git a/build/pkgs/pyproject_metadata/spkg-install.in b/build/pkgs/pyproject_metadata/spkg-install.in deleted file mode 100644 index 37ac1a53437..00000000000 --- a/build/pkgs/pyproject_metadata/spkg-install.in +++ /dev/null @@ -1,2 +0,0 @@ -cd src -sdh_pip_install . diff --git a/build/pkgs/python3/checksums.ini b/build/pkgs/python3/checksums.ini index 73dddf24722..3bac47d26a6 100644 --- a/build/pkgs/python3/checksums.ini +++ b/build/pkgs/python3/checksums.ini @@ -1,4 +1,4 @@ tarball=Python-VERSION.tar.xz -sha1=c221421f3ba734daaf013dbdc7b48aa725cea18e -sha256=f6d419a6d8743ab26700801b4908d26d97e8b986e14f95de31b32de2b0e79554 +sha1=d9b83c17a717e1cbd3ab6bd14cfe3e508e6d87b2 +sha256=fa8a2e12c5e620b09f53e65bcd87550d2e5a1e2e04bf8ba991dcc55113876397 upstream_url=https://www.python.org/ftp/python/VERSION/Python-VERSION.tar.xz diff --git a/build/pkgs/python3/package-version.txt b/build/pkgs/python3/package-version.txt index 455808f8e19..d9506ceba51 100644 --- a/build/pkgs/python3/package-version.txt +++ b/build/pkgs/python3/package-version.txt @@ -1 +1 @@ -3.12.4 +3.12.5 diff --git a/build/pkgs/sage_conf/version_requirements.txt b/build/pkgs/sage_conf/version_requirements.txt index 1c2fc34d235..f9a29f411e0 100644 --- a/build/pkgs/sage_conf/version_requirements.txt +++ b/build/pkgs/sage_conf/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-conf ~= 10.5b3 +sage-conf ~= 10.5b4 diff --git a/build/pkgs/sage_docbuild/version_requirements.txt b/build/pkgs/sage_docbuild/version_requirements.txt index 42b2834c11f..8c69b2c724f 100644 --- a/build/pkgs/sage_docbuild/version_requirements.txt +++ b/build/pkgs/sage_docbuild/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-docbuild ~= 10.5b3 +sage-docbuild ~= 10.5b4 diff --git a/build/pkgs/sage_setup/version_requirements.txt b/build/pkgs/sage_setup/version_requirements.txt index 1cc1b467e16..1dcac493c5d 100644 --- a/build/pkgs/sage_setup/version_requirements.txt +++ b/build/pkgs/sage_setup/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-setup ~= 10.5b3 +sage-setup ~= 10.5b4 diff --git a/build/pkgs/sage_sws2rst/version_requirements.txt b/build/pkgs/sage_sws2rst/version_requirements.txt index 4fc39dc2f32..da184780274 100644 --- a/build/pkgs/sage_sws2rst/version_requirements.txt +++ b/build/pkgs/sage_sws2rst/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sage-sws2rst ~= 10.5b3 +sage-sws2rst ~= 10.5b4 diff --git a/build/pkgs/sagelib/version_requirements.txt b/build/pkgs/sagelib/version_requirements.txt index 70c35b74cca..6e3bb64c65c 100644 --- a/build/pkgs/sagelib/version_requirements.txt +++ b/build/pkgs/sagelib/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-standard ~= 10.5b3 +sagemath-standard ~= 10.5b4 diff --git a/build/pkgs/sagemath_bliss/version_requirements.txt b/build/pkgs/sagemath_bliss/version_requirements.txt index 21f92c61b22..c6fb0e82600 100644 --- a/build/pkgs/sagemath_bliss/version_requirements.txt +++ b/build/pkgs/sagemath_bliss/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-bliss ~= 10.5b3 +sagemath-bliss ~= 10.5b4 diff --git a/build/pkgs/sagemath_categories/version_requirements.txt b/build/pkgs/sagemath_categories/version_requirements.txt index f78f1f84751..66dbe5766e0 100644 --- a/build/pkgs/sagemath_categories/version_requirements.txt +++ b/build/pkgs/sagemath_categories/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-categories ~= 10.5b3 +sagemath-categories ~= 10.5b4 diff --git a/build/pkgs/sagemath_coxeter3/version_requirements.txt b/build/pkgs/sagemath_coxeter3/version_requirements.txt index f1942993ae7..a955c0b9902 100644 --- a/build/pkgs/sagemath_coxeter3/version_requirements.txt +++ b/build/pkgs/sagemath_coxeter3/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-coxeter3 ~= 10.5b3 +sagemath-coxeter3 ~= 10.5b4 diff --git a/build/pkgs/sagemath_doc_html/dependencies b/build/pkgs/sagemath_doc_html/dependencies index 801c36fcd9e..40717629a77 100644 --- a/build/pkgs/sagemath_doc_html/dependencies +++ b/build/pkgs/sagemath_doc_html/dependencies @@ -1,4 +1,4 @@ -sagelib sphinx sphinx_copybutton sphinx_inline_tabs pplpy_doc | $(SAGERUNTIME) maxima networkx scipy sympy matplotlib pillow mathjax mpmath ipykernel jupyter_client conway_polynomials tachyon jmol ipywidgets sage_docbuild elliptic_curves furo fpylll graphs +sagelib sphinx sphinx_copybutton sphinx_inline_tabs pplpy_doc | $(SAGERUNTIME) maxima networkx scipy sympy matplotlib pillow mathjax mpmath ipykernel jupyter_client conway_polynomials tachyon ipywidgets sage_docbuild elliptic_curves furo fpylll graphs # Building the documentation has many dependencies, because all # documented modules are imported and because we use matplotlib to diff --git a/build/pkgs/sagemath_environment/version_requirements.txt b/build/pkgs/sagemath_environment/version_requirements.txt index 813d6d79688..480e8285f3b 100644 --- a/build/pkgs/sagemath_environment/version_requirements.txt +++ b/build/pkgs/sagemath_environment/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-environment ~= 10.5b3 +sagemath-environment ~= 10.5b4 diff --git a/build/pkgs/sagemath_mcqd/version_requirements.txt b/build/pkgs/sagemath_mcqd/version_requirements.txt index e1297c038d3..ad9c0830686 100644 --- a/build/pkgs/sagemath_mcqd/version_requirements.txt +++ b/build/pkgs/sagemath_mcqd/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-mcqd ~= 10.5b3 +sagemath-mcqd ~= 10.5b4 diff --git a/build/pkgs/sagemath_meataxe/version_requirements.txt b/build/pkgs/sagemath_meataxe/version_requirements.txt index 867597deb1e..0782881aee0 100644 --- a/build/pkgs/sagemath_meataxe/version_requirements.txt +++ b/build/pkgs/sagemath_meataxe/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-meataxe ~= 10.5b3 +sagemath-meataxe ~= 10.5b4 diff --git a/build/pkgs/sagemath_objects/version_requirements.txt b/build/pkgs/sagemath_objects/version_requirements.txt index 83940689774..a652cfa09f5 100644 --- a/build/pkgs/sagemath_objects/version_requirements.txt +++ b/build/pkgs/sagemath_objects/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-objects ~= 10.5b3 +sagemath-objects ~= 10.5b4 diff --git a/build/pkgs/sagemath_repl/version_requirements.txt b/build/pkgs/sagemath_repl/version_requirements.txt index a4f097b1b6e..e73e1ae8031 100644 --- a/build/pkgs/sagemath_repl/version_requirements.txt +++ b/build/pkgs/sagemath_repl/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-repl ~= 10.5b3 +sagemath-repl ~= 10.5b4 diff --git a/build/pkgs/sagemath_sirocco/version_requirements.txt b/build/pkgs/sagemath_sirocco/version_requirements.txt index ba5126542f8..d6d6f3820de 100644 --- a/build/pkgs/sagemath_sirocco/version_requirements.txt +++ b/build/pkgs/sagemath_sirocco/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-sirocco ~= 10.5b3 +sagemath-sirocco ~= 10.5b4 diff --git a/build/pkgs/sagemath_tdlib/version_requirements.txt b/build/pkgs/sagemath_tdlib/version_requirements.txt index 02cbd8f2d3f..e0801e2814c 100644 --- a/build/pkgs/sagemath_tdlib/version_requirements.txt +++ b/build/pkgs/sagemath_tdlib/version_requirements.txt @@ -1,2 +1,2 @@ # This file is updated on every release by the sage-update-version script -sagemath-tdlib ~= 10.5b3 +sagemath-tdlib ~= 10.5b4 diff --git a/build/pkgs/sagetex/dependencies_check b/build/pkgs/sagetex/dependencies_check index d24e23242d4..ff812202db7 100644 --- a/build/pkgs/sagetex/dependencies_check +++ b/build/pkgs/sagetex/dependencies_check @@ -1,4 +1,4 @@ -$(SAGERUNTIME) sympy elliptic_curves jmol +$(SAGERUNTIME) sympy elliptic_curves To build SageTeX, you just need Python, but to test (SAGE_CHECK=yes) SageTeX, you actually need to run Sage, produce plots,... diff --git a/build/pkgs/setuptools/checksums.ini b/build/pkgs/setuptools/checksums.ini index 2436821630d..97d83ec2dd7 100644 --- a/build/pkgs/setuptools/checksums.ini +++ b/build/pkgs/setuptools/checksums.ini @@ -1,4 +1,4 @@ tarball=setuptools-VERSION-py3-none-any.whl -sha1=49841be6743b2d129d01d02d5fd339dd693c99dc -sha256=c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32 +sha1=3756539d45341ca5cec9e2dfe11539faa066f5cd +sha256=b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e upstream_url=https://pypi.io/packages/py3/s/setuptools/setuptools-VERSION-py3-none-any.whl diff --git a/build/pkgs/setuptools/package-version.txt b/build/pkgs/setuptools/package-version.txt index 2a93f495d25..153e4cd6210 100644 --- a/build/pkgs/setuptools/package-version.txt +++ b/build/pkgs/setuptools/package-version.txt @@ -1 +1 @@ -69.5.1 +73.0.1 diff --git a/build/pkgs/trove_classifiers/checksums.ini b/build/pkgs/trove_classifiers/checksums.ini index 8d25c121cc2..42e0ec6eefe 100644 --- a/build/pkgs/trove_classifiers/checksums.ini +++ b/build/pkgs/trove_classifiers/checksums.ini @@ -1,4 +1,4 @@ tarball=trove_classifiers-VERSION-py3-none-any.whl -sha1=36240d053d16400380aee01f0879785693008a96 -sha256=678bd6fcc5218d72e3304e27a608acc9b91e17bd00c3f3d8c968497c843ad98b +sha1=8219f839a8223a9dd0912cde22d579cfa75a516e +sha256=ccc57a33717644df4daca018e7ec3ef57a835c48e96a1e71fc07eb7edac67af6 upstream_url=https://pypi.io/packages/py3/t/trove_classifiers/trove_classifiers-VERSION-py3-none-any.whl diff --git a/build/pkgs/trove_classifiers/package-version.txt b/build/pkgs/trove_classifiers/package-version.txt index c296ac66b65..981877628c9 100644 --- a/build/pkgs/trove_classifiers/package-version.txt +++ b/build/pkgs/trove_classifiers/package-version.txt @@ -1 +1 @@ -2024.4.10 +2024.7.2 diff --git a/build/pkgs/typing_extensions/checksums.ini b/build/pkgs/typing_extensions/checksums.ini index c22c17569af..5d4ca8ca5f2 100644 --- a/build/pkgs/typing_extensions/checksums.ini +++ b/build/pkgs/typing_extensions/checksums.ini @@ -1,4 +1,4 @@ tarball=typing_extensions-VERSION-py3-none-any.whl -sha1=049c6031f754e1c33932ce1c2ad78b857a70a244 -sha256=b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594 +sha1=0fb5b2732cc421561b1348cac1334eb6a4e0bb7f +sha256=04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d upstream_url=https://pypi.io/packages/py3/t/typing_extensions/typing_extensions-VERSION-py3-none-any.whl diff --git a/build/pkgs/typing_extensions/package-version.txt b/build/pkgs/typing_extensions/package-version.txt index 815588ef140..f1cd7de1de5 100644 --- a/build/pkgs/typing_extensions/package-version.txt +++ b/build/pkgs/typing_extensions/package-version.txt @@ -1 +1 @@ -4.12.0 +4.12.2 diff --git a/build/pkgs/wheel/checksums.ini b/build/pkgs/wheel/checksums.ini index ea4d40c1f27..3157a2efcec 100644 --- a/build/pkgs/wheel/checksums.ini +++ b/build/pkgs/wheel/checksums.ini @@ -1,4 +1,4 @@ tarball=wheel-VERSION-py3-none-any.whl -sha1=71a83a2237cb57ab45bdafed364564e36ca5dc95 -sha256=55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81 +sha1=65ec55742da04152c8b06d6586fb36d779d7883e +sha256=2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f upstream_url=https://pypi.io/packages/py3/w/wheel/wheel-VERSION-py3-none-any.whl diff --git a/build/pkgs/wheel/package-version.txt b/build/pkgs/wheel/package-version.txt index 8298bb08b2d..a8ab6c9666a 100644 --- a/build/pkgs/wheel/package-version.txt +++ b/build/pkgs/wheel/package-version.txt @@ -1 +1 @@ -0.43.0 +0.44.0 diff --git a/m4/pyproject_toml_metadata.m4 b/m4/pyproject_toml_metadata.m4 index 0d9824b1f2e..6a731e36c6a 100644 --- a/m4/pyproject_toml_metadata.m4 +++ b/m4/pyproject_toml_metadata.m4 @@ -17,5 +17,10 @@ classifiers = [ "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering :: Mathematics", ] -urls = {Homepage = "https://www.sagemath.org"} +urls = {download = "https://doc.sagemath.org/html/en/installation/index.html", + "release notes" = "https://github.com/sagemath/sage/releases", + source = "https://github.com/sagemath/sage", + documentation = "https://doc.sagemath.org", + homepage = "https://www.sagemath.org", + tracker = "https://github.com/sagemath/sage/issues"} requires-python = ">=3.9, <3.13" diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sage-conf_conda/VERSION.txt +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-bliss/VERSION.txt +++ b/pkgs/sagemath-bliss/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-coxeter3/VERSION.txt +++ b/pkgs/sagemath-coxeter3/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-mcqd/VERSION.txt +++ b/pkgs/sagemath-mcqd/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-meataxe/VERSION.txt +++ b/pkgs/sagemath-meataxe/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-sirocco/VERSION.txt +++ b/pkgs/sagemath-sirocco/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/pkgs/sagemath-tdlib/VERSION.txt +++ b/pkgs/sagemath-tdlib/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/src/VERSION.txt b/src/VERSION.txt index 674fddc2fcd..da4a0151f8b 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.5.beta3 +10.5.beta4 diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index 81efd01a4d8..8ab309685c7 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -1,206 +1,9 @@ #!/usr/bin/env sage-python -import argparse -import os import sys -# Note: the DOT_SAGE and SAGE_STARTUP_FILE environment variables have already been set by sage-env -DOT_SAGE = os.environ.get('DOT_SAGE', os.path.join(os.environ.get('HOME'), - '.sage')) - -# Override to not pick up user configuration, see Issue #20270 -os.environ['SAGE_STARTUP_FILE'] = os.path.join(DOT_SAGE, 'init-doctests.sage') - - -def _get_optional_defaults(): - """Return the default value for the --optional flag.""" - optional = ['sage', 'optional'] - - return ','.join(optional) +from sage.doctest.__main__ import main if __name__ == "__main__": - parser = argparse.ArgumentParser(usage="sage -t [options] filenames", - description="Run all tests in a file or a list of files whose extensions " - "are one of the following: " - ".py, .pyx, .pxd, .pxi, .sage, .spyx, .tex, .rst.") - parser.add_argument("-p", "--nthreads", dest="nthreads", - type=int, nargs='?', const=0, default=1, metavar="N", - help="test in parallel using N threads, with 0 interpreted as max(2, min(8, cpu_count())); " - "when run under the control of the GNU make jobserver (make -j), request as most N job slots") - parser.add_argument("-T", "--timeout", type=int, default=-1, help="timeout (in seconds) for doctesting one file, 0 for no timeout") - what = parser.add_mutually_exclusive_group() - what.add_argument("-a", "--all", action="store_true", default=False, help="test all files in the Sage library") - what.add_argument("--installed", action="store_true", default=False, help="test all installed modules of the Sage library") - parser.add_argument("--logfile", type=argparse.FileType('a'), metavar="FILE", help="log all output to FILE") - - parser.add_argument("--format", choices=["sage", "github"], default="sage", - help="set format of error messages and warnings") - parser.add_argument("-l", "--long", action="store_true", default=False, help="include lines with the phrase 'long time'") - parser.add_argument("-s", "--short", dest="target_walltime", nargs='?', - type=int, default=-1, const=300, metavar="SECONDS", - help="run as many doctests as possible in about 300 seconds (or the number of seconds given as an optional argument)") - parser.add_argument("--warn-long", dest="warn_long", nargs='?', - type=float, default=-1.0, const=1.0, metavar="SECONDS", - help="warn if tests take more time than SECONDS") - # By default, include all tests marked 'sagemath_doc_html' -- see - # https://github.com/sagemath/sage/issues/25345 and - # https://github.com/sagemath/sage/issues/26110: - parser.add_argument("--optional", metavar="FEATURES", default=_get_optional_defaults(), - help='only run tests including one of the "# optional" tags listed in FEATURES (separated by commas); ' - 'if "sage" is listed, will also run the standard doctests; ' - 'if "sagemath_doc_html" is listed, will also run the tests relying on the HTML documentation; ' - 'if "optional" is listed, will also run tests for installed optional packages or detected features; ' - 'if "external" is listed, will also run tests for available external software; ' - 'if set to "all", then all tests will be run; ' - 'use "!FEATURE" to disable tests marked "# optional - FEATURE". ' - 'Note that "!" needs to be quoted or escaped in the shell.') - parser.add_argument("--hide", metavar="FEATURES", default="", - help='run tests pretending that the software listed in FEATURES (separated by commas) is not installed; ' - 'if "all" is listed, will also hide features corresponding to all optional or experimental packages; ' - 'if "optional" is listed, will also hide features corresponding to optional packages.') - parser.add_argument("--probe", metavar="FEATURES", default="", - help='run tests that would not be run because one of the given FEATURES (separated by commas) is not installed; ' - 'report the tests that pass nevertheless') - parser.add_argument("--randorder", type=int, metavar="SEED", help="randomize order of tests") - parser.add_argument("--random-seed", dest="random_seed", type=int, metavar="SEED", help="random seed (integer) for fuzzing doctests", - default=os.environ.get("SAGE_DOCTEST_RANDOM_SEED")) - parser.add_argument("--global-iterations", "--global_iterations", type=int, default=0, help="repeat the whole testing process this many times") - parser.add_argument("--file-iterations", "--file_iterations", type=int, default=0, help="repeat each file this many times, stopping on the first failure") - parser.add_argument("--environment", type=str, default="sage.repl.ipython_kernel.all_jupyter", help="name of a module that provides the global environment for tests") - - parser.add_argument("-i", "--initial", action="store_true", default=False, help="only show the first failure in each file") - parser.add_argument("--exitfirst", action="store_true", default=False, help="end the test run immediately after the first failure or unexpected exception") - parser.add_argument("--force_lib", "--force-lib", action="store_true", default=False, help="do not import anything from the tested file(s)") - parser.add_argument("--if-installed", action="store_true", default=False, help="skip Python/Cython files that are not installed as modules") - parser.add_argument("--abspath", action="store_true", default=False, help="print absolute paths rather than relative paths") - parser.add_argument("--verbose", action="store_true", default=False, help="print debugging output during the test") - parser.add_argument("-d", "--debug", action="store_true", default=False, help="drop into a python debugger when an unexpected error is raised") - parser.add_argument("--only-errors", action="store_true", default=False, help="only output failures, not test successes") - - parser.add_argument("--gdb", action="store_true", default=False, help="run doctests under the control of gdb") - parser.add_argument("--lldb", action="store_true", default=False, help="run doctests under the control of lldb") - parser.add_argument("--valgrind", "--memcheck", action="store_true", default=False, - help="run doctests using Valgrind's memcheck tool. The log " - "files are named sage-memcheck.PID and can be found in " + - os.path.join(DOT_SAGE, "valgrind")) - parser.add_argument("--massif", action="store_true", default=False, - help="run doctests using Valgrind's massif tool. The log " - "files are named sage-massif.PID and can be found in " + - os.path.join(DOT_SAGE, "valgrind")) - parser.add_argument("--cachegrind", action="store_true", default=False, - help="run doctests using Valgrind's cachegrind tool. The log " - "files are named sage-cachegrind.PID and can be found in " + - os.path.join(DOT_SAGE, "valgrind")) - parser.add_argument("--omega", action="store_true", default=False, - help="run doctests using Valgrind's omega tool. The log " - "files are named sage-omega.PID and can be found in " + - os.path.join(DOT_SAGE, "valgrind")) - - parser.add_argument("-f", "--failed", action="store_true", default=False, - help="doctest only those files that failed in the previous run") - what.add_argument("-n", "--new", action="store_true", default=False, - help="doctest only those files that have been changed in the repository and not yet been committed") - parser.add_argument("--show-skipped", "--show_skipped", action="store_true", default=False, - help="print a summary at the end of each file of optional tests that were skipped") - - parser.add_argument("--stats_path", "--stats-path", default=os.path.join(DOT_SAGE, "timings2.json"), - help="path to a json dictionary for timings and failure status for each file from previous runs; it will be updated in this run") - parser.add_argument("--baseline_stats_path", "--baseline-stats-path", default=None, - help="path to a json dictionary for timings and failure status for each file, to be used as a baseline; it will not be updated") - - class GCAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): - gcopts = dict(DEFAULT=0, ALWAYS=1, NEVER=-1) - new_value = gcopts[values] - setattr(namespace, self.dest, new_value) - - parser.add_argument("--gc", - choices=["DEFAULT", "ALWAYS", "NEVER"], - default=0, - action=GCAction, - help="control garbarge collection " - "(ALWAYS: collect garbage before every test; NEVER: disable gc; DEFAULT: Python default)") - - # The --serial option is only really for internal use, better not - # document it. - parser.add_argument("--serial", action="store_true", default=False, help=argparse.SUPPRESS) - # Same for --die_timeout - parser.add_argument("--die_timeout", type=int, default=-1, help=argparse.SUPPRESS) - - parser.add_argument("filenames", help="file names", nargs='*') - - # custom treatment to separate properly - # one or several file names at the end - new_arguments = [] - need_filenames = True - in_filenames = False - afterlog = False - for arg in sys.argv[1:]: - if arg in ('-n', '--new', '-a', '--all', '--installed'): - need_filenames = False - elif need_filenames and not (afterlog or in_filenames) and os.path.exists(arg): - in_filenames = True - new_arguments.append('--') - new_arguments.append(arg) - afterlog = arg in ['--logfile', '--stats_path', '--stats-path', - '--baseline_stats_path', '--baseline-stats-path'] - - args = parser.parse_args(new_arguments) - - if not args.filenames and not (args.all or args.new or args.installed): - print('either use --new, --all, --installed, or some filenames') - sys.exit(2) - - # Limit the number of threads to 2 to save system resources. - # See Issue #23713, #23892, #30351 - if sys.platform == 'darwin': - os.environ["OMP_NUM_THREADS"] = "1" - else: - os.environ["OMP_NUM_THREADS"] = "2" - - os.environ["SAGE_NUM_THREADS"] = "2" - - from sage.doctest.control import DocTestController - DC = DocTestController(args, args.filenames) - err = DC.run() - - # Issue #33521: Do not run pytest if the pytest configuration is not available. - # This happens when the source tree is not available and SAGE_SRC falls back - # to SAGE_LIB. - from sage.env import SAGE_SRC - if not all(os.path.isfile(os.path.join(SAGE_SRC, f)) - for f in ["conftest.py", "tox.ini"]): - sys.exit(err) - - try: - exit_code_pytest = 0 - import pytest - pytest_options = [] - if args.verbose: - pytest_options.append("-v") - - # #35999: no filename in arguments defaults to "src" - if not args.filenames: - filenames = [SAGE_SRC] - else: - # #31924: Do not run pytest on individual Python files unless - # they match the pytest file pattern. However, pass names - # of directories. We use 'not os.path.isfile(f)' for this so that - # we do not silently hide typos. - filenames = [f for f in args.filenames - if f.endswith("_test.py") or not os.path.isfile(f)] - if filenames: - print(f"Running pytest on {filenames} with options {pytest_options}") - exit_code_pytest = pytest.main(filenames + pytest_options) - if exit_code_pytest == 5: - # Exit code 5 means there were no test files, pass in this case - exit_code_pytest = 0 - - except ModuleNotFoundError: - print("pytest is not installed in the venv, skip checking tests that rely on it") - - if err == 0: - sys.exit(exit_code_pytest) - else: - sys.exit(err) + sys.exit(main()) diff --git a/src/bin/sage-valgrind b/src/bin/sage-valgrind index ae0d5dae389..9c668c3bdd0 100755 --- a/src/bin/sage-valgrind +++ b/src/bin/sage-valgrind @@ -20,6 +20,7 @@ fi SUPP+=" --suppressions=$SAGE_EXTCODE/valgrind/pyalloc.supp" SUPP+=" --suppressions=$SAGE_EXTCODE/valgrind/sage.supp" SUPP+=" --suppressions=$SAGE_EXTCODE/valgrind/sage-additional.supp" +SUPP+=" --suppressions=$SAGE_EXTCODE/valgrind/valgrind-python.supp" MEMCHECK_FLAGS="--leak-resolution=high --leak-check=full --num-callers=25 $SUPP" diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 0ee1864e8ee..1c3e104e271 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.5.beta3' -SAGE_RELEASE_DATE='2024-09-03' -SAGE_VERSION_BANNER='SageMath version 10.5.beta3, Release Date: 2024-09-03' +SAGE_VERSION='10.5.beta4' +SAGE_RELEASE_DATE='2024-09-15' +SAGE_VERSION_BANNER='SageMath version 10.5.beta4, Release Date: 2024-09-15' diff --git a/src/doc/common/static/custom-tabs.css b/src/doc/common/static/custom-tabs.css new file mode 100644 index 00000000000..b38f14b1e3d --- /dev/null +++ b/src/doc/common/static/custom-tabs.css @@ -0,0 +1,13 @@ +/* Additional css for tabs */ + +.tab-set { + margin-top: 0px; +} + +.tab-set > label { + padding: 0.1em var(--tabs--pading-x); +} + +p.with-sage-tab { + margin-bottom:0.25rem +} diff --git a/src/doc/en/faq/faq-usage.rst b/src/doc/en/faq/faq-usage.rst index d03881c36a2..393a9c369e5 100644 --- a/src/doc/en/faq/faq-usage.rst +++ b/src/doc/en/faq/faq-usage.rst @@ -479,35 +479,6 @@ How do I run sage in daemon mode, i.e. as a service? There are several possibilities. Use ``screen``, ``nohup`` or ``disown``. -The show command for plotting 3-D objects does not work. -"""""""""""""""""""""""""""""""""""""""""""""""""""""""" - -The default live 3-D plotting for Sage 6.4+ uses -`Jmol/JSmol `_ -for viewing. From the command line the Jmol Java application is used, -and for in browser viewing either pure javascript or a Java applet -is used. By default in browsers pure javascript is used to avoid -the problems with some browsers that do not support java applet -plugins (namely Chrome). On each browser worksheet there is a -checkbox which must be checked before a 3-D plot is generated if -the user wants to use the Java applet (the applet is a little faster -with complex plots). - -The most likely reason for a malfunction is that you do not have -a Java Run Time Environment (JRE) installed or you have one older than -version 1.7. If things work from the command line another possibility -is that your browser does not have the proper plugin to support Java -applets (at present, 2014, plugins do not work with most versions of -Chrome). Make sure you have installed either the IcedTea browser -plugin (for linux see your package manager), see: -`IcedTea `_, -or the Oracle Java plugin see: -`Java `_. - -If you are using a Sage server over the web and even javascript rendering -does not work, you may have a problem with your browser's javascript -engine or have it turned off. - May I use Sage tools in a commercial environment? """"""""""""""""""""""""""""""""""""""""""""""""" diff --git a/src/doc/en/installation/index.rst b/src/doc/en/installation/index.rst index f88a92dc5c7..918d8fe9359 100644 --- a/src/doc/en/installation/index.rst +++ b/src/doc/en/installation/index.rst @@ -11,8 +11,7 @@ was made. More up-to-date information and details regarding supported platforms may have become available afterwards and can be found in the section "Availability and installation help" of the -`release tour `_ for each -SageMath release. +`release tour for each SageMath release `_. **Where would you like to run SageMath?** Pick one of the following sections. diff --git a/src/doc/en/installation/troubles.rst b/src/doc/en/installation/troubles.rst index 4a7c6d5581b..22d4e4772da 100644 --- a/src/doc/en/installation/troubles.rst +++ b/src/doc/en/installation/troubles.rst @@ -8,8 +8,8 @@ the :ref:`sec-installation-from-sources` or use one of the alternatives proposed at the end of :ref:`installation-guide`. If you have any problems building or running Sage, please take a look -at the Installation FAQ in the `Sage Release Tour -`_ corresponding to the version +at the `release tour +`_ corresponding to the version that you are installing. It may offer version-specific installation help that has become available after the release was made and is therefore not covered by this manual. diff --git a/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst b/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst index 3bc9308669b..c97efdecee4 100644 --- a/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst +++ b/src/doc/en/prep/Symbolics-and-Basic-Plotting.rst @@ -384,11 +384,6 @@ Below, you can experiment with several of the plotting options. Basic 3D Plotting ----------------- -There are several mechanisms for viewing three\-dimensional plots in -Sage, but we will stick to the default option in the notebook interface, -which is via javascript applets from the program `Jmol/JSmol -`_ . - Plotting a 3D plot is similar to plotting a 2D plot, but we need to specify ranges for two variables instead of one. diff --git a/src/doc/en/reference/graphs/index.rst b/src/doc/en/reference/graphs/index.rst index f681c083a08..c549876fe31 100644 --- a/src/doc/en/reference/graphs/index.rst +++ b/src/doc/en/reference/graphs/index.rst @@ -98,6 +98,7 @@ Libraries of algorithms sage/graphs/graph_decompositions/bandwidth sage/graphs/graph_decompositions/cutwidth sage/graphs/graph_decompositions/graph_products + sage/graphs/graph_decompositions/slice_decomposition sage/graphs/graph_decompositions/modular_decomposition sage/graphs/graph_decompositions/clique_separators sage/graphs/convexity_properties diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index d7fe85422ba..aaaf7a410c0 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -1326,6 +1326,9 @@ REFERENCES: no. 1 (2003): 97-111, http://www.moi.math.bas.bg/moiuser/~iliya/pdf_site/gf5srev.pdf. +.. [BS2007] \R. Bröker and P. Stevenhagen. *Constructing elliptic curves of + prime order*. [math.NT] (2007), :arXiv:`0712.2022`. + .. [BS2010] \P. Baseilhac and K. Shigechi. *A new current algebra and the reflection equation*. Lett. Math. Phys. **92** (2010), pp. 47-65. :arxiv:`0906.1482`. @@ -6405,7 +6408,7 @@ REFERENCES: .. [TCHP2008] Marc Tedder, Derek Corneil, Michel Habib and Christophe Paul, *Simple, linear-time modular decomposition*, 2008. - :arxiv:`0710.3901`. + :arxiv:`0710.3901v3`. .. [Tee1997] Tee, Garry J. "Continuous branches of inverses of the 12 Jacobi elliptic functions for real diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 8da15b32efa..8db70ca5e3e 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -133,7 +133,6 @@ This base class provides a lot more methods than a general parent:: 'is_commutative', 'is_field', 'is_integrally_closed', - 'is_noetherian', 'is_prime_field', 'is_subring', 'krull_dimension', diff --git a/src/doc/it/faq/faq-usage.rst b/src/doc/it/faq/faq-usage.rst index 5d3183769f7..677d1a24bc2 100644 --- a/src/doc/it/faq/faq-usage.rst +++ b/src/doc/it/faq/faq-usage.rst @@ -469,37 +469,6 @@ Ci sono parecchie possibilità. Puoi usare i programmi a riga di comando ``screen``, ``nohup`` o ``disown``. -Il comando show (mostra) per la visualizzazione di oggetti 3D non funziona. -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" - -La visualizzazione 3D in tempo reale per Sage dalla versione 6.4 in -avanti usa il pacchetto `Jmol/JSmol `_. -Dalla linea di comando viene utilizzata l'applicazione Java Jmol, -mentre per la visualizzazione dal browser viene usato puro javascript -oppure una Java applet. In genere nei browser è usato javascript puro -per evitare problemi con quei browser che non supportano i plugin per -le applet Java (ad esempio Chrome). In ogni worksheet su browser c'è -una casella da spuntare prima di generare una vista tridimensionale -qualora l'utente voglia usare l'applet Java (essa è un po' più veloce -con viste complicate). - -La ragione più probabile di un malfunzionamento è che non hai -installato l'ambiente runtime di Java (JRE) o che è più vecchio della -versione 1.7. Se le cose funzionano dalla riga di comando, -un'altra possibilità è che il tuo browser non abbia il plugin giusto -per supportare le Java applet (al momento, nel 2014, tali plugin non -lavorano con la maggior parte delle versioni di Chrome). Assicurati di -aver installato il plugin IcedTea (su Linux vedi il tuo gestore dei -pacchetti) o il plugin di Oracle Java -(vedi: `IcedTea `_ -e `Java `_). - -Se stai usando un server Sage sul web e anche la visualizzazione -tramite javascript non funziona, potresti avere un problema con la -funzionalità javascript del tuo browser, o potresti aver disabilitato -javascript. - - Posso usare gli strumenti di Sage in un ambiente commerciale? """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/src/sage/algebras/clifford_algebra.py b/src/sage/algebras/clifford_algebra.py index 2fe4a873ec6..92ad6c34d64 100644 --- a/src/sage/algebras/clifford_algebra.py +++ b/src/sage/algebras/clifford_algebra.py @@ -2914,7 +2914,7 @@ def __richcmp__(self, other, op): return contained and contains if op == op_NE: return not (contained and contains) - # remaining case < + # remaining case < return contained and not contains def __mul__(self, other): diff --git a/src/sage/algebras/fusion_rings/fusion_ring.py b/src/sage/algebras/fusion_rings/fusion_ring.py index 1ce6b920daa..56045d2dce6 100644 --- a/src/sage/algebras/fusion_rings/fusion_ring.py +++ b/src/sage/algebras/fusion_rings/fusion_ring.py @@ -1113,7 +1113,7 @@ def is_multiplicity_free(self): return k <= 2 ################################### - ### Braid group representations ### + # Braid group representations # ################################### def get_computational_basis(self, a, b, n_strands): diff --git a/src/sage/algebras/group_algebra.py b/src/sage/algebras/group_algebra.py index eb70b0c699f..8dc952f23b9 100644 --- a/src/sage/algebras/group_algebra.py +++ b/src/sage/algebras/group_algebra.py @@ -222,8 +222,9 @@ def _coerce_map_from_(self, S): hom_G = G.coerce_map_from(S_G) if hom_K is not None and hom_G is not None: return SetMorphism(S.Hom(self, category=self.category() | S.category()), - lambda x: self.sum_of_terms( (hom_G(g), hom_K(c)) for g,c in x )) + lambda x: self.sum_of_terms((hom_G(g), hom_K(c)) for g, c in x)) from sage.misc.persist import register_unpickle_override -register_unpickle_override('sage.algebras.group_algebras', 'GroupAlgebra', GroupAlgebra_class) +register_unpickle_override('sage.algebras.group_algebras', 'GroupAlgebra', + GroupAlgebra_class) diff --git a/src/sage/algebras/hall_algebra.py b/src/sage/algebras/hall_algebra.py index 84bebb13e38..bac7fa4f2fa 100644 --- a/src/sage/algebras/hall_algebra.py +++ b/src/sage/algebras/hall_algebra.py @@ -349,7 +349,7 @@ def coproduct_on_basis(self, la): S = self.tensor_square() if all(x == 1 for x in la): n = len(la) - return S.sum_of_terms([( (Partition([1]*r), Partition([1]*(n-r))), self._q**(-r*(n-r)) ) + return S.sum_of_terms([((Partition([1]*r), Partition([1]*(n-r))), self._q**(-r*(n-r))) for r in range(n+1)], distinct=True) I = HallAlgebraMonomials(self.base_ring(), self._q) @@ -482,9 +482,9 @@ def scalar(self, y): (4*q^2 + 9)/(q^2 - q) """ q = self.parent()._q - f = lambda la: ~( q**(sum(la) + 2*la.weighted_size()) + f = lambda la: ~(q**(sum(la) + 2*la.weighted_size()) * prod(prod((1 - q**-i) for i in range(1,k+1)) - for k in la.to_exp()) ) + for k in la.to_exp())) y = self.parent()(y) ret = q.parent().zero() for mx, cx in self: @@ -687,7 +687,7 @@ def coproduct_on_basis(self, a): + (q^-1)*I[1, 1] # I[1] + I[2] # I[1] + I[2, 1] # I[] """ S = self.tensor_square() - return S.prod(S.sum_of_terms([( (Partition([r]), Partition([n-r]) ), self._q**(-r*(n-r)) ) + return S.prod(S.sum_of_terms([((Partition([r]), Partition([n-r])), self._q**(-r*(n-r))) for r in range(n+1)], distinct=True) for n in a) def antipode_on_basis(self, a): diff --git a/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py b/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py index 3177185c088..8d674d0aaf1 100644 --- a/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py +++ b/src/sage/algebras/hecke_algebras/ariki_koike_algebra.py @@ -738,10 +738,10 @@ def algebra_generators(self): for i in range(self._n): r = list(self._zero_tuple) # Make a copy r[i] = 1 - d['L%s' % (i+1)] = self.monomial( (tuple(r), self._one_perm) ) + d['L%s' % (i+1)] = self.monomial((tuple(r), self._one_perm)) G = self._Pn.group_generators() for i in range(1, self._n): - d['T%s' % i] = self.monomial( (self._zero_tuple, G[i]) ) + d['T%s' % i] = self.monomial((self._zero_tuple, G[i])) return Family(sorted(d), lambda i: d[i]) def T(self, i=None): @@ -896,9 +896,9 @@ def product_on_basis(self, m1, m2): # combination of standard basis elements using the method and then, # recursively, multiply on the left and right by L1 and T2, # respectively. In other words, we multiply as L1*(T1*L2)*T2. - return ( self.monomial((L1, self._one_perm)) + return (self.monomial((L1, self._one_perm)) * self._product_Tw_L(T1, L2) - * self.monomial((self._zero_tuple, T2)) ) + * self.monomial((self._zero_tuple, T2))) def _product_LTwTv(self, L, w, v): r""" @@ -1023,7 +1023,7 @@ def _product_Tw_L(self, w, L): iaxpy(c, self._product_LTwTv(tuple(L), self._Pn.simple_reflections()[i], v), iL) # need T_i*T_v if a < b: - Ls = [ list(L) for k in range(b-a) ] # make copies of L + Ls = [list(L) for k in range(b-a)] # make copies of L for k in range(b-a): Ls[k][i-1] = a + k Ls[k][i] = b - k @@ -1031,7 +1031,7 @@ def _product_Tw_L(self, w, L): iaxpy(1, {(tuple(l), v): c for l in Ls}, iL) elif a > b: - Ls = [ list(L) for k in range(a-b) ] # make copies of L + Ls = [list(L) for k in range(a-b)] # make copies of L for k in range(a-b): Ls[k][i-1] = b + k Ls[k][i] = a - k @@ -1110,25 +1110,26 @@ def Ltuple(a, b): # return "small" powers of the generators without change if m < self._r: - return self.monomial( (Ltuple(0, m), self._one_perm) ) + return self.monomial((Ltuple(0, m), self._one_perm)) if i > 1: si = self._Pn.simple_reflections()[i-1] qsum = self.base_ring().one() - self._q**-1 # by calling _Li_power we avoid infinite recursion here - return ( self.sum_of_terms( ((Ltuple(c, m-c), si), qsum) for c in range(1, m) ) - + self._q**-1 * self.T(i-1) * self._Li_power(i-1, m) * self.T(i-1) ) + return (self.sum_of_terms(((Ltuple(c, m-c), si), qsum) for c in range(1, m)) + + self._q**-1 * self.T(i-1) * self._Li_power(i-1, m) * self.T(i-1)) # now left with the case i = 1 and m >= r if m > self._r: return self.monomial((Ltuple(0, 1), self._one_perm)) * self._Li_power(i,m-1) z = PolynomialRing(self.base_ring(), 'DUMMY').gen() - p = list(prod(z - val for val in self._u))#[:-1] - p.pop() # remove the highest power + p = list(prod(z - val for val in self._u)) # [:-1] + p.pop() # remove the highest power zero = self.base_ring().zero() return self._from_dict({(Ltuple(0, exp), self._one_perm): -coeff - for exp,coeff in enumerate(p) if coeff != zero}, + for exp, coeff in enumerate(p) + if coeff != zero}, remove_zeros=False, coerce=False) @cached_method @@ -1294,7 +1295,7 @@ def _from_LT_basis(self, m): True """ ret = self.prod(self.L(i+1)**k for i,k in enumerate(m[0])) - return ret * self.monomial( (self._zero_tuple, m[1]) ) + return ret * self.monomial((self._zero_tuple, m[1])) @cached_method def algebra_generators(self): @@ -1337,9 +1338,9 @@ def T(self, i=None): return [self.T(j) for j in range(self._n)] if i == 0: - return self.monomial( ((1,) + self._zero_tuple[1:], self._one_perm) ) + return self.monomial(((1,) + self._zero_tuple[1:], self._one_perm)) s = self._Pn.simple_reflections() - return self.monomial( (self._zero_tuple, s[i]) ) + return self.monomial((self._zero_tuple, s[i])) @cached_method def L(self, i=None): @@ -1514,7 +1515,7 @@ def product_on_basis(self, m1, m2): return L * M * R # The current product of T's and the type A Hecke algebra - tprod = [( [(k, a) for k, a in enumerate(t2) if a != 0], {s2: one} )] + tprod = [([(k, a) for k, a in enumerate(t2) if a != 0], {s2: one})] # s1 through t2 for i in reversed(s1.reduced_word()): diff --git a/src/sage/algebras/lie_algebras/bgg_dual_module.py b/src/sage/algebras/lie_algebras/bgg_dual_module.py index 5f0fa10ddfc..c4e060131da 100644 --- a/src/sage/algebras/lie_algebras/bgg_dual_module.py +++ b/src/sage/algebras/lie_algebras/bgg_dual_module.py @@ -423,7 +423,7 @@ def _acted_upon_(self, scalar, self_on_left=False): ##################################################################### -## Simple modules +# Simple modules # This is an abuse as the monoid is not free. diff --git a/src/sage/algebras/lie_algebras/center_uea.py b/src/sage/algebras/lie_algebras/center_uea.py index c155a797ca5..54057bc9735 100644 --- a/src/sage/algebras/lie_algebras/center_uea.py +++ b/src/sage/algebras/lie_algebras/center_uea.py @@ -6,22 +6,22 @@ - Travis Scrimshaw (2024-01-02): Initial version """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2024 Travis Scrimshaw # # 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/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** -#from sage.structure.unique_representation import UniqueRepresentation -#from sage.structure.parent import Parent +# from sage.structure.unique_representation import UniqueRepresentation +# from sage.structure.parent import Parent from sage.combinat.free_module import CombinatorialFreeModule from sage.combinat.integer_lists.invlex import IntegerListsLex from sage.matrix.constructor import matrix -from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid #, IndexedFreeAbelianMonoidElement +from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid from sage.monoids.indexed_free_monoid import IndexedMonoid from sage.combinat.root_system.coxeter_group import CoxeterGroup from sage.combinat.integer_vector_weighted import iterator_fast as intvecwt_iterator diff --git a/src/sage/algebras/lie_algebras/classical_lie_algebra.py b/src/sage/algebras/lie_algebras/classical_lie_algebra.py index ebe892d3436..15badc6881f 100644 --- a/src/sage/algebras/lie_algebras/classical_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/classical_lie_algebra.py @@ -379,15 +379,15 @@ def build_assoc(row): continue basis_pivots.add(p) if self._sparse: - added.append(self.element_class( self, build_assoc(cur_mat[i]) )) + added.append(self.element_class(self, build_assoc(cur_mat[i]))) else: - added.append(self.element_class( self, self._assoc(cur_mat[i].list()) )) + added.append(self.element_class(self, self._assoc(cur_mat[i].list()))) cur_mat = cur_mat.submatrix(nrows=len(pivots)) if self._sparse: - basis = [self.element_class( self, build_assoc(cur_mat[i]) ) + basis = [self.element_class(self, build_assoc(cur_mat[i])) for i in range(cur_mat.rank())] else: - basis = [self.element_class( self, self._assoc(cur_mat[i].list()) ) + basis = [self.element_class(self, self._assoc(cur_mat[i].list())) for i in range(cur_mat.rank())] return Family(basis) @@ -1077,7 +1077,7 @@ def __init__(self, R): ####################################### -## Compact real form +# Compact real form class MatrixCompactRealForm(FinitelyGeneratedLieAlgebra): r""" @@ -1558,7 +1558,7 @@ def monomial_coefficients(self, copy=False): ####################################### -## Chevalley Basis +# Chevalley Basis class LieAlgebraChevalleyBasis(LieAlgebraWithStructureCoefficients): r""" @@ -2054,7 +2054,7 @@ def _weight_action(self, m, wt): # enough in the ambient space to correctly convert things to do # the scalar product. alc = wt.parent().simple_coroots() - return R(wt.scalar( alc[aci[m]] )) + return R(wt.scalar(alc[aci[m]])) def affine(self, kac_moody=True): r""" diff --git a/src/sage/algebras/lie_algebras/onsager.py b/src/sage/algebras/lie_algebras/onsager.py index d4d224ea847..0de70c0a630 100644 --- a/src/sage/algebras/lie_algebras/onsager.py +++ b/src/sage/algebras/lie_algebras/onsager.py @@ -318,7 +318,7 @@ def alternating_central_extension(self): Element = LieAlgebraElement ##################################################################### -## q-Onsager algebra (the quantum group) +# q-Onsager algebra (the quantum group) class QuantumOnsagerAlgebra(CombinatorialFreeModule): @@ -795,10 +795,10 @@ def a(m, p): assert m > 0 terms = q**-2 * self.monomial(B[kr] * B[kl]) terms -= self.monomial(B[1,m]) - temp = ( -sum(q**(-2*(p-1)) * self.monomial(B[1,m-2*p]) + temp = (-sum(q**(-2*(p-1)) * self.monomial(B[1,m-2*p]) for p in range(1, (m - 1) // 2 + 1)) + sum(a(m,p) * self.monomial(B[0,kr[1]-p]) * self.monomial(B[0,p+kl[1]]) - for p in range(1, m // 2 + 1)) ) + for p in range(1, m // 2 + 1))) terms += (q**-2 - 1) * temp else: r = -kr[1] - 1 @@ -812,10 +812,10 @@ def a(m, p): terms -= (q**2-q**-2) * sum(q**(2*(r-1-k)) * self.monomial(B[0,-(k+1)]) * self.monomial(B[0,-r+kl[1]+k]) for k in range(r)) m = -r + kl[1] + 1 - temp = ( -sum(q**(-2*(p-1)) * self.monomial(B[1,m-2*p]) + temp = (-sum(q**(-2*(p-1)) * self.monomial(B[1,m-2*p]) for p in range(1, (m - 1) // 2 + 1)) + sum(a(m,p) * self.monomial(B[0,m-p-1]) * self.monomial(B[0,p-1]) - for p in range(1, m // 2 + 1)) ) + for p in range(1, m // 2 + 1))) terms += (q**-2 - 1) * q**(2*r) * temp else: # [B[rd+a0], B[sd+a1]] r > s @@ -826,10 +826,10 @@ def a(m, p): terms -= (q**2-q**-2) * sum(q**(2*(kl[1]-1-k)) * self.monomial(B[0,-(r-kl[1]+k+1)]) * self.monomial(B[0,k]) for k in range(kl[1])) m = r - kl[1] + 1 - temp = ( -sum(q**(-2*(p-1)) * self.monomial(B[1,m-2*p]) + temp = (-sum(q**(-2*(p-1)) * self.monomial(B[1,m-2*p]) for p in range(1, (m - 1) // 2 + 1)) + sum(a(m,p) * self.monomial(B[0,-p]) * self.monomial(B[0,p-m]) - for p in range(1, m // 2 + 1)) ) + for p in range(1, m // 2 + 1))) terms += (q**-2 - 1) * q**(2*kl[1]) * temp terms = -q**2 * terms elif kl[0] == 1 and kr[0] == 0: @@ -877,7 +877,7 @@ def a(m, p): for h in range(1, ell)) - q**(2*(ell-1)) * self.monomial(B[0,-(p-ell+1)] * B[1,kl[1]-ell]) for ell in range(1, kl[1])) - else: #kl[0] == 0 and kr[0] == 1: + else: # kl[0] == 0 and kr[0] == 1: terms = self.monomial(B[kr] * B[kl]) if kl[1] < kr[1]: # [B[pd+a1], B[md]] with p < m @@ -923,7 +923,7 @@ def a(m, p): return self.monomial(lhs // B[kl]) * terms * self.monomial(rhs // B[kr]) ##################################################################### -## ACE of the Onsager algebra +# ACE of the Onsager algebra class OnsagerAlgebraACE(InfinitelyGeneratedLieAlgebra, IndexedGenerators): diff --git a/src/sage/algebras/lie_algebras/rank_two_heisenberg_virasoro.py b/src/sage/algebras/lie_algebras/rank_two_heisenberg_virasoro.py index 4957ecf7bc9..b3fa2b0c7f4 100644 --- a/src/sage/algebras/lie_algebras/rank_two_heisenberg_virasoro.py +++ b/src/sage/algebras/lie_algebras/rank_two_heisenberg_virasoro.py @@ -211,7 +211,7 @@ def K(self, i=None): """ if i is None: return Family(self._KI, self.K) - return self.monomial( ('K', i) ) + return self.monomial(('K', i)) def t(self, a, b): r""" @@ -225,7 +225,7 @@ def t(self, a, b): """ if a == b == 0: raise ValueError("no t(0, 0) element") - return self.monomial( ('t', self._v(a,b)) ) + return self.monomial(('t', self._v(a,b))) def E(self, a, b): r""" @@ -239,7 +239,7 @@ def E(self, a, b): """ if a == b == 0: raise ValueError("no E(0, 0) element") - return self.monomial( ('E', self._v(a,b)) ) + return self.monomial(('E', self._v(a,b))) def _v(self, a, b): r""" @@ -324,10 +324,10 @@ def _an_element_(self): d = self.monomial v = self._v return ( - d( ('E',v(1,-3)) ) - - self.base_ring().an_element() * d( ('t',v(-1,3)) ) - + d( ('E',v(2,2)) ) - + d( ('K',3) ) + d(('E',v(1,-3))) + - self.base_ring().an_element() * d(('t',v(-1,3))) + + d(('E',v(2,2))) + + d(('K',3)) ) def some_elements(self): @@ -345,9 +345,9 @@ def some_elements(self): """ d = self.monomial v = self._v - return [d( ('E',v(1,1)) ), d( ('E',v(-2,-2)) ), d( ('E',v(0,1)) ), - d( ('t',v(1,1)) ), d( ('t',v(4,-1)) ), d( ('t',v(2,3)) ), - d( ('K',2) ), d( ('K',4) ), self.an_element()] + return [d(('E',v(1,1))), d(('E',v(-2,-2))), d(('E',v(0,1))), + d(('t',v(1,1))), d(('t',v(4,-1))), d(('t',v(2,3))), + d(('K',2)), d(('K',4)), self.an_element()] class Element(LieAlgebraElement): pass diff --git a/src/sage/algebras/lie_algebras/symplectic_derivation.py b/src/sage/algebras/lie_algebras/symplectic_derivation.py index f9a2e483330..af294c4c4b0 100644 --- a/src/sage/algebras/lie_algebras/symplectic_derivation.py +++ b/src/sage/algebras/lie_algebras/symplectic_derivation.py @@ -261,9 +261,9 @@ def _an_element_(self): """ d = self.monomial return ( - d( _Partitions([2,1]) ) - - self.base_ring().an_element() * d( _Partitions([5,2,2,1]) ) - + d( _Partitions([2*self._g-1, self._g+1, 2, 1, 1]) ) + d(_Partitions([2,1])) + - self.base_ring().an_element() * d(_Partitions([5,2,2,1])) + + d(_Partitions([2*self._g-1, self._g+1, 2, 1, 1])) ) def some_elements(self): @@ -279,8 +279,8 @@ def some_elements(self): """ d = self.monomial g = self._g - return [d( _Partitions([2,1]) ), d( _Partitions([g+3,g+1]) ), d( _Partitions([2,1,1])), - d( _Partitions([2*g-1,2*g-2]) ), d( _Partitions([2*g-2,g-1,1]) ), + return [d(_Partitions([2,1])), d(_Partitions([g+3,g+1])), d(_Partitions([2,1,1])), + d(_Partitions([2*g-1,2*g-2])), d(_Partitions([2*g-2,g-1,1])), self.an_element()] class Element(LieAlgebraElement): diff --git a/src/sage/algebras/lie_algebras/verma_module.py b/src/sage/algebras/lie_algebras/verma_module.py index 778a9486707..01320616c5a 100644 --- a/src/sage/algebras/lie_algebras/verma_module.py +++ b/src/sage/algebras/lie_algebras/verma_module.py @@ -843,7 +843,7 @@ def _acted_upon_(self, scalar, self_on_left=False): ##################################################################### -## Morphisms and Homset +# Morphisms and Homset class VermaModuleMorphism(Morphism): diff --git a/src/sage/algebras/lie_algebras/virasoro.py b/src/sage/algebras/lie_algebras/virasoro.py index 0284a10e5e5..961c6fb9bbd 100644 --- a/src/sage/algebras/lie_algebras/virasoro.py +++ b/src/sage/algebras/lie_algebras/virasoro.py @@ -652,7 +652,7 @@ class Element(LieAlgebraElement): pass ##################################################################### -## Representations +# Representations class ChargelessRepresentation(CombinatorialFreeModule): diff --git a/src/sage/algebras/lie_conformal_algebras/affine_lie_conformal_algebra.py b/src/sage/algebras/lie_conformal_algebras/affine_lie_conformal_algebra.py index a1ccb69653d..e9f697e8257 100644 --- a/src/sage/algebras/lie_conformal_algebras/affine_lie_conformal_algebra.py +++ b/src/sage/algebras/lie_conformal_algebras/affine_lie_conformal_algebra.py @@ -96,7 +96,7 @@ def __init__(self, R, ct, names=None, prefix=None, bracket=None): ct = CartanType(ct) except IndexError: raise ValueError("ct must be a valid Cartan Type") - if not (ct.is_finite() and ct.is_irreducible ): + if not (ct.is_finite() and ct.is_irreducible): raise ValueError("only affine algebras of simple finite dimensional" "Lie algebras are implemented") hv = Integer(ct.dual_coxeter_number()) diff --git a/src/sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py b/src/sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py index fe7954e1aea..e66489d49ca 100644 --- a/src/sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py +++ b/src/sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py @@ -134,7 +134,7 @@ def __init__(self, R, ngens=None, gram_matrix=None, names=None, names,index_set = standardize_names_index_set(names=names, index_set=index_set, ngens=ngens) - bosondict = { (i,j): {1: {('K',0): gram_matrix[index_set.rank(i), + bosondict = {(i,j): {1: {('K',0): gram_matrix[index_set.rank(i), index_set.rank(j)]}} for i in index_set for j in index_set} GradedLieConformalAlgebra.__init__(self,R,bosondict,names=names, diff --git a/src/sage/algebras/lie_conformal_algebras/free_fermions_lie_conformal_algebra.py b/src/sage/algebras/lie_conformal_algebras/free_fermions_lie_conformal_algebra.py index 4b8cb428f0b..40810602ac4 100644 --- a/src/sage/algebras/lie_conformal_algebras/free_fermions_lie_conformal_algebra.py +++ b/src/sage/algebras/lie_conformal_algebras/free_fermions_lie_conformal_algebra.py @@ -125,7 +125,7 @@ def __init__(self, R, ngens=None, gram_matrix=None, names=None, names,index_set = standardize_names_index_set(names=names, index_set=index_set, ngens=ngens) - fermiondict = { (i,j): {0: {('K',0): gram_matrix[index_set.rank(i), + fermiondict = {(i,j): {0: {('K', 0): gram_matrix[index_set.rank(i), index_set.rank(j)]}} for i in index_set for j in index_set} from sage.rings.rational_field import QQ diff --git a/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra.py b/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra.py index 2631d965307..528a587d795 100644 --- a/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra.py +++ b/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra.py @@ -308,8 +308,9 @@ class LieConformalAlgebra(UniqueRepresentation, Parent): """ @staticmethod def __classcall_private__(cls, R=None, arg0=None, index_set=None, - central_elements=None, category=None, prefix=None, - names=None, latex_names=None, parity=None, weights=None, **kwds): + central_elements=None, category=None, + prefix=None, names=None, latex_names=None, + parity=None, weights=None, **kwds): """ Lie conformal algebra factory. diff --git a/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_structure_coefs.py b/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_structure_coefs.py index 4fe54a98963..3cb8f645cd5 100644 --- a/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_structure_coefs.py +++ b/src/sage/algebras/lie_conformal_algebras/lie_conformal_algebra_with_structure_coefs.py @@ -275,7 +275,7 @@ def __init__(self, R, s_coeff, index_set=None, central_elements=None, s_coeff = dict(s_coeff) self._s_coeff = Family({k: tuple((j, sum(c*self.monomial(i) - for i,c in v )) for j,v in s_coeff[k]) for k in s_coeff}) + for i,c in v)) for j,v in s_coeff[k]) for k in s_coeff}) self._parity = dict(zip(self.gens(),parity+(0,)*len(central_elements))) def structure_coefficients(self): diff --git a/src/sage/algebras/orlik_solomon.py b/src/sage/algebras/orlik_solomon.py index 7302b430738..113bb29418f 100644 --- a/src/sage/algebras/orlik_solomon.py +++ b/src/sage/algebras/orlik_solomon.py @@ -791,9 +791,9 @@ def action(g, m): # computing the invariant will be a block matrix. To avoid dealing # with huge matrices, we can split it up into graded pieces. - max_deg = max([b.degree() for b in OS.basis()]) - B = [] #initialize the basis - for d in range(max_deg+1): + max_deg = max(b.degree() for b in OS.basis()) + B = [] # initialize the basis + for d in range(max_deg + 1): OS_d = OS.homogeneous_component(d) OSG_d = OS_d.invariant_module(G, action=action, category=category) B += [OS_d.lift(OSG_d.lift(b)) for b in OSG_d.basis()] diff --git a/src/sage/algebras/orlik_terao.py b/src/sage/algebras/orlik_terao.py index 707b7f51060..60c3c60e6db 100644 --- a/src/sage/algebras/orlik_terao.py +++ b/src/sage/algebras/orlik_terao.py @@ -292,7 +292,7 @@ def degree_on_basis(self, m): """ return len(m) - ## Multiplication + # Multiplication def product_on_basis(self, a, b): r""" diff --git a/src/sage/algebras/q_system.py b/src/sage/algebras/q_system.py index cea0332d4f4..a8342434847 100644 --- a/src/sage/algebras/q_system.py +++ b/src/sage/algebras/q_system.py @@ -446,12 +446,12 @@ def Q(self, a, m): if m == t[a] * self._level: return self.one() if m == 1: - return self.monomial( self._indices.gen((a,1)) ) + return self.monomial(self._indices.gen((a,1))) #if self._cartan_type.type() == 'A' and self._level is None: # return self._jacobi_trudy(a, m) I = self._cm.index_set() p = self._Q_poly(a, m) - return p.subs({ g: self.Q(I[i], 1) for i,g in enumerate(self._poly.gens()) }) + return p.subs({g: self.Q(I[i], 1) for i,g in enumerate(self._poly.gens())}) @cached_method def _Q_poly(self, a, m): diff --git a/src/sage/algebras/quantum_groups/fock_space.py b/src/sage/algebras/quantum_groups/fock_space.py index 8a7ce3e9386..d1d98d3c184 100644 --- a/src/sage/algebras/quantum_groups/fock_space.py +++ b/src/sage/algebras/quantum_groups/fock_space.py @@ -749,7 +749,7 @@ def N_left(la, x, i): return (sum(1 for y in P._addable(la, i) if P._above(x, y)) - sum(1 for y in P._removable(la, i) if P._above(x, y))) q = P.realization_of()._q - return P.sum_of_terms(( la.remove_cell(*x), c * q**(-N_left(la, x, i)) ) + return P.sum_of_terms((la.remove_cell(*x), c * q**(-N_left(la, x, i))) for la,c in self for x in P._removable(la, i)) def e(self, *data): @@ -845,8 +845,8 @@ def N_right(la, x, i): return (sum(1 for y in P._addable(la, i) if P._above(y, x)) - sum(1 for y in P._removable(la, i) if P._above(y, x))) q = P.realization_of()._q - return P.sum_of_terms( (la.add_cell(*x), c * q**N_right(la, x, i)) - for la,c in self for x in P._addable(la, i) ) + return P.sum_of_terms((la.add_cell(*x), c * q**N_right(la, x, i)) + for la,c in self for x in P._addable(la, i)) def f(self, *data): r""" @@ -1384,7 +1384,7 @@ def _G_to_fock_basis(self, la): ############################################################################### -## Bases Category +# Bases Category class FockSpaceBases(Category_realization_of_parent): r""" @@ -1605,7 +1605,7 @@ def __getitem__(self, i): return self.monomial(i) ############################################################################### -## Truncated Fock space +# Truncated Fock space class FockSpaceTruncated(FockSpace): @@ -2178,7 +2178,7 @@ def _G_to_fock_basis(self, la, algorithm='GW'): mu = _Partitions([p - x for p in la]) def add_cols(nu): - return _Partitions([ v + x for v in list(nu) + [0]*(k - len(nu)) ]) + return _Partitions([v + x for v in list(nu) + [0]*(k - len(nu))]) return fock.sum_of_terms((add_cols(nu), c) for nu,c in self._G_to_fock_basis(mu)) # For critical partitions diff --git a/src/sage/algebras/quantum_groups/representations.py b/src/sage/algebras/quantum_groups/representations.py index 7042c5aba76..7d42aa78787 100644 --- a/src/sage/algebras/quantum_groups/representations.py +++ b/src/sage/algebras/quantum_groups/representations.py @@ -124,7 +124,7 @@ def K_on_basis(self, i, b, power=1): """ WLR = self.basis().keys().weight_lattice_realization() alc = WLR.simple_coroots() - return self.term( b, self._q**(b.weight().scalar(alc[i]) * self._d[i] * power) ) + return self.term(b, self._q**(b.weight().scalar(alc[i]) * self._d[i] * power)) class CyclicRepresentation(QuantumGroupRepresentation): diff --git a/src/sage/algebras/quantum_oscillator.py b/src/sage/algebras/quantum_oscillator.py index a688283705f..7ac3509238f 100644 --- a/src/sage/algebras/quantum_oscillator.py +++ b/src/sage/algebras/quantum_oscillator.py @@ -122,7 +122,7 @@ class QuantumOscillatorAlgebra(CombinatorialFreeModule): - [Kuniba2022]_ Section 3.2 """ @staticmethod - def __classcall_private__(cls, q=None, R=None): + def __classcall_private__(cls, q=None, R=None): r""" Standardize input to ensure a unique representation. diff --git a/src/sage/algebras/rational_cherednik_algebra.py b/src/sage/algebras/rational_cherednik_algebra.py index 58b3ce5441d..c3ff9ff25e6 100644 --- a/src/sage/algebras/rational_cherednik_algebra.py +++ b/src/sage/algebras/rational_cherednik_algebra.py @@ -245,19 +245,19 @@ def algebra_generators(self): def gen_map(k): if k[0] == 's': i = int(k[1:]) - return self.monomial( (self._hd.one(), + return self.monomial((self._hd.one(), self._weyl.group_generators()[i], - self._h.one()) ) + self._h.one())) if k[1] == 'c': i = int(k[2:]) - return self.monomial( (self._hd.one(), + return self.monomial((self._hd.one(), self._weyl.one(), - self._h.monoid_generators()[i]) ) + self._h.monoid_generators()[i])) i = int(k[1:]) - return self.monomial( (self._hd.monoid_generators()[i], + return self.monomial((self._hd.monoid_generators()[i], self._weyl.one(), - self._h.one()) ) + self._h.one())) return Family(keys, gen_map) @cached_method @@ -351,16 +351,16 @@ def commute_w_hd(w, al): # al is given as a dictionary del dr[ir] # We now commute right roots past the left reflections: s Ra = Ra' s - cur = self._from_dict({ (hd, s*right[1], right[2]): c * cc + cur = self._from_dict({(hd, s*right[1], right[2]): c * cc for s,c in terms - for hd, cc in commute_w_hd(s, dr) }) - cur = self.monomial( (left[0], left[1], self._h(dl)) ) * cur + for hd, cc in commute_w_hd(s, dr)}) + cur = self.monomial((left[0], left[1], self._h(dl))) * cur # Add back in the commuted h and hd elements - rem = self.monomial( (left[0], left[1], self._h(dl)) ) - rem = rem * self.monomial( (self._hd({ir:1}), self._weyl.one(), - self._h({il:1})) ) - rem = rem * self.monomial( (self._hd(dr), right[1], right[2]) ) + rem = self.monomial((left[0], left[1], self._h(dl))) + rem = rem * self.monomial((self._hd({ir:1}), self._weyl.one(), + self._h({il:1}))) + rem = rem * self.monomial((self._hd(dr), right[1], right[2])) return cur + rem @@ -376,17 +376,17 @@ def commute_w_hd(w, al): # al is given as a dictionary ret *= x**dl[k] ret = ret.dict() w = left[1]*right[1] - return self._from_dict({ (left[0], w, + return self._from_dict({(left[0], w, self._h({I[i]: e for i,e in enumerate(k) if e != 0}) * right[2] ): ret[k] - for k in ret }) + for k in ret}) # Otherwise dr is non-trivial and we have La Ls Ra Rs Rac, # so we must commute Ls Ra = Ra' Ls w = left[1]*right[1] - return self._from_dict({ (left[0] * hd, w, right[2]): c - for hd, c in commute_w_hd(left[1], dr) }) + return self._from_dict({(left[0] * hd, w, right[2]): c + for hd, c in commute_w_hd(left[1], dr)}) @cached_method def _product_coroot_root(self, i, j): @@ -429,12 +429,12 @@ def _product_coroot_root(self, i, j): al = Q.simple_root(j) R = self.base_ring() - terms = [( self._weyl.one(), self._t * R(ac.scalar(al)) )] + terms = [(self._weyl.one(), self._t * R(ac.scalar(al)))] for s in self._reflections: # p[0] is the root, p[1] is the coroot, p[2] the value c_s pr, pc, c = self._reflections[s] - terms.append(( s, c * R(ac.scalar(pr) * pc.scalar(al) - / pc.scalar(pr)) )) + terms.append((s, c * R(ac.scalar(pr) * pc.scalar(al) + / pc.scalar(pr)))) return tuple(terms) def degree_on_basis(self, m): diff --git a/src/sage/algebras/splitting_algebra.py b/src/sage/algebras/splitting_algebra.py index 6fe2880d825..cfcb86e5c28 100644 --- a/src/sage/algebras/splitting_algebra.py +++ b/src/sage/algebras/splitting_algebra.py @@ -349,10 +349,10 @@ def __init__(self, monic_polynomial, names='X', iterate=True, warning=True): if not check.is_zero(): continue root_inv = self.one() - for pos in range(deg_cf-1 ): - root_inv = (-1 )**(pos+1 ) * cf[deg_cf-pos-1 ] - root_inv * root + for pos in range(deg_cf-1): + root_inv = (-1)**(pos+1) * cf[deg_cf-pos-1] - root_inv * root verbose("inverse %s of root %s" % (root_inv, root)) - root_inv = (-1 )**(deg_cf) * cf0_inv * root_inv + root_inv = (-1)**(deg_cf) * cf0_inv * root_inv self._invertible_elements.update({root:root_inv}) verbose("adding inverse %s of root %s" % (root_inv, root)) invert_items = list(self._invertible_elements.items()) diff --git a/src/sage/algebras/steenrod/steenrod_algebra_mult.py b/src/sage/algebras/steenrod/steenrod_algebra_mult.py index c087bb65ba4..053290bc5ed 100644 --- a/src/sage/algebras/steenrod/steenrod_algebra_mult.py +++ b/src/sage/algebras/steenrod/steenrod_algebra_mult.py @@ -312,9 +312,9 @@ def milnor_multiplication(r,s): else: sum = sum + M[i][j] * 2**j else: - sum = sum + M[i][j] * 2**j - j = j + 1 - i = i + 1 + sum = sum + M[i][j] * 2**j + j += 1 + i += 1 return result @@ -784,7 +784,7 @@ def adem(a, b, c=0, p=2, generic=None): return result # p odd if a == 0 and b == 0: - return {(c,): 1} + return {(c,): 1} if c == 0: bockstein = 0 A = a diff --git a/src/sage/algebras/tensor_algebra.py b/src/sage/algebras/tensor_algebra.py index 0d323a6ebb7..3a5f8d9c033 100644 --- a/src/sage/algebras/tensor_algebra.py +++ b/src/sage/algebras/tensor_algebra.py @@ -583,7 +583,7 @@ def coproduct_on_basis(self, m): # for w in Word(range(p)).shuffle(range(p, k)) ) ##################################################################### -## TensorAlgebra functor +# TensorAlgebra functor class TensorAlgebraFunctor(ConstructionFunctor): @@ -684,7 +684,7 @@ def _apply_functor_to_morphism(self, f): return D.module_morphism(phi, codomain=C) ##################################################################### -## Lift map from the base ring +# Lift map from the base ring class BaseRingLift(Morphism): diff --git a/src/sage/algebras/yangian.py b/src/sage/algebras/yangian.py index b17a1de0554..748586a30b9 100644 --- a/src/sage/algebras/yangian.py +++ b/src/sage/algebras/yangian.py @@ -575,13 +575,13 @@ def product_on_gens(self, a, b): # This is the special term of x = 1 x1 = self.zero() if b[1] == a[2]: - x1 += self.monomial( I.gen((a[0]+b[0]-1, a[1], b[2])) ) + x1 += self.monomial(I.gen((a[0]+b[0]-1, a[1], b[2]))) if a[1] == b[2]: - x1 -= self.monomial( I.gen((a[0]+b[0]-1, b[1], a[2])) ) + x1 -= self.monomial(I.gen((a[0]+b[0]-1, b[1], a[2]))) return self.monomial(I.gen(b) * I.gen(a)) + x1 + self.sum( - self.monomial( I.gen((x-1, b[1], a[2])) * I.gen((a[0]+b[0]-x, a[1], b[2])) ) - - self.product_on_gens( (a[0]+b[0]-x, b[1], a[2]), (x-1, a[1], b[2]) ) + self.monomial(I.gen((x-1, b[1], a[2])) * I.gen((a[0]+b[0]-x, a[1], b[2]))) + - self.product_on_gens((a[0]+b[0]-x, b[1], a[2]), (x-1, a[1], b[2])) for x in range(2, b[0]+1)) def coproduct_on_basis(self, m): @@ -610,9 +610,9 @@ def coproduct_on_basis(self, m): """ T = self.tensor_square() I = self._indices - return T.prod(T.monomial( (I.one(), I.gen((a[0],a[1],a[2]))) ) - + T.monomial( (I.gen((a[0],a[1],a[2])), I.one()) ) - + T.sum_of_terms([(( I.gen((s,a[1],k)), I.gen((a[0]-s,k,a[2])) ), 1) + return T.prod(T.monomial((I.one(), I.gen((a[0],a[1],a[2])))) + + T.monomial((I.gen((a[0],a[1],a[2])), I.one())) + + T.sum_of_terms([((I.gen((s,a[1],k)), I.gen((a[0]-s,k,a[2]))), 1) for k in range(1, self._n+1) for s in range(1, a[0])]) for a,exp in m._sorted_items() for p in range(exp)) @@ -881,12 +881,12 @@ def product_on_gens(self, a, b): x1 = self.zero() if a[0]+b[0]-1 <= self._level: if b[1] == a[2]: - x1 += self.monomial( I.gen((a[0]+b[0]-1, a[1], b[2])) ) + x1 += self.monomial(I.gen((a[0]+b[0]-1, a[1], b[2]))) if a[1] == b[2]: - x1 -= self.monomial( I.gen((a[0]+b[0]-1, b[1], a[2])) ) + x1 -= self.monomial(I.gen((a[0]+b[0]-1, b[1], a[2]))) return self.monomial(I.gen(b) * I.gen(a)) + x1 + self.sum( - self.monomial( I.gen((x-1, b[1], a[2])) * I.gen((a[0]+b[0]-x, a[1], b[2])) ) + self.monomial(I.gen((x-1, b[1], a[2])) * I.gen((a[0]+b[0]-x, a[1], b[2]))) - self.product_on_gens((a[0]+b[0]-x, b[1], a[2]), (x-1, a[1], b[2])) for x in range(2, b[0]+1) if a[0]+b[0]-x <= self._level) @@ -1043,8 +1043,8 @@ def antipode_on_basis(self, m): + 10*tbar(1)[1,2]*tbar(1)[1,3]^3*tbar(3)[1,2] + 15*tbar(1)[1,2]^2*tbar(1)[1,3]^2*tbar(3)[1,3] """ - return self.prod( (-1)**exp * self.monomial(a**exp) - for a,exp in reversed(list(m)) ) + return self.prod((-1)**exp * self.monomial(a**exp) + for a,exp in reversed(list(m))) def coproduct_on_basis(self, m): """ diff --git a/src/sage/algebras/yokonuma_hecke_algebra.py b/src/sage/algebras/yokonuma_hecke_algebra.py index 8f366ff3401..7de8ff07798 100644 --- a/src/sage/algebras/yokonuma_hecke_algebra.py +++ b/src/sage/algebras/yokonuma_hecke_algebra.py @@ -245,10 +245,10 @@ def algebra_generators(self): for i in range(self._n): r = list(zero) # Make a copy r[i] = 1 - d['t%s' % (i+1)] = self.monomial( (tuple(r), one) ) + d['t%s' % (i+1)] = self.monomial((tuple(r), one)) G = self._Pn.group_generators() for i in range(1, self._n): - d['g%s' % i] = self.monomial( (tuple(zero), G[i]) ) + d['g%s' % i] = self.monomial((tuple(zero), G[i])) return Family(sorted(d), lambda i: d[i]) @cached_method @@ -494,5 +494,5 @@ def __invert__(self): H = self.parent() t,w = self.support_of_term() c = ~self.coefficients()[0] - telt = H.monomial( (tuple((H._d - e) % H._d for e in t), H._Pn.one()) ) + telt = H.monomial((tuple((H._d - e) % H._d for e in t), H._Pn.one())) return c * telt * H.prod(H.inverse_g(i) for i in reversed(w.reduced_word())) diff --git a/src/sage/combinat/composition_tableau.py b/src/sage/combinat/composition_tableau.py index e90fe55f6a5..1e05da84c4e 100644 --- a/src/sage/combinat/composition_tableau.py +++ b/src/sage/combinat/composition_tableau.py @@ -211,10 +211,7 @@ def descent_set(self): sage: CompositionTableau([[1],[3,2],[4,4]]).descent_set() [1, 3] """ - cols = {} - for row in self: - for col, i in enumerate(row): - cols[i] = col + cols = {i: col for row in self for col, i in enumerate(row)} return sorted(i for i in cols if i + 1 in cols and cols[i + 1] >= cols[i]) def descent_composition(self): diff --git a/src/sage/combinat/designs/bibd.py b/src/sage/combinat/designs/bibd.py index da6dd6c09dd..49c930ccb38 100644 --- a/src/sage/combinat/designs/bibd.py +++ b/src/sage/combinat/designs/bibd.py @@ -1355,10 +1355,10 @@ def BIBD_from_arc_in_desarguesian_projective_plane(n,k,existence=False): :doi:`10.1016/S0021-9800(69)80095-5` """ q = (n-1)//(k-1)-1 - if (k % 2 or - q % 2 or - q <= k or - n != (k-1)*(q+1)+1 or + if (k % 2 or + q % 2 or + q <= k or + n != (k-1)*(q+1)+1 or not is_prime_power(k) or not is_prime_power(q)): if existence: @@ -1391,12 +1391,10 @@ def BIBD_from_arc_in_desarguesian_projective_plane(n,k,existence=False): # [Denniston69] is the set of all elements of K of degree < log_n # (seeing elements of K as polynomials in 'a') - K_iter = list(K) # faster iterations - log_n = is_prime_power(n,get_data=True)[1] - C = [(x,y,one) - for x in K_iter - for y in K_iter - if Q(x,y).polynomial().degree() < log_n] + K_iter = list(K) # faster iterations + log_n = is_prime_power(n, get_data=True)[1] + C = [(x, y, one) for x in K_iter for y in K_iter + if Q(x, y).polynomial().degree() < log_n] from sage.combinat.designs.block_design import DesarguesianProjectivePlaneDesign return DesarguesianProjectivePlaneDesign(q).trace(C)._blocks diff --git a/src/sage/combinat/designs/block_design.py b/src/sage/combinat/designs/block_design.py index bdf37227019..0c8b5b3fd38 100644 --- a/src/sage/combinat/designs/block_design.py +++ b/src/sage/combinat/designs/block_design.py @@ -42,7 +42,7 @@ --------------------- """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 Peter Dobcsanyi # Copyright (C) 2007 David Joyner # @@ -51,7 +51,7 @@ # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from sage.arith.misc import binomial, integer_floor, is_prime_power from sage.categories.sets_cat import EmptySetError from sage.misc.lazy_import import lazy_import @@ -69,7 +69,7 @@ BlockDesign = IncidenceStructure -### utility functions ------------------------------------------------------- +# utility functions ----------------------------------------------------- def tdesign_params(t, v, k, L): @@ -338,9 +338,10 @@ def DesarguesianProjectivePlaneDesign(n, point_coordinates=True, check=True): """ K = FiniteField(n, 'a') n2 = n**2 - relabel = {x:i for i,x in enumerate(K)} - Kiter = relabel # it is much faster to iterate through a dict than through - # the finite field K + relabel = {x: i for i, x in enumerate(K)} + Kiter = relabel + # it is much faster to iterate through a dict than through + # the finite field K # we decompose the (equivalence class) of points [x:y:z] of the projective # plane into an affine plane, an affine line and a point. At the same time, @@ -996,14 +997,14 @@ def HadamardDesign(n): """ from sage.combinat.matrices.hadamard_matrix import hadamard_matrix from sage.matrix.constructor import matrix - H = hadamard_matrix(n+1) #assumed to be normalised. + H = hadamard_matrix(n + 1) # assumed to be normalised. H1 = H.matrix_from_columns(range(1,n+1)) H2 = H1.matrix_from_rows(range(1,n+1)) J = matrix(ZZ,n,n,[1]*n*n) MS = J.parent() - A = MS((H2+J)/2) # convert -1's to 0's; coerce entries to ZZ + A = MS((H2+J)/2) # convert -1's to 0's; coerce entries to ZZ # A is the incidence matrix of the block design - return IncidenceStructure(incidence_matrix=A,name='HadamardDesign') + return IncidenceStructure(incidence_matrix=A, name='HadamardDesign') def Hadamard3Design(n): @@ -1060,10 +1061,10 @@ def Hadamard3Design(n): raise ValueError("The Hadamard design with n = %s does not extend to a three design." % n) from sage.combinat.matrices.hadamard_matrix import hadamard_matrix from sage.matrix.constructor import matrix, block_matrix - H = hadamard_matrix(n) #assumed to be normalised. + H = hadamard_matrix(n) # assumed to be normalised. H1 = H.matrix_from_columns(range(1, n)) J = matrix(ZZ, n, n-1, [1]*(n-1)*n) - A1 = (H1+J)/2 - A2 = (J-H1)/2 - A = block_matrix(1, 2, [A1, A2]) #the incidence matrix of the design. + A1 = (H1 + J) / 2 + A2 = (J - H1) / 2 + A = block_matrix(1, 2, [A1, A2]) # the incidence matrix of the design. return IncidenceStructure(incidence_matrix=A, name='HadamardThreeDesign') diff --git a/src/sage/combinat/designs/database.py b/src/sage/combinat/designs/database.py index 7c797481773..dc614bd7b25 100644 --- a/src/sage/combinat/designs/database.py +++ b/src/sage/combinat/designs/database.py @@ -1109,7 +1109,7 @@ def OA_10_205(): baer_subplane_size = 4**2+4+1 B = [0, 1, 22, 33, 83, 122, 135, 141, 145, 159, 175, 200, 226, 229, 231, 238, 246] - pplane = [[(xx+i) % pplane_size for xx in B] for i in range(pplane_size)] + pplane = [[(xx+i) % pplane_size for xx in B] for i in range(pplane_size)] baer_subplane = set([i*pplane_size/baer_subplane_size for i in range(baer_subplane_size)]) p = list(baer_subplane)[0] @@ -1284,7 +1284,7 @@ def OA_11_254(): # Base block of a PG(2,19) B = (0,1,19,28,96,118,151,153,176,202,240,254,290,296,300,307,337,361,366,369) - BIBD = [[(x+i) % 381 for x in B] for i in range(381)] + BIBD = [[(x+i) % 381 for x in B] for i in range(381)] # We only keep points congruent to 0,1 mod 3 and relabel the PBD. The result is # a (254,{11,13,16})-PBD @@ -1708,7 +1708,7 @@ def OA_10_469(): 731,824,837,848,932,1002,1051,1055,1089,1105,1145,1165,1196,1217,1226, 1274,1281,1309,1405) - BIBD = [[(x+i) % 1407 for x in B] for i in range(1407)] + BIBD = [[(x+i) % 1407 for x in B] for i in range(1407)] # Only keep points v congruent to 0 mod 3 and relabel PBD = [[x//3 for x in B if x % 3 == 0] for B in BIBD] @@ -4171,7 +4171,7 @@ def RBIBD_120_8_1(): # A (precomputed) set that every block of the BIBD intersects on 0 or 2 points hyperoval = [128, 192, 194, 4, 262, 140, 175, 48, 81, 180, 245, 271, 119, 212, 249, 189, 62, 255] - #for B in BIBD: + # for B in BIBD: # len_trace = sum(x in hyperoval for x in B) # assert len_trace == 0 or len_trace == 2 @@ -4577,13 +4577,9 @@ def HigmanSimsDesign(): from sage.combinat.designs.block_design import WittDesign from .incidence_structures import IncidenceStructure W = WittDesign(24) - a,b = 0,1 - Wa = [set(B) for B in W - if (a in B and - b not in B)] - Wb = [set(B) for B in W - if (b in B and - a not in B)] + a, b = 0, 1 + Wa = [set(B) for B in W if a in B and b not in B] + Wb = [set(B) for B in W if b in B and a not in B] H = [[i for i, A in enumerate(Wa) if len(A & B) != 2] for B in Wb] diff --git a/src/sage/combinat/designs/difference_family.py b/src/sage/combinat/designs/difference_family.py index 789f5d9a281..2ec008f16db 100644 --- a/src/sage/combinat/designs/difference_family.py +++ b/src/sage/combinat/designs/difference_family.py @@ -123,8 +123,8 @@ def block_stabilizer(G, B): S = [] for b in B: # fun: if we replace +(-b) with -b it completely fails!! - bb0 = op(b,b0) # bb0 = b-B[0] - if all(op(bb0,c) in B for c in B): + bb0 = op(b, b0) # bb0 = b-B[0] + if all(op(bb0, c) in B for c in B): S.append(bb0) return S @@ -279,7 +279,7 @@ def is_difference_family(G, D, v=None, k=None, l=None, verbose=False): for c in d: if b == c: continue - gg = mul(b,inv(c)) # = b-c or bc^{-1} + gg = mul(b, inv(c)) # = b-c or bc^{-1} if gg not in tmp_counter: tmp_counter[gg] = 0 where[gg].add(i) @@ -319,7 +319,7 @@ def is_difference_family(G, D, v=None, k=None, l=None, verbose=False): g, counter[g], sorted(where[g]))) if too_much: print("Too much:") - for g in too_much: + for g in too_much: print(" {} is obtained {} times in blocks {}".format( g, counter[g], sorted(where[g]))) if too_few or too_much: @@ -388,9 +388,10 @@ def singer_difference_set(q,d): # build a polynomial c over GF(q) such that GF(q)[x] / (c(x)) is a # GF(q**(d+1)) and such that x is a multiplicative generator. p,e = q.factor()[0] - c = conway_polynomial(p,e*(d+1)) - if e != 1: # i.e. q is not a prime, so we factorize c over GF(q) and pick - # one of its factor + c = conway_polynomial(p, e*(d+1)) + if e != 1: + # i.e. q is not a prime, so we factorize c over GF(q) and pick + # one of its factor K = GF(q,'z') c = c.change_ring(K).factor()[0][0] else: @@ -454,7 +455,7 @@ def df_q_6_1(K, existence=False, check=True): xx = x**5 to_coset = {x**i * xx**j: i for i in range(5) for j in range((v-1)/5)} - for c in to_coset: # the loop runs through all nonzero elements of K + for c in to_coset: # the loop runs through all nonzero elements of K if c == one or c == r or c == r2: continue if len(set(to_coset[elt] for elt in (r-one, c*(r-one), c-one, c-r, c-r**2))) == 5: @@ -796,13 +797,13 @@ def one_radical_difference_family(K, k): A = [r**i - 1 for i in range(1,m+1)] else: m = k // 2 - r = x ** ((q-1) // (k-1)) # (k-1)-th root of unity + r = x ** ((q-1) // (k-1)) # (k-1)-th root of unity A = [r**i - 1 for i in range(1,m)] A.append(K.one()) # instead of the complicated multiplicative group K^*/(±C) we use the # discrete logarithm to convert everything into the additive group Z/cZ - c = m * (q-1) // e # cardinal of ±C + c = m * (q-1) // e # cardinal of ±C from sage.groups.generic import discrete_log logA = [discrete_log(a,x) % c for a in A] @@ -1036,18 +1037,18 @@ def are_mcfarland_1973_parameters(v, k, lmbda, return_parameters=False): 96 20 4 4 1 """ if v <= k or k <= lmbda: - return (False,None) if return_parameters else False + return (False, None) if return_parameters else False k = ZZ(k) lmbda = ZZ(lmbda) - qs,r = (k - lmbda).sqrtrem() # sqrt(k-l) should be q^s + qs, r = (k - lmbda).sqrtrem() # sqrt(k-l) should be q^s if r or (qs*(qs-1)) % lmbda: - return (False,None) if return_parameters else False + return (False, None) if return_parameters else False q = qs*(qs-1) // lmbda + 1 if (q <= 1 or - v * (q-1) != qs*q * (qs*q+q-2) or + v * (q-1) != qs*q * (qs*q+q-2) or k * (q-1) != qs * (qs*q-1)): - return (False,None) if return_parameters else False + return (False, None) if return_parameters else False # NOTE: below we compute the value of s so that qs = q^s. If the method # is_power_of of integers would be able to return the exponent, we could use @@ -1057,7 +1058,7 @@ def are_mcfarland_1973_parameters(v, k, lmbda, return_parameters=False): p2,a2 = q.is_prime_power(get_data=True) if a1 == 0 or a2 == 0 or p1 != p2 or a1 % a2: - return (False,None) if return_parameters else False + return (False, None) if return_parameters else False return (True, (q, a1//a2)) if return_parameters else True @@ -1265,10 +1266,10 @@ def turyn_1965_3x3xK(k=4): else: raise ValueError("k must be 2 or 4") - L = [[(0,1),(1,1),(2,1),(0,2),(1,2),(2,2)], # complement of y=0 - [(0,0),(1,1),(2,2)], # x-y=0 - [(0,0),(1,2),(2,1)], # x+y=0 - [(0,0),(0,1),(0,2)]] # x=0 + L = [[(0,1),(1,1),(2,1),(0,2),(1,2),(2,2)], # complement of y=0 + [(0,0),(1,1),(2,2)], # x-y=0 + [(0,0),(1,2),(2,1)], # x+y=0 + [(0,0),(0,1),(0,2)]] # x=0 return G, [[G(v + k) for l, k in zip(L, K) for v in l]] @@ -1835,7 +1836,7 @@ def supplementary_difference_set_from_rel_diff_set(q, existence=False, check=Tru from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing P = PolynomialRing(ZZ, 'x') - #Compute psi3, psi4 + # Compute psi3, psi4 hall = 0 for d in set1: hall += P.monomial(d[0]) diff --git a/src/sage/combinat/designs/evenly_distributed_sets.pyx b/src/sage/combinat/designs/evenly_distributed_sets.pyx index 744cb4e3513..1d1a7acb8ec 100644 --- a/src/sage/combinat/designs/evenly_distributed_sets.pyx +++ b/src/sage/combinat/designs/evenly_distributed_sets.pyx @@ -218,24 +218,24 @@ cdef class EvenlyDistributedSetsBacktracker: raise ValueError(f"{K} is not a field") cdef unsigned int q = K.cardinality() cdef unsigned int e = k*(k-1)/2 - if (q-1) % (2*e) != 0: + if (q-1) % (2*e): raise ValueError("k(k-1)={} does not divide q-1={}".format(k*(k-1),q-1)) - cdef unsigned int m = (q-1)/e + cdef unsigned int m = (q - 1) // e self.q = q self.e = e self.k = k - self.m = (q-1) / e + self.m = (q - 1) // e self.K = K self.diff = check_calloc(q, sizeof(unsigned int *)) self.diff[0] = check_malloc(q*q*sizeof(unsigned int)) - for i in range(1,self.q): + for i in range(1, self.q): self.diff[i] = self.diff[i-1] + q self.ratio = check_calloc(q, sizeof(unsigned int *)) self.ratio[0] = check_malloc(q*q*sizeof(unsigned int)) - for i in range(1,self.q): + for i in range(1, self.q): self.ratio[i] = self.ratio[i-1] + q self.B = check_malloc(k*sizeof(unsigned int)) diff --git a/src/sage/combinat/designs/ext_rep.py b/src/sage/combinat/designs/ext_rep.py index 5297ad3357a..93f55f6daed 100644 --- a/src/sage/combinat/designs/ext_rep.py +++ b/src/sage/combinat/designs/ext_rep.py @@ -869,11 +869,11 @@ def _start_element(self, name, attrs): check_dtrs_protocols('source', attrs['dtrs_protocol']) if self.list_of_designs_start_proc: self.list_of_designs_start_proc(attrs) - #self.outf.write('<%s' % name) - #pp_attributes(self.outf, attrs, indent='', precision_stack=[]) - #self.outf.write('>\n') + # self.outf.write('<%s' % name) + # pp_attributes(self.outf, attrs, indent='', precision_stack=[]) + # self.outf.write('>\n') elif name == 'designs': - pass # self.outf.write(' <%s>\n' % name) + pass # self.outf.write(' <%s>\n' % name) if self.in_item: for k, v in attrs.items(): attrs[k] = _encode_attribute(v) @@ -916,7 +916,7 @@ def _end_element(self, name): if name == 'block' or name == 'permutation' \ or name == 'preimage' or name == 'ksubset' \ or name == 'cycle_type' or name == 'row': - # these enclose lists of numbers + # these enclose lists of numbers children.append(ps) else: # the rest is a single number @@ -929,19 +929,19 @@ def _end_element(self, name): if self.save_designs: init_bd = XTree(self.current_node[2][0]) self.list_of_designs.append((init_bd.v, list(init_bd.blocks))) - #print_subxt(self.current_node[2][0], level=2, outf=self.outf) + # print_subxt(self.current_node[2][0], level=2, outf=self.outf) self._init() elif name == 'info': if self.info_proc: self.info_proc(self.current_node[2][0]) - #print_subxt(self.current_node[2][0], level=1, outf=self.outf) + # print_subxt(self.current_node[2][0], level=1, outf=self.outf) self._init() else: if name == 'designs': if self.designs_end_proc: self.designs_end_proc() - #self.outf.write(' ') - #self.outf.write('\n' % name) + # self.outf.write(' ') + # self.outf.write('\n' % name) def _char_data(self, data): """ @@ -962,9 +962,8 @@ def _char_data(self, data): {'b': 26, 'id': 't2-v13-b26-r6-k3-L1-0', 'v': 13}, ['[ DESIGN-1.1, GRAPE-4.2, GAPDoc-0.9999, GAP-4.4.3]']) """ - if self.in_item: - #@ this stripping may distort char data in the subtree + # @ this stripping may distort char data in the subtree # if they are not bracketed in some way. data = data.strip() if data: diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index 148cc11fdfa..e9964af900c 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -142,7 +142,7 @@ class IncidenceStructure: True """ def __init__(self, points=None, blocks=None, incidence_matrix=None, - name=None, check=True, copy=True): + name=None, check=True, copy=True): r""" TESTS:: @@ -271,7 +271,7 @@ def __repr__(self): Incidence structure with 7 points and 7 blocks """ return 'Incidence structure with {} points and {} blocks'.format( - self.num_points(), self.num_blocks()) + self.num_points(), self.num_blocks()) __str__ = __repr__ @@ -309,7 +309,7 @@ def __eq__(self, other): return self._blocks == other._blocks if (self.num_points() != other.num_points() or - self.num_blocks() != other.num_blocks()): + self.num_blocks() != other.num_blocks()): return False p_to_i = self._point_to_index if self._point_to_index else list(range(self.num_points())) @@ -409,12 +409,12 @@ def canonical_label(self): from sage.graphs.graph import Graph g = Graph() n = self.num_points() - g.add_edges((i+n,x) for i,b in enumerate(self._blocks) for x in b) - canonical_label = g.canonical_label([list(range(n)),list(range(n,n+self.num_blocks()))],certificate=True)[1] + g.add_edges((i+n, x) for i, b in enumerate(self._blocks) for x in b) + canonical_label = g.canonical_label([list(range(n)), list(range(n, n+self.num_blocks()))], certificate=True)[1] canonical_label = [canonical_label[x] for x in range(n)] self._canonical_label = canonical_label - return dict(zip(self._points,self._canonical_label)) + return dict(zip(self._points, self._canonical_label)) def is_isomorphic(self, other, certificate=False): r""" @@ -475,25 +475,25 @@ def is_isomorphic(self, other, certificate=False): """ if (self.num_points() != other.num_points() or self.num_blocks() != other.num_blocks() or - sorted(self.block_sizes()) != sorted(other.block_sizes())): + sorted(self.block_sizes()) != sorted(other.block_sizes())): return {} if certificate else False A_canon = self.canonical_label() B_canon = other.canonical_label() - A = self.relabel(A_canon,inplace=False) - B = other.relabel(B_canon,inplace=False) + A = self.relabel(A_canon, inplace=False) + B = other.relabel(B_canon, inplace=False) if A == B: if certificate: - B_canon_rev = {y:x for x,y in B_canon.items()} - return {x:B_canon_rev[xint] for x,xint in A_canon.items()} + B_canon_rev = {y: x for x, y in B_canon.items()} + return {x: B_canon_rev[xint] for x, xint in A_canon.items()} else: return True else: return {} if certificate else False - def isomorphic_substructures_iterator(self, H2,induced=False): + def isomorphic_substructures_iterator(self, H2, induced=False): r""" Iterate over all copies of ``H2`` contained in ``self``. @@ -560,7 +560,7 @@ def isomorphic_substructures_iterator(self, H2,induced=False): 5616 """ from sage.combinat.designs.subhypergraph_search import SubHypergraphSearch - return SubHypergraphSearch(self,H2,induced=induced) + return SubHypergraphSearch(self, H2, induced=induced) def copy(self): r""" @@ -581,7 +581,7 @@ def copy(self): IS = IncidenceStructure(self._blocks, name=self._name, check=False) - IS.relabel(dict(zip(range(self.num_points()),self._points))) + IS.relabel(dict(zip(range(self.num_points()), self._points))) IS._canonical_label = None if self._canonical_label is None else self._canonical_label[:] return IS @@ -723,7 +723,7 @@ def trace(self, points, min_size=1, multiset=True): if not multiset: blocks = set(blocks) IS = IncidenceStructure(blocks) - IS.relabel({i:self._points[i] for i in int_points}) + IS.relabel({i: self._points[i] for i in int_points}) return IS def ground_set(self): @@ -830,7 +830,7 @@ def degree(self, p=None, subset=False): # degree of a point if not subset: if self._point_to_index: - p = self._point_to_index.get(p,-1) + p = self._point_to_index.get(p, -1) else: p = p if (p >= 0 and p < len(self._points)) else -1 return sum((p in b) for b in self._blocks) if p != -1 else 0 @@ -838,7 +838,7 @@ def degree(self, p=None, subset=False): # degree of a set else: if self._point_to_index: - p = set(self._point_to_index.get(x,-1) for x in p) + p = set(self._point_to_index.get(x, -1) for x in p) else: p = set(p) if all(x >= 0 and x < len(self._points) for x in p) else set([-1]) @@ -888,12 +888,12 @@ def degrees(self, size=None): return {p: d[i] for i, p in enumerate(self._points)} else: from itertools import combinations - d = {t:0 for t in combinations(range(self.num_points()),size)} + d = {t: 0 for t in combinations(range(self.num_points()), size)} for b in self._blocks: - for s in combinations(b,size): + for s in combinations(b, size): d[s] += 1 if self._point_to_index: - return {tuple([self._points[x] for x in s]):v for s,v in d.items()} + return {tuple([self._points[x] for x in s]): v for s, v in d.items()} else: return d @@ -1112,8 +1112,8 @@ def intersection_graph(self, sizes=None): sizes = PositiveIntegers() elif sizes in PositiveIntegers(): sizes = (sizes,) - V = [Set(v) for v in self] - return Graph([V, lambda x,y: len(x & y) in sizes], loops=False) + V = [Set(v) for v in self] + return Graph([V, lambda x, y: len(x & y) in sizes], loops=False) def incidence_matrix(self): r""" @@ -1150,7 +1150,7 @@ def incidence_matrix(self): A[i, j] = 1 return A - def incidence_graph(self,labels=False): + def incidence_graph(self, labels=False): r""" Return the incidence graph of the incidence structure. @@ -1201,7 +1201,7 @@ def incidence_graph(self,labels=False): for b in self.blocks(): b = Set(b) G.add_vertex(b) - G.add_edges((b,x) for x in b) + G.add_edges((b, x) for x in b) return G else: @@ -1243,7 +1243,7 @@ def is_berge_cyclic(self): return not self.incidence_graph().is_forest() - def complement(self,uniform=False): + def complement(self, uniform=False): r""" Return the complement of the incidence structure. @@ -1303,7 +1303,7 @@ def complement(self,uniform=False): num_blocks = self.num_blocks() i = 0 from itertools import combinations - for B in combinations(range(self.num_points()),k): + for B in combinations(range(self.num_points()), k): B = list(B) while i < num_blocks and self._blocks[i] < B: i += 1 @@ -1311,12 +1311,12 @@ def complement(self,uniform=False): i += 1 continue blocks.append(B) - I = IncidenceStructure(blocks,copy=False) + I = IncidenceStructure(blocks, copy=False) else: X = set(range(self.num_points())) I = IncidenceStructure([X.difference(B) for B in self._blocks]) - I.relabel({i:self._points[i] for i in range(self.num_points())}) + I.relabel({i: self._points[i] for i in range(self.num_points())}) return I def relabel(self, perm=None, inplace=True): @@ -1387,7 +1387,7 @@ def relabel(self, perm=None, inplace=True): self._point_to_index = None return - if isinstance(perm, (list,tuple)): + if isinstance(perm, (list, tuple)): perm = dict(zip(self._points, perm)) if not isinstance(perm, dict): @@ -1608,32 +1608,32 @@ def is_t_design(self, t=None, v=None, k=None, l=None, return_parameters=False): b = self.num_blocks() # Trivial wrong answers - if (any(len(block) != k for block in self._blocks) or # non k-uniform - v != self.num_points()): - return (False, (0,0,0,0)) if return_parameters else False + if (any(len(block) != k for block in self._blocks) or # non k-uniform + v != self.num_points()): + return (False, (0, 0, 0, 0)) if return_parameters else False # Trivial case t>k if (t is not None and t > k): if (l is None or l == 0): - return (True, (t,v,k,0)) if return_parameters else True + return (True, (t, v, k, 0)) if return_parameters else True else: - return (False, (0,0,0,0)) if return_parameters else False + return (False, (0, 0, 0, 0)) if return_parameters else False # Trivial case k=0 if k == 0: if (l is None or l == 0): - return (True, (0,v,k,b)) if return_parameters else True + return (True, (0, v, k, b)) if return_parameters else True else: - return (False, (0,0,0,0)) if return_parameters else False + return (False, (0, 0, 0, 0)) if return_parameters else False # Trivial case k=v (includes v=0) if k == v: if t is None: t = v if l is None or b == l: - return (True, (t,v,k,b)) if return_parameters else True + return (True, (t, v, k, b)) if return_parameters else True else: - return (True, (0,0,0,0)) if return_parameters else False + return (True, (0, 0, 0, 0)) if return_parameters else False # Handbook of combinatorial design theorem II.4.8: # @@ -1642,30 +1642,30 @@ def is_t_design(self, t=None, v=None, k=None, l=None, return_parameters=False): # # We look for the largest t such that self is a t-design from itertools import combinations - for tt in (range(1,k+1) if t is None else [t]): + for tt in (range(1, k + 1) if t is None else [t]): # is lambda an integer? - if (b*binomial(k,tt)) % binomial(v,tt) != 0: + if (b * binomial(k, tt)) % binomial(v, tt): tt -= 1 break s = {} for block in self._blocks: - for i in combinations(block,tt): - s[i] = s.get(i,0) + 1 + for i in combinations(block, tt): + s[i] = s.get(i, 0) + 1 if len(set(s.values())) != 1: tt -= 1 break - ll = b*binomial(k,tt) // binomial(v,tt) + ll = (b * binomial(k, tt)) // binomial(v, tt) if ((t is not None and t != tt) or - (l is not None and l != ll)): - return (False, (0,0,0,0)) if return_parameters else False + (l is not None and l != ll)): + return (False, (0, 0, 0, 0)) if return_parameters else False else: if tt == 0: ll = b - return (True, (tt,v,k,ll)) if return_parameters else True + return (True, (tt, v, k, ll)) if return_parameters else True def is_generalized_quadrangle(self, verbose=False, parameters=False): r""" @@ -1758,9 +1758,9 @@ def is_generalized_quadrangle(self, verbose=False, parameters=False): if parameters: s = self.is_uniform() t = self.is_regular() - s = s-1 if (s is not False and s >= 2) else False - t = t-1 if (t is not False and t >= 2) else False - return (s,t) + s = s - 1 if (s is not False and s >= 2) else False + t = t - 1 if (t is not False and t >= 2) else False + return (s, t) else: return True @@ -1812,10 +1812,10 @@ def dual(self, algorithm=None): v = DD['v'].sage() gB = [[x - 1 for x in b] for b in DD['blocks'].sage()] return IncidenceStructure(list(range(v)), gB, name=None, check=False) - else: - return IncidenceStructure( - incidence_matrix=self.incidence_matrix().transpose(), - check=False) + + return IncidenceStructure( + incidence_matrix=self.incidence_matrix().transpose(), + check=False) def automorphism_group(self): r""" @@ -1856,9 +1856,9 @@ def automorphism_group(self): from sage.groups.perm_gps.permgroup import PermutationGroup g = Graph() n = self.num_points() - g.add_edges((i+n,x) for i,b in enumerate(self._blocks) for x in b) + g.add_edges((i + n, x) for i, b in enumerate(self._blocks) for x in b) ag = g.automorphism_group(partition=[list(range(n)), - list(range(n,n+self.num_blocks()))]) + list(range(n, n + self.num_blocks()))]) if self._point_to_index: gens = [[tuple([self._points[i] for i in cycle if (not cycle or cycle[0] < n)]) @@ -1971,18 +1971,18 @@ def is_resolvable(self, certificate=False, solver=None, verbose=0, check=True, # Lists of blocks containing i for every i dual = [[] for _ in domain] - for i,B in enumerate(self._blocks): + for i, B in enumerate(self._blocks): for x in B: dual[x].append(i) # Each class is a partition for t in range(n_classes): for x in domain: - p.add_constraint(p.sum(b[t,i] for i in dual[x]) == 1) + p.add_constraint(p.sum(b[t, i] for i in dual[x]) == 1) # Each set appears exactly once for i in range(len(self._blocks)): - p.add_constraint(p.sum(b[t,i] for t in range(n_classes)) == 1) + p.add_constraint(p.sum(b[t, i] for t in range(n_classes)) == 1) try: p.solve(log=verbose) @@ -1998,8 +1998,8 @@ def is_resolvable(self, certificate=False, solver=None, verbose=0, check=True, if check and self._classes is not False: assert sorted(id(c) for cls in self._classes for c in cls) == sorted(id(b) for b in self._blocks), "some set does not appear exactly once" domain = list(range(self.num_points())) - for i,c in enumerate(self._classes): - assert sorted(sum(c,[])) == domain, "class {} is not a partition".format(i) + for i, c in enumerate(self._classes): + assert sorted(sum(c, [])) == domain, "class {} is not a partition".format(i) if self._classes is False: return (False, []) if certificate else False @@ -2069,7 +2069,7 @@ def coloring(self, k=None, solver=None, verbose=0, 3 """ if k is None: - for k in range(self.num_points()+1): + for k in range(self.num_points() + 1): try: return self.coloring(k) except ValueError: @@ -2093,11 +2093,11 @@ def coloring(self, k=None, solver=None, verbose=0, b = p.new_variable(binary=True) for x in range(self.num_points()): - p.add_constraint(p.sum(b[x,i] for i in range(k)) == 1) + p.add_constraint(p.sum(b[x, i] for i in range(k)) == 1) for s in self._blocks: for i in range(k): - p.add_constraint(p.sum(b[x,i] for x in s) <= len(s)-1) + p.add_constraint(p.sum(b[x, i] for x in s) <= len(s) - 1) try: p.solve(log=verbose) @@ -2106,13 +2106,13 @@ def coloring(self, k=None, solver=None, verbose=0, col = [[] for _ in range(k)] - for (x,i),v in p.get_values(b, convert=bool, tolerance=integrality_tolerance).items(): + for (x, i), v in p.get_values(b, convert=bool, tolerance=integrality_tolerance).items(): if v: col[i].append(self._points[x]) return col - def edge_coloring(self): + def edge_coloring(self) -> list: r""" Compute a proper edge-coloring. @@ -2185,13 +2185,13 @@ def _spring_layout(self): for x in s: g.add_edge((0, s), (1, x)) - _ = g.plot(iterations=50000,save_pos=True) + _ = g.plot(iterations=50000, save_pos=True) # The values are rounded as TikZ does not like accuracy. return {k[1]: (round(x, 3), round(y, 3)) for k, (x, y) in g.get_pos().items()} - def _latex_(self): + def _latex_(self) -> str: r""" Return a TikZ representation of the incidence structure. @@ -2246,11 +2246,12 @@ def _latex_(self): pos = self._spring_layout() tex = "\\begin{tikzpicture}[scale=3]\n" - colors = ["black", "red", "green", "blue", "cyan", "magenta", "yellow","pink","brown"] - colored_sets = [(s,i) for i,S in enumerate(self.edge_coloring()) for s in S] + colors = ["black", "red", "green", "blue", "cyan", + "magenta", "yellow", "pink", "brown"] + colored_sets = [(s, i) for i, S in enumerate(self.edge_coloring()) for s in S] # Prints each set with its color - for s,i in colored_sets: + for s, i in colored_sets: current_color = colors[i % len(colors)] if len(s) == 2: @@ -2283,7 +2284,7 @@ def _latex_(self): tex += "\\end{tikzpicture}" return tex - def is_spread(self, spread): + def is_spread(self, spread) -> bool: r""" Check whether the input is a spread for ``self``. @@ -2340,10 +2341,7 @@ def is_spread(self, spread): points.difference_update(sblock) - if points: - return False - - return True + return not points from sage.misc.rest_index_of_methods import gen_rest_table_index diff --git a/src/sage/combinat/designs/orthogonal_arrays.py b/src/sage/combinat/designs/orthogonal_arrays.py index ae1d48388bb..daac0f322cf 100644 --- a/src/sage/combinat/designs/orthogonal_arrays.py +++ b/src/sage/combinat/designs/orthogonal_arrays.py @@ -955,12 +955,12 @@ def orthogonal_array(k,n,t=2,resolvable=False, check=True,existence=False,explai # Constructions from the database III (Quasi-difference matrices) elif (may_be_available and - (n,1) in QDM and + (n, 1) in QDM and any(kk >= k and mu <= lmbda and (orthogonal_array(k,u,existence=True) is True) for (_,lmbda,mu,u),(kk,_) in QDM[n,1].items())): _OA_cache_set(k,n,True) - for (nn,lmbda,mu,u),(kk,f) in QDM[n,1].items(): - if (kk >= k and + for (nn, lmbda, mu, u), (kk, f) in QDM[n,1].items(): + if (kk >= k and mu <= lmbda and (orthogonal_array(k,u,existence=True) is True)): if existence: @@ -1236,11 +1236,11 @@ def incomplete_orthogonal_array(k,n,holes,resolvable=False, existence=False): raise EmptySetError("The total size of holes must be smaller or equal than the size of the ground set") if (max_hole == 1 and - resolvable and + resolvable and sum_of_holes != n): if existence: return False - raise EmptySetError("There is no resolvable incomplete OA({},{}) whose holes' sizes sum to {} equivalent to OA(k+1,n) if max_hole == 1 and resolvable: @@ -1366,11 +1366,11 @@ def incomplete_orthogonal_array(k,n,holes,resolvable=False, existence=False): # Equal holes [h,h,...] with h>1 through OA product construction # # (i.e. OA(k,n1)-x.OA(k,1) and OA(k,n2) ==> OA(k,n1.n2)-x.OA(k,n2) ) - elif (min_hole > 1 and - max_hole == min_hole and - n % min_hole == 0 and # h divides n + elif (min_hole > 1 and + max_hole == min_hole and + n % min_hole == 0 and # h divides n orthogonal_array(k,min_hole,existence=True) and # OA(k,h) - incomplete_orthogonal_array(k,n//min_hole,[1]*number_of_holes,existence=True)): # OA(k,n/h)-x.OA(k,1) + incomplete_orthogonal_array(k,n//min_hole,[1]*number_of_holes,existence=True)): # OA(k,n/h)-x.OA(k,1) if existence: return True h = min_hole @@ -1540,7 +1540,7 @@ def OA_relabel(OA, k, n, blocks=tuple(), matrix=None, symbol_list=None): """ if blocks: l = [] - for i,B in enumerate(zip(*blocks)): # the blocks are disjoint + for i, B in enumerate(zip(*blocks)): # the blocks are disjoint if len(B) != len(set(B)): raise RuntimeError("Two block have the same coordinate for one of the k dimensions") @@ -1941,7 +1941,7 @@ def QDM_from_Vmt(m,t,V): for e in V: L.append(e*wm**i) for ii in range(m+2): - M.append(L[-ii:]+L[:-ii]) # cyclic shift + M.append(L[-ii:]+L[:-ii]) # cyclic shift M.append([0]*(m+2)) diff --git a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py index edad950fc88..7366fb42a5a 100644 --- a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py +++ b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py @@ -537,9 +537,9 @@ def construction_q_x(k, q, x, check=True, explain_construction=False): # Add rows, extended with p1 and p2 p1 = q**2 - p2 = p1+1 - TD.extend([[ii*q+i for ii in range(q)]+[p1] for i in range(1,q)]) - TD.append( [ii*q for ii in range(q)]+[p1,p2]) + p2 = p1 + 1 + TD.extend(([ii*q + i for ii in range(q)] + [p1] for i in range(1, q))) + TD.append([ii*q for ii in range(q)] + [p1, p2]) # Add Columns. We do not add some columns which would have size 1 after we # delete points. @@ -1453,9 +1453,9 @@ def brouwer_separable_design(k,t,q,x,check=False,verbose=False,explain_construct else: partition_of_blocks_of_size_t[plane-t].append([relabel[xx] for xx in B if xx % m < t]) - ############################################################################### + ########################################################################### # Separable design built ! - #------------------------- + # ------------------------ # # At this point we have a PBD on t*(q**2+q+1) points. Its blocks are # split into: @@ -1466,7 +1466,7 @@ def brouwer_separable_design(k,t,q,x,check=False,verbose=False,explain_construct # - blocks_of_size_q_plus_t : contains all t*(q**2+q+1)blocks of size q+t, # covering the same number of points: it is a # symmetric design. - ############################################################################### + ########################################################################### ############################################## # Part 2: Build an OA on t(q^2+q+1)+x points # diff --git a/src/sage/combinat/designs/resolvable_bibd.py b/src/sage/combinat/designs/resolvable_bibd.py index 27a5bdcae32..ccc11176d54 100644 --- a/src/sage/combinat/designs/resolvable_bibd.py +++ b/src/sage/combinat/designs/resolvable_bibd.py @@ -311,14 +311,14 @@ def kirkman_triple_system(v,existence=False): b.remove(8) X = sum(X, []) + [8] gdd4.relabel({v:i for i,v in enumerate(X)}) - gdd4 = gdd4.is_resolvable(True)[1] # the relabeled classes + gdd4 = gdd4.is_resolvable(True)[1] # the relabeled classes X = [B for B in gdd7 if 14 in B] for b in X: b.remove(14) X = sum(X, []) + [14] gdd7.relabel({v:i for i,v in enumerate(X)}) - gdd7 = gdd7.is_resolvable(True)[1] # the relabeled classes + gdd7 = gdd7.is_resolvable(True)[1] # the relabeled classes # The first parallel class contains 01(n'-1), the second contains # 23(n'-1), etc.. @@ -573,7 +573,7 @@ def PBD_4_7(v,check=True, existence=False): # On these groups a (15+7,{4,7})-PBD is pasted, in such a way that the 7 # new points are a set of the final PBD PBD22 = PBD_4_7(15+7) - S = next(SS for SS in PBD22 if len(SS) == 7) # a set of size 7 + S = next(SS for SS in PBD22 if len(SS) == 7) # a set of size 7 PBD22.relabel({v:i for i,v in enumerate([i for i in range(15+7) if i not in S] + S)}) for B in PBD22: @@ -741,19 +741,19 @@ def PBD_4_7_from_Y(gdd,check=True): raise RuntimeError("A group has size {} but I do not know how to " "build a ({},[4,7])-PBD".format(gs,3*gs+1)) - GDD = {} # the GDD we will need + GDD = {} # the GDD we will need if 4 in block_sizes: - #GDD[4] = GDD_from_BIBD(3*4,4) + # GDD[4] = GDD_from_BIBD(3*4,4) GDD[4] = group_divisible_design(3*4,K=[4],G=[3]) if 5 in block_sizes: - #GDD[5] = GDD_from_BIBD(3*5,4) + # GDD[5] = GDD_from_BIBD(3*5,4) GDD[5] = group_divisible_design(3*5,K=[4],G=[3]) if 7 in block_sizes: # It is obtained from a PBD_4_7(22) by removing a point only contained # in sets of size 4 GDD[7] = PBD_4_7(22) x = set(range(22)).difference(*[S for S in GDD[7] if len(S) != 4]).pop() - relabel = sum((S for S in GDD[7] if x in S),[]) # the groups must be 012,345,... + relabel = sum((S for S in GDD[7] if x in S),[]) # the groups must be 012,345,... relabel = [xx for xx in relabel if xx != x]+[x] GDD[7].relabel({v:i for i,v in enumerate(relabel)}) GDD[7] = [S for S in GDD[7] if 21 not in S] diff --git a/src/sage/combinat/designs/subhypergraph_search.pyx b/src/sage/combinat/designs/subhypergraph_search.pyx index b7a71a17b86..8de300efe2a 100644 --- a/src/sage/combinat/designs/subhypergraph_search.pyx +++ b/src/sage/combinat/designs/subhypergraph_search.pyx @@ -133,7 +133,7 @@ cdef inline int bs_get(uint64_t * bitset, int index) noexcept: r""" Return a bit of a bitset """ - return (bitset[index/64]>>(index%64))&1 + return (bitset[index//64]>>(index%64))&1 cdef inline void bs_set(uint64_t * bitset, int index, int bit) noexcept: r""" @@ -142,8 +142,8 @@ cdef inline void bs_set(uint64_t * bitset, int index, int bit) noexcept: "bit" *MUST* be equal to either 0 or to 1. The code does not involve any "if". """ - bitset[index/64] &= ~(( 1)< bit)< 1)< bit)< sig_malloc(sizeof(int)*n) h.sets = sig_malloc(h.m*sizeof(uint64_t *)) h.set_space = sig_calloc(h.m*(h.limbs+1),sizeof(uint64_t)) diff --git a/src/sage/combinat/designs/twographs.py b/src/sage/combinat/designs/twographs.py index 281f4fac153..131916b89c9 100644 --- a/src/sage/combinat/designs/twographs.py +++ b/src/sage/combinat/designs/twographs.py @@ -70,7 +70,7 @@ class TwoGraph(IncidenceStructure): :mod:`~sage.combinat.designs.twographs` module. """ def __init__(self, points=None, blocks=None, incidence_matrix=None, - name=None, check=False, copy=True): + name=None, check=False, copy=True): r""" Constructor of the class. diff --git a/src/sage/combinat/diagram.py b/src/sage/combinat/diagram.py index f5b8f9ad864..cc3c1dfe0b6 100644 --- a/src/sage/combinat/diagram.py +++ b/src/sage/combinat/diagram.py @@ -336,12 +336,9 @@ def _latex_(self): lr = r'\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}}' - array = [] - for i in range(self._n_rows): - row = [] - for j in range(self._n_cols): - row.append("\\phantom{x}" if (i, j) in self else None) - array.append(row) + array = [[("\\phantom{x}" if (i, j) in self else None) + for j in range(self._n_cols)] + for i in range(self._n_rows)] def end_line(r): # give the line ending to row ``r`` diff --git a/src/sage/combinat/diagram_algebras.py b/src/sage/combinat/diagram_algebras.py index 364a55c74c9..6410a4a93ea 100644 --- a/src/sage/combinat/diagram_algebras.py +++ b/src/sage/combinat/diagram_algebras.py @@ -3013,7 +3013,7 @@ def jucys_murphy_element(self, i): sage: L = [P.L(i/2) for i in range(1,2*k+1)] sage: all(x.dual() == x for x in L) True - sage: all(x * y == y * x for x in L for y in L) # long time + sage: all(x * y == y * x for x, y in Subsets(L, 2)) # long time True sage: Lsum = sum(L) sage: gens = [P.s(i) for i in range(1,k)] @@ -3045,13 +3045,13 @@ def jucys_murphy_element(self, i): The same tests for a half integer partition algebra:: - sage: k = 9/2 + sage: k = 7/2 sage: R. = QQ[] sage: P = PartitionAlgebra(k, n) sage: L = [P.L(i/2) for i in range(1,2*k+1)] sage: all(x.dual() == x for x in L) True - sage: all(x * y == y * x for x in L for y in L) # long time + sage: all(x * y == y * x for x, y in Subsets(L, 2)) # long time True sage: Lsum = sum(L) sage: gens = [P.s(i) for i in range(1,k-1/2)] @@ -5847,11 +5847,9 @@ def to_Brauer_partition(l, k=None): True """ L = to_set_partition(l, k=k) - L2 = [] paired = [] not_paired = [] - for i in L: - L2.append(list(i)) + L2 = (list(i) for i in L) for i in L2: if len(i) > 2: raise ValueError("blocks must have size at most 2, but {} has {}".format(i, len(i))) diff --git a/src/sage/combinat/dlx.py b/src/sage/combinat/dlx.py index 383dda056be..d25f7414d0d 100644 --- a/src/sage/combinat/dlx.py +++ b/src/sage/combinat/dlx.py @@ -484,10 +484,7 @@ def AllExactCovers(M): ones = [] r = 1 # damn 1-indexing for R in M.rows(): - row = [] - for i in range(len(R)): - if R[i]: - row.append(i + 1) # damn 1-indexing + row = [i for i, Ri in enumerate(R, start=1) if Ri] ones.append([r, row]) r += 1 for s in DLXMatrix(ones): diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 96d35859873..2750ea15e9c 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -9437,9 +9437,9 @@ def graph(self, edge_labels='words_in_out'): transitions = state.transitions if not transitions: isolated_vertices.append(state.label()) - for t in transitions: - graph_data.append((t.from_state.label(), t.to_state.label(), - label_fct(t))) + graph_data.extend((t.from_state.label(), t.to_state.label(), + label_fct(t)) + for t in transitions) G = DiGraph(graph_data, multiedges=True, loops=True) G.add_vertices(isolated_vertices) diff --git a/src/sage/combinat/free_dendriform_algebra.py b/src/sage/combinat/free_dendriform_algebra.py index 2acfee1b337..4be2c514778 100644 --- a/src/sage/combinat/free_dendriform_algebra.py +++ b/src/sage/combinat/free_dendriform_algebra.py @@ -939,14 +939,12 @@ def merge(self, other): return self ret = list(self.vars) cur_vars = set(ret) - for v in other.vars: - if v not in cur_vars: - ret.append(v) + ret.extend(v for v in other.vars if v not in cur_vars) return DendriformFunctor(Alphabet(ret)) - else: - return None - def _repr_(self): + return None + + def _repr_(self) -> str: """ TESTS:: diff --git a/src/sage/combinat/free_prelie_algebra.py b/src/sage/combinat/free_prelie_algebra.py index 1ea7d3274a1..6e7525d8b23 100644 --- a/src/sage/combinat/free_prelie_algebra.py +++ b/src/sage/combinat/free_prelie_algebra.py @@ -1023,14 +1023,12 @@ def merge(self, other): return self ret = list(self.vars) cur_vars = set(ret) - for v in other.vars: - if v not in cur_vars: - ret.append(v) + ret.extend(v for v in other.vars if v not in cur_vars) return PreLieFunctor(Alphabet(ret)) - else: - return None - def _repr_(self): + return None + + def _repr_(self) -> str: """ TESTS:: diff --git a/src/sage/combinat/gelfand_tsetlin_patterns.py b/src/sage/combinat/gelfand_tsetlin_patterns.py index be4e236b099..5c3c485e549 100644 --- a/src/sage/combinat/gelfand_tsetlin_patterns.py +++ b/src/sage/combinat/gelfand_tsetlin_patterns.py @@ -302,11 +302,9 @@ def boxed_entries(self) -> tuple: sage: G.boxed_entries() ((1, 0),) """ - ret = [] - for i in range(1, len(self)): - for j in range(len(self[i])): - if self[i][j] == self[i - 1][j]: - ret.append((i, j)) + ret = [(i, j) for i in range(1, len(self)) + for j, selfij in enumerate(self[i]) + if selfij == self[i - 1][j]] return tuple(ret) @cached_method @@ -324,11 +322,9 @@ def circled_entries(self) -> tuple: sage: G.circled_entries() ((1, 1), (2, 0)) """ - ret = [] - for i in range(1, len(self)): - for j in range(len(self[i])): - if self[i][j] == self[i - 1][j + 1]: - ret.append((i, j)) + ret = [(i, j) for i in range(1, len(self)) + for j, selfij in enumerate(self[i]) + if selfij == self[i - 1][j + 1]] return tuple(ret) @cached_method @@ -349,11 +345,9 @@ def special_entries(self) -> tuple: sage: G.special_entries() ((2, 0),) """ - ret = [] - for i in range(1, len(self)): - for j in range(len(self[i])): - if self[i-1][j] > self[i][j] and self[i][j] > self[i-1][j+1]: - ret.append((i, j)) + ret = [(i, j) for i in range(1, len(self)) + for j, selfij in enumerate(self[i]) + if self[i - 1][j] > selfij > self[i - 1][j + 1]] return tuple(ret) def number_of_boxes(self) -> int: diff --git a/src/sage/combinat/graph_path.py b/src/sage/combinat/graph_path.py index 2fb255579dd..df84b1acdf7 100644 --- a/src/sage/combinat/graph_path.py +++ b/src/sage/combinat/graph_path.py @@ -236,11 +236,7 @@ def paths_from_source_to_target(self, source, target): [[2, 3, 4], [2, 4]] """ source_paths = self.outgoing_paths(source) - paths = [] - for path in source_paths: - if path[-1] == target: - paths.append(path) - return paths + return [path for path in source_paths if path[-1] == target] def paths(self): """ diff --git a/src/sage/combinat/integer_vector.py b/src/sage/combinat/integer_vector.py index 516d2114ffc..6848609cf5d 100644 --- a/src/sage/combinat/integer_vector.py +++ b/src/sage/combinat/integer_vector.py @@ -1174,19 +1174,17 @@ def _list_rec(self, n, k): EXAMPLES:: sage: IV = IntegerVectors(2,3) - sage: IV._list_rec(2,3) + sage: list(IV._list_rec(2,3)) [(2, 0, 0), (1, 1, 0), (1, 0, 1), (0, 2, 0), (0, 1, 1), (0, 0, 2)] """ - res = [] - if k == 1: - return [(n, )] + yield (n,) + return for nbar in range(n + 1): n_diff = n - nbar for rest in self._list_rec(nbar, k - 1): - res.append((n_diff,) + rest) - return res + yield (n_diff,) + rest def __iter__(self): """ diff --git a/src/sage/combinat/knutson_tao_puzzles.py b/src/sage/combinat/knutson_tao_puzzles.py index e1e4f746f42..f7a7c513c52 100644 --- a/src/sage/combinat/knutson_tao_puzzles.py +++ b/src/sage/combinat/knutson_tao_puzzles.py @@ -2064,12 +2064,9 @@ def _fill_piece(self, nw_label, ne_label, pieces) -> list[PuzzlePiece]: sage: ps._fill_piece('0', '0', ps._bottom_deltas) [0/0\0] """ - output = [] - for piece in pieces: - if (piece['north_west'] == nw_label and - piece['north_east'] == ne_label): - output.append(piece) - return output + return [piece for piece in pieces + if (piece['north_west'] == nw_label and + piece['north_east'] == ne_label)] @cached_method def _fill_strip(self, nw_labels, ne_label, pieces, final_pieces=None): diff --git a/src/sage/combinat/misc.py b/src/sage/combinat/misc.py index 404c032a8dc..d04b615be80 100644 --- a/src/sage/combinat/misc.py +++ b/src/sage/combinat/misc.py @@ -202,11 +202,7 @@ def _monomial_exponent_to_lower_factorial(me, x): sage: _monomial_exponent_to_lower_factorial(([2,2,2]),a) x^2*y^2*z^2 - x^2*y^2*z - x^2*y*z^2 - x*y^2*z^2 + x^2*y*z + x*y^2*z + x*y*z^2 - x*y*z """ - terms = [] - for i in range(len(me)): - for j in range(me[i]): - terms.append( x[i]-j ) - return prod(terms) + return prod(x[i] - j for i, mei in enumerate(me) for j in range(mei)) def umbral_operation(poly): @@ -235,7 +231,7 @@ def umbral_operation(poly): exponents = poly.exponents() coefficients = poly.coefficients() length = len(exponents) - return sum( [coefficients[i]*_monomial_exponent_to_lower_factorial(exponents[i],x) for i in range(length)] ) + return sum(coefficients[i]*_monomial_exponent_to_lower_factorial(exponents[i], x) for i in range(length)) class IterableFunctionCall: diff --git a/src/sage/combinat/parallelogram_polyomino.py b/src/sage/combinat/parallelogram_polyomino.py index ed9308e82e9..c60c72c10d2 100644 --- a/src/sage/combinat/parallelogram_polyomino.py +++ b/src/sage/combinat/parallelogram_polyomino.py @@ -1974,12 +1974,9 @@ def widths(self) -> list: sage: pp.widths() [] """ - widths = [] uw = self.upper_widths() lw = self.lower_widths() - for i in range(len(lw)): - widths.append(uw[i] - lw[i]) - return widths + return [up - lo for up, lo in zip(uw, lw)] def degree_convexity(self) -> int: r""" @@ -3387,11 +3384,10 @@ def get_BS_nodes(self): sage: pp.set_options(drawing_components=dict(tree=True)) sage: view(pp) # not tested """ - result = [] - for h in range(1, self.height()): - result.append(self._get_node_position_at_row(h)) - for w in range(1, self.width()): - result.append(self._get_node_position_at_column(w)) + result = [self._get_node_position_at_row(h) + for h in range(1, self.height())] + result.extend(self._get_node_position_at_column(w) + for w in range(1, self.width())) return result def get_right_BS_nodes(self): diff --git a/src/sage/combinat/partition_algebra.py b/src/sage/combinat/partition_algebra.py index f0d15115ac9..91e79be4cbc 100644 --- a/src/sage/combinat/partition_algebra.py +++ b/src/sage/combinat/partition_algebra.py @@ -364,9 +364,7 @@ def __iter__(self): True """ for p in Permutations(self.k): - res = [] - for i in range(self.k): - res.append(Set([i + 1, -p[i]])) + res = [Set([i, -pi]) for i, pi in enumerate(p, start=1)] yield self.element_class(self, res) @@ -433,10 +431,7 @@ def __iter__(self): {{1, -3}, {2, -2}, {4, -4}, {3, -1}}] """ for p in Permutations(self.k): - res = [] - for i in range(self.k): - res.append(Set([i + 1, -p[i]])) - + res = [Set([i, -pi]) for i, pi in enumerate(p, start=1)] res.append(Set([self.k + 1, -self.k - 1])) yield self.element_class(self, res) @@ -1941,8 +1936,8 @@ def to_set_partition(l, k=None): to_be_added -= spart sp.append(spart) - for singleton in to_be_added: - sp.append(Set([singleton])) + sp.extend(Set([singleton]) + for singleton in to_be_added) return Set(sp) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 7cf80d0fe92..377f01b132a 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -7722,6 +7722,52 @@ def simple_reflection(self, i): g[i] = i return self.element_class(self, g, check=False) + @cached_method + def reflection_index_set(self): + r""" + Return the index set of the reflections of ``self``. + + .. SEEALSO:: + + - :meth:`reflection` + - :meth:`reflections` + + EXAMPLES:: + + sage: P = Permutations(4) + sage: P.reflection_index_set() + ((1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)) + """ + return tuple([tuple(c) for c in itertools.combinations(range(1, self.n+1), 2)]) + + def reflection(self, i): + r""" + Return the reflection indexed by ``i`` of ``self``. + + This returns the permutation with cycle `i = (a, b)`. + + .. SEEALSO:: + + - :meth:`reflections_index_set` + - :meth:`reflections` + + EXAMPLES:: + + sage: P = Permutations(4) + sage: for i in P.reflection_index_set(): + ....: print('%s %s'%(i, P.reflection(i))) + (1, 2) [2, 1, 3, 4] + (1, 3) [3, 2, 1, 4] + (1, 4) [4, 2, 3, 1] + (2, 3) [1, 3, 2, 4] + (2, 4) [1, 4, 3, 2] + (3, 4) [1, 2, 4, 3] + """ + data = list(range(1, self.n+1)) + data[i[0]-1] = i[1] + data[i[1]-1] = i[0] + return self.element_class(self, data, check=False) + class Element(Permutation): def has_left_descent(self, i, mult=None): r""" diff --git a/src/sage/combinat/ribbon_shaped_tableau.py b/src/sage/combinat/ribbon_shaped_tableau.py index d33cc115396..dd7218c899c 100644 --- a/src/sage/combinat/ribbon_shaped_tableau.py +++ b/src/sage/combinat/ribbon_shaped_tableau.py @@ -350,18 +350,17 @@ def from_permutation(self, p): [[1, 2], [3]], [[1], [2], [3]]] """ - if p == []: + if not p: return self.element_class(self, []) comp = p.descents() - if comp == []: + if not comp: return self.element_class(self, [p[:]]) - r = [] - r.append([p[j] for j in range(comp[0])]) - for i in range(len(comp) - 1): - r.append([p[j] for j in range(comp[i], comp[i + 1])]) + r = [[p[j] for j in range(comp[0])]] + r.extend([p[j] for j in range(comp[i], comp[i + 1])] + for i in range(len(comp) - 1)) r.append([p[j] for j in range(comp[-1], len(p))]) r.reverse() return self.element_class(self, r) diff --git a/src/sage/combinat/rsk.py b/src/sage/combinat/rsk.py index 5e276d5ebcf..1fa0ba62b4e 100644 --- a/src/sage/combinat/rsk.py +++ b/src/sage/combinat/rsk.py @@ -2957,12 +2957,10 @@ def _backward_format_output(self, obj1, obj2, output): if j == 0: df.append([]) if j > 0 and obj1[j] < obj1[j-1]: - for _ in range(obj1[j-1]-obj1[j]): - df.append([]) + df.extend([] for _ in range(obj1[j-1]-obj1[j])) df[-1].append(obj2[j]) if obj1: - for a in range(obj1[-1]-1): - df.append([]) + df.extend([] for a in range(obj1[-1]-1)) # If biword is empty, return a decreasing factorization with 1 factor else: df.append([]) diff --git a/src/sage/combinat/shifted_primed_tableau.py b/src/sage/combinat/shifted_primed_tableau.py index 37bbd402c14..6d889425d28 100644 --- a/src/sage/combinat/shifted_primed_tableau.py +++ b/src/sage/combinat/shifted_primed_tableau.py @@ -2716,9 +2716,11 @@ def _add_strip(sub_tab, full_tab, length): if sub_tab and len(sub_tab) < len(full_tab): plat_list.append(min(sub_tab[-1] + primed_strip[-2] - 1, full_tab[len(sub_tab)])) - for row in reversed(range(1, len(sub_tab))): - plat_list.append(min(sub_tab[row-1]+primed_strip[row-1]-1, full_tab[row]) - - sub_tab[row] - primed_strip[row]) + plat_list.extend( + min(sub_tab[row-1] + primed_strip[row-1] - 1, full_tab[row]) + - sub_tab[row] - primed_strip[row] + for row in reversed(range(1, len(sub_tab)))) + if sub_tab: plat_list.append(full_tab[0] - sub_tab[0] - primed_strip[0]) else: diff --git a/src/sage/combinat/skew_partition.py b/src/sage/combinat/skew_partition.py index bee333dc9a5..aad38f9afbf 100644 --- a/src/sage/combinat/skew_partition.py +++ b/src/sage/combinat/skew_partition.py @@ -1032,12 +1032,9 @@ def cells(self): """ outer = self.outer() inner = self.inner()[:] - inner += [0]*(len(outer)-len(inner)) - res = [] - for i in range(len(outer)): - for j in range(inner[i], outer[i]): - res.append( (i,j) ) - return res + inner += [0] * (len(outer) - len(inner)) + return [(i, j) for i, outi in enumerate(outer) + for j in range(inner[i], outi)] def to_list(self): """ @@ -1148,15 +1145,13 @@ def rows_intersection_set(self): sage: skp.rows_intersection_set() == cells True """ - res = [] outer = self.outer() inner = self.inner() - inner += [0] * int(len(outer)-len(inner)) + inner += [0] * (len(outer) - len(inner)) - for i in range(len(outer)): - for j in range(outer[i]): - if outer[i] != inner[i]: - res.append((i,j)) + res = [(i, j) for i, outi in enumerate(outer) + for j in range(outi) + if outi != inner[i]] return Set(res) def columns_intersection_set(self): @@ -1238,7 +1233,7 @@ def jacobi_trudi(self): h = SymmetricFunctions(QQ).homogeneous() H = MatrixSpace(h, nn) - q = q + [0]*int(nn-len(q)) + q = q + [0] * (nn - len(q)) m = [] for i in range(1,nn+1): row = [] diff --git a/src/sage/combinat/skew_tableau.py b/src/sage/combinat/skew_tableau.py index ca57594ed71..44189013d5c 100644 --- a/src/sage/combinat/skew_tableau.py +++ b/src/sage/combinat/skew_tableau.py @@ -1172,10 +1172,10 @@ def row_stabilizer(self): # tableau, by including the identity permutation on the set [1..k]. k = self.size() gens = [list(range(1, k + 1))] - for row in self: - for j in range(len(row) - 1): - if row[j] is not None: - gens.append((row[j], row[j + 1])) + gens.extend((row[j], row[j + 1]) + for row in self + for j in range(len(row) - 1) + if row[j] is not None) return PermutationGroup(gens) def column_stabilizer(self): @@ -1777,12 +1777,10 @@ def cells(self): sage: s.cells() [(0, 1), (0, 2), (1, 0), (2, 0)] """ - res = [] - for i in range(len(self)): - for j in range(len(self[i])): - if self[i][j] is not None: - res.append((i, j)) - return res + return [(i, j) + for i, selfi in enumerate(self) + for j in range(len(selfi)) + if selfi[j] is not None] def cells_containing(self, i): r""" @@ -1950,12 +1948,11 @@ def from_expr(self, expr): sage: SkewTableaux().from_expr([[1,1],[[5],[3,4],[1,2]]]) [[None, 1, 2], [None, 3, 4], [5]] """ - skp = [] outer = expr[1] inner = expr[0] + [0] * (len(outer) - len(expr[0])) - for i in range(len(outer)): - skp.append([None] * (inner[i]) + outer[-(i + 1)]) + skp = [[None] * (inner[i]) + outer[-(i + 1)] + for i in range(len(outer))] return self.element_class(self, skp) diff --git a/src/sage/combinat/specht_module.py b/src/sage/combinat/specht_module.py index 0114a8f191b..1af53083f89 100644 --- a/src/sage/combinat/specht_module.py +++ b/src/sage/combinat/specht_module.py @@ -1146,18 +1146,12 @@ def _to_diagram(D): if isinstance(D, Diagram): return D if D in _Partitions: - D = _Partitions(D).cells() - elif D in SkewPartitions(): - D = SkewPartitions()(D).cells() - elif D in IntegerVectors(): - cells = [] - for i, row in enumerate(D): - for j in range(row): - cells.append((i, j)) - D = cells - else: - D = [tuple(cell) for cell in D] - return D + return _Partitions(D).cells() + if D in SkewPartitions(): + return SkewPartitions()(D).cells() + if D in IntegerVectors(): + return [(i, j) for i, row in enumerate(D) for j in range(row)] + return [tuple(cell) for cell in D] def specht_module_spanning_set(D, SGA=None): diff --git a/src/sage/combinat/subset.py b/src/sage/combinat/subset.py index 5fea2f5f97b..6c8d923ce9f 100644 --- a/src/sage/combinat/subset.py +++ b/src/sage/combinat/subset.py @@ -321,8 +321,10 @@ def __contains__(self, value): True sage: 2 in S False + sage: {1, 2} in S + True """ - if value not in Sets(): + if value not in Sets() and not isinstance(value, (set, frozenset)): return False return all(v in self._s for v in value) diff --git a/src/sage/combinat/symmetric_group_algebra.py b/src/sage/combinat/symmetric_group_algebra.py index b9ea545cd60..47820fc23a5 100644 --- a/src/sage/combinat/symmetric_group_algebra.py +++ b/src/sage/combinat/symmetric_group_algebra.py @@ -1043,10 +1043,8 @@ def central_orthogonal_idempotents(self): - :meth:`central_orthogonal_idempotent` """ - out = [] - for key in sorted(self._blocks_dictionary, reverse=True): - out.append(self.central_orthogonal_idempotent(key)) - return out + return [self.central_orthogonal_idempotent(key) + for key in sorted(self._blocks_dictionary, reverse=True)] def central_orthogonal_idempotent(self, la, block=True): r""" @@ -2016,9 +2014,9 @@ def seminormal_basis(self, mult='l2r'): basis = [] for part in Partitions_n(self.n): stp = StandardTableaux_shape(part) - for t1 in stp: - for t2 in stp: - basis.append(self.epsilon_ik(t1, t2, mult=mult)) + basis.extend(self.epsilon_ik(t1, t2, mult=mult) + for t1 in stp + for t2 in stp) return basis def dft(self, form=None, mult='l2r'): diff --git a/src/sage/combinat/tableau.py b/src/sage/combinat/tableau.py index 19f9033fcea..80e3391b279 100644 --- a/src/sage/combinat/tableau.py +++ b/src/sage/combinat/tableau.py @@ -1148,12 +1148,10 @@ def descents(self): sage: Tableau( [[1,2,3],[4,5]] ).descents() [(1, 0), (1, 1)] """ - descents = [] - for i in range(1, len(self)): - for j in range(len(self[i])): - if self[i][j] > self[i-1][j]: - descents.append((i, j)) - return descents + return [(i, j) + for i in range(1, len(self)) + for j, selfij in enumerate(self[i]) + if selfij > self[i-1][j]] def major_index(self): """ @@ -1214,15 +1212,15 @@ def inversions(self): for j, entry in enumerate(row): # c is in position (i,j) # find the d that satisfy condition 1 - for k in range(j+1, len(row)): - if entry > row[k]: - inversions.append(((i, j), (i, k))) + inversions.extend(((i, j), (i, k)) + for k in range(j + 1, len(row)) + if entry > row[k]) # find the d that satisfy condition 2 if i == 0: continue - for k in range(j): - if entry > previous_row[k]: - inversions.append(((i, j), (i-1, k))) + inversions.extend(((i, j), (i - 1, k)) + for k in range(j) + if entry > previous_row[k]) previous_row = row return inversions diff --git a/src/sage/combinat/tableau_tuple.py b/src/sage/combinat/tableau_tuple.py index a24ac5ca809..fd847c4ad1e 100644 --- a/src/sage/combinat/tableau_tuple.py +++ b/src/sage/combinat/tableau_tuple.py @@ -1084,10 +1084,8 @@ def row_stabilizer(self): # tableau, by including the identity permutation on the set [1..n]. n = max(self.entries()) gens = [list(range(1, n + 1))] - for t in self: - for i in range(len(t)): - for j in range(len(t[i]) - 1): - gens.append((t[i][j], t[i][j + 1])) + gens.extend((ti[j], ti[j + 1]) for t in self + for ti in t for j in range(len(ti) - 1)) return PermutationGroup(gens) def column_stabilizer(self): @@ -2644,11 +2642,10 @@ def an_element(self): ([[1, 2]], [], []) """ if self.size() == 0: - return self.element_class(self, [[] for _ in range(self.level())]) + return self.element_class(self, [[]] * self.level()) tab = [[list(range(1, self.size() + 1))]] - for _ in range(self.level() - 1): - tab.append([]) + tab.extend([] for _ in range(self.level() - 1)) return self.element_class(self, tab) diff --git a/src/sage/combinat/words/words.py b/src/sage/combinat/words/words.py index c230c90057d..f33c868d636 100644 --- a/src/sage/combinat/words/words.py +++ b/src/sage/combinat/words/words.py @@ -293,7 +293,7 @@ def _sortkey_letters(self, letter1): rk = self.alphabet().rank return rk(letter1) - def __eq__(self, other): + def __eq__(self, other) -> bool: r""" TESTS:: @@ -309,7 +309,7 @@ def __eq__(self, other): return self is other or (type(self) is type(other) and self.alphabet() == other.alphabet()) - def __ne__(self, other): + def __ne__(self, other) -> bool: r""" TESTS:: @@ -479,7 +479,8 @@ def _word_from_word(self, data): return self._element_classes['list'](self, data) from sage.combinat.words.word_datatypes import (WordDatatype_str, - WordDatatype_list, WordDatatype_tuple) + WordDatatype_list, + WordDatatype_tuple) if isinstance(data, WordDatatype_str): return self._element_classes['str'](self, data._data) if isinstance(data, WordDatatype_tuple): @@ -487,8 +488,8 @@ def _word_from_word(self, data): if isinstance(data, WordDatatype_list): return self._element_classes['list'](self, data._data) - from sage.combinat.words.word_infinite_datatypes import (WordDatatype_callable, - WordDatatype_iter) + from sage.combinat.words.word_infinite_datatypes import \ + (WordDatatype_callable, WordDatatype_iter) if isinstance(data, WordDatatype_callable): length = data.length() data = data._func @@ -849,7 +850,7 @@ def __call__(self, data=None, length=None, datatype=None, caching=True, check=Tr self._check(w) return w - def _repr_(self): + def _repr_(self) -> str: """ EXAMPLES:: @@ -875,14 +876,14 @@ def _an_element_(self): """ try: some_letters = list(self.alphabet().some_elements()) - except Exception: + except (TypeError, ValueError, AttributeError, NotImplementedError): return self([]) if len(some_letters) == 1: return self([some_letters[0]] * 3) - else: - a, b = some_letters[:2] - return self([b, a, b]) + + a, b = some_letters[:2] + return self([b, a, b]) def iterate_by_length(self, l=1): r""" @@ -912,10 +913,20 @@ def iterate_by_length(self, l=1): Traceback (most recent call last): ... TypeError: the parameter l (='a') must be an integer + + TESTS:: + + sage: W = FiniteWords(NN) + sage: list(W.iterate_by_length(1)) + Traceback (most recent call last): + ... + NotImplementedError: cannot iterate over words for infinite alphabets """ if not isinstance(l, (int, Integer)): raise TypeError("the parameter l (=%r) must be an integer" % l) cls = self._element_classes['tuple'] + if not self.alphabet().is_finite(): + raise NotImplementedError("cannot iterate over words for infinite alphabets") for w in itertools.product(self.alphabet(), repeat=l): yield cls(self, w) @@ -961,7 +972,7 @@ def __iter__(self): for l in itertools.count(): yield from self.iterate_by_length(l) - def __contains__(self, x): + def __contains__(self, x) -> bool: """ Test whether ``self`` contains ``x``. diff --git a/src/sage/doctest/__main__.py b/src/sage/doctest/__main__.py new file mode 100644 index 00000000000..9c1c7931325 --- /dev/null +++ b/src/sage/doctest/__main__.py @@ -0,0 +1,235 @@ +import argparse +import os +import sys + +# Note: the DOT_SAGE and SAGE_STARTUP_FILE environment variables have already been set by sage-env +DOT_SAGE = os.environ.get('DOT_SAGE', os.path.join(os.environ.get('HOME'), + '.sage')) + +# Override to not pick up user configuration, see Issue #20270 +os.environ['SAGE_STARTUP_FILE'] = os.path.join(DOT_SAGE, 'init-doctests.sage') + + +def _get_optional_defaults(): + """Return the default value for the --optional flag.""" + optional = ['sage', 'optional'] + + return ','.join(optional) + + +def _make_parser(): + r""" + Return the :class:`argparse.ArgumentParser`. + + TESTS: + + Test that the defaults are the consistent:: + + sage: from sage.doctest.control import DocTestDefaults + sage: from sage.doctest.__main__ import _make_parser + sage: os.environ.pop('SAGE_DOCTEST_RANDOM_SEED', None) + ... + sage: parser = _make_parser() + sage: args = parser.parse_args([]) + sage: DD = DocTestDefaults(runtest_default=True); DD + DocTestDefaults(abspath=False, file_iterations=0, global_iterations=0, + optional='sage,optional', random_seed=None, + stats_path='.../timings2.json') + sage: D = copy(args.__dict__) + sage: del D['filenames'] + sage: DA = DocTestDefaults(runtest_default=True, **D); DA + DocTestDefaults(abspath=False, file_iterations=0, global_iterations=0, + optional='sage,optional', random_seed=None, + stats_path='.../timings2.json') + """ + parser = argparse.ArgumentParser(usage="sage -t [options] filenames", + description="Run all tests in a file or a list of files whose extensions " + "are one of the following: " + ".py, .pyx, .pxd, .pxi, .sage, .spyx, .tex, .rst.") + parser.add_argument("-p", "--nthreads", dest="nthreads", + type=int, nargs='?', const=0, default=1, metavar="N", + help="test in parallel using N threads, with 0 interpreted as max(2, min(8, cpu_count())); " + "when run under the control of the GNU make jobserver (make -j), request as most N job slots") + parser.add_argument("-T", "--timeout", type=int, default=-1, help="timeout (in seconds) for doctesting one file, 0 for no timeout") + what = parser.add_mutually_exclusive_group() + what.add_argument("-a", "--all", action="store_true", default=False, help="test all files in the Sage library") + what.add_argument("--installed", action="store_true", default=False, help="test all installed modules of the Sage library") + parser.add_argument("--logfile", type=argparse.FileType('a'), metavar="FILE", help="log all output to FILE") + + parser.add_argument("--format", choices=["sage", "github"], default="sage", + help="set format of error messages and warnings") + parser.add_argument("-l", "--long", action="store_true", default=False, help="include lines with the phrase 'long time'") + parser.add_argument("-s", "--short", dest="target_walltime", nargs='?', + type=int, default=-1, const=300, metavar="SECONDS", + help="run as many doctests as possible in about 300 seconds (or the number of seconds given as an optional argument)") + parser.add_argument("--warn-long", dest="warn_long", nargs='?', + type=float, default=-1.0, const=1.0, metavar="SECONDS", + help="warn if tests take more time than SECONDS") + # By default, include all tests marked 'sagemath_doc_html' -- see + # https://github.com/sagemath/sage/issues/25345 and + # https://github.com/sagemath/sage/issues/26110: + parser.add_argument("--optional", metavar="FEATURES", default=_get_optional_defaults(), + help='only run tests including one of the "# optional" tags listed in FEATURES (separated by commas); ' + 'if "sage" is listed, will also run the standard doctests; ' + 'if "sagemath_doc_html" is listed, will also run the tests relying on the HTML documentation; ' + 'if "optional" is listed, will also run tests for installed optional packages or detected features; ' + 'if "external" is listed, will also run tests for available external software; ' + 'if set to "all", then all tests will be run; ' + 'use "!FEATURE" to disable tests marked "# optional - FEATURE". ' + 'Note that "!" needs to be quoted or escaped in the shell.') + parser.add_argument("--hide", metavar="FEATURES", default="", + help='run tests pretending that the software listed in FEATURES (separated by commas) is not installed; ' + 'if "all" is listed, will also hide features corresponding to all optional or experimental packages; ' + 'if "optional" is listed, will also hide features corresponding to optional packages.') + parser.add_argument("--probe", metavar="FEATURES", default="", + help='run tests that would not be run because one of the given FEATURES (separated by commas) is not installed; ' + 'report the tests that pass nevertheless') + parser.add_argument("--randorder", type=int, metavar="SEED", help="randomize order of tests") + parser.add_argument("--random-seed", dest="random_seed", type=int, metavar="SEED", help="random seed (integer) for fuzzing doctests", + default=os.environ.get("SAGE_DOCTEST_RANDOM_SEED")) + parser.add_argument("--global-iterations", "--global_iterations", type=int, default=0, help="repeat the whole testing process this many times") + parser.add_argument("--file-iterations", "--file_iterations", type=int, default=0, help="repeat each file this many times, stopping on the first failure") + parser.add_argument("--environment", type=str, default="sage.repl.ipython_kernel.all_jupyter", help="name of a module that provides the global environment for tests") + + parser.add_argument("-i", "--initial", action="store_true", default=False, help="only show the first failure in each file") + parser.add_argument("--exitfirst", action="store_true", default=False, help="end the test run immediately after the first failure or unexpected exception") + parser.add_argument("--force_lib", "--force-lib", action="store_true", default=False, help="do not import anything from the tested file(s)") + parser.add_argument("--if-installed", action="store_true", default=False, help="skip Python/Cython files that are not installed as modules") + parser.add_argument("--abspath", action="store_true", default=False, help="print absolute paths rather than relative paths") + parser.add_argument("--verbose", action="store_true", default=False, help="print debugging output during the test") + parser.add_argument("-d", "--debug", action="store_true", default=False, help="drop into a python debugger when an unexpected error is raised") + parser.add_argument("--only-errors", action="store_true", default=False, help="only output failures, not test successes") + + parser.add_argument("--gdb", action="store_true", default=False, help="run doctests under the control of gdb") + parser.add_argument("--lldb", action="store_true", default=False, help="run doctests under the control of lldb") + parser.add_argument("--valgrind", "--memcheck", action="store_true", default=False, + help="run doctests using Valgrind's memcheck tool. The log " + "files are named sage-memcheck.PID and can be found in " + + os.path.join(DOT_SAGE, "valgrind")) + parser.add_argument("--massif", action="store_true", default=False, + help="run doctests using Valgrind's massif tool. The log " + "files are named sage-massif.PID and can be found in " + + os.path.join(DOT_SAGE, "valgrind")) + parser.add_argument("--cachegrind", action="store_true", default=False, + help="run doctests using Valgrind's cachegrind tool. The log " + "files are named sage-cachegrind.PID and can be found in " + + os.path.join(DOT_SAGE, "valgrind")) + parser.add_argument("--omega", action="store_true", default=False, + help="run doctests using Valgrind's omega tool. The log " + "files are named sage-omega.PID and can be found in " + + os.path.join(DOT_SAGE, "valgrind")) + + parser.add_argument("-f", "--failed", action="store_true", default=False, + help="doctest only those files that failed in the previous run") + what.add_argument("-n", "--new", action="store_true", default=False, + help="doctest only those files that have been changed in the repository and not yet been committed") + parser.add_argument("--show-skipped", "--show_skipped", action="store_true", default=False, + help="print a summary at the end of each file of optional tests that were skipped") + + parser.add_argument("--stats_path", "--stats-path", default=os.path.join(DOT_SAGE, "timings2.json"), + help="path to a json dictionary for timings and failure status for each file from previous runs; it will be updated in this run") + parser.add_argument("--baseline_stats_path", "--baseline-stats-path", default=None, + help="path to a json dictionary for timings and failure status for each file, to be used as a baseline; it will not be updated") + + class GCAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + gcopts = dict(DEFAULT=0, ALWAYS=1, NEVER=-1) + new_value = gcopts[values] + setattr(namespace, self.dest, new_value) + + parser.add_argument("--gc", + choices=["DEFAULT", "ALWAYS", "NEVER"], + default=0, + action=GCAction, + help="control garbarge collection " + "(ALWAYS: collect garbage before every test; NEVER: disable gc; DEFAULT: Python default)") + + # The --serial option is only really for internal use, better not + # document it. + parser.add_argument("--serial", action="store_true", default=False, help=argparse.SUPPRESS) + # Same for --die_timeout + parser.add_argument("--die_timeout", type=int, default=-1, help=argparse.SUPPRESS) + + parser.add_argument("filenames", help="file names", nargs='*') + return parser + + +def main(): + parser = _make_parser() + # custom treatment to separate properly + # one or several file names at the end + new_arguments = [] + need_filenames = True + in_filenames = False + afterlog = False + for arg in sys.argv[1:]: + if arg in ('-n', '--new', '-a', '--all', '--installed'): + need_filenames = False + elif need_filenames and not (afterlog or in_filenames) and os.path.exists(arg): + in_filenames = True + new_arguments.append('--') + new_arguments.append(arg) + afterlog = arg in ['--logfile', '--stats_path', '--stats-path', + '--baseline_stats_path', '--baseline-stats-path'] + + args = parser.parse_args(new_arguments) + + if not args.filenames and not (args.all or args.new or args.installed): + print('either use --new, --all, --installed, or some filenames') + return 2 + + # Limit the number of threads to 2 to save system resources. + # See Issue #23713, #23892, #30351 + if sys.platform == 'darwin': + os.environ["OMP_NUM_THREADS"] = "1" + else: + os.environ["OMP_NUM_THREADS"] = "2" + + os.environ["SAGE_NUM_THREADS"] = "2" + + from sage.doctest.control import DocTestController + DC = DocTestController(args, args.filenames) + err = DC.run() + + # Issue #33521: Do not run pytest if the pytest configuration is not available. + # This happens when the source tree is not available and SAGE_SRC falls back + # to SAGE_LIB. + from sage.env import SAGE_SRC + if not all(os.path.isfile(os.path.join(SAGE_SRC, f)) + for f in ["conftest.py", "tox.ini"]): + return err + + try: + exit_code_pytest = 0 + import pytest + pytest_options = [] + if args.verbose: + pytest_options.append("-v") + + # #35999: no filename in arguments defaults to "src" + if not args.filenames: + filenames = [SAGE_SRC] + else: + # #31924: Do not run pytest on individual Python files unless + # they match the pytest file pattern. However, pass names + # of directories. We use 'not os.path.isfile(f)' for this so that + # we do not silently hide typos. + filenames = [f for f in args.filenames + if f.endswith("_test.py") or not os.path.isfile(f)] + if filenames: + print(f"Running pytest on {filenames} with options {pytest_options}") + exit_code_pytest = pytest.main(filenames + pytest_options) + if exit_code_pytest == 5: + # Exit code 5 means there were no test files, pass in this case + exit_code_pytest = 0 + + except ModuleNotFoundError: + print("pytest is not installed in the venv, skip checking tests that rely on it") + + if err == 0: + return exit_code_pytest + return err + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 34c7e1299c5..e6fcc29a61b 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -63,28 +63,39 @@ class DocTestDefaults(SageObject): """ This class is used for doctesting the Sage doctest module. - It fills in attributes to be the same as the defaults defined in - ``sage-runtests``, expect for a few places, - which is mostly to make doctesting more predictable. + INPUT: + + - ``runtest_default`` -- (boolean, default ``False``); if ``True``, + fills in attribute to be the same as the defaults defined in + ``sage-runtests``. If ``False``, change defaults in a few places + for use in doctests of the doctester, which is mostly to make + doctesting more predictable. + + - ``**kwds`` -- attributes to override defaults EXAMPLES:: sage: from sage.doctest.control import DocTestDefaults - sage: D = DocTestDefaults() - sage: D + sage: D = DocTestDefaults(); D DocTestDefaults() sage: D.timeout -1 Keyword arguments become attributes:: - sage: D = DocTestDefaults(timeout=100) - sage: D + sage: D = DocTestDefaults(timeout=100); D DocTestDefaults(timeout=100) sage: D.timeout 100 + + The defaults for ``sage-runtests``:: + + sage: D = DocTestDefaults(runtest_default=True); D + DocTestDefaults(abspath=False, file_iterations=0, global_iterations=0, + optional='sage,optional', random_seed=None, + stats_path='.../timings2.json') """ - def __init__(self, **kwds): + def __init__(self, runtest_default=False, **kwds): """ Edit these parameters after creating an instance. @@ -109,15 +120,15 @@ def __init__(self, **kwds): self.long = False self.warn_long = -1.0 self.randorder = None - self.random_seed = 0 - self.global_iterations = 1 # sage-runtests default is 0 - self.file_iterations = 1 # sage-runtests default is 0 + self.random_seed = None if runtest_default else 0 + self.global_iterations = 0 if runtest_default else 1 + self.file_iterations = 0 if runtest_default else 1 self.environment = "sage.repl.ipython_kernel.all_jupyter" self.initial = False self.exitfirst = False self.force_lib = False self.if_installed = False - self.abspath = True # sage-runtests default is False + self.abspath = not runtest_default self.verbose = False self.debug = False self.only_errors = False @@ -139,7 +150,10 @@ def __init__(self, **kwds): # automatically anyway. However, this default is still used for # displaying user-defined optional tags and we don't want to see # the auto_optional_tags there. - self.optional = {'sage'} | auto_optional_tags + if runtest_default: + self.optional = ','.join(['sage', 'optional']) + else: + self.optional = {'sage'} | auto_optional_tags self.hide = '' self.probe = '' @@ -149,7 +163,8 @@ def __init__(self, **kwds): # We don't want to use the real stats file by default so that # we don't overwrite timings for the actual running doctests. - self.stats_path = os.path.join(DOT_SAGE, "timings_dt_test.json") + self.stats_path = os.path.join( + DOT_SAGE, "timings2.json" if runtest_default else "timings_dt_test.json") self.__dict__.update(kwds) def _repr_(self): @@ -970,7 +985,7 @@ def expand_files_into_sources(self): sage: DC = DocTestController(DD, [dirname]) sage: DC.expand_files_into_sources() sage: len(DC.sources) - 15 + 16 sage: DC.sources[0].options.optional True @@ -1082,6 +1097,7 @@ def sort_sources(self): sage.doctest.control sage.doctest.check_tolerance sage.doctest.all + sage.doctest.__main__ sage.doctest """ if self.options.nthreads > 1 and len(self.sources) > self.options.nthreads: @@ -1259,25 +1275,33 @@ def _optional_tags_string(self): def _assemble_cmd(self): """ - Assembles a shell command used in running tests under gdb, lldb, or valgrind. + Assemble a shell command used in running tests under gdb, lldb, or valgrind. EXAMPLES:: sage: from sage.doctest.control import DocTestDefaults, DocTestController sage: DC = DocTestController(DocTestDefaults(timeout=123), ["hello_world.py"]) sage: print(DC._assemble_cmd()) - sage-runtests --serial --timeout=123 hello_world.py + ...python... -m sage.doctest --serial... --timeout=123... hello_world.py """ - cmd = "sage-runtests --serial " - opt = dict_difference(self.options.__dict__, DocTestDefaults().__dict__) - if "all" in opt: - raise ValueError("You cannot run gdb/lldb/valgrind on the whole sage library") - for o in ("all", "long", "force_lib", "verbose", "failed", "new"): + cmd = f"{shlex.quote(sys.executable)} -m sage.doctest --serial " + opt = dict_difference(self.options.__dict__, DocTestDefaults(runtest_default=True).__dict__) + # Options with no argument + for o in ("all", "installed", "long", "initial", "exitfirst", + "force_lib", "if_installed", "abspath", "verbose", + "debug", "only_errors", "failed", "new", + "show_skipped"): if o in opt: - cmd += "--%s " % o - for o in ("timeout", "randorder", "stats_path"): + cmd += "--%s " % o.replace('_', '-') + # Options with one argument + for o in ("timeout", "die_timeout", "logfile", "warn_long", "randorder", + "random_seed", "global_iterations", "file_iterations", + "environment", "baseline_stats_path", "stats_path"): if o in opt: - cmd += "--%s=%s " % (o, opt[o]) + cmd += "--%s=%s " % (o.replace('_', '-'), opt[o]) + # One with a different dest + if "target_walltime" in opt: + cmd += "--%s=%s " % ("short", opt[o]) if "optional" in opt: cmd += "--optional={} ".format(self._optional_tags_string()) return cmd + " ".join(self.files) @@ -1301,14 +1325,14 @@ def run_val_gdb(self, testing=False): sage: DD = DocTestDefaults(gdb=True) sage: DC = DocTestController(DD, ["hello_world.py"]) sage: DC.run_val_gdb(testing=True) - exec gdb --eval-command="run" --args ...python... sage-runtests --serial --timeout=0 hello_world.py + exec gdb --eval-command="run" --args ...python... -m sage.doctest --serial... --timeout=0... hello_world.py :: sage: DD = DocTestDefaults(valgrind=True, optional='all', timeout=172800) sage: DC = DocTestController(DD, ["hello_world.py"]) sage: DC.run_val_gdb(testing=True) - exec valgrind --tool=memcheck --leak-resolution=high --leak-check=full --num-callers=25 --suppressions="...valgrind/pyalloc.supp" --suppressions="...valgrind/sage.supp" --suppressions="...valgrind/sage-additional.supp" --log-file=.../valgrind/sage-memcheck.%p... sage-runtests --serial --timeout=172800 --optional=all hello_world.py + exec valgrind --tool=memcheck --leak-resolution=high --leak-check=full --num-callers=25 --suppressions=.../valgrind/pyalloc.supp --suppressions=.../valgrind/sage.supp --suppressions=.../valgrind/sage-additional.supp --suppressions=.../valgrind/valgrind-python.supp --log-file=.../valgrind/sage-memcheck.%p ...python... -m sage.doctest --serial... --timeout=172800... --optional=all hello_world.py """ try: sage_cmd = self._assemble_cmd() @@ -1318,13 +1342,12 @@ def run_val_gdb(self, testing=False): opt = self.options if opt.gdb: - cmd = f'''exec gdb --eval-command="run" --args {shlex.quote(sys.executable)} ''' + cmd = f'''exec gdb --eval-command="run" --args ''' flags = "" if opt.logfile: sage_cmd += f" --logfile {shlex.quote(opt.logfile)}" elif opt.lldb: - sage_cmd = sage_cmd.replace('sage-runtests', '$(command -v sage-runtests)') - cmd = f'''exec lldb --one-line "process launch" --one-line "cont" -- {sys.executable} ''' + cmd = f'''exec lldb --one-line "process launch" --one-line "cont" -- ''' flags = "" else: if opt.logfile is None: @@ -1343,9 +1366,9 @@ def run_val_gdb(self, testing=False): flags = os.getenv("SAGE_MEMCHECK_FLAGS") if flags is None: flags = "--leak-resolution=high --leak-check=full --num-callers=25 " - flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE, "valgrind", "pyalloc.supp")) - flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE, "valgrind", "sage.supp")) - flags += '''--suppressions="%s" ''' % (os.path.join(SAGE_EXTCODE, "valgrind", "sage-additional.supp")) + for supp in ["pyalloc.supp", "sage.supp", "sage-additional.supp", "valgrind-python.supp"]: + fname = os.path.join(SAGE_EXTCODE, "valgrind", supp) + flags += f"--suppressions={shlex.quote(fname)} " elif opt.massif: toolname = "massif" flags = os.getenv("SAGE_MASSIF_FLAGS", "--depth=6 ") diff --git a/src/sage/features/jmol.py b/src/sage/features/jmol.py index 52104202a40..cf5780094bd 100644 --- a/src/sage/features/jmol.py +++ b/src/sage/features/jmol.py @@ -36,7 +36,7 @@ def __init__(self): filename='JmolData.jar', search_path=jmol_search_path, spkg='jmol', - type='standard', + type='optional', description="Java viewer for chemical structures in 3D") diff --git a/src/sage/graphs/connectivity.pyx b/src/sage/graphs/connectivity.pyx index b390e0c9877..ba515980093 100644 --- a/src/sage/graphs/connectivity.pyx +++ b/src/sage/graphs/connectivity.pyx @@ -23,7 +23,8 @@ Here is what the module can do: :meth:`connected_components_sizes` | Return the sizes of the connected components as a list. :meth:`blocks_and_cut_vertices` | Return the blocks and cut vertices of the graph. :meth:`blocks_and_cuts_tree` | Return the blocks-and-cuts tree of the graph. - :meth:`is_cut_edge` | Return ``True`` if the input edge is a cut-edge or a bridge. + :meth:`is_cut_edge` | Check whether the input edge is a cut-edge or a bridge. + :meth:`is_edge_cut` | Check whether the input edges form an edge cut. :meth:`is_cut_vertex` | Check whether the input vertex is a cut-vertex. :meth:`edge_connectivity` | Return the edge connectivity of the graph. :meth:`vertex_connectivity` | Return the vertex connectivity of the graph. @@ -70,6 +71,7 @@ Methods # **************************************************************************** from sage.misc.superseded import deprecation +from sage.sets.disjoint_set cimport DisjointSet def is_connected(G): @@ -732,13 +734,160 @@ def blocks_and_cuts_tree(G): return g +def is_edge_cut(G, edges): + """ + Check whether ``edges`` form an edge cut. + + A set of edges is an edge cut of a graph if its removal increases the number + of connected components. In a digraph, we consider the number of (weakly) + connected components. + + This method is not working for (di)graphs with multiple edges. Furthermore, + edge labels are ignored. + + INPUT: + + - ``G`` -- a (di)graph + + - ``edges`` -- a set of edges + + EXAMPLES: + + A cycle graph of order 4:: + + sage: from sage.graphs.connectivity import is_edge_cut + sage: G = graphs.CycleGraph(4) + sage: is_edge_cut(G, [(1, 2)]) + False + sage: is_edge_cut(G, [(1, 2), (2, 3)]) + True + sage: is_edge_cut(G, [(1, 2), (3, 0)]) + True + + A pending edge is a cut-edge:: + + sage: G.add_edge((0, 5, 'silly')) + sage: is_edge_cut(G, [(0, 5, 'silly')]) + True + + Edge labels are ignored, even if specified:: + + sage: G.add_edge((2, 5, 'xyz')) + sage: is_edge_cut(G, [(0, 5), (2, 5)]) + True + sage: is_edge_cut(G, [(0, 5), (2, 5, 'xyz')]) + True + sage: is_edge_cut(G, [(0, 5, 'silly'), (2, 5)]) + True + sage: is_edge_cut(G, [(0, 5, 'aa'), (2, 5, 'bb')]) + True + + The graph can have loops:: + + sage: G.allow_loops(True) + sage: G.add_edge(0, 0) + sage: is_edge_cut(G, [(0, 5), (2, 5)]) + True + sage: is_edge_cut(G, [(0, 0), (0, 5), (2, 5)]) + True + + Multiple edges are not allowed:: + + sage: G.allow_multiple_edges(True) + sage: is_edge_cut(G, [(0, 5), (2, 5)]) + Traceback (most recent call last): + ... + ValueError: This method is not known to work on graphs with + multiedges. Perhaps this method can be updated to handle them, but in + the meantime if you want to use it please disallow multiedges using + allow_multiple_edges(). + + An error is raised if an element of ``edges`` is not an edge of `G`:: + + sage: G = graphs.CycleGraph(4) + sage: is_edge_cut(G, [(0, 2)]) + Traceback (most recent call last): + ... + ValueError: edge (0, 2) is not an edge of the graph + + For digraphs, this method considers the number of (weakly) connected + components:: + + sage: G = digraphs.Circuit(4) + sage: is_edge_cut(G, [(0, 1)]) + False + sage: G = digraphs.Circuit(4) + sage: is_edge_cut(G, [(0, 1), (1, 2)]) + True + + For disconnected (di)graphs, the method checks if the number of (weakly) + connected components increases:: + + sage: G = graphs.CycleGraph(4) * 2 + sage: is_edge_cut(G, [(1, 2), (2, 3)]) + True + sage: G = digraphs.Circuit(4) * 2 + sage: is_edge_cut(G, [(0, 1), (1, 2)]) + True + """ + G._scream_if_not_simple(allow_loops=True) + + cdef set C = set() # set of edges of the potential cut + cdef set S = set() # set of incident vertices + for e in edges: + u, v = e[0], e[1] + if not G.has_edge(u, v): + raise ValueError("edge {0} is not an edge of the graph".format(repr(e))) + if u == v: + # We ignore loops + continue + if G.degree(u) == 1 or G.degree(v) == 1: + # e is a pending edge and so a cut-edge + return True + S.add(u) + S.add(v) + C.add((u, v)) + if not G.is_directed(): + C.add((v, u)) + + cdef list queue + cdef set seen + DS = DisjointSet(G) + + for comp in G.connected_components(): + if not S.intersection(comp): + # This component is not involved in the cut + continue + + # We run a DFS in comp from any vertex and avoid edges in C + start = comp[0] + queue = [start] + seen = set(queue) + while queue: + v = queue.pop() + for e in G.edge_iterator(vertices=[v], labels=False, ignore_direction=True, sort_vertices=False): + if e in C: + continue + w = e[1] if e[0] == v else e[0] + if w not in seen: + seen.add(w) + DS.union(v, w) + queue.append(w) + + # We now check if some vertices of comp have not been reached + if len(set(DS.find(v) for v in comp)) > 1: + return True + + return False + + def is_cut_edge(G, u, v=None, label=None): """ - Return ``True`` if the input edge is a cut-edge or a bridge. + Check whether the edge ``(u, v)`` is a cut-edge or a bridge of graph ``G``. A cut edge (or bridge) is an edge that when removed increases - the number of connected components. This function works with - simple graphs as well as graphs with loops and multiedges. In + the number of connected components. This function works with + simple graphs as well as graphs with loops and multiedges. In a digraph, a cut edge is an edge that when removed increases the number of (weakly) connected components. @@ -787,20 +936,7 @@ def is_cut_edge(G, u, v=None, label=None): Traceback (most recent call last): ... ValueError: edge not in graph - - TESTS: - - If ``G`` is not a Sage graph, an error is raised:: - - sage: is_cut_edge('I am not a graph',0) - Traceback (most recent call last): - ... - TypeError: the input must be a Sage graph """ - from sage.graphs.generic_graph import GenericGraph - if not isinstance(G, GenericGraph): - raise TypeError("the input must be a Sage graph") - if label is None: if v is None: try: diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index 02590ac8f7c..3ff37a8d918 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -28,6 +28,7 @@ :meth:`~DiGraphGenerators.ImaseItoh` | Return the digraph of Imase and Itoh of order `n` and degree `d`. :meth:`~DiGraphGenerators.Kautz` | Return the Kautz digraph of degree `d` and diameter `D`. :meth:`~DiGraphGenerators.nauty_directg` | Return an iterator yielding digraphs using nauty's ``directg`` program. + :meth:`~DiGraphGenerators.nauty_posetg` | Return an iterator yielding Hasse diagrams of posets using nauty's ``genposetg`` program. :meth:`~DiGraphGenerators.Paley` | Return a Paley digraph on `q` vertices. :meth:`~DiGraphGenerators.Path` | Return a directed path on `n` vertices. :meth:`~DiGraphGenerators.RandomDirectedAcyclicGraph` | Return a random (weighted) directed acyclic graph of order `n`. @@ -558,14 +559,14 @@ def tournaments_nauty(self, n, ``None`` (default), then the min/max out-degree is not constrained - ``debug`` -- boolean (default: ``False``); if ``True`` the first line - of genbg's output to standard error is captured and the first call to - the generator's ``next()`` function will return this line as a string. - A line leading with ">A" indicates a successful initiation of the - program with some information on the arguments, while a line beginning - with ">E" indicates an error with the input. + of gentourng's output to standard error is captured and the first call + to the generator's ``next()`` function will return this line as a + string. A line leading with ">A" indicates a successful initiation of + the program with some information on the arguments, while a line + beginning with ">E" indicates an error with the input. - ``options`` -- string; anything else that should be forwarded as input - to Nauty's genbg. See its documentation for more information : + to Nauty's gentourng. See its documentation for more information : ``_. EXAMPLES:: @@ -758,6 +759,63 @@ def nauty_directg(self, graphs, options='', debug=False): if line and line[0] == '&': yield DiGraph(line[1:], format='dig6') + def nauty_posetg(self, options='', debug=False): + r""" + Return a generator which creates all posets using ``nauty``. + + Here a poset is seen through its Hasse diagram, which is + an acyclic and transitively reduced digraph. + + INPUT: + + - ``options`` -- string (default: ``""``); a string passed to + ``genposetg`` as if it was run at a system command line. + At a minimum, you *must* pass the number of vertices you desire + and a choice between ``o`` and ``t`` for the output order. + + - ``debug`` -- boolean (default: ``False``); if ``True`` the first line + of ``genposetg``'s output to standard error is captured and the first + call to the generator's ``next()`` function will return this line as a + string. A line leading with ">A" indicates a successful initiation of + the program with some information on the arguments, while a line + beginning with ">E" indicates an error with the input. + + The possible options, obtained as output of ``genposetg --help``:: + + n: the number of vertices, between 0 and 16 + o: digraph6 output in arbitrary order + t: digraph6 output in topological order + + EXAMPLES:: + + sage: gen = digraphs.nauty_posetg("5 o") + sage: len(list(gen)) + 63 + + This coincides with :oeis:`A000112`. + """ + import shlex + from sage.features.nauty import NautyExecutable + geng_path = NautyExecutable("genposetg").absolute_filename() + sp = subprocess.Popen(shlex.quote(geng_path) + f" {options}", shell=True, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True, + encoding='latin-1') + msg = sp.stderr.readline() + if debug: + yield msg + elif msg.startswith('>E'): + raise ValueError('wrong format of parameter option') + gen = sp.stdout + while True: + try: + s = next(gen) + except StopIteration: + # Exhausted list of graphs from nauty genposetg + return + G = DiGraph(s[1:-1], format='dig6') + yield G + def Complete(self, n, loops=False): r""" Return the complete digraph on `n` vertices. @@ -1486,7 +1544,9 @@ def RandomDirectedGNM(self, n, m, loops=False): sage: D.num_verts() 10 sage: D.loops() - [(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None), (4, 4, None), (5, 5, None), (6, 6, None), (7, 7, None), (8, 8, None), (9, 9, None)] + [(0, 0, None), (1, 1, None), (2, 2, None), (3, 3, None), + (4, 4, None), (5, 5, None), (6, 6, None), (7, 7, None), + (8, 8, None), (9, 9, None)] TESTS:: diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index e6e5312b8a8..aa10d15d4d8 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -3710,7 +3710,7 @@ def nauty_gentreeg(options='', debug=False): ... ValueError: wrong format of parameter options sage: list(graphs.nauty_gentreeg("3 -x", debug=True)) - ['>E Usage: ...gentreeg [-D#] [-Z#:#] [-ulps] [-q] n [res/mod] ... + ['>E Usage: ...gentreeg [-D#] [-Z#:#] [-ulps] [-q] n... [res/mod] ... sage: list(graphs.nauty_gentreeg("3", debug=True)) ['>A ...gentreeg ...\n', Graph on 3 vertices] """ diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index f789b1d7f8d..6ed8446ae1c 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -243,7 +243,8 @@ :meth:`~GenericGraph.connected_components_sizes` | Return the sizes of the connected components as a list. :meth:`~GenericGraph.blocks_and_cut_vertices` | Compute the blocks and cut vertices of the graph. :meth:`~GenericGraph.blocks_and_cuts_tree` | Compute the blocks-and-cuts tree of the graph. - :meth:`~GenericGraph.is_cut_edge` | Return ``True`` if the input edge is a cut-edge or a bridge. + :meth:`~GenericGraph.is_cut_edge` | Check whether the input edge is a cut-edge or a bridge. + :meth:`~GenericGraph.`is_edge_cut` | Check whether the input edges form an edge cut. :meth:`~GenericGraph.is_cut_vertex` | Return ``True`` if the input vertex is a cut-vertex. :meth:`~GenericGraph.edge_cut` | Return a minimum edge cut between vertices `s` and `t` :meth:`~GenericGraph.vertex_cut` | Return a minimum vertex cut between non-adjacent vertices `s` and `t` @@ -25000,6 +25001,7 @@ def is_self_complementary(self): from sage.graphs.connectivity import blocks_and_cut_vertices from sage.graphs.connectivity import blocks_and_cuts_tree from sage.graphs.connectivity import is_cut_edge + from sage.graphs.connectivity import is_edge_cut from sage.graphs.connectivity import is_cut_vertex from sage.graphs.connectivity import edge_connectivity from sage.graphs.connectivity import vertex_connectivity diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 4b0b43e5adb..134fe000df9 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -362,7 +362,7 @@ sage: G = graphs.RandomGNP(15,.3) sage: G.show() # needs sage.plot -And you can view it in three dimensions via jmol with ``show3d()``. :: +And you can view it in three dimensions with ``show3d()``. :: sage: G.show3d() # needs sage.plot @@ -10200,6 +10200,7 @@ def bipartite_double(self, extended=False): from sage.graphs.graph_decompositions.clique_separators import atoms_and_clique_separators from sage.graphs.graph_decompositions.bandwidth import bandwidth from sage.graphs.graph_decompositions.cutwidth import cutwidth + from sage.graphs.graph_decompositions.slice_decomposition import slice_decomposition matching_polynomial = LazyImport('sage.graphs.matchpoly', 'matching_polynomial', at_startup=True) from sage.graphs.cliquer import all_max_clique as cliques_maximum from sage.graphs.cliquer import all_cliques diff --git a/src/sage/graphs/graph_decompositions/slice_decomposition.pxd b/src/sage/graphs/graph_decompositions/slice_decomposition.pxd new file mode 100644 index 00000000000..fcd14a25300 --- /dev/null +++ b/src/sage/graphs/graph_decompositions/slice_decomposition.pxd @@ -0,0 +1,17 @@ +from libcpp.vector cimport vector + +from sage.structure.sage_object cimport SageObject +from sage.graphs.base.c_graph cimport CGraph + +cdef void extended_lex_BFS( + CGraph cg, vector[int] &sigma, vector[int] *sigma_inv, + int initial_v_int, vector[int] *pred, vector[size_t] *xslice_len, + vector[vector[int]] *lex_label) except * + +cdef class SliceDecomposition(SageObject): + cdef tuple sigma + cdef dict sigma_inv + cdef vector[size_t] xslice_len + cdef dict lex_label + cdef object _graph_class + cdef object _underlying_graph diff --git a/src/sage/graphs/graph_decompositions/slice_decomposition.pyx b/src/sage/graphs/graph_decompositions/slice_decomposition.pyx new file mode 100644 index 00000000000..39a85485dfa --- /dev/null +++ b/src/sage/graphs/graph_decompositions/slice_decomposition.pyx @@ -0,0 +1,1079 @@ +# cython: binding=True +# distutils: language = c++ +# distutils: extra_compile_args = -std=c++11 +r""" +Slice decomposition + +This module implements an extended lexBFS algorithm for computing the slice +decomposition of undirected graphs and the class :class:`~SliceDecomposition` to +represent such decompositions. + +A formal definition of slice decompositions can be found in Section 3.2 of +[TCHP2008]_ and a description of the extended lexBFS algorithm is given in +Section 3.3 of [TCHP2008]_. + +AUTHORS: + +- Cyril Bouvier (2024-06-25): initial version +""" +# **************************************************************************** +# Copyright (C) 2024 Cyril Bouvier +# +# 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 libcpp.algorithm cimport swap +from cython.operator cimport dereference as deref + +from sage.graphs.base.c_graph cimport CGraphBackend +from sage.data_structures.bitset_base cimport bitset_in + +cdef void extended_lex_BFS( + CGraph cg, vector[int] &sigma, vector[int] *sigma_inv, + int initial_v_int, vector[int] *pred, vector[size_t] *xslice_len, + vector[vector[int]] *lex_label) except *: + r""" + Perform a extended lexicographic breadth first search (LexBFS) on the + undirected graph `G`. + + In addition to computing a LexBFS ordering, the extended LexBFS algorithm + can be used to compute the slice decomposition of the graph. + + This function implements the `O(n+m)` time algorithm proposed in [HMPV2000]_ + and [TCHP2008]_. + + INPUT: + + - ``cg`` -- a ``CGraph``. This function ignores loops and multiple edges and + assumes that the graph is undirected. + + - ``sigma`` -- vector of ``int`` to store the ordering of the vertices + resulting from the LexBFS traversal. At the end, the vector will have size + `n` (the number of vertices of the graph). + + - ``sigma_inv`` -- a pointer to a vector to store the inverse of the + permutation ``sigma``. ``sigma_inv`` can be ``NULL`` if the caller does + not need it (but, note that, the inverse of ``sigma`` is still needed by + the algorithm, so it does not save time nor memory to have ``sigma_inv`` + equal to ``NULL``). At the end, if ``sigma_inv`` is not NULL, the vector + pointer by it will have size `n` (the number of vertices of the graph) + and will satisfy: + * sigma[deref(sigma_inv)[v_int]] = v_int + * deref(sigma_inv)[sigma[i]] = i + + - ``initial_v_int`` -- the first vertex to consider. It can be `-1`; in this + case the first active vertex (corresponding to the first bit set in + ``cg.active_vertices``) will be taken as first vertex. + + - ``pred`` -- a pointer to a vector of int to store the predecessor of a + vertex in the LexBFS traversal. ``pred`` can be ``NULL`` if the caller + does not need it (and the information will not be computed by the + algorithm). At the end, if ``pred`` is not NULL, the vector pointer by it + will have size `n` (the number of vertices of the graph) and pred[i] will + be either -1 (if sigma[i] as no predecessor) or a positive value less than + n such that the predecessor of sigma[i] is sigma[pred[i]]. + + - ``xslice_len`` -- a pointer to a vector of size_t to store the length of + the x-slices associated with the lexBFS traversal. ``xslice_len`` can be + ``NULL`` if the caller does not need it (and the information will not be + computed by the algorithm). At the end, if ``xslice_len`` is not NULL, the + vector pointer by it will have size `n` (the number of vertices of the + graph) and the length of the x-slice starting at sigma[i] will be + xslice_len[i]. + + - ``lex_label`` -- a pointer to a vector of vector[int] to store the + lexicographic labels associated with the lexBFS traversal. ``lex_label`` + can be ``NULL`` if the caller does not need it (and the information will + not be computed by the algorithm). At the end, if ``lex_label`` is not + NULL, the vector pointer by it will have size `n` (the number of + vertices of the graph) and the lexicographic label of sigma[i] + will given by lex_label[i]. + + ALGORITHM: + + This algorithm uses the notion of *partition refinement* to determine the + exact position of the vertices in the ordering. + + Consider an ordering `\sigma` of the vertices. For a vertex `v`, we define + `N_i(v) = \{u | u \in N(v) \text{ and } \sigma(u) < i\}`, that is the subset + of neighbors of `v` appearing before the `i`-th vertex in the ordering + `\sigma`. Now, a part of an ordering `\sigma` is a set of consecutive + vertices, `S = \{u | i \leq \sigma(u) \leq j\}`, such that for any `u \in + S`, we have `N_i(u) = N_i(\sigma^{-1}(i))` and for any `v` such that `j < + \sigma(v)`, `N_i(v) \neq N_i(\sigma^{-1}(i))`. The *head* of a part is the + first position of its vertices. + + The algorithm starts with a single part containing all vertices. Then, when + the position of the `i`-th vertex `v` is fixed, it explores the neighbors of + `v` that have not yet been ordered. Consider a part `S` such that `N(x)\cap + S \neq \emptyset`. The algorithm will rearrange the ordering of the vertices + in `S` so that the first vertices are the neighbors of `v`. The subpart + containing the neighbors of `v` is assigned a new name, and the head of `S` + is set to the position of the first vertex of `S \setminus N(v)` in the + ordering `\sigma`. + + Observe that each arc of the graph can induce the subdivision of a part. + Hence, the algorithm can use up to `m + 1` different parts. + + The time complexity of this algorithm is in `O(n + m)`, and our + implementation follows that complexity ``SparseGraph``. For ``DenseGraph``, + the complexity is `O(n^2)`. See [HMPV2000]_ and [TCHP2008]_ for more + details. + + This implementation of extended LexBFS offers some guarantee on the order in + which the vertices appear in the computed ordering: in case of a tie between + lexicographic labels during the computation, this function will "choose" the + vertices in the order in which they appear during the enumeration of the + neighbors of their last common neighbor. + For example, if `(u_0, ..., u_k)` is the beginning of the ordering being + computed and the vertices `v` and `w` currently have the same lexicographic + label (it means that they have the same neighbors in `(u_0, ..., u_k)`). + Let call `u_j` their last neighbor in the current ordering (*i.e.*, for all + `i > j`, `u_i` is not a neighbor of `v` and `w`). This implementation + will choose `v` for the next vertex of the ordering if and only if `v` + appeared before `w` when the neighbors of `u_j` where enumerated. + + One possible use of this guarantee is that the caller can reorder the + adjacency list of vertices (by using, for example, a static sparse graph) + to force the computed LexBFS order to respect a previous one. + + EXAMPLES:: + + To see how it can be used, see the code of the lex_BFS method (in + traversals.pyx) or of the class SliceDecomposition in this module. + + TESTS: + + Indirect doctests:: + + sage: G = graphs.HouseGraph() + sage: G.slice_decomposition() + [0[1[2]] [3] [4]] + sage: G.lex_BFS(algorithm="fast") + [0, 1, 2, 3, 4] + """ + cdef int n = cg.num_verts + # Variables for the partition refinement algorithm + cdef size_t max_nparts = cg.num_arcs // 2 + 1 + cdef bint need_to_delete_sigma_inv = sigma_inv == NULL + if sigma_inv == NULL: + sigma_inv = new vector[int]() + cdef vector[size_t] part_of = vector[size_t](n, 0) + cdef vector[size_t] part_len # only used if xslice_len != NULL (see below) + cdef vector[size_t] part_head = vector[size_t](max_nparts) + cdef vector[size_t] subpart = vector[size_t](max_nparts) + cdef size_t p, part_of_i, nparts, old_nparts + # Temporary variables + cdef int max_degree = 0 + cdef int i, j, k, l, u_int, v_int, t_int + + # Resize vectors + sigma.resize(n) + deref(sigma_inv).resize(cg.active_vertices.size) + if pred != NULL: + deref(pred).clear() + deref(pred).resize(n, -1) # initialize pred[i] to -1 for 0 <= i < n + if xslice_len != NULL: + deref(xslice_len).resize(n) + part_len.resize(max_nparts) + if lex_label != NULL: + deref(lex_label).resize(n) + + # Initialize the position of vertices in sigma (and compute max_degree) + if initial_v_int >= 0: + sigma[0] = initial_v_int + deref(sigma_inv)[initial_v_int] = 0 + i = 1 + else: + i = 0 + for v_int in range( cg.active_vertices.size): + if bitset_in(cg.active_vertices, v_int): + if v_int != initial_v_int: + sigma[i] = v_int + deref(sigma_inv)[v_int] = i + i = i + 1 + max_degree = max(max_degree, cg.out_degrees[v_int]) + + # Variables needed to iterate over neighbors of a vertex + cdef int nneighbors + cdef vector[int] neighbors = vector[int](max_degree) + + # Initialize partition: one part containing all the vertices + nparts = 1 + # all element of part_of are already initialized to 0 + part_head[0] = 0 + subpart[0] = 0 + if xslice_len != NULL: + part_len[0] = n + + # Main loop + for i in range(n): + old_nparts = nparts + + part_of_i = part_of[i] + + # put i out of its part (updating part_len if needed) + part_head[part_of_i] += 1 + if xslice_len != NULL: + deref(xslice_len)[i] = part_len[part_of_i] + part_len[part_of_i] -= 1 + + v_int = sigma[i] + + # Iterate over the neighbors of v + nneighbors = cg.out_neighbors_unsafe (v_int, neighbors.data(), max_degree) + for k in range(nneighbors): + u_int = neighbors[k] + j = deref(sigma_inv)[u_int] # get the position of u + if j <= i: + continue # already taken care of + + if lex_label != NULL: + deref(lex_label)[j].push_back (v_int) + + p = part_of[j] # get the part of u + l = part_head[p] # get the beginning of the part containing u + + # if not last and next elem belongs in the same part (ie #part >= 2) + if l < n - 1 and part_of[l + 1] == p: + if l != j: # not already first elem of the part + # Place u at the position of the head of the part + t_int = sigma[l] + deref(sigma_inv)[t_int], deref(sigma_inv)[u_int] = j, l + sigma[j], sigma[l] = t_int, u_int + if lex_label != NULL: + swap[vector[int]](deref(lex_label)[j], + deref(lex_label)[l]) + j = l + part_head[p] += 1 # move the head of the part to next elem + + # if part p was not already cut in two during this iteration, we + # create a new part using subpart + if subpart[p] < old_nparts: + subpart[p] = nparts + part_head[nparts] = j + if xslice_len != NULL: + part_len[nparts] = 0 + subpart[nparts] = 0 + nparts += 1 + + # Finally, we update the name of the part for position j and set v + # as predecessor of u + part_of[j] = subpart[p] + if xslice_len != NULL: + part_len[p] -= 1 + part_len[subpart[p]] += 1 + if pred != NULL: + deref(pred)[j] = i + + if need_to_delete_sigma_inv: + del sigma_inv + + +def slice_decomposition(G, initial_vertex=None): + r""" + Compute a slice decomposition of the simple undirected graph + + INPUT: + + - ``G`` -- a Sage graph. + + - ``initial_vertex`` -- (default: ``None``); the first vertex to consider. + + OUTPUT: + + An object of type :class:`~sage.graphs.graph_decompositions.slice_decomposition.SliceDecomposition` + that represents a slice decomposition of ``G`` + + .. NOTE:: + + Loops and multiple edges are ignored during the computation of the slice + decomposition. + + ALGORITHM: + + The method use the algorithm based on "partition refinement" described in + [HMPV2000]_ and [TCHP2008]_. + The time complexity of this algorithm is in `O(n + m)`, and our + implementation follows that complexity for ``SparseGraph``. For + ``DenseGraph``, the complexity is `O(n^2)`. + + EXAMPLES: + + Slice decomposition of the Petersen Graph:: + + sage: G = graphs.PetersenGraph() + sage: SD = G.slice_decomposition(); SD + [0[1[4[5]]] [2[6]] [3] [9] [7] [8]] + + The graph can have loops or multiple edges but they are ignored:: + + sage: H = Graph(G,loops=True,multiedges=True) + sage: H.add_edges([(4, 4), (2, 2), (1, 6)]) + sage: SD2 = H.slice_decomposition() + sage: SD2 == SD + True + sage: SD2.underlying_graph() == G.to_simple(immutable=True) + True + + The tree corresponding to the slice decomposition can be displayed using + ``view``:: + + sage: from sage.graphs.graph_latex import check_tkz_graph + sage: check_tkz_graph() # random - depends on Tex installation + sage: view(G) # not tested + sage: latex(G) # to obtain the corresponding LaTeX code + \begin{tikzpicture} + ... + \end{tikzpicture} + + Slice decompositions are only defined for undirected graphs:: + + sage: from sage.graphs.graph_decompositions.slice_decomposition import slice_decomposition + sage: slice_decomposition(DiGraph()) + Traceback (most recent call last): + ... + ValueError: parameter G must be an undirected graph + """ + return SliceDecomposition(G, initial_vertex=initial_vertex) + + +cdef class SliceDecomposition(SageObject): + + def __init__(self, G, initial_vertex=None): + r""" + Represents a slice decomposition of a simple directed graph. + + INPUT: + + - ``G`` -- a Sage graph. + + - ``initial_vertex`` -- (default: ``None``); the first vertex to + consider. + + .. SEEALSO:: + + * :meth:`~slice_decomposition` -- compute a slice decomposition of + the simple undirected graph + * Section 3.2 of [TCHP2008]_ for a formal definition. + + EXAMPLES: + + The constructor of the :class:`~SliceDecomposition` class is called by + the :meth:`~slice_decomposition` method of undirected graphs:: + + sage: from sage.graphs.graph_decompositions.slice_decomposition import SliceDecomposition + sage: G = graphs.PetersenGraph() + sage: SliceDecomposition(G) == G.slice_decomposition() + True + + The vertex appearing first in the slice decomposition can be specified:: + + sage: from sage.graphs.graph_decompositions.slice_decomposition import SliceDecomposition + sage: SliceDecomposition(graphs.PetersenGraph(), initial_vertex=3) + [3[2[4[8]]] [1[7]] [0] [9] [6] [5]] + + Slice decompositions are not defined for directed graphs:: + + sage: from sage.graphs.graph_decompositions.slice_decomposition import SliceDecomposition + sage: SliceDecomposition(DiGraph()) + Traceback (most recent call last): + ... + ValueError: parameter G must be an undirected graph + + .. automethod:: __getitem__ + """ + if G.is_directed(): + raise ValueError("parameter G must be an undirected graph") + + if initial_vertex is not None and initial_vertex not in G: + raise LookupError(f"vertex ({initial_vertex}) is not a vertex of the graph") + + cdef CGraphBackend Gbackend = G._backend + cdef CGraph cg = Gbackend.cg() + + self._graph_class = type(G) + + cdef int initial_v_int + if initial_vertex is not None: + # we already checked that initial_vertex is in G + initial_v_int = Gbackend.get_vertex(initial_vertex) + else: + initial_v_int = -1 + + cdef vector[int] sigma + cdef vector[vector[int]] lex_label + + # Compute the slice decomposition using the extended lexBFS algorithm + extended_lex_BFS(cg, sigma, NULL, initial_v_int, NULL, + &(self.xslice_len), &lex_label) + + # Translate the results with the actual vertices of the graph + self.sigma = tuple(Gbackend.vertex_label(v_int) for v_int in sigma) + self.sigma_inv = {v: i for i, v in enumerate(self.sigma)} + self.lex_label = {i: tuple(Gbackend.vertex_label(v_int) for v_int in lli) + for i, lli in enumerate(lex_label)} + + def __eq__(self, other): + """ + Return whether ``self`` and ``other`` are equal. + + TESTS:: + + sage: G = graphs.PetersenGraph() + sage: SD = G.slice_decomposition() + sage: SD == SD + True + sage: SD == G.slice_decomposition() + True + + sage: P3 = graphs.PathGraph(3) + sage: SD1 = P3.slice_decomposition(initial_vertex=0) + sage: SD2 = P3.slice_decomposition(initial_vertex=2) + sage: SD1 == SD2 + False + sage: SD3 = graphs.CompleteGraph(3).slice_decomposition() + sage: SD1 == SD3 # same lexBFS but different slice for 1 + False + sage: SD4 = Graph([(0,1), (0,2)]).slice_decomposition() + sage: SD3 == SD4 # same lexBFS and slices but different active edges + False + """ + if not isinstance(other, type(self)): + return False + + cdef SliceDecomposition sd = other + + return self.sigma_inv == sd.sigma_inv \ + and self.lex_label == sd.lex_label \ + and self.xslice_len == sd.xslice_len + + def __hash__(self): + r""" + Compute a hash of a ``SliceDecomposition`` object. + + TESTS:: + + sage: P3 = graphs.PathGraph(3) + sage: SD1 = P3.slice_decomposition(initial_vertex=0) + sage: SD2 = P3.slice_decomposition(initial_vertex=2) + sage: len({SD1: 1, SD2: 2}) # indirect doctest + 2 + """ + return hash((tuple(self.sigma_inv.items()), + tuple(self.lex_label.items()), + tuple(self.xslice_len))) + + def __getitem__(self, v): + r""" + Return the data about the x-slice of the vertex `v`. + + INPUT: + + - ``v`` -- a vertex of the graph corresponding to the slice + decomposition. + + OUTPUT: + + A dictionnary with the keys: + + * ``"pivot"`` -- the vertex `v` given as parameter + + * ``"slice"`` -- the slice of `v` (see :meth:`~slice`) + + * ``"active_edges"`` -- the actives edges of `v` (see + :meth:`~active_edges`) + + * ``"lexicographic_label"`` -- the lexicographic label of `v` (see + :meth:`~lexicographic_label`) + + * ``"sequence"`` -- the x-slice sequence of `v` (see + :meth:`~xslice_sequence`) + + This method can also be called via :meth:`xslice_data`. + + EXAMPLES: + + :: + + sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False) + sage: SD = G.slice_decomposition(initial_vertex='x') + sage: SD.xslice_data('a') + {'active_edges': [('a', 'b'), + ('a', 'c'), + ('a', 'd'), + ('a', 'e'), + ('a', 'f'), + ('c', 'g'), + ('d', 'g'), + ('f', 'g')], + 'lexicographic_label': ['x'], + 'pivot': 'a', + 'sequence': [['a'], ['b', 'c', 'd', 'e', 'f'], ['g']], + 'slice': ['a', 'b', 'c', 'd', 'e', 'f', 'g']} + sage: SD.xslice_data('u') + {'active_edges': [], + 'lexicographic_label': ['a', 'b', 'c', 'd', 'e', 'f', 'g'], + 'pivot': 'u', + 'sequence': [['u'], ['y', 'z']], + 'slice': ['u', 'y', 'z']} + + Some values of the returned dictionnary can be obtained via other + methods (:meth:`~slice`, :meth:`~xslice_sequence`, + :meth:`~active_edges`, :meth:`~lexicographic_label`):: + + sage: SD.slice('a') + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + sage: SD.xslice_data('a')['slice'] + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + + sage: SD.xslice_sequence('a') + [['a'], ['b', 'c', 'd', 'e', 'f'], ['g']] + sage: SD.xslice_data('a')['sequence'] + [['a'], ['b', 'c', 'd', 'e', 'f'], ['g']] + + sage: SD.active_edges('b') == SD.xslice_data('b')['active_edges'] + True + + sage: SD.lexicographic_label('u') + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + sage: SD.xslice_data('u')['lexicographic_label'] + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + + TESTS:: + + sage: G = graphs.RandomGNP(15, 0.3) + sage: SD = G.slice_decomposition() + sage: all(SD[v]['slice'] == SD.slice(v) for v in G) + True + sage: all(SD[v]['sequence'] == SD.xslice_sequence(v) for v in G) + True + sage: all(SD[v]['active_edges'] == SD.active_edges(v) for v in G) + True + sage: all(SD[v]['lexicographic_label'] == SD.lexicographic_label(v) for v in G) + True + + sage: SD = graphs.PetersenGraph().slice_decomposition() + sage: SD['John'] + Traceback (most recent call last): + ... + LookupError: vertex (John) does not appear in the slice decomposition + """ + if v not in self.sigma_inv: + raise LookupError(f"vertex ({v}) does not appear in the slice " + "decomposition") + cdef size_t i = self.sigma_inv[v] + return {'pivot': v, + 'slice': self._slice(i), + 'sequence': self._xslice_sequence(i), + 'lexicographic_label': self._xslice_lex_label(i), + 'active_edges': self._xslice_active_edges(i), + } + + def lexBFS_order(self): + r""" + Return the lexBFS order corresponding to the slice decomposition. + + EXAMPLES:: + + sage: from sage.graphs.traversals import _is_valid_lex_BFS_order + sage: G = graphs.PetersenGraph(); SD = G.slice_decomposition() + sage: SD.lexBFS_order() + [0, 1, 4, 5, 2, 6, 3, 9, 7, 8] + sage: _is_valid_lex_BFS_order(G, SD.lexBFS_order()) + True + + TESTS:: + + sage: from sage.graphs.traversals import _is_valid_lex_BFS_order + sage: for _ in range(5): + ....: G = graphs.RandomGNP(15, 0.3) + ....: SD = G.slice_decomposition() + ....: _is_valid_lex_BFS_order(G, SD.lexBFS_order()) + True + True + True + True + True + """ + return list(self.sigma) + + def xslice_data(self, v): + r""" + Return the data about the x-slice of the vertex `v`. + + This method is a wrapper around :meth:`SliceDecomposition.__getitem__` + + TESTS:: + + sage: G = graphs.RandomGNP(15, 0.3) + sage: SD = G.slice_decomposition() + sage: all(SD[v] == SD.xslice_data(v) for v in G) + True + """ + return self[v] + + def slice(self, v): + r""" + Return the slice of the vertex `v`. + + The slice of `v` is the list of vertices `u` such that the neighbors of + `u` that are before `v` in the lexBFS order are that same that the + neighbors of `v` that are before `v` in the lexBFS order (*i.e.*, the + lexicographic label of `v`). It can be shown that it is a factor of the + lexBFS order. + + INPUT: + + - ``v`` -- a vertex of the graph corresponding to the slice + decomposition. + + OUTPUT: + + A list of vertices + + EXAMPLES: + + :: + + sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False) + sage: SD = G.slice_decomposition(initial_vertex='x') + sage: SD.slice('a') + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + + The vertices of the slice have the same neighborhood "on the left":: + + sage: pos = lambda v: SD.lexBFS_order().index(v) + sage: lla = set(SD.lexicographic_label('a')) + sage: all(lla == {u for u in G.neighbors(v) if pos(u) < pos('a')} \ + ....: for v in SD.slice('a')) + True + + The slice is a factor of the lexBFS order:: + + sage: ''.join(SD.slice('a')) in ''.join(SD.lexBFS_order()) + True + + The slice of the initial vertex is the whole graph:: + + sage: SD.slice('x') == SD.lexBFS_order() + True + + TESTS:: + + sage: SD.slice('Michael') + Traceback (most recent call last): + ... + LookupError: vertex (Michael) does not appear in the slice decomposition + """ + if v not in self.sigma_inv: + raise LookupError(f"vertex ({v}) does not appear in the slice " + "decomposition") + cdef size_t i = self.sigma_inv[v] + return self._slice(i) + + def xslice_sequence(self, v): + r""" + Return the x-slice sequence of the vertex `v`. + + INPUT: + + - ``v`` -- a vertex of the graph corresponding to the slice + decomposition. + + OUTPUT: + + A list of list corresponding to the x-slice sequence of ``v``. + + EXAMPLES: + + :: + + sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False) + sage: SD = G.slice_decomposition(initial_vertex='x') + sage: SD.xslice_sequence('x') + [['x'], ['a', 'b', 'c', 'd', 'e', 'f', 'g'], ['u', 'y', 'z'], ['v', 'w']] + sage: SD.xslice_sequence('a') + [['a'], ['b', 'c', 'd', 'e', 'f'], ['g']] + + The flatten x-slice sequence of a vertex corresponds to the slice of the + same vertex:: + + sage: from itertools import chain + sage: all(list(chain(*SD.xslice_sequence(v))) == SD.slice(v) \ + ....: for v in G) + True + + The first list of the sequence is always a singleton containing the + input vertex:: + + sage: all(SD.xslice_sequence(v)[0] == [v] for v in G) + True + + If the length of the slice if more than 1, the second list of the + sequence is either, all the remaining vertices of the slice of `v`, if + `v` is isolated in the subgraph induced by the slice of `v`, or the + neighbors of `v` in the subgraph induced by the slice of `v`:: + + sage: all(SD.xslice_sequence(v)[1] == SD.slice(v)[1:] for v in G \ + ....: if G.subgraph(SD.slice(v)).degree(v) == 0 \ + ....: and len(SD.slice(v)) > 1) + True + sage: for v in G: + ....: if len(SD.slice(v)) > 1: + ....: xslice_seq = SD.xslice_sequence(v) + ....: S = G.subgraph(SD.slice(v)) + ....: if S.degree(v) > 0: + ....: set(xslice_seq[1]) == set(S.neighbor_iterator(v)) + True + True + True + True + + TESTS:: + + sage: SD = graphs.PetersenGraph().slice_decomposition() + sage: SD.xslice_sequence('Terry') + Traceback (most recent call last): + ... + LookupError: vertex (Terry) does not appear in the slice decomposition + """ + if v not in self.sigma_inv: + raise LookupError(f"vertex ({v}) does not appear in the slice " + "decomposition") + cdef size_t i = self.sigma_inv[v] + return self._xslice_sequence(i) + + def lexicographic_label(self, v): + r""" + Return the lexicographic label of the vertex `v`. + + The lexicographic label of a vertex `v` is the list of all the + neighbors of `v` that appear before `v` in the lexBFS ordering + corresponding to the slice decomposition. + + INPUT: + + - ``v`` -- a vertex of the graph corresponding to the slice + decomposition. + + OUTPUT: + + A list of vertices. + + EXAMPLES:: + + sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False) + sage: SD = G.slice_decomposition(initial_vertex='x') + sage: SD.lexicographic_label('f') + ['x', 'a', 'c', 'd'] + sage: pos = lambda v: SD.lexBFS_order().index(v) + sage: set(SD.lexicographic_label('f')) \ + ....: == {v for v in G.neighbors('f') if pos(v) < pos('f')} + True + + TESTS:: + + sage: SD = graphs.PetersenGraph().slice_decomposition() + sage: SD.lexicographic_label('Eric') + Traceback (most recent call last): + ... + LookupError: vertex (Eric) does not appear in the slice decomposition + """ + if v not in self.sigma_inv: + raise LookupError(f"vertex ({v}) does not appear in the slice " + "decomposition") + cdef size_t i = self.sigma_inv[v] + return self._xslice_lex_label(i) + + def active_edges(self, v): + r""" + Return the active edges of the vertex `v`. + + An edge `(u, w)` is said to be active for `v` if `u` and `w` belongs + to two differents slices of the x-slice sequence of `v`. Note that it + defines a partition of the edges of the underlying graph. + + INPUT: + + - ``v`` -- a vertex of the graph corresponding to the slice + decomposition. + + OUTPUT: + + A list of edges + + EXAMPLES: + + :: + + sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False) + sage: SD = G.slice_decomposition(initial_vertex='x') + sage: SD.xslice_sequence('a') + [['a'], ['b', 'c', 'd', 'e', 'f'], ['g']] + sage: ('c', 'g') in SD.active_edges('a') + True + sage: ('a', 'c') in SD.active_edges('a') + True + sage: ('c', 'd') in SD.active_edges('a') # c and d in same slice + False + sage: ('a', 'u') in SD.active_edges('a') # u not in x-slice of a + False + + The set of active edges of every vertex is a partition of the edges:: + + sage: from itertools import chain + sage: E = list(chain(*(SD.active_edges(v) for v in G))) + sage: G.size() == len(E) == len(set(E)) \ + ....: and all(G.has_edge(u, w) for v in G for u, w in SD.active_edges(v)) + True + + TESTS:: + + sage: SD = graphs.PetersenGraph().slice_decomposition() + sage: SD.active_edges('Graham') + Traceback (most recent call last): + ... + LookupError: vertex (Graham) does not appear in the slice decomposition + """ + if v not in self.sigma_inv: + raise LookupError(f"vertex ({v}) does not appear in the slice " + "decomposition") + cdef size_t i = self.sigma_inv[v] + return self._xslice_active_edges(i) + + def _slice(self, size_t idx): + r""" + This method is for internal use only + + TESTS: + + Indirect doctests:: + + sage: SD = graphs.HouseGraph().slice_decomposition() + sage: SD.slice(1) + [1, 2] + """ + return list(self.sigma[idx:idx+self.xslice_len[idx]]) + + def _xslice_sequence(self, size_t idx): + r""" + This method is for internal use only + + TESTS: + + Indirect doctests:: + + sage: SD = graphs.HouseGraph().slice_decomposition() + sage: SD.xslice_sequence(0) + [[0], [1, 2], [3], [4]] + """ + cdef size_t l = self.xslice_len[idx] + cdef size_t j = idx + 1 + cdef size_t lj + + S = [ [self.sigma[idx]] ] + while j < idx + l: + lj = self.xslice_len[j] + S.append(list(self.sigma[j:j+lj])) + j += lj + assert j == idx + l, "slice decomposition is ill-formed" + return S + + def _xslice_lex_label(self, size_t idx): + r""" + This method is for internal use only + + TESTS: + + Indirect doctests:: + + sage: SD = graphs.HouseGraph().slice_decomposition() + sage: SD.lexicographic_label(3) + [1, 2] + """ + return list(self.lex_label[idx]) + + def _xslice_active_edges(self, size_t idx): + r""" + This method is for internal use only + + TESTS: + + Indirect doctests:: + + sage: SD = graphs.HouseGraph().slice_decomposition() + sage: SD.active_edges(0) + [(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)] + """ + cdef size_t l = self.xslice_len[idx] + cdef size_t llv_prefix = len(self.lex_label[idx]) + cdef size_t j = idx + 1 + cdef size_t lj + + A = [] + while j < idx + l: + lj = self.xslice_len[j] + llj = self.lex_label[j] + for u in self.sigma[j:j+lj]: + for w in llj[llv_prefix:]: + A.append((w, u)) + j += lj + assert j == idx + l, "slice decomposition is ill-formed" + return A + + def underlying_graph(self): + r""" + Return the underlying graph corresponding to the slice decomposition. + + If `G` was the graph given as parameter to compute the slice + decomposition, the underlying graph corresponds to ``G.to_simple()`` + where labels are ignored, *i.e.*, it is the input graph without loops, + multiple edges and labels. + + .. NOTE:: + + This method is mostly defined to test the computation of + lexicographic labels and actives edges. + + EXAMPLES: + + :: + + sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False) + sage: SD = G.slice_decomposition(initial_vertex='x') + sage: SD.underlying_graph() == G + True + + The graph can have loops or multiple edges but they are ignored:: + + sage: G = graphs.CubeConnectedCycle(2) # multiple edges + sage: SD = G.slice_decomposition() + sage: SD.underlying_graph() == G.to_simple(immutable=True) + True + + sage: G = graphs.CubeConnectedCycle(1) # loops + sage: SD = G.slice_decomposition() + sage: SD.underlying_graph() == G.to_simple(immutable=True) + True + + TESTS:: + + sage: for _ in range(5): + ....: G = graphs.RandomGNP(15, 0.3) + ....: SD = G.slice_decomposition() + ....: SD.underlying_graph() == G + True + True + True + True + True + """ + if not hasattr(self, '_underlying_graph'): + vertices = self.sigma + edges = [(u, v) for i, v in enumerate(self.sigma) + for u in self.lex_label[i]] + data = [vertices, edges] + Gclass = self._graph_class + self._underlying_graph = Gclass(data, format='vertices_and_edges', + immutable=True) + return self._underlying_graph + + def _repr_(self): + r""" + Return a string representation of a ``SliceDecomposition`` object. + + TESTS:: + + sage: G = graphs.PetersenGraph(); SD = G.slice_decomposition() + sage: repr(SD) + '[0[1[4[5]]] [2[6]] [3] [9] [7] [8]]' + sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False) + sage: SD = G.slice_decomposition(initial_vertex='x'); repr(SD) + '[x[a[b[c[d]] [e[f]]] [g]] [u[y[z]]] [v[w]]]' + """ + def inner_repr(idx): + l = self.xslice_len[idx] + S = [] + if l > 1: + j = idx + 1 + while j < idx + l: + lj = self.xslice_len[j] + S.append(inner_repr(j)) + j += lj + assert j == idx + l, "slice decomposition is ill-formed" + return f'{self.sigma[idx]}' + ' '.join(f'[{s}]' for s in S) + return f'[{inner_repr(0)}]' + + def _latex_(self): + r""" + Return a string to render, using `\LaTeX`, the slice decomposition as a + tree. + + TESTS:: + + sage: from sage.graphs.graph_latex import check_tkz_graph + sage: check_tkz_graph() # random - depends on Tex installation + sage: G = graphs.PetersenGraph(); SD = G.slice_decomposition() + sage: latex(SD) + \begin{tikzpicture} + ... + v0 -- {l0, v1, v4, v6, v7, v8, v9}; + v1 -- {l1, v2}; + v2 -- {l2, v3}; + v3 -- {l3}; + v4 -- {l4, v5}; + v5 -- {l5}; + v6 -- {l6}; + v7 -- {l7}; + v8 -- {l8}; + v9 -- {l9}; + ... + \end{tikzpicture} + + """ + from sage.misc.latex import latex + + latex.add_package_to_preamble_if_available("tikz") + latex.add_to_preamble(r"\usetikzlibrary{arrows,shapes,fit}") + latex.add_to_preamble(r"\usetikzlibrary{graphs,graphdrawing}") + latex.add_to_preamble(r"\usegdlibrary{trees}") + + # Call latex() on all vertices + sigma_latex = [ latex(v) for v in self.sigma ] + slices = [[] for _ in self.sigma] + + lines = [ r"\begin{tikzpicture}" ] + lines.append(r"\graph [tree layout,level distance=0,level sep=1em," + r"sibling distance=0,sibling sep=0.6em," + r"tail anchor=center,head anchor=north," + r"nodes={draw,rectangle,inner xsep=0.2em},edges={thick}]") + lines.append("{") + bo, bc = "{", "}" # to write { and } in f-strings + # Create the nodes and leaves of the slice decomposition tree + for i in range(len(self.sigma)): + l = self.xslice_len[i] + label = r"\ ".join(sigma_latex[i:i+l]) + lines.append(f" v{i}[as={bo}${label}${bc}];") + lines.append(f" l{i}[draw=none,as={bo}${sigma_latex[i]}${bc}];") + j = i + 1 + slices[i].append(f"l{i}") + while j < i + l: + slices[i].append(f"v{j}") + j += self.xslice_len[j] + # Create the edges of the slice decomposition tree + for i, S in enumerate(slices): + lines.append(f" v{i} -- " + "{" + ", ".join(S) + "};") + lines.append("};") + # Add dahsed red boxes around xslices + for i, S in enumerate(slices): + fit=" ".join(f"({s})" for s in S) + lines.append(rf"\node (s{i}) [rectangle,inner xsep=0.2em,draw=red," + f"densely dashed,fit={fit}]{bo}{bc};") + + lines.append(r"\end{tikzpicture}") + return "\n".join(lines) diff --git a/src/sage/graphs/graph_generators.py b/src/sage/graphs/graph_generators.py index e8049664aae..f0b6c4472ed 100644 --- a/src/sage/graphs/graph_generators.py +++ b/src/sage/graphs/graph_generators.py @@ -1007,12 +1007,12 @@ def nauty_geng(self, options='', debug=False): def nauty_genbg(self, options='', debug=False): r""" - Return a generator which creates bipartite graphs from nauty's ``genbg`` + Return a generator which creates bipartite graphs from nauty's ``genbgL`` program. INPUT: - - ``options`` -- string (default: ``""``); a string passed to ``genbg`` + - ``options`` -- string (default: ``""``); a string passed to ``genbgL`` as if it was run at a system command line. At a minimum, you *must* pass the number of vertices you desire in each side. Sage expects the bipartite graphs to be in nauty's "graph6" format, do not set an @@ -1025,12 +1025,12 @@ def nauty_genbg(self, options='', debug=False): the program with some information on the arguments, while a line beginning with ">E" indicates an error with the input. - The possible options, obtained as output of ``genbg --help``:: + The possible options, obtained as output of ``genbgL --help``:: n1 : the number of vertices in the first class. - We must have n1=1..24. + We must have n1=1..30. n2 : the number of vertices in the second class. - We must have n2=0..32 and n1+n2=1..32. + We must have n2=0..64 and n1+n2=1..64. mine:maxe : : a range for the number of edges :0 means ' or more' except in the case 0:0 res/mod : only generate subset res out of subsets 0..mod-1 @@ -1058,8 +1058,8 @@ def nauty_genbg(self, options='', debug=False): -v : display counts by number of edges to stderr -l : canonically label output graphs - Options which cause ``genbg`` to use an output format different than the - ``graph6`` format are not listed above (``-s``, ``-a``) as they will + Options which cause ``genbgL`` to use an output format different than + the ``graph6`` format are not listed above (``-s``, ``-a``) as they will confuse the creation of a Sage graph. Option ``-q`` which suppress auxiliary output (except from ``-v``) should never be used as we are unable to recover the partition of the vertices of the bipartite graph @@ -1113,16 +1113,16 @@ def nauty_genbg(self, options='', debug=False): sage: len(list(gen)) 17 - The ``debug`` switch can be used to examine ``genbg``'s reaction to the + The ``debug`` switch can be used to examine ``genbgL``'s reaction to the input in the ``options`` string. A message starting with ">A" indicates success and a message starting with ">E" indicates a failure:: sage: gen = graphs.nauty_genbg("2 3", debug=True) sage: print(next(gen)) - >A ...genbg n=2+3 e=0:6 d=0:0 D=3:2 + >A ...genbg... n=2+3 e=0:6 d=0:0 D=3:2 sage: gen = graphs.nauty_genbg("-c2 3", debug=True) sage: next(gen) - '>E Usage: ...genbg [-c -ugs -vq -lzF] [-Z#] [-D#] [-A] [-d#|-d#:#] [-D#|-D#:#] n1 n2... + '>E Usage: ...genbg... [-c -ugs -vq -lzF] [-Z#] [-D#] [-A] [-d#|-d#:#] [-D#|-D#:#] n1 n2... Check that the partition of the bipartite graph is consistent:: @@ -1141,34 +1141,35 @@ def nauty_genbg(self, options='', debug=False): ... ValueError: wrong format of parameter options sage: list(graphs.nauty_genbg("-c1 2", debug=True)) - ['>E Usage: ...genbg [-c -ugs -vq -lzF] [-Z#] [-D#] [-A] [-d#|-d#:#] [-D#|-D#:#] n1 n2... + ['>E Usage: ...genbg... [-c -ugs -vq -lzF] [-Z#] [-D#] [-A] [-d#|-d#:#] [-D#|-D#:#] n1 n2... sage: list(graphs.nauty_genbg("-c 1 2", debug=True)) - ['>A ...genbg n=1+2 e=2:2 d=1:1 D=2:1 c...\n', Bipartite graph on 3 vertices] + ['>A ...genbg... n=1+2 e=2:2 d=1:1 D=2:1 c...\n', Bipartite graph on 3 vertices] - We must have n1=1..24, n2=0..32 and n1+n2=1..32 (:issue:`34179`):: + We must have n1=1..30, n2=0..64 and n1+n2=1..64 (:issue:`34179`, + :issue:`38618`):: - sage: next(graphs.nauty_genbg("25 1", debug=False)) + sage: next(graphs.nauty_genbg("31 1", debug=False)) Traceback (most recent call last): ... ValueError: wrong format of parameter options - sage: next(graphs.nauty_genbg("25 1", debug=True)) - '>E ...genbg: must have n1=1..24, n1+n2=1..32... - sage: next(graphs.nauty_genbg("24 9", debug=True)) - '>E ...genbg: must have n1=1..24, n1+n2=1..32... - sage: next(graphs.nauty_genbg("1 31", debug=False)) - Bipartite graph on 32 vertices - sage: next(graphs.nauty_genbg("1 32", debug=True)) - '>E ...genbg: must have n1=1..24, n1+n2=1..32... - sage: next(graphs.nauty_genbg("0 32", debug=True)) - '>E ...genbg: must have n1=1..24, n1+n2=1..32... + sage: next(graphs.nauty_genbg("31 1", debug=True)) + '>E ...genbg...: must have n1=1..30, n1+n2=1..64... + sage: next(graphs.nauty_genbg("30 40", debug=True)) + '>E ...genbg...: must have n1=1..30, n1+n2=1..64... + sage: next(graphs.nauty_genbg("1 63", debug=False)) + Bipartite graph on 64 vertices + sage: next(graphs.nauty_genbg("1 64", debug=True)) + '>E ...genbg...: must have n1=1..30, n1+n2=1..64... + sage: next(graphs.nauty_genbg("0 2", debug=True)) + '>E ...genbg...: must have n1=1..30, n1+n2=1..64... sage: next(graphs.nauty_genbg("2 0", debug=False)) Bipartite graph on 2 vertices sage: next(graphs.nauty_genbg("2 -1", debug=True)) - '>E Usage: ...genbg [-c -ugs -vq -lzF] [-Z#] [-D#] [-A] [-d#|-d#:#] [-D#|-D#:#] n1 n2... + '>E Usage: ...genbg... [-c -ugs -vq -lzF] [-Z#] [-D#] [-A] [-d#|-d#:#] [-D#|-D#:#] n1 n2... """ import shlex from sage.features.nauty import NautyExecutable - genbg_path = NautyExecutable("genbg").absolute_filename() + genbg_path = NautyExecutable("genbgL").absolute_filename() sp = subprocess.Popen(shlex.quote(genbg_path) + " {0}".format(options), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, @@ -1200,7 +1201,7 @@ def nauty_genbg(self, options='', debug=False): try: s = next(gen) except StopIteration: - # Exhausted list of bipartite graphs from nauty genbg + # Exhausted list of bipartite graphs from nauty genbgL return G = BipartiteGraph(s[:-1], format='graph6', partition=partition) yield G diff --git a/src/sage/graphs/traversals.pyx b/src/sage/graphs/traversals.pyx index fe9095001dd..c1b0f883578 100644 --- a/src/sage/graphs/traversals.pyx +++ b/src/sage/graphs/traversals.pyx @@ -20,12 +20,37 @@ Graph traversals :meth:`~maximum_cardinality_search` | Return an ordering of the vertices according a maximum cardinality search. :meth:`~maximum_cardinality_search_M` | Return the ordering and the edges of the triangulation produced by MCS-M. + +ALGORITHM: + +For :meth:`~lex_BFS` with ``algorithm="slow"``, :meth:`~lex_DFS`, +:meth:`~lex_UP` and :meth:`~lex_DOWN` the same generic implementation is used. +It corresponds to an implementation the generic algorithm described in +"Algorithm 1" of [Mil2017]_. + +This algorithm maintains for each vertex left in the graph a lexicographic label +corresponding to the vertices already removed. The vertex of maximal +lexicographic label is then removed, and the lexicographic labels of its +neighbors are updated. Depending on how the update is done, it corresponds to +LexBFS, LexUP, LexDFS or LexDOWN: during the `i`-th iteration of the algorithm +`n-i` (for LexBFS and LexDOWN) or `i` (for LexDFS and LexUP) is appended (for +LexBFS and LexUP) or prepended (for LexDFS and LexDOWN) to the lexicographic +labels of all neighbors of the selected vertex that are left in the graph. + +The time complexity of the algorithm is `O(mn)` for ``SparseGraph`` and +`O(\max\{mn, n^2\})` for ``DenseGraph``, where `n` is the number of vertices +and `m` is the number of edges. + +See [CK2008]_ and [Mil2017]_ for more details on the algorithm and graphs +searching. + Methods ------- """ # **************************************************************************** # Copyright (C) 2019 Georgios Giapitzakis Tzintanos # David Coudert +# 2024 Cyril Bouvier # # 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 @@ -47,15 +72,166 @@ from memory_allocator cimport MemoryAllocator from sage.graphs.base.static_sparse_graph cimport init_short_digraph from sage.graphs.base.static_sparse_graph cimport free_short_digraph from sage.graphs.base.static_sparse_graph cimport out_degree +from sage.graphs.base.c_graph cimport CGraph, CGraphBackend +from sage.graphs.graph_decompositions.slice_decomposition cimport \ + extended_lex_BFS + + +def _lex_order_common(G, algo, reverse, tree, initial_vertex): + r""" + Perform a lexicographic search (LexBFS, LexUP, LexDFS or LexDOWN) on the + graph. + + INPUT: + + - ``G`` -- a sage graph + + - ``algo`` -- string; the name of the actual algorithm among: + + - ``"lex_BFS"`` + + - ``"lex_UP"`` + + - ``"lex_DFS"`` + + - ``"lex_DOWN"`` + + - ``reverse`` -- whether to return the vertices in discovery order, or the + reverse + + - ``tree`` -- whether to return the discovery directed tree (each vertex + being linked to the one that saw it last) + + - ``initial_vertex`` -- the first vertex to consider, can be None + + .. NOTE:: + + Loops and multiple edges are ignored and directed graphs are considered + as undirected graphs. + + ALGORITHM: + + See the documentation of the :mod:`~sage.graphs.traversals` module. + + TESTS: + + Lex ordering of a graph on one vertex:: + + sage: Graph(1).lex_BFS(tree=True, algorithm="slow") + ([0], Digraph on 1 vertex) + sage: Graph(1).lex_UP(tree=True) + ([0], Digraph on 1 vertex) + sage: Graph(1).lex_DFS(tree=True) + ([0], Digraph on 1 vertex) + sage: Graph(1).lex_DOWN(tree=True) + ([0], Digraph on 1 vertex) + + Lex ordering of an empty (di)graph is an empty sequence:: + + sage: g = Graph() + sage: g.lex_BFS(algorithm="slow") + [] + sage: g.lex_BFS(algorithm="slow", tree=True) + ([], Digraph on 0 vertices) + sage: g.lex_UP() + [] + sage: g.lex_UP(tree=True) + ([], Digraph on 0 vertices) + sage: g.lex_DFS() + [] + sage: g.lex_DFS(tree=True) + ([], Digraph on 0 vertices) + sage: g.lex_DOWN() + [] + sage: g.lex_DFS(tree=True) + ([], Digraph on 0 vertices) + + Lex UP ordering of a symmetric digraph should be the same as the Lex UP + ordering of the corresponding undirected graph:: + + sage: G = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)]) + sage: H = DiGraph(G) + sage: G.lex_BFS(algorithm="slow") == H.lex_BFS(algorithm="slow") + True + sage: G.lex_UP() == H.lex_UP() + True + sage: G.lex_DFS() == H.lex_DFS() + True + sage: G.lex_DOWN() == H.lex_DOWN() + True + + ``initial_vertex`` should be a valid graph vertex:: + + sage: G = graphs.CompleteGraph(6) + sage: G.lex_BFS(initial_vertex='foo', algorithm="slow") + Traceback (most recent call last): + ... + ValueError: 'foo' is not a graph vertex + sage: G.lex_UP(initial_vertex='foo') + Traceback (most recent call last): + ... + ValueError: 'foo' is not a graph vertex + sage: G.lex_DFS(initial_vertex='foo') + Traceback (most recent call last): + ... + ValueError: 'foo' is not a graph vertex + sage: G.lex_DOWN(initial_vertex='foo') + Traceback (most recent call last): + ... + ValueError: 'foo' is not a graph vertex + """ + if initial_vertex is not None and initial_vertex not in G: + raise ValueError(f"'{initial_vertex}' is not a graph vertex") + + if algo not in ("lex_BFS", "lex_UP", "lex_DFS", "lex_DOWN"): + raise ValueError(f"unknown algorithm '{algo}'") + + cdef size_t n = G.order() + cdef list sigma = [] + cdef dict predecessor = {} + + cdef bint right = algo in ("lex_BFS", "lex_UP") + cdef bint decr = algo in ("lex_BFS", "lex_DOWN") + + cdef size_t cur_label = n if decr else -1 + cdef int label_incr = -1 if decr else 1 + + # Perform the search + lexicographic_label = {u: deque() for u in G} + if initial_vertex is not None: + # append or appendleft does not matter here, as the deque is empty + lexicographic_label[initial_vertex].append(cur_label) + while lexicographic_label: + u = max(lexicographic_label, key=lexicographic_label.get) + lexicographic_label.pop(u) + sigma.append(u) + cur_label += label_incr + for v in G.neighbor_iterator(u): # graphs are considered undirected + if v in lexicographic_label: + if right: + lexicographic_label[v].append(cur_label) + else: + lexicographic_label[v].appendleft(cur_label) + predecessor[v] = u + + if reverse: + sigma.reverse() + + if tree: + from sage.graphs.digraph import DiGraph + edges = predecessor.items() + g = DiGraph([G, edges], format='vertices_and_edges', sparse=True) + return sigma, g + return sigma def _is_valid_lex_BFS_order(G, L): r""" - Check whether `L` is a valid lex BFS ordering of the vertices of `G`. + Check whether ``L`` is a valid LexBFS ordering of the vertices of ``G``. Given two vertices `a` and `b` of `G = (V, E)`, we write `a < b` if `a` has a smaller label than `b`, and so if `a` is after `b` in the ordering `L`. - It is proved in [DNB1996]_ that any lex BFS ordering satisfies that, + It is proved in [DNB1996]_ that any LexBFS ordering satisfies that, if `a < b < c` and `ac \in E` and `bc \not\in E`, then there exists `d\in V` such that `c < d`, `db \in E` and `da \not\in E`. @@ -65,6 +241,21 @@ def _is_valid_lex_BFS_order(G, L): - ``L`` -- list; an ordering of the vertices of `G` + OUTPUT: + + - ``True`` if ``L`` is a LexBFS ordering of ``G``; ``False`` otherwise + + .. NOTE:: + + Loops and multiple edges are ignored for LexBFS ordering and directed + graphs are considered as undirected graphs. + + .. SEEALSO:: + + * :wikipedia:`Lexicographic_breadth-first_search` + * :meth:`~sage.graphs.generic_graph.GenericGraph.lex_BFS` -- perform a + lexicographic depth first search (LexBFS) on the graph + TESTS:: sage: from sage.graphs.traversals import _is_valid_lex_BFS_order @@ -87,7 +278,7 @@ def _is_valid_lex_BFS_order(G, L): """ # Convert G to a simple undirected graph if G.has_loops() or G.has_multiple_edges() or G.is_directed(): - G = G.to_simple(immutable=True, to_undirected=True) + G = G.to_simple(immutable=False, to_undirected=True) cdef int n = G.order() @@ -115,126 +306,7 @@ def _is_valid_lex_BFS_order(G, L): return True -cdef lex_BFS_fast_short_digraph(short_digraph sd, uint32_t *sigma, uint32_t *pred): - r""" - Perform a lexicographic breadth first search (LexBFS) on the graph. - - This method implements the `O(n+m)` time algorithm proposed in [HMPV2000]_. - - The method assumes that the initial vertex is vertex `0` and feeds input - arrays ``sigma`` and ``pred`` with respectively the ordering of the vertices - and the predecessor in the traversal. - - This algorithm uses the notion of *slices*, i.e., subsets of consecutive - vertices in the ordering, and iteratively refines the slices by subdividing - them into sub-slices to determine the exact position of the vertices in the - ordering. - - Consider an ordering `\sigma` of the vertices. For a vertex `v`, we define - `N_i(v) = \{u | u \in N(v) \text{ and } \sigma(u) < i\}`, that is the subset - of neighbors of `v` appearing before the `i`-th vertex in the ordering - `\sigma`. Now, a slice of an ordering `\sigma` is a set of consecutive - vertices, `S = \{u | i \leq \sigma(u) \leq j\}`, such that for any `u \in - S`, we have `N_i(u) = N_i(\sigma^{-1}(i))` and for any `v` such that `j < - \sigma(v)`, `N_i(v) \neq N_i(\sigma^{-1}(i))`. The *head* of a slice is the - first position of its vertices. - - The algorithm starts with a single slice containing all vertices. Then, when - the position of the `i`-th vertex `v` is fixed, it explores the neighbors of - `v` that have not yet been ordered. Consider a slice `S` such that `N(x)\cap - S \neq \emptyset`. The algorithm will rearrange the ordering of the vertices - in `S` so that the first vertices are the neighbors of `v`. The sub-slice - containing the neighbors of `v` is assigned a new slice name, and the head - of slice `S` is set to the position of the first vertex of `S \setminus - N(v)` in the ordering `\sigma`. - - Observe that each arc of the graph can induce the subdivision of a - slice. Hence, the algorithm can use up to `m + 1` different slices. - - INPUT: - - - ``sd`` -- a ``short_digraph`` - - - ``sigma`` -- array of size ``n`` to store the ordering of the vertices - resulting from the LexBFS traversal from vertex 0. This method assumes - that this array has already been allocated. However, there is no need to - initialize it. - - - ``pred`` -- array of size ``n`` to store the predecessor of a vertex in - the LexBFS traversal from vertex 0. This method assumes that this array - has already been allocated and initialized (e.g., ``pred[i] = i``). - - EXAMPLES: - - Lex BFS ordering of the 3-sun graph:: - - sage: g = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)]) - sage: g.lex_BFS(algorithm='fast') - [1, 2, 3, 5, 4, 6] - """ - cdef uint32_t n = sd.n - cdef uint32_t n_slice = sd.m + 1 - cdef MemoryAllocator mem = MemoryAllocator() - cdef uint32_t *sigma_inv = mem.allocarray(n, sizeof(uint32_t)) - cdef uint32_t *slice_of = mem.allocarray(n, sizeof(uint32_t)) - cdef uint32_t *slice_head = mem.allocarray(n_slice, sizeof(uint32_t)) - cdef uint32_t *subslice = mem.allocarray(n_slice, sizeof(uint32_t)) - cdef uint32_t i, j, k, l, a, old_k, v - cdef int wi - - # Initialize slices (slice_of, slice_head, subslice) to 0 - memset(slice_of, 0, n * sizeof(uint32_t)) - slice_head[0] = 0 - subslice[0] = 0 - - # Initialize the position of vertices in sigma - for i in range(n): - sigma[i] = i - sigma_inv[i] = i - - k = 1 - for i in range(n): - old_k = k - v = sigma[i] - - # We update the labeling of all unordered neighbors of v - for wi in range(out_degree(sd, v)): - w = sd.neighbors[v][wi] - j = sigma_inv[w] - if j <= i: - # w has already been ordered - continue - - # Get the name of the slice for position j - a = slice_of[j] - if slice_head[a] <= i: - # This slice cannot start at a position less than i - slice_head[a] = i + 1 - - # Get the position of the head of the slice - l = slice_head[a] - if l < n - 1 and slice_of[l + 1] == a: - if l != j: - # Place w at the position of the head of the slice - u = sigma[l] - sigma_inv[u], sigma_inv[w] = j, l - sigma[j], sigma[l] = u, w - j = l - slice_head[a] += 1 - if subslice[a] < old_k: - # Form a new slice - subslice[a] = k - slice_head[k] = j - subslice[k] = 0 - k += 1 - - # Finally, we update the name of the slice for position j and set v - # as predecessor of w - slice_of[j] = subslice[a] - pred[w] = v - - -def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm='fast'): +def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm="fast"): r""" Perform a lexicographic breadth first search (LexBFS) on the graph. @@ -247,62 +319,38 @@ def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm='fast') - ``tree`` -- boolean (default: ``False``); whether to return the discovery directed tree (each vertex being linked to the one that saw - it for the first time) + it last) - ``initial_vertex`` -- (default: ``None``) the first vertex to consider - ``algorithm`` -- string (default: ``'fast'``); algorithm to use among: - - ``'slow'`` -- this algorithm maintains for each vertex left in the graph - a code corresponding to the vertices already removed. The vertex of - maximal code (according to the lexicographic order) is then removed, and - the codes are updated. See for instance [CK2008]_ for more details. The - time complexity of this algorithm as described in [CK2008]_ is in - `O(n + m)`, where `n` is the number of vertices and `m` is the number of - edges, but our implementation is in `O(n^2)`. + - ``'slow'`` -- it use the generic algorithm for all the lexicographic + searchs. See the documentation of the :mod:`~sage.graphs.traversals` + module for more details. - - ``'fast'`` -- this algorithm uses the notion of *slices* to refine the - position of the vertices in the ordering. The time complexity of this - algorithm is in `O(n + m)`, and our implementation follows that - complexity for ``SparseGraph``. For ``DenseGraph``, the complexity is - `O(n^2)`. See [HMPV2000]_ and next section for more details. + - ``'fast'`` -- this algorithm uses the notion of *partition refinement* + to determine the position of the vertices in the ordering. The time + complexity of this algorithm is in `O(n + m)`, and our implementation + follows that complexity for ``SparseGraph``. For ``DenseGraph``, + the complexity is `O(n^2)`. See [HMPV2000]_ and [TCHP2008]_ for more + details. This algorithm is also used to compute slice decompositions of + undirected graphs, a more thorough description can be found in the + documentation of the + :mod:`~sage.graphs.graph_decompositions.slice_decomposition` module. - Loops and multiple edges are ignored during the computation of ``lex_BFS`` - and directed graphs are converted to undirected graphs. - - ALGORITHM: + .. NOTE:: - The ``'fast'`` algorithm is the `O(n + m)` time algorithm proposed in - [HMPV2000]_, where `n` is the number of vertices and `m` is the number of - edges. It uses the notion of *slices*, i.e., subsets of consecutive vertices - in the ordering, and iteratively refines the slices by subdividing them into - sub-slices to determine the exact position of the vertices in the ordering. - - Consider an ordering `\sigma` of the vertices. For a vertex `v`, we define - `N_i(v) = \{u | u \in N(v) \text{ and } \sigma(u) < i\}`, that is the subset - of neighbors of `v` appearing before the `i`-th vertex in the ordering - `\sigma`. Now, a slice of an ordering `\sigma` is a set of consecutive - vertices, `S = \{u | i \leq \sigma(u) \leq j\}`, such that for any `u \in - S`, we have `N_i(u) = N_i(\sigma^{-1}(i))` and for any `v` such that `j < - \sigma(v)`, `N_i(v) \neq N_i(\sigma^{-1}(i))`. The *head* of a slice is the - first position of its vertices. - - The algorithm starts with a single slice containing all vertices. Then, when - the position of the `i`-th vertex `v` is fixed, it explores the neighbors of - `v` that have not yet been ordered. Consider a slice `S` such that `N(x)\cap - S \neq \emptyset`. The algorithm will rearrange the ordering of the vertices - in `S` so that the first vertices are the neighbors of `v`. The sub-slice - containing the neighbors of `v` is assigned a new slice name, and the head - of slice `S` is set to the position of the first vertex of `S \setminus - N(v)` in the ordering `\sigma`. - - Observe that each arc of the graph can induce the subdivision of a - slice. Hence, the algorithm can use up to `m + 1` different slices. + Loops and multiple edges are ignored during the computation of + ``lex_BFS`` and directed graphs are converted to undirected graphs. .. SEEALSO:: * :wikipedia:`Lexicographic_breadth-first_search` + * :mod:`~sage.graphs.graph_decompositions.slice_decomposition` module + and :meth:`~sage.graphs.graph.Graph.slice_decomposition` -- compute a + slice decomposition of the graph using an extended lex BFS algorithm * :meth:`~sage.graphs.generic_graph.GenericGraph.lex_DFS` -- perform a lexicographic depth first search (LexDFS) on the graph * :meth:`~sage.graphs.generic_graph.GenericGraph.lex_UP` -- perform a @@ -315,7 +363,7 @@ def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm='fast') A Lex BFS is obviously an ordering of the vertices:: sage: g = graphs.CompleteGraph(6) - sage: len(g.lex_BFS()) == g.order() + sage: set(g.lex_BFS()) == set(g) True Lex BFS ordering of the 3-sun graph:: @@ -388,104 +436,77 @@ def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm='fast') Lex BFS ordering of a graph on one vertex:: - sage: Graph(1).lex_BFS(tree=True) + sage: Graph(1).lex_BFS(algorithm="fast", tree=True) ([0], Digraph on 1 vertex) Lex BFS ordering of an empty (di)graph is an empty sequence:: sage: g = Graph() - sage: g.lex_BFS() + sage: g.lex_BFS(algorithm="fast") [] + sage: g.lex_BFS(algorithm="fast", tree=True) + ([], Digraph on 0 vertices) Lex BFS ordering of a symmetric digraph should be the same as the Lex BFS ordering of the corresponding undirected graph:: sage: G = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)]) sage: H = DiGraph(G) - sage: G.lex_BFS() == H.lex_BFS() + sage: G.lex_BFS(algorithm="fast") == H.lex_BFS(algorithm="fast") True ``initial_vertex`` should be a valid graph vertex:: sage: G = graphs.CompleteGraph(6) - sage: G.lex_BFS(initial_vertex='foo') + sage: G.lex_BFS(algorithm="fast", initial_vertex='foo') Traceback (most recent call last): ... ValueError: 'foo' is not a graph vertex """ if initial_vertex is not None and initial_vertex not in G: - raise ValueError("'{}' is not a graph vertex".format(initial_vertex)) - if algorithm not in ['slow', 'fast']: - raise ValueError("unknown algorithm '{}'".format(algorithm)) - if tree: - from sage.graphs.digraph import DiGraph + raise ValueError(f"'{initial_vertex}' is not a graph vertex") - # Convert G to a simple undirected graph - if G.has_loops() or G.has_multiple_edges() or G.is_directed(): - G = G.to_simple(immutable=True, to_undirected=True) - - cdef size_t n = G.order() - if not n: - return ([], DiGraph(sparse=True)) if tree else [] + if algorithm == "slow": + return _lex_order_common(G, "lex_BFS", reverse, tree, initial_vertex) - # Build adjacency list of G - cdef list int_to_v = list(G) + if algorithm != "fast": + raise ValueError(f"unknown algorithm '{algorithm}'") - # If an initial vertex is given, we place it at first position - cdef size_t i - if initial_vertex is not None: - i = int_to_v.index(initial_vertex) - int_to_v[0], int_to_v[i] = int_to_v[i], int_to_v[0] - - # Copying the whole graph to obtain the list of neighbors quicker than by - # calling out_neighbors. This data structure is well documented in the - # module sage.graphs.base.static_sparse_graph - cdef short_digraph sd - init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_v, - sort_neighbors=False) + cdef size_t n = G.order() - # Initialize the predecessors array - cdef MemoryAllocator mem = MemoryAllocator() - cdef uint32_t *sigma_int = mem.allocarray(n, sizeof(uint32_t)) - cdef uint32_t *pred = mem.allocarray(n, sizeof(uint32_t)) - for i in range(n): - pred[i] = i + # For algorithm "fast" we need to convert G to an undirected graph + if G.is_directed(): + G = G.to_undirected() + # Initialize variables needed by the fast and slow algorithms + cdef CGraphBackend Gbackend = G._backend + cdef CGraph cg = Gbackend.cg() cdef list sigma = [] - cdef set vertices - cdef list code - cdef int now, v, vi, int_neighbor + cdef dict predecessor = {} + # Initialize variables needed by the fast algorithm + cdef vector[int] sigma_int + cdef vector[int] pred + # Initialize variables needed by the slow algorithm + cdef dict lexicographic_label + # Temporary variables + cdef int vi, i, initial_v_int # Perform Lex BFS - if algorithm == "fast": - lex_BFS_fast_short_digraph(sd, sigma_int, pred) - sigma = [int_to_v[sigma_int[i]] for i in range(n)] - - else: # "slow" algorithm - vertices = set(range(n)) - code = [[] for i in range(n)] - - # The initial_vertex is at position 0 and so named 0 in sd - code[0].append(n + 1) - now = 1 - while vertices: - v = max(vertices, key=code.__getitem__) - vertices.remove(v) - sigma.append(int_to_v[v]) - for vi in range(out_degree(sd, v)): - int_neighbor = sd.neighbors[v][vi] - if int_neighbor in vertices: - code[int_neighbor].append(n - now) - pred[int_neighbor] = v - now += 1 - - free_short_digraph(sd) + if initial_vertex is not None: + # we already checked that initial_vertex is in G + initial_v_int = Gbackend.get_vertex(initial_vertex) + else: + initial_v_int = -1 + extended_lex_BFS(cg, sigma_int, NULL, initial_v_int, &pred, NULL, NULL) + sigma = [ Gbackend.vertex_label(vi) for vi in sigma_int ] + predecessor = { u: sigma[i] for u, i in zip(sigma, pred) if i != -1 } if reverse: sigma.reverse() if tree: - edges = [(int_to_v[i], int_to_v[pred[i]]) for i in range(n) if pred[i] != i] + from sage.graphs.digraph import DiGraph + edges = predecessor.items() g = DiGraph([G, edges], format='vertices_and_edges', sparse=True) return sigma, g return sigma @@ -504,28 +525,15 @@ def lex_UP(G, reverse=False, tree=False, initial_vertex=None): - ``tree`` -- boolean (default: ``False``); whether to return the discovery directed tree (each vertex being linked to the one that saw - it for the first time) + it last) - ``initial_vertex`` -- (default: ``None``) the first vertex to consider - Loops and multiple edges are ignored during the computation of ``lex_UP`` - and directed graphs are converted to undirected graphs. - - ALGORITHM: - - This algorithm maintains for each vertex left in the graph a code - corresponding to the vertices already removed. The vertex of maximal - code (according to the lexicographic order) is then removed, and the - codes are updated. During the `i`-th iteration of the algorithm `i` is - appended to the codes of all neighbors of the selected vertex that are left - in the graph. - - Time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)` for - ``DenseGraph`` where `n` is the number of vertices and `m` is the number of - edges. + .. NOTE:: - See [Mil2017]_ for more details on the algorithm. + Loops and multiple edges are ignored during the computation of + ``lex_UP`` and directed graphs are converted to undirected graphs. .. SEEALSO:: @@ -536,12 +544,16 @@ def lex_UP(G, reverse=False, tree=False, initial_vertex=None): * :meth:`~sage.graphs.generic_graph.GenericGraph.lex_DOWN` -- perform a lexicographic DOWN search (LexDOWN) on the graph + ALGORITHM: + + See the documentation of the :mod:`~sage.graphs.traversals` module. + EXAMPLES: A Lex UP is obviously an ordering of the vertices:: sage: g = graphs.CompleteGraph(6) - sage: len(g.lex_UP()) == g.order() + sage: set(g.lex_UP()) == set(g) True Lex UP ordering of the 3-sun graph:: @@ -569,103 +581,8 @@ def lex_UP(G, reverse=False, tree=False, initial_vertex=None): ['000', '001', '010', '101', '110', '111', '011', '100'] sage: G.lex_DOWN(initial_vertex='000') ['000', '001', '100', '011', '010', '110', '111', '101'] - - TESTS: - - Lex UP ordering of a graph on one vertex:: - - sage: Graph(1).lex_UP(tree=True) - ([0], Digraph on 1 vertex) - - Lex UP ordering of an empty (di)graph is an empty sequence:: - - sage: g = Graph() - sage: g.lex_UP() - [] - - Lex UP ordering of a symmetric digraph should be the same as the Lex UP - ordering of the corresponding undirected graph:: - - sage: G = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)]) - sage: H = DiGraph(G) - sage: G.lex_UP() == H.lex_UP() - True - - ``initial_vertex`` should be a valid graph vertex:: - - sage: G = graphs.CompleteGraph(6) - sage: G.lex_UP(initial_vertex='foo') - Traceback (most recent call last): - ... - ValueError: 'foo' is not a graph vertex """ - if initial_vertex is not None and initial_vertex not in G: - raise ValueError("'{}' is not a graph vertex".format(initial_vertex)) - - # Convert G to a simple undirected graph - if G.has_loops() or G.has_multiple_edges() or G.is_directed(): - G = G.to_simple(immutable=True, to_undirected=True) - - cdef int nV = G.order() - - if not nV: - if tree: - from sage.graphs.digraph import DiGraph - g = DiGraph(sparse=True) - return [], g - return [] - - # Build adjacency list of G - cdef list int_to_v = list(G) - - cdef short_digraph sd - init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_v, - sort_neighbors=False) - - # Perform Lex UP - - cdef list code = [[] for i in range(nV)] - - def l_func(x): - return code[x] - - cdef list value = [] - - # Initialize the predecessors array - cdef MemoryAllocator mem = MemoryAllocator() - cdef int *pred = mem.allocarray(nV, sizeof(int)) - memset(pred, -1, nV * sizeof(int)) - - cdef set vertices = set(range(nV)) - - cdef int source = 0 if initial_vertex is None else int_to_v.index(initial_vertex) - code[source].append(nV + 1) - - cdef int now = 1, v, int_neighbor - while vertices: - v = max(vertices, key=l_func) - vertices.remove(v) - for i in range(0, out_degree(sd, v)): - int_neighbor = sd.neighbors[v][i] - if int_neighbor in vertices: - code[int_neighbor].append(now) - pred[int_neighbor] = v - value.append(int_to_v[v]) - now += 1 - - free_short_digraph(sd) - - if reverse: - value.reverse() - - if tree: - from sage.graphs.digraph import DiGraph - g = DiGraph(sparse=True) - g.add_vertices(G) - edges = [(int_to_v[i], int_to_v[pred[i]]) for i in range(nV) if pred[i] != -1] - g.add_edges(edges) - return value, g - return value + return _lex_order_common(G, "lex_UP", reverse, tree, initial_vertex) def lex_DFS(G, reverse=False, tree=False, initial_vertex=None): @@ -681,27 +598,15 @@ def lex_DFS(G, reverse=False, tree=False, initial_vertex=None): - ``tree`` -- boolean (default: ``False``); whether to return the discovery directed tree (each vertex being linked to the one that saw - it for the first time) + it last) - ``initial_vertex`` -- (default: ``None``) the first vertex to consider - Loops and multiple edges are ignored during the computation of ``lex_DFS`` - and directed graphs are converted to undirected graphs. - - ALGORITHM: - - This algorithm maintains for each vertex left in the graph a code - corresponding to the vertices already removed. The vertex of maximal - code (according to the lexicographic order) is then removed, and the - codes are updated. Lex DFS differs from Lex BFS only in the way codes are - updated after each iteration. - - Time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)` for - ``DenseGraph`` where `n` is the number of vertices and `m` is the number of - edges. + .. NOTE:: - See [CK2008]_ for more details on the algorithm. + Loops and multiple edges are ignored during the computation of + ``lex_DFS`` and directed graphs are converted to undirected graphs. .. SEEALSO:: @@ -712,12 +617,16 @@ def lex_DFS(G, reverse=False, tree=False, initial_vertex=None): * :meth:`~sage.graphs.generic_graph.GenericGraph.lex_DOWN` -- perform a lexicographic DOWN search (LexDOWN) on the graph + ALGORITHM: + + See the documentation of the :mod:`~sage.graphs.traversals` module. + EXAMPLES: A Lex DFS is obviously an ordering of the vertices:: sage: g = graphs.CompleteGraph(6) - sage: len(g.lex_DFS()) == g.order() + sage: set(g.lex_DFS()) == set(g) True Lex DFS ordering of the 3-sun graph:: @@ -745,104 +654,8 @@ def lex_DFS(G, reverse=False, tree=False, initial_vertex=None): ['000', '001', '010', '101', '110', '111', '011', '100'] sage: G.lex_DOWN(initial_vertex='000') ['000', '001', '100', '011', '010', '110', '111', '101'] - - TESTS: - - Lex DFS ordering of a graph on one vertex:: - - sage: Graph(1).lex_DFS(tree=True) - ([0], Digraph on 1 vertex) - - Lex DFS ordering of an empty (di)graph is an empty sequence:: - - sage: g = Graph() - sage: g.lex_DFS() - [] - - Lex DFS ordering of a symmetric digraph should be the same as the Lex DFS - ordering of the corresponding undirected graph:: - - sage: G = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)]) - sage: H = DiGraph(G) - sage: G.lex_DFS() == H.lex_DFS() - True - - ``initial_vertex`` should be a valid graph vertex:: - - sage: G = graphs.CompleteGraph(6) - sage: G.lex_DFS(initial_vertex='foo') - Traceback (most recent call last): - ... - ValueError: 'foo' is not a graph vertex """ - if initial_vertex is not None and initial_vertex not in G: - raise ValueError("'{}' is not a graph vertex".format(initial_vertex)) - - # Convert G to a simple undirected graph - if G.has_loops() or G.has_multiple_edges() or G.is_directed(): - G = G.to_simple(immutable=True, to_undirected=True) - - cdef int nV = G.order() - - if not nV: - if tree: - from sage.graphs.digraph import DiGraph - g = DiGraph(sparse=True) - return [], g - return [] - - # Build adjacency list of G - cdef list int_to_v = list(G) - - cdef short_digraph sd - init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_v, - sort_neighbors=False) - - # Perform Lex DFS - - # We are using deque in order to prepend items in list efficiently - cdef list code = [deque([]) for i in range(nV)] - - def l_func(x): - return code[x] - - cdef list value = [] - - # initialize the predecessors array - cdef MemoryAllocator mem = MemoryAllocator() - cdef int *pred = mem.allocarray(nV, sizeof(int)) - memset(pred, -1, nV * sizeof(int)) - - cdef set vertices = set(range(nV)) - - cdef int source = 0 if initial_vertex is None else int_to_v.index(initial_vertex) - code[source].appendleft(0) - - cdef int now = 1, v, int_neighbor - while vertices: - v = max(vertices, key=l_func) - vertices.remove(v) - for i in range(0, out_degree(sd, v)): - int_neighbor = sd.neighbors[v][i] - if int_neighbor in vertices: - code[int_neighbor].appendleft(now) - pred[int_neighbor] = v - value.append(int_to_v[v]) - now += 1 - - free_short_digraph(sd) - - if reverse: - value.reverse() - - if tree: - from sage.graphs.digraph import DiGraph - g = DiGraph(sparse=True) - g.add_vertices(G) - edges = [(int_to_v[i], int_to_v[pred[i]]) for i in range(nV) if pred[i] != -1] - g.add_edges(edges) - return value, g - return value + return _lex_order_common(G, "lex_DFS", reverse, tree, initial_vertex) def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None): @@ -858,28 +671,15 @@ def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None): - ``tree`` -- boolean (default: ``False``); whether to return the discovery directed tree (each vertex being linked to the one that saw - it for the first time) + it) - ``initial_vertex`` -- (default: ``None``) the first vertex to consider - Loops and multiple edges are ignored during the computation of ``lex_DOWN`` - and directed graphs are converted to undirected graphs. - - ALGORITHM: - - This algorithm maintains for each vertex left in the graph a code - corresponding to the vertices already removed. The vertex of maximal - code (according to the lexicographic order) is then removed, and the - codes are updated. During the `i`-th iteration of the algorithm `n-i` is - prepended to the codes of all neighbors of the selected vertex that are left - in the graph. - - Time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)` for - ``DenseGraph`` where `n` is the number of vertices and `m` is the number of - edges. + .. NOTE:: - See [Mil2017]_ for more details on the algorithm. + Loops and multiple edges are ignored during the computation of + ``lex_DOWN`` and directed graphs are converted to undirected graphs. .. SEEALSO:: @@ -890,12 +690,16 @@ def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None): * :meth:`~sage.graphs.generic_graph.GenericGraph.lex_UP` -- perform a lexicographic UP search (LexUP) on the graph + ALGORITHM: + + See the documentation of the :mod:`~sage.graphs.traversals` module. + EXAMPLES: A Lex DOWN is obviously an ordering of the vertices:: sage: g = graphs.CompleteGraph(6) - sage: len(g.lex_DOWN()) == g.order() + sage: set(g.lex_DOWN()) == set(g) True Lex DOWN ordering of the 3-sun graph:: @@ -923,104 +727,8 @@ def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None): ['000', '001', '010', '101', '110', '111', '011', '100'] sage: G.lex_DOWN(initial_vertex='000') ['000', '001', '100', '011', '010', '110', '111', '101'] - - TESTS: - - Lex DOWN ordering of a graph on one vertex:: - - sage: Graph(1).lex_DOWN(tree=True) - ([0], Digraph on 1 vertex) - - Lex DOWN ordering of an empty (di)graph is an empty sequence:: - - sage: g = Graph() - sage: g.lex_DOWN() - [] - - Lex DOWN ordering of a symmetric digraph should be the same as the Lex DOWN - ordering of the corresponding undirected graph:: - - sage: G = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)]) - sage: H = DiGraph(G) - sage: G.lex_DOWN() == H.lex_DOWN() - True - - ``initial_vertex`` should be a valid graph vertex:: - - sage: G = graphs.CompleteGraph(6) - sage: G.lex_DOWN(initial_vertex='foo') - Traceback (most recent call last): - ... - ValueError: 'foo' is not a graph vertex """ - if initial_vertex is not None and initial_vertex not in G: - raise ValueError("'{}' is not a graph vertex".format(initial_vertex)) - - # Convert G to a simple undirected graph - if G.has_loops() or G.has_multiple_edges() or G.is_directed(): - G = G.to_simple(immutable=True, to_undirected=True) - - cdef int nV = G.order() - - if not nV: - if tree: - from sage.graphs.digraph import DiGraph - g = DiGraph(sparse=True) - return [], g - return [] - - # Build adjacency list of G - cdef list int_to_v = list(G) - - cdef short_digraph sd - init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_v, - sort_neighbors=False) - - # Perform Lex DOWN - - # We are using deque in order to prepend items in list efficiently - cdef list code = [deque([]) for i in range(nV)] - - def l_func(x): - return code[x] - - cdef list value = [] - - # initialize the predecessors array - cdef MemoryAllocator mem = MemoryAllocator() - cdef int *pred = mem.allocarray(nV, sizeof(int)) - memset(pred, -1, nV * sizeof(int)) - - cdef set vertices = set(range(nV)) - - cdef int source = 0 if initial_vertex is None else int_to_v.index(initial_vertex) - code[source].appendleft(0) - - cdef int now = 1, v, int_neighbor - while vertices: - v = max(vertices, key=l_func) - vertices.remove(v) - for i in range(0, out_degree(sd, v)): - int_neighbor = sd.neighbors[v][i] - if int_neighbor in vertices: - code[int_neighbor].appendleft(nV - now) - pred[int_neighbor] = v - value.append(int_to_v[v]) - now += 1 - - free_short_digraph(sd) - - if reverse: - value.reverse() - - if tree: - from sage.graphs.digraph import DiGraph - g = DiGraph(sparse=True) - g.add_vertices(G) - edges = [(int_to_v[i], int_to_v[pred[i]]) for i in range(nV) if pred[i] != -1] - g.add_edges(edges) - return value, g - return value + return _lex_order_common(G, "lex_DOWN", reverse, tree, initial_vertex) def lex_M(self, triangulation=False, labels=False, initial_vertex=None, algorithm=None): diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 1a55db32508..63257688cc3 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -426,6 +426,50 @@ def simple_reflection(self, i): """ return self([(i, self._domain[self._domain.index(i)+1])], check=False) + @cached_method + def reflection_index_set(self): + r""" + Return the index set of the reflections of ``self``. + + .. SEEALSO:: + + - :meth:`reflection` + - :meth:`reflections` + + EXAMPLES:: + + sage: S5 = SymmetricGroup(5) + sage: S5.reflection_index_set() + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + """ + return tuple(range(len(self.reflections()))) + + def reflection(self, i): + r""" + Return the `i`-th reflection of ``self``. + + For `i` in `1,\dots,N`, this gives the `i`-th reflection of + ``self``. + + .. SEEALSO:: + + - :meth:`reflections_index_set` + - :meth:`reflections` + + EXAMPLES:: + + sage: S4 = SymmetricGroup(4) + sage: for i in S4.reflection_index_set(): + ....: print('%s %s'%(i, S4.reflection(i))) + 0 (1,2) + 1 (1,3) + 2 (1,4) + 3 (2,3) + 4 (2,4) + 5 (3,4) + """ + return self.reflections()[i] + def reflections(self): """ Return the list of all reflections in ``self``. diff --git a/src/sage/libs/coxeter3/decl.pxd b/src/sage/libs/coxeter3/decl.pxd index 4f9c7b0c186..1f968c418bf 100644 --- a/src/sage/libs/coxeter3/decl.pxd +++ b/src/sage/libs/coxeter3/decl.pxd @@ -33,7 +33,6 @@ cdef extern from "coxeter/coxtypes.h" namespace "coxtypes": ctypedef unsigned short Length ctypedef Ulong StarOp # for numbering star operations - ################# # CoxWord # ################# diff --git a/src/sage/libs/ecl.pxd b/src/sage/libs/ecl.pxd index 19472171403..202d0824e37 100644 --- a/src/sage/libs/ecl.pxd +++ b/src/sage/libs/ecl.pxd @@ -147,7 +147,6 @@ cdef extern from "ecl/ecl.h": ecl_character ecl_char(cl_object s, cl_index i) ecl_character ecl_char_set(cl_object s, cl_index i, ecl_character c) - # S-expr evaluation and function calls cl_object cl_safe_eval(cl_object form, cl_object env, cl_object value) diff --git a/src/sage/libs/linbox/fflas.pxd b/src/sage/libs/linbox/fflas.pxd index d5b077cf045..886f5c44cfa 100644 --- a/src/sage/libs/linbox/fflas.pxd +++ b/src/sage/libs/linbox/fflas.pxd @@ -28,7 +28,6 @@ cdef extern from "fflas-ffpack/fflas-ffpack.h" namespace "FFLAS": FflasNoTrans FflasTrans - ctypedef enum FFLAS_SIDE: FflasRight diff --git a/src/sage/libs/m4rie.pxd b/src/sage/libs/m4rie.pxd index 51bbf8303d9..c56e33b6945 100644 --- a/src/sage/libs/m4rie.pxd +++ b/src/sage/libs/m4rie.pxd @@ -173,7 +173,6 @@ cdef extern from "m4rie/m4rie.h": void mzd_slice_row_add(mzd_slice_t *A, size_t sourcerow, size_t destrow) - void mzd_slice_row_clear_offset(mzd_slice_t *A, size_t row, size_t coloffset) void mzd_slice_print(mzd_slice_t *A) diff --git a/src/sage/libs/meataxe.pxd b/src/sage/libs/meataxe.pxd index 0a928e19c37..a3bbc810cdc 100644 --- a/src/sage/libs/meataxe.pxd +++ b/src/sage/libs/meataxe.pxd @@ -113,7 +113,6 @@ cdef extern from "meataxe.h": Matrix_t *MatLoad(char *fn) except? NULL int MatSave(Matrix_t *mat, char *fn) except -1 - ## Basic Arithmetic ## general rule: dest is changed, src/mat are unchanged! Matrix_t *MatTransposed(Matrix_t *src) except NULL Matrix_t *MatAdd(Matrix_t *dest, Matrix_t *src) except NULL diff --git a/src/sage/libs/ntl/GF2X.pxd b/src/sage/libs/ntl/GF2X.pxd index 9342f63244c..2d3b5edafad 100644 --- a/src/sage/libs/ntl/GF2X.pxd +++ b/src/sage/libs/ntl/GF2X.pxd @@ -58,7 +58,6 @@ cdef extern from "ntlwrap.h": void GF2XModulus_build "build"(GF2XModulus_c F, GF2X_c f) # MUST be called before using the modulus long GF2XModulus_deg "deg"(GF2XModulus_c F) - GF2X_c GF2XModulus_GF2X "GF2X" (GF2XModulus_c m) GF2X_c GF2X_IrredPolyMod "IrredPolyMod" (GF2X_c g, GF2XModulus_c F) @@ -72,7 +71,6 @@ cdef extern from "ntlwrap.h": void GF2X_PowerXPlusAMod_pre "PowerXPlusAMod"(GF2X_c x, GF2_c a, GF2_c e, GF2XModulus_c F) void GF2X_PowerXPlusAMod_long_pre "PowerXPlusAMod"(GF2X_c x, GF2_c a, long e, GF2XModulus_c F) - # x = g(h) mod f; deg(h) < n void GF2X_CompMod "CompMod"(GF2X_c x, GF2X_c g, GF2X_c h, GF2XModulus_c F) # xi = gi(h) mod f (i=1,2), deg(h) < n. diff --git a/src/sage/libs/singular/decl.pxd b/src/sage/libs/singular/decl.pxd index 0c9f2cc993c..dddc452fd98 100644 --- a/src/sage/libs/singular/decl.pxd +++ b/src/sage/libs/singular/decl.pxd @@ -151,7 +151,6 @@ cdef extern from "singular/Singular/libsingular.h": void (*cfWrite)(number* a, const n_Procs_s* r) void (*cfNormalize)(number* a, const n_Procs_s* r) - bint (*cfDivBy)(number* a, number* b, const n_Procs_s* r) bint (*cfEqual)(number* a,number* b, const n_Procs_s* ) bint (*cfIsZero)(number* a, const n_Procs_s* ) # algebraic number comparison with zero @@ -160,7 +159,6 @@ cdef extern from "singular/Singular/libsingular.h": bint (*cfGreaterZero)(number* a, const n_Procs_s* ) void (*cfPower)(number* a, int i, number* * result, const n_Procs_s* r) # algebraic number power - ring *extRing int ch mpz_ptr modBase @@ -211,7 +209,6 @@ cdef extern from "singular/Singular/libsingular.h": int pCompIndex # index of components unsigned long bitmask # mask for getting single exponents - n_Procs_s* cf # coefficient field/ring int ref @@ -793,7 +790,6 @@ cdef extern from "singular/Singular/libsingular.h": number *nlCopy(number *) - # number to integer handle long SR_TO_INT(number *) @@ -813,7 +809,6 @@ cdef extern from "singular/Singular/libsingular.h": void id_Delete(ideal **, ring *) - # lifting ideal *idLift(ideal *mod, ideal *submod, ideal **rest, int goodShape, int isSB, int divide) diff --git a/src/sage/libs/symmetrica/sb.pxi b/src/sage/libs/symmetrica/sb.pxi index 5ea0b3ad62c..b3bbb061596 100644 --- a/src/sage/libs/symmetrica/sb.pxi +++ b/src/sage/libs/symmetrica/sb.pxi @@ -51,7 +51,6 @@ def mult_schubert_schubert_symmetrica(a, b): freeall(cres) raise err - sig_on() mult_schubert_schubert(ca, cb, cres) sig_off() diff --git a/src/sage/libs/symmetrica/schur.pxi b/src/sage/libs/symmetrica/schur.pxi index a5257cf304b..4fe867ce93a 100644 --- a/src/sage/libs/symmetrica/schur.pxi +++ b/src/sage/libs/symmetrica/schur.pxi @@ -28,7 +28,6 @@ cdef extern from 'symmetrica/def.h': INT t_HOMSYM_MONOMIAL(OP a, OP b) INT t_HOMSYM_ELMSYM(OP a, OP b) - INT t_POWSYM_SCHUR(OP a, OP b) INT t_SCHUR_POWSYM(OP a, OP b) INT t_POWSYM_HOMSYM(OP a, OP b) diff --git a/src/sage/matrix/matrix_modn_dense_template.pxi b/src/sage/matrix/matrix_modn_dense_template.pxi index ebf1bdeb7e9..36830da0549 100644 --- a/src/sage/matrix/matrix_modn_dense_template.pxi +++ b/src/sage/matrix/matrix_modn_dense_template.pxi @@ -854,7 +854,6 @@ cdef class Matrix_modn_dense_template(Matrix_dense): A.subdivide(*self.subdivisions()) return A - cpdef _add_(self, right): r""" Add two dense matrices over `\Z/n\Z`. @@ -898,7 +897,6 @@ cdef class Matrix_modn_dense_template(Matrix_dense): sig_off() return M - cpdef _sub_(self, right): r""" Subtract two dense matrices over `\Z/n\Z`. @@ -1404,7 +1402,6 @@ cdef class Matrix_modn_dense_template(Matrix_dense): self.cache(cache_key, g) return g - def minpoly(self, var='x', algorithm='linbox', proof=None): """ Return the minimal polynomial of ``self``. diff --git a/src/sage/matrix/strassen.pyx b/src/sage/matrix/strassen.pyx index eb42f8640b6..00807e39c4d 100644 --- a/src/sage/matrix/strassen.pyx +++ b/src/sage/matrix/strassen.pyx @@ -1,19 +1,19 @@ """ Generic Asymptotically Fast Strassen Algorithms -Sage implements asymptotically fast echelon form and matrix +This implements asymptotically fast echelon form and matrix multiplication algorithms. """ -#***************************************************************************** +# *************************************************************************** # Copyright (C) 2005, 2006 William Stein # # 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/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# *************************************************************************** from sage.matrix.matrix_window cimport MatrixWindow @@ -27,11 +27,13 @@ def strassen_window_multiply(C, A,B, cutoff): multiplied, and that C is the correct size to receive the product, and that they are all defined over the same ring. - Uses strassen multiplication at high levels and then uses - MatrixWindow methods at low levels. EXAMPLES: The following matrix + Uses Strassen multiplication at high levels and then uses + MatrixWindow methods at low levels. + + EXAMPLES: The following matrix dimensions are chosen especially to exercise the eight possible parity combinations that could occur while subdividing the matrix - in the strassen recursion. The base case in both cases will be a + in the Strassen recursion. The base case in both cases will be a (4x5) matrix times a (5x6) matrix. :: @@ -248,7 +250,9 @@ cdef subtract_strassen_product(MatrixWindow result, MatrixWindow A, MatrixWindow def strassen_echelon(MatrixWindow A, cutoff): """ Compute echelon form, in place. Internal function, call with - M.echelonize(algorithm='strassen') Based on work of Robert Bradshaw + M.echelonize(algorithm='strassen') + + Based on work of Robert Bradshaw and David Harvey at MSRI workshop in 2006. INPUT: @@ -324,13 +328,13 @@ cdef strassen_echelon_c(MatrixWindow A, Py_ssize_t cutoff, Py_ssize_t mul_cutoff nrows = A.nrows() ncols = A.ncols() - if (nrows <= cutoff or ncols <= cutoff): + if nrows <= cutoff or ncols <= cutoff: return list(A.echelon_in_place()) cdef Py_ssize_t top_h, bottom_cut, bottom_h, bottom_start, top_cut cdef Py_ssize_t prev_pivot_count cdef Py_ssize_t split - split = nrows / 2 + split = nrows // 2 cdef MatrixWindow top, bottom, top_left, top_right, bottom_left, bottom_right, clear @@ -485,7 +489,7 @@ class int_range: Repetitions are not considered. - Useful class for dealing with pivots in the strassen echelon, could + Useful class for dealing with pivots in the Strassen echelon, could have much more general application INPUT: @@ -555,7 +559,7 @@ class int_range: if indices is None: self._intervals = [] return - elif range is not None: + if range is not None: self._intervals = [(int(indices), int(range))] else: self._intervals = [] @@ -629,11 +633,8 @@ class int_range: sage: I.to_list() [1, 2, 3] """ - all = [] - for iv in self._intervals: - for i in range(iv[0], iv[0]+iv[1]): - all.append(i) - return all + return [i for iv0, iv1 in self._intervals + for i in range(iv0, iv0 + iv1)] def __iter__(self): r""" @@ -676,10 +677,7 @@ class int_range: sage: len(I) 3 """ - len = 0 - for iv in self._intervals: - len = len + iv[1] - return int(len) + return sum(iv1 for _, iv1 in self._intervals) def __add__(self, right): r""" @@ -705,9 +703,9 @@ class int_range: sage: I + J [(1, 6), (20, 4)] """ - all = self.to_list() - all.extend(right.to_list()) - return int_range(all) + full_list = self.to_list() + full_list.extend(right.to_list()) + return int_range(full_list) def __sub__(self, right): r""" @@ -733,11 +731,9 @@ class int_range: sage: J - I [(6, 1), (20, 4)] """ - all = self.to_list() - for i in right.to_list(): - if i in all: - all.remove(i) - return int_range(all) + right_list = set(right.to_list()) + diff = [i for i in self.to_list() if i not in right_list] + return int_range(diff) def __mul__(self, right): r""" @@ -757,17 +753,16 @@ class int_range: sage: J * I [(4, 2)] """ - intersection = [] - all = self.to_list() - for i in right.to_list(): - if i in all: - intersection.append(i) + self_list = set(self.to_list()) + intersection = [i for i in right.to_list() if i in self_list] return int_range(intersection) # Useful test code: def test(n, m, R, c=2): r""" + Test code for the Strassen algorithm. + INPUT: - ``n`` -- integer @@ -799,56 +794,57 @@ def test(n, m, R, c=2): # below aren't callable now without using Pyrex. -## todo: doc cutoff parameter as soon as I work out what it really means +# todo: doc cutoff parameter as soon as I work out what it really means + +# EXAMPLES: -## EXAMPLES: -## The following matrix dimensions are chosen especially to exercise the -## eight possible parity combinations that could occur while subdividing -## the matrix in the strassen recursion. The base case in both cases will -## be a (4x5) matrix times a (5x6) matrix. +# The following matrix dimensions are chosen especially to exercise the +# eight possible parity combinations that could occur while subdividing +# the matrix in the strassen recursion. The base case in both cases will +# be a (4x5) matrix times a (5x6) matrix. -## TODO -- the doctests below are currently not -## tested/enabled/working -- enable them when linear algebra -## restructuring gets going. +# TODO -- the doctests below are currently not +# tested/enabled/working -- enable them when linear algebra +# restructing gets going. -## sage: dim1 = 64; dim2 = 83; dim3 = 101 -## sage: R = MatrixSpace(QQ, dim1, dim2) -## sage: S = MatrixSpace(QQ, dim2, dim3) -## sage: T = MatrixSpace(QQ, dim1, dim3) +# sage: dim1 = 64; dim2 = 83; dim3 = 101 +# sage: R = MatrixSpace(QQ, dim1, dim2) +# sage: S = MatrixSpace(QQ, dim2, dim3) +# sage: T = MatrixSpace(QQ, dim1, dim3) -## sage: A = R.random_element(range(-30, 30)) -## sage: B = S.random_element(range(-30, 30)) -## sage: C = T(0) -## sage: D = T(0) +# sage: A = R.random_element(range(-30, 30)) +# sage: B = S.random_element(range(-30, 30)) +# sage: C = T(0) +# sage: D = T(0) -## sage: A_window = A.matrix_window(0, 0, dim1, dim2) -## sage: B_window = B.matrix_window(0, 0, dim2, dim3) -## sage: C_window = C.matrix_window(0, 0, dim1, dim3) -## sage: D_window = D.matrix_window(0, 0, dim1, dim3) +# sage: A_window = A.matrix_window(0, 0, dim1, dim2) +# sage: B_window = B.matrix_window(0, 0, dim2, dim3) +# sage: C_window = C.matrix_window(0, 0, dim1, dim3) +# sage: D_window = D.matrix_window(0, 0, dim1, dim3) -## sage: from sage.matrix.strassen import strassen_window_multiply -## sage: strassen_window_multiply(C_window, A_window, B_window, 2) # use strassen method -## sage: D_window.set_to_prod(A_window, B_window) # use naive method -## sage: C_window == D_window -## True +# sage: from sage.matrix.strassen import strassen_window_multiply +# sage: strassen_window_multiply(C_window, A_window, B_window, 2) # use strassen method +# sage: D_window.set_to_prod(A_window, B_window) # use naive method +# sage: C_window == D_window +# True -## sage: dim1 = 79; dim2 = 83; dim3 = 101 -## sage: R = MatrixSpace(QQ, dim1, dim2) -## sage: S = MatrixSpace(QQ, dim2, dim3) -## sage: T = MatrixSpace(QQ, dim1, dim3) +# sage: dim1 = 79; dim2 = 83; dim3 = 101 +# sage: R = MatrixSpace(QQ, dim1, dim2) +# sage: S = MatrixSpace(QQ, dim2, dim3) +# sage: T = MatrixSpace(QQ, dim1, dim3) -## sage: A = R.random_element(range(30)) -## sage: B = S.random_element(range(30)) -## sage: C = T(0) -## sage: D = T(0) +# sage: A = R.random_element(range(30)) +# sage: B = S.random_element(range(30)) +# sage: C = T(0) +# sage: D = T(0) -## sage: A_window = A.matrix_window(0, 0, dim1, dim2) -## sage: B_window = B.matrix_window(0, 0, dim2, dim3) -## sage: C_window = C.matrix_window(0, 0, dim1, dim3) +# sage: A_window = A.matrix_window(0, 0, dim1, dim2) +# sage: B_window = B.matrix_window(0, 0, dim2, dim3) +# sage: C_window = C.matrix_window(0, 0, dim1, dim3) -## sage: strassen_window_multiply(C, A, B, 2) # use strassen method -## sage: D.set_to_prod(A, B) # use naive method +# sage: strassen_window_multiply(C, A, B, 2) # use strassen method +# sage: D.set_to_prod(A, B) # use naive method -## sage: C == D -## True +# sage: C == D +# True diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index b6dd1073e4f..3642251c465 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -5394,7 +5394,7 @@ cdef class Matroid(SageObject): E = set(self.groundset()) Q = set(list(E)[:m]) E = E-Q - for r in range(len(Q)/2 + 1): + for r in range(len(Q) // 2 + 1): R = set(list(E)[:r]) for Q1 in map(set, combinations(Q, r)): Q2 = Q-Q1 diff --git a/src/sage/misc/replace_dot_all.py b/src/sage/misc/replace_dot_all.py index 16ac2124377..ea51a9b3159 100644 --- a/src/sage/misc/replace_dot_all.py +++ b/src/sage/misc/replace_dot_all.py @@ -456,8 +456,8 @@ def walkdir_replace_dot_all(dir, file_regex=r'.*[.](py|pyx|pxi)$', package_regex finally: # Print report also when interrupted if verbosity: - log_messages = sorted(log_messages.rstrip().split('\n')) - for i, message in enumerate(log_messages, start=1): + log_messages_split = sorted(log_messages.rstrip().split('\n')) + for i, message in enumerate(log_messages_split, start=1): # add index to each line print(f'{i}. {message.rstrip()}') report = 'REPORT:\n' diff --git a/src/sage/misc/temporary_file.py b/src/sage/misc/temporary_file.py index ff8c7a751ac..998260be8eb 100644 --- a/src/sage/misc/temporary_file.py +++ b/src/sage/misc/temporary_file.py @@ -23,11 +23,10 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -import io +import atexit import os import tempfile - -import atexit +from typing import IO # Until tmp_dir() and tmp_filename() are removed, we use this directory # as the parent for all temporary files & directories created by them. @@ -41,7 +40,7 @@ # temporary directory ################################################################# -def tmp_dir(name='dir_', ext=''): +def tmp_dir(name='dir_', ext='') -> str: r""" Create and return a temporary directory in ``$HOME/.sage/temp/hostname/pid/`` @@ -84,7 +83,7 @@ def tmp_dir(name='dir_', ext=''): # temporary filename ################################################################# -def tmp_filename(name='tmp_', ext=''): +def tmp_filename(name='tmp_', ext='') -> str: r""" Create and return a temporary file in ``$HOME/.sage/temp/hostname/pid/`` @@ -163,8 +162,8 @@ class atomic_write: mode bits of the file were changed manually). (Not to be confused with the file opening mode.) - - ``binary`` -- boolean (default: ``True`` on Python 2, ``False`` on Python - 3); the underlying file is opened in binary mode. If ``False`` then it is + - ``binary`` -- boolean (default: ``False``); + the underlying file is opened in binary mode. If ``False`` then it is opened in text mode and an encoding with which to write the file may be supplied. @@ -299,7 +298,7 @@ class atomic_write: False """ def __init__(self, target_filename, append=False, mode=0o666, - binary=None, **kwargs): + binary=False, **kwargs) -> None: """ TESTS:: @@ -320,13 +319,11 @@ def __init__(self, target_filename, append=False, mode=0o666, os.umask(umask) self.mode = mode & (~umask) - # 'binary' mode is the default on Python 2, whereas 'text' mode is the - # default on Python 3--this reflects consistent handling of the default - # str type on the two platforms - self.binary = False if binary is None else binary + # 'text' mode is the default on Python 3 + self.binary = binary self.kwargs = kwargs - def __enter__(self): + def __enter__(self) -> IO: """ Create and return a temporary file in ``self.tmpdir`` (normally the same directory as the target file). @@ -372,7 +369,7 @@ def __enter__(self): return self.tempfile - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb) -> None: """ If the ``with`` block was successful, move the temporary file to the target file. Otherwise, delete the temporary file. @@ -457,7 +454,7 @@ class atomic_dir: ....: h.read() 'Second' """ - def __init__(self, target_directory): + def __init__(self, target_directory) -> None: r""" TESTS:: @@ -492,7 +489,7 @@ def __enter__(self): self.tempname = os.path.abspath(tdir.name) return tdir - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb) -> None: """ If the ``with`` block was successful, move the temporary directory to the target directory. Otherwise, delete the temporary directory. @@ -518,7 +515,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): try: os.rename(self.tempname, self.target) except OSError: - # Race: Another thread or process must have created the directory + # Race: Another thread or process must have created + # the directory pass else: # Failure: delete temporary file @@ -528,7 +526,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): _spyx_tmp = None -def spyx_tmp(): +def spyx_tmp() -> str: r""" The temporary directory used to store pyx files. diff --git a/src/sage/modular/arithgroup/congroup.pyx b/src/sage/modular/arithgroup/congroup.pyx index ee988672a4f..d55c0c0ce7c 100644 --- a/src/sage/modular/arithgroup/congroup.pyx +++ b/src/sage/modular/arithgroup/congroup.pyx @@ -116,7 +116,7 @@ def degeneracy_coset_representatives_gamma0(int N, int M, int t): # total number of coset representatives that we'll find n = Gamma0(N).index() / Gamma0(M).index() k = 0 # number found so far - Ndivt = N / t + Ndivt = N // t R = check_allocarray(4 * n, sizeof(int)) halfmax = 2*(n+10) while k < n: @@ -126,10 +126,10 @@ def degeneracy_coset_representatives_gamma0(int N, int M, int t): g = arith_int.c_xgcd_int(-cc,dd,&bb,&aa) if g == 0: continue - cc = cc / g + cc = cc // g if cc % M != 0: continue - dd = dd / g + dd = dd // g # Test if we've found a new coset representative. is_new = 1 for i in range(k): @@ -217,7 +217,7 @@ def degeneracy_coset_representatives_gamma1(int N, int M, int t): # total number of coset representatives that we'll find n = Gamma1(N).index() / Gamma1(M).index() d = arith_int.c_gcd_int(t, N // t) - n = n / d + n = n // d k = 0 # number found so far Ndivt = N // t R = check_allocarray(4 * n, sizeof(int)) @@ -229,10 +229,10 @@ def degeneracy_coset_representatives_gamma1(int N, int M, int t): g = arith_int.c_xgcd_int(-cc, dd, &bb, &aa) if g == 0: continue - cc = cc / g + cc = cc // g if cc % M != 0: continue - dd = dd / g + dd = dd // g if M != 1 and dd % M != 1: continue # Test if we've found a new coset representative. diff --git a/src/sage/modular/hypergeometric_misc.pxd b/src/sage/modular/hypergeometric_misc.pxd index 00bf9a97e9a..ccd013ca2ee 100644 --- a/src/sage/modular/hypergeometric_misc.pxd +++ b/src/sage/modular/hypergeometric_misc.pxd @@ -1,2 +1,3 @@ -cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, +cpdef hgm_coeffs(long long p, unsigned int f, + int prec, gamma, m, int D, gtable, int gtable_prec, bint use_longs) diff --git a/src/sage/modular/hypergeometric_misc.pyx b/src/sage/modular/hypergeometric_misc.pyx index 3be8e4dd545..f3e37b515d9 100644 --- a/src/sage/modular/hypergeometric_misc.pyx +++ b/src/sage/modular/hypergeometric_misc.pyx @@ -6,7 +6,8 @@ from cpython cimport array from cysignals.signals cimport sig_check from sage.rings.integer cimport Integer -cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, +cpdef hgm_coeffs(long long p, unsigned int f, + int prec, gamma, m, int D, gtable, int gtable_prec, bint use_longs): r""" Compute coefficients for the hypergeometric trace formula. @@ -35,8 +36,8 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, """ from sage.rings.padics.factory import Zp - cdef int gl, j, k, l, v, gv - cdef long long i, q1, w, w1, w2, q2, r, r1 + cdef int gl, j, k, v, gv + cdef long long i, l, q1, w, w1, w2, q2, r, r1 cdef bint flip, use_longlongs q1 = p ** f - 1 diff --git a/src/sage/modular/modform/eis_series_cython.pyx b/src/sage/modular/modform/eis_series_cython.pyx index 8126d1f8135..73ffdfab324 100644 --- a/src/sage/modular/modform/eis_series_cython.pyx +++ b/src/sage/modular/modform/eis_series_cython.pyx @@ -107,12 +107,12 @@ cpdef Ek_ZZ(int k, int prec=10): # compute the valuation of n at p additional_p_powers = 0 - temp_index = ind / p + temp_index = ind // p remainder = 0 while not remainder: additional_p_powers += 1 prev_index = temp_index - temp_index = temp_index / p + temp_index = temp_index // p remainder = prev_index - p*temp_index # if we need a new sum, it has to be the next uncomputed one. diff --git a/src/sage/modular/modform/ring.py b/src/sage/modular/modform/ring.py index 21d28730de6..bf4457d7748 100644 --- a/src/sage/modular/modform/ring.py +++ b/src/sage/modular/modform/ring.py @@ -200,8 +200,9 @@ def __init__(self, group, base_ring=QQ): - ``group`` -- a congruence subgroup of `\SL_2(\ZZ)`, or a positive integer `N` (interpreted as `\Gamma_0(N)`) - - ``base_ring``-- ring (default: `\QQ`); a base ring, which should be - `\QQ`, `\ZZ`, or the integers mod `p` for some prime `p` + - ``base_ring`` -- ring (default: `\QQ`); a base ring, which + should be `\QQ`, `\ZZ`, or the integers mod `p` for some prime + `p` TESTS: @@ -241,17 +242,18 @@ def __init__(self, group, base_ring=QQ): self.__cached_gens = [] self.__cached_cusp_maxweight = ZZ(-1) self.__cached_cusp_gens = [] - Parent.__init__(self, base=base_ring, category=GradedAlgebras(base_ring)) + cat = GradedAlgebras(base_ring).Commutative() + Parent.__init__(self, base=base_ring, category=cat) def change_ring(self, base_ring): r""" - Return the ring of modular forms over the given base ring and the same - group as ``self``. + Return a ring of modular forms over a new base ring of the same + congruence subgroup. INPUT: - - ``base_ring`` -- a base ring, which should be `\QQ`, `\ZZ`, or the - integers mod `p` for some prime `p` + - ``base_ring`` -- a base ring, which should be `\QQ`, `\ZZ`, or + the integers mod `p` for some prime `p` EXAMPLES:: @@ -266,7 +268,7 @@ def change_ring(self, base_ring): def some_elements(self): r""" - Return a list of generators of ``self``. + Return some elements of this ring. EXAMPLES:: @@ -278,7 +280,7 @@ def some_elements(self): def group(self): r""" - Return the congruence subgroup for which this is the ring of modular forms. + Return the congruence subgroup of this ring of modular forms. EXAMPLES:: @@ -290,14 +292,13 @@ def group(self): def gen(self, i): r""" - Return the `i`-th generator of ``self``. + Return the `i`-th generator of this ring. INPUT: - - ``i`` -- integer; correspond to the `i`-th modular form generating - the ring of modular forms + - ``i`` -- integer - OUTPUT: a ``GradedModularFormElement`` + OUTPUT: an instance of :class:`~sage.modular.modform.GradedModularFormElement` EXAMPLES:: @@ -313,7 +314,7 @@ def gen(self, i): def ngens(self): r""" - Return the number of generators of ``self``. + Return the number of generators of this ring. EXAMPLES:: @@ -333,19 +334,25 @@ def ngens(self): def polynomial_ring(self, names, gens=None): r""" - Return a polynomial ring of which ``self`` is a quotient. + Return a polynomial ring of which this ring of modular forms is + a quotient. INPUT: - - ``names`` -- list or tuple of names (strings), or a comma separated - string - - ``gens`` -- (default: ``None``) list of generator of - ``self``. If ``gens`` is ``None`` then the generators returned by + - ``names`` -- a list or tuple of names (strings), or a comma + separated string; consists in the names of the polynomial + ring variables + - ``gens`` -- list of modular forms generating this ring + (default: ``None``); if ``gens`` is ``None`` then the list of + generators returned by the method :meth:`~sage.modular.modform.find_generator.ModularFormsRing.gen_forms` - is used instead + is used instead. Note that we do not check if the list is + indeed a generating set. - OUTPUT: a multivariate polynomial ring in the variable ``names``. Each variable of the - polynomial ring correspond to a generator given in gens (following the ordering of the list). + OUTPUT: a multivariate polynomial ring in the variable + ``names``. Each variable of the polynomial ring correspond to a + generator given in the list ``gens`` (following the ordering of + the list). EXAMPLES:: @@ -358,7 +365,8 @@ def polynomial_ring(self, names, gens=None): sage: M.polynomial_ring('g', gens) Multivariate Polynomial Ring in g0, g1, g2 over Rational Field - The degrees of the variables are the weights of the corresponding forms:: + The degrees of the variables are the weights of the + corresponding forms:: sage: M = ModularFormsRing(1) sage: P. = M.polynomial_ring() @@ -372,12 +380,13 @@ def polynomial_ring(self, names, gens=None): if gens is None: gens = self.gen_forms() degs = [f.weight() for f in gens] - return PolynomialRing(self.base_ring(), len(gens), names, order=TermOrder('wdeglex', degs)) # Should we remove the deg lexicographic ordering here? + return PolynomialRing(self.base_ring(), len(gens), names, + order=TermOrder('wdeglex', degs)) def _generators_variables_dictionnary(self, poly_parent, gens): r""" - Utility function that returns a dictionary giving an association between - polynomial ring generators and generators of modular forms ring. + Return a dictionary giving an association between polynomial + ring generators and generators of modular forms ring. INPUT: @@ -398,23 +407,28 @@ def _generators_variables_dictionnary(self, poly_parent, gens): nb_var = poly_parent.ngens() nb_gens = self.ngens() if nb_var != nb_gens: - raise ValueError('the number of variables (%s) must be equal to the number of generators of the modular forms ring (%s)' % (nb_var, self.ngens())) + raise ValueError('the number of variables (%s) must be equal to' + ' the number of generators of the modular forms' + ' ring (%s)' % (nb_var, self.ngens())) return {poly_parent.gen(i): self(gens[i]) for i in range(0, nb_var)} def from_polynomial(self, polynomial, gens=None): r""" - Convert the given polynomial to a graded form living in ``self``. If - ``gens`` is ``None`` then the list of generators given by the method - :meth:`gen_forms` will be used. Otherwise, ``gens`` should be a list of - generators. + Return a graded modular form constructed by evaluating a given + multivariate polynomial at a set of generators. INPUT: - - ``polynomial`` -- a multivariate polynomial. The variables names of - the polynomial should be different from ``'q'``. The number of - variable of this polynomial should equal the number of generators - - ``gens`` -- list (default: ``None``); of generators of the modular - forms ring + - ``polynomial`` -- a multivariate polynomial. The variables + names of the polynomial should be different from ``'q'``. The + number of variable of this polynomial should equal the number + of given generators. + - ``gens`` -- list of modular forms generating this ring + (default: ``None``); if ``gens`` is ``None`` then the list of + generators returned by the method + :meth:`~sage.modular.modform.find_generator.ModularFormsRing.gen_forms` + is used instead. Note that we do not check if the list is + indeed a generating set. OUTPUT: a ``GradedModularFormElement`` given by the polynomial relation ``polynomial`` @@ -437,7 +451,8 @@ def from_polynomial(self, polynomial, gens=None): sage: M.from_polynomial(P(1/2)) 1/2 - Note that the number of variables must be equal to the number of generators:: + Note that the number of variables must be equal to the number of + generators:: sage: x, y = polygens(QQ, 'x, y') sage: M(x + y) @@ -470,7 +485,7 @@ def from_polynomial(self, polynomial, gens=None): def _element_constructor_(self, forms_datum): r""" - The call method of ``self``. + Return the graded modular form corresponding to the given data. INPUT: @@ -524,7 +539,7 @@ def _element_constructor_(self, forms_datum): else: raise ValueError('the group (%s) and/or the base ring (%s) of the given modular form is not consistant with the base space: %s' % (forms_datum.group(), forms_datum.base_ring(), self)) elif forms_datum in self.base_ring(): - forms_dictionary = {0:forms_datum} + forms_dictionary = {0: forms_datum} elif isinstance(forms_datum, MPolynomial): return self.from_polynomial(forms_datum) elif isinstance(forms_datum, PowerSeries_poly): @@ -577,7 +592,8 @@ def one(self): def _coerce_map_from_(self, M): r""" - Code to make ModularFormRing work well with coercion framework. + Return ``True`` if there is a coercion map from ``M`` to this + ring. TESTS:: @@ -623,7 +639,7 @@ def __richcmp__(self, other, op): def _repr_(self): r""" - String representation of ``self``. + Return the string representation of ``self``. EXAMPLES:: @@ -636,7 +652,8 @@ def _repr_(self): def modular_forms_of_weight(self, weight): """ - Return the space of modular forms on this group of the given weight. + Return the space of modular forms of the given weight and the + same congruence subgroup. EXAMPLES:: @@ -650,23 +667,30 @@ def modular_forms_of_weight(self, weight): def generators(self, maxweight=8, prec=10, start_gens=[], start_weight=2): r""" - If `R` is the base ring of self, then this function calculates a set of - modular forms which generate the `R`-algebra of all modular forms of - weight up to ``maxweight`` with coefficients in `R`. + Return a list of generator of this ring as a list of pairs + `(k, f)` where `k` is an integer and `f` is a univariate power + series in `q` corresponding to the `q`-expansion of a modular + form of weight `k`. + + More precisely, if `R` is the base ring of self, then this + function calculates a set of modular forms which generate the + `R`-algebra of all modular forms of weight up to ``maxweight`` + with coefficients in `R`. INPUT: - - ``maxweight``-- integer (default: 8); check up to this weight for - generators + - ``maxweight`` -- integer (default: 8); check up to this weight + for generators - - ``prec``-- integer (default: 10); return `q`-expansions to this - precision + - ``prec`` -- integer (default: 10); return `q`-expansions to + this precision - - ``start_gens``-- list (default: ``[]``); list of pairs `(k, f)`, or - triples `(k, f, F)`, where: + - ``start_gens`` -- list (default: ``[]``); list of pairs + `(k, f)`, or triples `(k, f, F)`, where: - `k` is an integer, - - `f` is the `q`-expansion of a modular form of weight `k`, as a power series over the base ring of self, + - `f` is the `q`-expansion of a modular form of weight `k`, + as a power series over the base ring of self, - `F` (if provided) is a modular form object corresponding to F. If this list is nonempty, we find a minimal generating set containing @@ -787,7 +811,8 @@ def generators(self, maxweight=8, prec=10, start_gens=[], start_weight=2): for x in start_gens: if len(x) == 2: if x[1].prec() < prec: - raise ValueError("Requested precision cannot be higher than precision of approximate starting generators!") + raise ValueError("Requested precision cannot be higher" + " than precision of approximate starting generators!") sgs.append((x[0], x[1], None)) else: sgs.append(x) @@ -807,22 +832,22 @@ def generators(self, maxweight=8, prec=10, start_gens=[], start_weight=2): def gen_forms(self, maxweight=8, start_gens=[], start_weight=2): r""" - Return a list of modular forms generating this ring (as an algebra over - the appropriate base ring). + Return a list of modular forms generating this ring (as an algebra + over the appropriate base ring). This method differs from :meth:`generators` only in that it returns graded modular form objects, rather than bare `q`-expansions. INPUT: - - ``maxweight``-- integer (default: 8); calculate forms generating all - forms up to this weight + - ``maxweight`` -- integer (default: 8); calculate forms + generating all forms up to this weight - - ``start_gens``-- list (default: ``[]``); list of modular forms. If - this list is nonempty, we find a minimal generating set containing - these forms + - ``start_gens`` -- list (default: ``[]``); a list of + modular forms. If this list is nonempty, we find a minimal + generating set containing these forms. - - ``start_weight``-- integer (default: 2); calculate the graded + - ``start_weight`` -- integer (default: 2); calculate the graded subalgebra of forms of weight at least ``start_weight`` .. NOTE:: @@ -842,18 +867,20 @@ def gen_forms(self, maxweight=8, start_gens=[], start_weight=2): sage: A[0].parent() Modular Forms space of dimension 2 for Congruence Subgroup Gamma0(11) of weight 2 over Rational Field """ - sgs = tuple( (F.weight(), None, F) for F in start_gens ) + sgs = tuple((F.weight(), None, F) for F in start_gens) G = self._find_generators(maxweight, sgs, start_weight) - return [F for k,f,F in G] + return [F for k, f, F in G] gens = gen_forms def _find_generators(self, maxweight, start_gens, start_weight): r""" - For internal use. This function is called by :meth:`generators` and - :meth:`gen_forms`: it returns a list of triples `(k, f, F)` where `F` - is a modular form of weight `k` and `f` is its `q`-expansion coerced - into the base ring of ``self``. + Returns a list of triples `(k, f, F)` where `F` is a modular + form of weight `k` and `f` is its `q`-expansion coerced into the + base ring of self. + + For internal use. This function is called by :meth:`generators` + and :meth:`gen_forms`. INPUT: @@ -973,19 +1000,19 @@ def _find_generators(self, maxweight, start_gens, start_weight): @cached_method def q_expansion_basis(self, weight, prec=None, use_random=True): r""" - Calculate a basis of `q`-expansions for the space of modular forms of the - given weight for this group, calculated using the ring generators given - by ``find_generators``. + Return a basis of `q`-expansions for the space of modular forms + of the given weight for this group, calculated using the ring + generators given by ``find_generators``. INPUT: - - ``weight`` -- integer; the weight - - ``prec`` -- integer or ``None`` (default: ``None``); power series - precision. If ``None``, the precision defaults to the Sturm bound for - the requested level and weight. - - ``use_random`` -- boolean (default: ``True``); whether or not to use a - randomized algorithm when building up the space of forms at the given - weight from known generators of small weight + - ``weight`` -- the weight + - ``prec`` -- integer (default: ``None``); power series + precision. If ``None``, the precision defaults to the Sturm + bound for the requested level and weight. + - ``use_random`` -- boolean (default: ``True``); whether or not to + use a randomized algorithm when building up the space of forms + at the given weight from known generators of small weight. EXAMPLES:: @@ -1041,8 +1068,8 @@ def q_expansion_basis(self, weight, prec=None, use_random=True): def cuspidal_ideal_generators(self, maxweight=8, prec=None): r""" - Calculate generators for the ideal of cuspidal forms in this ring, as a - module over the whole ring. + Return a set of generators for the ideal of cuspidal forms in + this ring, as a module over the whole ring. EXAMPLES:: @@ -1063,7 +1090,7 @@ def cuspidal_ideal_generators(self, maxweight=8, prec=None): for j,f,F in self.__cached_cusp_gens: if f.prec() >= working_prec: f = F.qexp(working_prec).change_ring(self.base_ring()) - G.append( (j,f,F) ) + G.append((j, f, F)) else: k = 2 G = [] @@ -1114,22 +1141,23 @@ def cuspidal_ideal_generators(self, maxweight=8, prec=None): if prec is None: return G elif prec <= working_prec: - return [ (k, f.truncate_powerseries(prec), F) for k,f,F in G] + return [(k, f.truncate_powerseries(prec), F) for k,f,F in G] else: # user wants increased precision, so we may as well cache that - Gnew = [ (k, F.qexp(prec).change_ring(self.base_ring()), F) for k,f,F in G] + Gnew = [(k, F.qexp(prec).change_ring(self.base_ring()), F) for k, f, F in G] self.__cached_cusp_gens = Gnew return Gnew def cuspidal_submodule_q_expansion_basis(self, weight, prec=None): r""" - Calculate a basis of `q`-expansions for the space of cusp forms of + Return a basis of `q`-expansions for the space of cusp forms of weight ``weight`` for this group. INPUT: - - ``weight`` -- integer; the weight - - ``prec`` -- integer or ``None`` precision of `q`-expansions to return + - ``weight`` -- the weight + - ``prec`` -- integer (default: ``None``) precision of + `q`-expansions to return ALGORITHM: Uses the method :meth:`cuspidal_ideal_generators` to calculate generators of the ideal of cusp forms inside this ring. Then diff --git a/src/sage/modular/modsym/heilbronn.pyx b/src/sage/modular/modsym/heilbronn.pyx index a14f2e6ccac..82adf9efa20 100644 --- a/src/sage/modular/modsym/heilbronn.pyx +++ b/src/sage/modular/modsym/heilbronn.pyx @@ -383,7 +383,7 @@ cdef class HeilbronnCremona(Heilbronn): # NOTE: In C, -p/2 means "round toward 0", but in Python it # means "round down." sig_on() - for r in range(-p/2, p/2+1): + for r in range(-p // 2, p // 2 + 1): x1 = p x2 = -r y1 = 0 @@ -394,7 +394,7 @@ cdef class HeilbronnCremona(Heilbronn): x3 = 0 y3 = 0 q = 0 - list_append4(L, x1,x2,y1,y2) + list_append4(L, x1, x2, y1, y2) while b: q = roundf(a / b) c = a - b*q @@ -406,8 +406,8 @@ cdef class HeilbronnCremona(Heilbronn): y3 = q*y2 - y1 y1 = y2 y2 = y3 - list_append4(L, x1,x2, y1,y2) - self.length = L.i/4 + list_append4(L, x1, x2, y1, y2) + self.length = L.i // 4 sig_off() @@ -491,7 +491,7 @@ cdef class HeilbronnMerel(Heilbronn): for a in range(1, n+1): ## We have ad-bc=n so c=0 and ad=n, or b=(ad-n)/c ## Must have ad - n >= 0, so d must be >= Ceiling(n/a). - q = n/a + q = n // a if q*a == n: d = q for b in range(a): @@ -503,10 +503,10 @@ cdef class HeilbronnMerel(Heilbronn): ## Divisor c of bc must satisfy Floor(bc/c) lt a and c lt d. ## c ge (bc div a + 1) <=> Floor(bc/c) lt a (for integers) ## c le d - 1 <=> c lt d - for c in range(bc/a + 1, d): + for c in range(bc // a + 1, d): if bc % c == 0: - list_append4(L,a,bc/c,c,d) - self.length = L.i/4 + list_append4(L, a, bc // c, c, d) + self.length = L.i // 4 sig_off() diff --git a/src/sage/modular/modsym/p1list.pyx b/src/sage/modular/modsym/p1list.pyx index 5e48d6b1369..7ceda12d5c8 100644 --- a/src/sage/modular/modsym/p1list.pyx +++ b/src/sage/modular/modsym/p1list.pyx @@ -94,8 +94,8 @@ cdef int c_p1_normalize_int(int N, int u, int v, # Now g = s*u + t*N, so s is a "pseudo-inverse" of u mod N # Adjust s modulo N/g so it is coprime to N. - if g!=1: - d = N/g + if g != 1: + d = N // g while arith_int.c_gcd_int(s,N) != 1: s = (s+d) % N @@ -105,8 +105,8 @@ cdef int c_p1_normalize_int(int N, int u, int v, min_v = v min_t = 1 - if g!=1: - Ng = N/g + if g != 1: + Ng = N // g vNg = (v*Ng) % N t = 1 for k in range(2, g + 1): @@ -355,8 +355,8 @@ cdef int c_p1_normalize_llong(int N, int u, int v, # Now g = s*u + t*N, so s is a "pseudo-inverse" of u mod N # Adjust s modulo N/g so it is coprime to N. - if g!=1: - d = N/g + if g != 1: + d = N // g while arith_int.c_gcd_int(s,N) != 1: s = (s+d) % N @@ -367,8 +367,8 @@ cdef int c_p1_normalize_llong(int N, int u, int v, min_v = v min_t = 1 - if g!=1: - Ng = N/g + if g != 1: + Ng = N // g vNg = ((v * Ng) % ll_N) t = 1 for k in range(2, g + 1): @@ -640,8 +640,8 @@ cdef int p1_normalize_xgcdtable(int N, int u, int v, # Now g = s*u + t*N, so s is a "pseudo-inverse" of u mod N # Adjust s modulo N/g so it is coprime to N. - if g!=1: - d = N/g + if g != 1: + d = N // g while t_g[s] != 1: # while arith_int.c_gcd_int(s,N) != 1: s = (s+d) % N @@ -651,8 +651,8 @@ cdef int p1_normalize_xgcdtable(int N, int u, int v, min_v = v min_t = 1 - if g!=1: - Ng = N/g + if g != 1: + Ng = N // g vNg = (v*Ng) % N t = 1 for k in range(2, g + 1): @@ -1237,17 +1237,17 @@ def lift_to_sl2z_int(int c, int d, int N): # compute prime-to-d part of m. while True: - g = arith_int.c_gcd_int(m,d) + g = arith_int.c_gcd_int(m, d) if g == 1: break - m = m / g + m = m // g # compute prime-to-N part of m. while True: - g = arith_int.c_gcd_int(m,N) + g = arith_int.c_gcd_int(m, N) if g == 1: break - m = m / g + m = m // g d += N * m g = arith_int.c_xgcd_int(c, d, &z1, &z2) @@ -1299,7 +1299,7 @@ def lift_to_sl2z_llong(llong c, llong d, int N): g = arith_llong.c_xgcd_longlong(c, d, &z1, &z2) # We're lucky: z1*c + z2*d = 1. - if g==1: + if g == 1: return [z2, -z1, c, d] # Have to try harder. @@ -1307,17 +1307,17 @@ def lift_to_sl2z_llong(llong c, llong d, int N): # compute prime-to-d part of m. while True: - g = arith_llong.c_gcd_longlong(m,d) + g = arith_llong.c_gcd_longlong(m, d) if g == 1: break - m = m / g + m = m // g # compute prime-to-N part of m. while True: - g = arith_llong.c_gcd_longlong(m,N) + g = arith_llong.c_gcd_longlong(m, N) if g == 1: break - m = m / g + m = m // g d += N * m g = arith_llong.c_xgcd_longlong(c, d, &z1, &z2) diff --git a/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py b/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py index fb869fc7551..aa2478af088 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py +++ b/src/sage/quadratic_forms/quadratic_form__local_field_invariants.py @@ -17,7 +17,7 @@ # **************************************************************************** ########################################################################### -# TO DO: Add routines for hasse invariants at all places, anisotropic +# TO DO: Add routines for Hasse invariants at all places, anisotropic # places, is_semi_definite, and support for number fields. ########################################################################### @@ -165,7 +165,19 @@ def rational_diagonal_form(self, return_matrix=False): sage: T[0,0] = 13 Traceback (most recent call last): ... - ValueError: matrix is immutable; please change a copy instead (i.e., use copy(M) to change a copy of M). + ValueError: matrix is immutable; please change a copy instead + (i.e., use copy(M) to change a copy of M). + + Test for a singular form:: + + sage: m = matrix(GF(11), [[1,5,0,0], [5,1,9,0], [0,9,1,5], [0,0,5,1]]) + sage: qf = QuadraticForm(m) + sage: Q, T = qf.rational_diagonal_form(return_matrix=True) + sage: T + [ 1 6 5 10] + [ 0 1 10 9] + [ 0 0 1 2] + [ 0 0 0 1] """ Q, T = self._rational_diagonal_form_and_transformation() T.set_immutable() @@ -173,19 +185,17 @@ def rational_diagonal_form(self, return_matrix=False): # Quadratic forms do not support immutability, so we need to make # a copy to be safe. Q = deepcopy(Q) - - if return_matrix: - return Q, T - else: - return Q + return (Q, T) if return_matrix else Q @cached_method def _rational_diagonal_form_and_transformation(self): """ Return a diagonal form equivalent to the given quadratic from and - the corresponding transformation matrix. This is over the fraction - field of the base ring of the given quadratic form. + the corresponding transformation matrix. + + This is over the fraction field of the base ring of the given + quadratic form. OUTPUT: a tuple `(D,T)` where @@ -238,13 +248,13 @@ def _rational_diagonal_form_and_transformation(self): D = MS() for i in range(n): D[i, i] = R[i, i] - Q = Q.parent()(D) + newQ = Q.parent()(D) # Transformation matrix (inverted) T = MS(R.sage()) for i in range(n): T[i, i] = K.one() try: - return Q, ~T + return newQ, ~T except ZeroDivisionError: # Singular case is not fully supported by PARI pass @@ -276,7 +286,8 @@ def _rational_diagonal_form_and_transformation(self): temp = MS(1) for j in range(i + 1, n): if Q[i, j] != 0: - temp[i, j] = -Q[i, j] / (Q[i, i] * 2) # This should only occur when Q[i,i] != 0, which the above step guarantees. + temp[i, j] = -Q[i, j] / (Q[i, i] * 2) + # This should only occur when Q[i,i] != 0, which the above step guarantees. Q = Q(temp) T = T * temp diff --git a/src/sage/rings/finite_rings/hom_finite_field.pyx b/src/sage/rings/finite_rings/hom_finite_field.pyx index d55e1a0a178..9e825650fca 100644 --- a/src/sage/rings/finite_rings/hom_finite_field.pyx +++ b/src/sage/rings/finite_rings/hom_finite_field.pyx @@ -150,7 +150,6 @@ cdef class SectionFiniteFieldHomomorphism_generic(Section): return root raise ValueError("%s is not in the image of %s" % (x, self._inverse)) - def _repr_(self): """ Return a string representation of this section. @@ -167,7 +166,6 @@ cdef class SectionFiniteFieldHomomorphism_generic(Section): """ return "Section of %s" % self._inverse - def _latex_(self): r""" Return a latex representation of this section. @@ -321,7 +319,6 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): f = f.map_coefficients(bm) return f(self.im_gens()[0]) - def is_injective(self): """ Return ``True`` since a embedding between finite fields is @@ -338,7 +335,6 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): """ return True - def is_surjective(self): """ Return ``True`` if this embedding is surjective (and hence an @@ -358,7 +354,6 @@ cdef class FiniteFieldHomomorphism_generic(RingHomomorphism_im_gens): """ return self.domain().cardinality() == self.codomain().cardinality() - @cached_method def section(self): """ @@ -544,7 +539,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): self._q = domain.characteristic() ** self._power RingHomomorphism.__init__(self, Hom(domain, domain)) - def _repr_(self): """ Return a string representation of this endomorphism. @@ -568,7 +562,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): s += " %s" % self.domain() return s - def _repr_short(self): """ Return a short string representation of this endomorphism. @@ -591,7 +584,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): s = "%s |--> %s^(%s^%s)" % (name, name, self.domain().characteristic(), self._power) return s - def _latex_(self): r""" Return a latex representation of this endomorphism. @@ -615,7 +607,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): s = "%s \\mapsto %s^{%s^{%s}}" % (name, name, self.domain().characteristic(), self._power) return s - cpdef Element _call_(self, x): """ TESTS:: @@ -632,7 +623,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): else: return x.pth_power(self._power) - def order(self): """ Return the order of this endomorphism. @@ -673,7 +663,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ return self._power - def __pow__(self, n, modulus): """ Return the `n`-th iterate of this endomorphism. @@ -735,7 +724,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): else: return RingHomomorphism._composition(self, right) - def fixed_field(self): """ Return the fixed field of ``self``. @@ -780,7 +768,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): f = FiniteFieldHomomorphism_generic(Hom(k, self.domain())) return k, f - def is_injective(self): """ Return ``True`` since any power of the Frobenius endomorphism @@ -795,7 +782,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ return True - def is_surjective(self): """ Return ``True`` since any power of the Frobenius endomorphism @@ -810,7 +796,6 @@ cdef class FrobeniusEndomorphism_finite_field(FrobeniusEndomorphism_generic): """ return True - def is_identity(self): """ Return ``True`` if this morphism is the identity morphism. diff --git a/src/sage/rings/polynomial/multi_polynomial_element.py b/src/sage/rings/polynomial/multi_polynomial_element.py index 2f3abcfc26b..4af0397a70d 100644 --- a/src/sage/rings/polynomial/multi_polynomial_element.py +++ b/src/sage/rings/polynomial/multi_polynomial_element.py @@ -1290,8 +1290,17 @@ def is_homogeneous(self): False sage: (x^2*y + y^2*x).is_homogeneous() True + + The weight of the parent ring is respected:: + + sage: term_order = TermOrder("wdegrevlex", [1, 3]) + sage: R. = PolynomialRing(Qp(5), order=term_order) + sage: (x + y).is_homogeneous() + False + sage: (x^3 + y).is_homogeneous() + True """ - return self.element().is_homogeneous() + return self.element().is_homogeneous(self.parent().term_order().weights()) def _homogenize(self, var): r""" diff --git a/src/sage/rings/polynomial/polydict.pyx b/src/sage/rings/polynomial/polydict.pyx index caa60be81e6..fe2438c8e4f 100644 --- a/src/sage/rings/polynomial/polydict.pyx +++ b/src/sage/rings/polynomial/polydict.pyx @@ -672,7 +672,7 @@ cdef class PolyDict: ans[ETuple(t)] = self.__repn[S] return self._new(ans) - def is_homogeneous(self): + def is_homogeneous(self, tuple w=None): r""" Return whether this polynomial is homogeneous. @@ -688,12 +688,20 @@ cdef class PolyDict: """ if not self.__repn: return True + cdef size_t s it = iter(self.__repn) - cdef size_t s = ( next(it)).unweighted_degree() - for elt in it: - if ( elt).unweighted_degree() != s: - return False - return True + if w is None: + s = ( next(it)).unweighted_degree() + for elt in it: + if ( elt).unweighted_degree() != s: + return False + return True + else: + s = ( next(it)).weighted_degree(w) + for elt in it: + if ( elt).weighted_degree(w) != s: + return False + return True def is_constant(self): """ diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx index 5879909eb1d..8aa1d13b5c1 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx @@ -102,7 +102,6 @@ cdef class Polynomial_integer_dense_flint(Polynomial): """ fmpz_poly_init(self._poly) - def __dealloc__(self): r""" Call the underlying FLINT fmpz_poly destructor @@ -652,7 +651,6 @@ cdef class Polynomial_integer_dense_flint(Polynomial): sig_off() return x - cpdef _sub_(self, right): r""" Return ``self`` minus ``right``. @@ -672,7 +670,6 @@ cdef class Polynomial_integer_dense_flint(Polynomial): sig_off() return x - cpdef _neg_(self): r""" Return negative of ``self``. @@ -834,7 +831,6 @@ cdef class Polynomial_integer_dense_flint(Polynomial): sig_off() return x - @coerce_binop def lcm(self, right): """ @@ -955,7 +951,6 @@ cdef class Polynomial_integer_dense_flint(Polynomial): else: return self._parent(rr), ss, tt - cpdef _mul_(self, right): r""" Return ``self`` multiplied by ``right``. @@ -1360,11 +1355,10 @@ cdef class Polynomial_integer_dense_flint(Polynomial): return real_roots(self) -## def __copy__(self): -## f = Polynomial_integer_dense(self.parent()) -## f._poly = self._poly.copy() -## return f - + # def __copy__(self): + # f = Polynomial_integer_dense(self.parent()) + # f._poly = self._poly.copy() + # return f def degree(self, gen=None): """ @@ -1485,7 +1479,6 @@ cdef class Polynomial_integer_dense_flint(Polynomial): variable = self.parent().variable_name() return pari(self.list()).Polrev(variable) - def squarefree_decomposition(Polynomial_integer_dense_flint self): """ Return the square-free decomposition of ``self``. This is @@ -1743,7 +1736,6 @@ cdef class Polynomial_integer_dense_flint(Polynomial): """ return [self.get_unsafe(i) for i in range(self.degree()+1)] - @coerce_binop def resultant(self, other, proof=True): """ diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx index 3f94f66f90b..142c7b324ad 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx @@ -69,6 +69,7 @@ from sage.libs.ntl.ZZX cimport * from sage.rings.polynomial.evaluation_ntl cimport ZZX_evaluation_mpfr, ZZX_evaluation_mpfi + cdef class Polynomial_integer_dense_ntl(Polynomial): r""" A dense polynomial over the integers, implemented via NTL. @@ -83,7 +84,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): x._is_gen = 0 return x - def __init__(self, parent, x=None, check=True, is_gen=False, construct=False): r""" EXAMPLES:: @@ -236,7 +236,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): mpz_to_ZZ(&y, (a).value) ZZX_SetCoeff(self._poly, i, y) - def content(self): r""" Return the greatest common divisor of the coefficients of this @@ -445,7 +444,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): (right)._poly) return x - cpdef _sub_(self, right): r""" Return ``self`` minus ``right``. @@ -463,7 +461,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): (right)._poly) return x - cpdef _neg_(self): r""" Return negative of ``self``. @@ -479,7 +476,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): ZZX_negate(x._poly, self._poly) return x - @coerce_binop def quo_rem(self, right): r""" @@ -565,7 +561,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): return qq, rr - @coerce_binop def gcd(self, right): r""" @@ -587,7 +582,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): del temp return x - @coerce_binop def lcm(self, right): """ @@ -677,7 +671,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): S = self.parent() return S(rr), ss, tt - cpdef _mul_(self, right): r""" Return ``self`` multiplied by ``right``. @@ -733,7 +726,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): ZZX_mul_ZZ(x._poly, self._poly, _right) return x - def __floordiv__(self, right): """ EXAMPLES:: @@ -785,7 +777,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): mpz_to_ZZ(&y, (value).value) ZZX_SetCoeff(self._poly, n, y) - def real_root_intervals(self): """ Return isolating intervals for the real roots of this polynomial. @@ -803,11 +794,10 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): return real_roots(self) -## def __copy__(self): -## f = Polynomial_integer_dense(self.parent()) -## f._poly = self._poly.copy() -## return f - + # def __copy__(self): + # f = Polynomial_integer_dense(self.parent()) + # f._poly = self._poly.copy() + # return f def degree(self, gen=None): """ @@ -855,7 +845,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): del temp return x - def __pari__(self, variable=None): """ EXAMPLES:: @@ -869,7 +858,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): variable = self.parent().variable_name() return pari(self.list()).Polrev(variable) - def squarefree_decomposition(self): """ Return the square-free decomposition of ``self``. This is @@ -1101,7 +1089,6 @@ cdef class Polynomial_integer_dense_ntl(Polynomial): """ return [self.get_unsafe(i) for i in range(self.degree()+1)] - @coerce_binop def resultant(self, other, proof=True): """ diff --git a/src/sage/rings/polynomial/polynomial_template.pxi b/src/sage/rings/polynomial/polynomial_template.pxi index 64398898d0d..402e6831245 100644 --- a/src/sage/rings/polynomial/polynomial_template.pxi +++ b/src/sage/rings/polynomial/polynomial_template.pxi @@ -588,7 +588,6 @@ cdef class Polynomial_template(Polynomial): return -2 return result - def __pow__(self, ee, modulus): """ EXAMPLES:: diff --git a/src/sage/rings/polynomial/skew_polynomial_finite_field.pyx b/src/sage/rings/polynomial/skew_polynomial_finite_field.pyx index a901022b308..0c8c660c18c 100644 --- a/src/sage/rings/polynomial/skew_polynomial_finite_field.pyx +++ b/src/sage/rings/polynomial/skew_polynomial_finite_field.pyx @@ -100,7 +100,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): N = self._parent._working_center(self.reduced_norm(var=False)) return N.is_irreducible() - def type(self, N): r""" Return the `N`-type of this skew polynomial (see definition below). @@ -209,7 +208,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): self._types[N] = type return type - # Finding divisors # ---------------- @@ -288,7 +286,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): continue return D - def _reduced_norm_factor_uniform(self): r""" Return a factor of the reduced norm of this skew @@ -357,7 +354,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): if random < count[i]: return F[i][0] - def _irreducible_divisors(self, bint right): r""" Return an iterator over all irreducible monic @@ -469,7 +465,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): d, _ = quo_rem2(P, d) yield d - def right_irreducible_divisor(self, uniform=False): r""" Return a right irreducible divisor of this skew polynomial. @@ -617,7 +612,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): if LD.degree() == degN: return LD - def right_irreducible_divisors(self): r""" Return an iterator over all irreducible monic right divisors @@ -686,7 +680,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): """ return self._irreducible_divisors(False) - def count_irreducible_divisors(self): r""" Return the number of irreducible monic divisors of @@ -744,7 +737,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): count += (cardL**m - 1) // (cardL - 1) return count - # Finding factorizations # ---------------------- @@ -926,7 +918,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): factors.reverse() return Factorization(factors, sort=False, unit=unit) - def factor(self, uniform=False): r""" Return a factorization of this skew polynomial. @@ -994,7 +985,6 @@ cdef class SkewPolynomial_finite_field_dense(SkewPolynomial_finite_order_dense): F = self._factorization return F - def count_factorizations(self): r""" Return the number of factorizations (as a product of a diff --git a/src/sage/rings/polynomial/skew_polynomial_finite_order.pyx b/src/sage/rings/polynomial/skew_polynomial_finite_order.pyx index 0abdfca57bc..f90484cba62 100644 --- a/src/sage/rings/polynomial/skew_polynomial_finite_order.pyx +++ b/src/sage/rings/polynomial/skew_polynomial_finite_order.pyx @@ -518,7 +518,6 @@ cdef class SkewPolynomial_finite_order_dense(SkewPolynomial_generic_dense): return center(self._optbound) return self.reduced_norm() - def optimal_bound(self): r""" Return the optimal bound of this skew polynomial (i.e. diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 5d2f2ca2216..074cf983b27 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -4485,6 +4485,26 @@ def as_number_field_element(self, minimal=False, embedded=False, prec=53): """ return number_field_elements_from_algebraics(self, minimal=minimal, embedded=embedded, prec=prec) + def is_integral(self): + r""" + Check if this number is an algebraic integer. + + EXAMPLES:: + + sage: QQbar(sqrt(-23)).is_integral() + True + sage: AA(sqrt(23/2)).is_integral() + False + + TESTS: + + Method should return the same value as :meth:`NumberFieldElement.is_integral`:: + + sage: for a in [QQbar(2^(1/3)), AA(2^(1/3)), QQbar(sqrt(1/2)), AA(1/2), AA(2), QQbar(1/2)]: + ....: assert a.as_number_field_element()[1].is_integral() == a.is_integral() + """ + return all(a in ZZ for a in self.minpoly()) + def exactify(self): """ Compute an exact representation for this number. diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index d3915484b8b..59cf58e3513 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -21,7 +21,7 @@ The class inheritance hierarchy is: - :class:`Algebra` (deprecated and essentially removed) - :class:`CommutativeRing` - - :class:`NoetherianRing` (deprecated) + - :class:`NoetherianRing` (deprecated and essentially removed) - :class:`CommutativeAlgebra` (deprecated and essentially removed) - :class:`IntegralDomain` (deprecated) @@ -1160,8 +1160,7 @@ cdef class IntegralDomain(CommutativeRing): - ``category`` -- (default: ``None``) a category, or ``None`` This method is used by all the abstract subclasses of - :class:`IntegralDomain`, like :class:`NoetherianRing`, - :class:`Field`, ... in order to + :class:`IntegralDomain`, like :class:`Field`, ... in order to avoid cascade calls Field.__init__ -> IntegralDomain.__init__ -> ... @@ -1247,7 +1246,7 @@ cdef class NoetherianRing(CommutativeRing): _default_category = NoetherianRings() def __init__(self, *args, **kwds): - deprecation(37234, "use the category DedekindDomains") + deprecation(37234, "use the category NoetherianRings") super().__init__(*args, **kwds) @@ -1306,6 +1305,11 @@ _Fields = Fields() cdef class Field(CommutativeRing): """ Generic field + + TESTS:: + + sage: QQ.is_noetherian() + True """ _default_category = _Fields @@ -1425,17 +1429,6 @@ cdef class Field(CommutativeRing): """ return True - def is_noetherian(self): - """ - Return ``True`` since fields are Noetherian rings. - - EXAMPLES:: - - sage: QQ.is_noetherian() - True - """ - return True - def krull_dimension(self): """ Return the Krull dimension of this field, which is 0. diff --git a/src/sage/rings/semirings/non_negative_integer_semiring.py b/src/sage/rings/semirings/non_negative_integer_semiring.py index cc144253328..4fc0309a44d 100644 --- a/src/sage/rings/semirings/non_negative_integer_semiring.py +++ b/src/sage/rings/semirings/non_negative_integer_semiring.py @@ -1,18 +1,19 @@ r""" Non Negative Integer Semiring """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2010 Nicolas Borie # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#****************************************************************************** +# https://www.gnu.org/licenses/ +# ***************************************************************************** from sage.sets.non_negative_integers import NonNegativeIntegers from sage.categories.semirings import Semirings from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets from sage.sets.family import Family + class NonNegativeIntegerSemiring(NonNegativeIntegers): r""" A class for the semiring of the nonnegative integers. @@ -69,9 +70,10 @@ def __init__(self): Category of facade infinite enumerated commutative semirings sage: TestSuite(NN).run() """ - NonNegativeIntegers.__init__(self, category=(Semirings().Commutative(), InfiniteEnumeratedSets()) ) + NonNegativeIntegers.__init__(self, category=(Semirings().Commutative(), + InfiniteEnumeratedSets())) - def _repr_(self): + def _repr_(self) -> str: r""" EXAMPLES:: diff --git a/src/sage/rings/semirings/tropical_mpolynomial.py b/src/sage/rings/semirings/tropical_mpolynomial.py index a482e097f89..0fa89866a12 100644 --- a/src/sage/rings/semirings/tropical_mpolynomial.py +++ b/src/sage/rings/semirings/tropical_mpolynomial.py @@ -37,6 +37,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.rings.polynomial.multi_polynomial_element import MPolynomial_polydict + class TropicalMPolynomial(MPolynomial_polydict): r""" A multivariate tropical polynomial. @@ -305,7 +306,7 @@ def plot3d(self, color='random'): corner = set() for i in axes[0]: for j in axes[1]: - corner.add((i,j)) + corner.add((i, j)) marks = corner | vertices | edge # Calculate the value of polynomial at each marked point @@ -316,9 +317,8 @@ def plot3d(self, color='random'): mark_terms = [] value = self(T(mark[0]), T(mark[1])) value_terms = [term(T(mark[0]), T(mark[1])) for term in terms] - for i in range(len(terms)): - if value_terms[i] == value: - mark_terms.append(terms[i]) + mark_terms.extend(terms[i] for i in range(len(terms)) + if value_terms[i] == value) point_terms[(R(mark[0]), R(mark[1]), value.lift())] = mark_terms # Plot the points that attained its value at one term only diff --git a/src/sage/rings/semirings/tropical_polynomial.py b/src/sage/rings/semirings/tropical_polynomial.py index f1d41c17ee0..b2d595e7e0a 100644 --- a/src/sage/rings/semirings/tropical_polynomial.py +++ b/src/sage/rings/semirings/tropical_polynomial.py @@ -37,6 +37,7 @@ from sage.structure.parent import Parent from sage.rings.polynomial.polynomial_element_generic import Polynomial_generic_sparse + class TropicalPolynomial(Polynomial_generic_sparse): r""" A univariate tropical polynomial. @@ -332,9 +333,8 @@ def factor(self): roots_order[root] += 1 else: roots_order[root] = 1 - factors = [] - for root in roots_order: - factors.append((R([root, 0]), roots_order[root])) + factors = [(R([root, 0]), roots_order[root]) + for root in roots_order] return Factorization(factors, unit=unit) def piecewise_function(self): @@ -386,10 +386,10 @@ def piecewise_function(self): f = intercept + gradient*x return f - unique_root = sorted(list(set(self.roots()))) + unique_root = sorted(set(self.roots())) pieces = [] domain = [] - for i in range(len(unique_root)+1): + for i in range(len(unique_root) + 1): if i == 0: test_number = R(unique_root[i] - 1) elif i == len(unique_root): @@ -579,12 +579,12 @@ def _latex_(self): if x.find("-") == 0: x = "\\left(" + x + "\\right)" if n > 1: - v = "|%s^{%s}" % (name, n) + v = f"|{name}^{{{n}}}" elif n == 1: - v = "|%s" % name + v = f"|{name}" else: v = "" - s += "%s %s" % (x, v) + s += f"{x} {v}" s = s.replace("|", "") if s == " ": return self.parent().base().zero()._latex_() @@ -968,7 +968,7 @@ def interpolation(self, points): result = self.one() for root, order in roots.items(): - result *= self([root,0])**order + result *= self([root, 0])**order test_value = result(R(points[0][0])) unit = R(points[0][1] - test_value.lift()) result *= unit diff --git a/src/sage/rings/semirings/tropical_variety.py b/src/sage/rings/semirings/tropical_variety.py index a8328ee3e48..e65b2408904 100644 --- a/src/sage/rings/semirings/tropical_variety.py +++ b/src/sage/rings/semirings/tropical_variety.py @@ -29,6 +29,7 @@ from sage.rings.infinity import infinity from sage.structure.unique_representation import UniqueRepresentation + class TropicalVariety(UniqueRepresentation, SageObject): r""" A tropical variety in `\RR^n`. @@ -195,9 +196,8 @@ def __init__(self, poly): self._poly = poly self._hypersurface = [] tropical_roots = [] - variables = [] - for name in poly.parent().variable_names(): - variables.append(SR.var(name)) + variables = [SR.var(name) + for name in poly.parent().variable_names()] # Convert each term to its linear function linear_eq = {} @@ -214,9 +214,7 @@ def __init__(self, poly): sol = solve(linear_eq[keys[0]] == linear_eq[keys[1]], variables) # Parametric solution of the chosen two terms - final_sol = [] - for s in sol[0]: - final_sol.append(s.right()) + final_sol = [s.right() for s in sol[0]] xy_interval = [] xy_interval.append(tuple(final_sol)) @@ -257,9 +255,8 @@ def __init__(self, poly): xy_interval.append(parameter_solution[0]) tropical_roots.append(xy_interval) # Calculate the order - index_diff = [] - for i in range(len(keys[0])): - index_diff.append(abs(keys[0][i] - keys[1][i])) + index_diff = [abs(ai - bi) + for ai, bi in zip(keys[0], keys[1])] order = gcd(index_diff) temp_order.append(order) temp_keys.append(keys) @@ -270,7 +267,7 @@ def __init__(self, poly): dim_param = 0 if tropical_roots: dim_param = len(tropical_roots[0][0]) - 1 - vars = [SR.var('t{}'.format(i)) for i in range(1, dim_param+1)] + vars = [SR.var(f't{i}') for i in range(1, dim_param + 1)] for arg in tropical_roots: subs_dict = {} index_vars = 0 @@ -450,7 +447,6 @@ def _components_intersection(self): import operator from sage.functions.min_max import max_symbolic, min_symbolic from sage.symbolic.relation import solve - from sage.symbolic.expression import Expression from sage.sets.set import Set def update_result(result): @@ -480,7 +476,7 @@ def update_result(result): # Checking there are no conditions with the same variables # that use the <= and >= operators simultaneously unique_sol_param = set() - temp = [s for s in sol_param_sim] + temp = list(sol_param_sim) op_temp = {i: set(temp[i].operands()) for i in range(len(temp))} for s_value in op_temp.values(): match_keys = [k for k, v in op_temp.items() if v == s_value] @@ -598,8 +594,7 @@ def _axes(self): for eqn in self._hypersurface[0][0]: for op in eqn.operands(): if op.is_numeric(): - if op > bound: - bound = op + bound = max(op, bound) return [[-bound, bound]] * 3 u_set = set() @@ -668,10 +663,8 @@ def _axes(self): zmin = z zmax = z else: - if z < zmin: - zmin = z - if z > zmax: - zmax = z + zmin = min(z, zmin) + zmax = max(z, zmax) axes.append([zmin, zmax]) return axes @@ -783,8 +776,8 @@ def find_edge_vertices(i): # Find the interval of parameter for outer vertex for index in range(len(comps)): - interval1 = RealSet(-infinity,infinity) # represent t1 - interval2 = RealSet(-infinity,infinity) # represent t2 + interval1 = RealSet(-infinity, infinity) # represent t1 + interval2 = RealSet(-infinity, infinity) # represent t2 is_doublevar = False for i, point in enumerate(comps[index][0]): pv = point.variables() @@ -952,8 +945,7 @@ def _axes(self): temp_operands += eq.operands() for op in temp_operands: if op.is_numeric(): - if abs(op) > bound: - bound = abs(op) + bound = max(abs(op), bound) return [[-bound, bound]] * 2 verts = self.vertices() @@ -1002,11 +994,11 @@ def vertices(self): if lower != -infinity: x = parametric_function[0].subs(**{str(var): lower}) y = parametric_function[1].subs(**{str(var): lower}) - vertices.add((x,y)) + vertices.add((x, y)) if upper != infinity: x = parametric_function[0].subs(**{str(var): upper}) y = parametric_function[1].subs(**{str(var): upper}) - vertices.add((x,y)) + vertices.add((x, y)) return vertices def _parameter_intervals(self): diff --git a/src/sage/schemes/elliptic_curves/all.py b/src/sage/schemes/elliptic_curves/all.py index 84f7b0d5a50..df527315527 100644 --- a/src/sage/schemes/elliptic_curves/all.py +++ b/src/sage/schemes/elliptic_curves/all.py @@ -27,10 +27,10 @@ lazy_import('sage.schemes.elliptic_curves.jacobian', 'Jacobian') lazy_import('sage.schemes.elliptic_curves.ell_finite_field', 'special_supersingular_curve') - lazy_import('sage.schemes.elliptic_curves.ell_rational_field', ['cremona_curves', 'cremona_optimal_curves']) +from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_with_prime_order from sage.schemes.elliptic_curves.cm import (cm_orders, cm_j_invariants, cm_j_invariants_and_orders, diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index fcd23e7243f..65b49cfe1bd 100755 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -15,6 +15,8 @@ - Lorenz Panny, John Cremona (2023-02): ``.twists()`` - Lorenz Panny (2023): ``special_supersingular_curve()`` + +- Martin Grenouilloux, Gareth Ma (2024-09): ``EllipticCurve_with_prime_order()`` """ # **************************************************************************** @@ -1341,6 +1343,113 @@ def is_ordinary(self, proof=True): """ return not is_j_supersingular(self.j_invariant(), proof=proof) + def has_order(self, value, num_checks=8): + r""" + Return ``True`` if the curve has order ``value``. + + INPUT: + + - ``value`` -- integer in the Hasse-Weil range for this curve + + - ``num_checks``-- integer (default: `8`); the number of times to check + whether ``value`` times a random point on this curve equals the + identity. If ``value`` is a prime and the curve is over a field of + order at least `5`, it is sufficient to pass in ``num_checks=1`` - + see the examples below. + + .. NOTE:: + + Since the method is probabilistic, there is a possibility for the + method to yield false positives (i.e. returning ``True`` even when + the result is ``False``). Even worse, it is possible for this to + happen even when ``num_checks`` is increased arbitrarily. See below + for an example and :issue:`38617` for an open discussion. + + EXAMPLES: + + For curves over small finite fields, the order is computed and compared + directly:: + + sage: E = EllipticCurve(GF(7), [0, 1]) + sage: E.order() + 12 + sage: E.has_order(12, num_checks=0) + True + sage: E.has_order(11, num_checks=0) + False + sage: E.has_order(13, num_checks=0) + False + + This tests the method on a random curve:: + + sage: # long time (10s) + sage: p = random_prime(2**128, lbound=2**127) + sage: K = GF((p, 2), name="a") + sage: E = EllipticCurve(K, [K.random_element() for _ in range(2)]) + sage: N = E.order() + sage: E.has_order(N, num_checks=20) + True + sage: E.has_order(N + 1) + False + + This demonstrates the bug mentioned in the NOTE above. The last return + value should be ``False`` after :issue:`38617` is fixed:: + + sage: E = EllipticCurve(GF(5443568676570036275321323), [0, 13]) + sage: N = 2333145661241 + sage: E.order() == N^2 + True + sage: E.has_order(N^2) + True + sage: del E._order + sage: E.has_order(N^2 + N) + True + + Due to the nature of the algorithm (testing multiple of points) and the Hasse-Weil bound, we see that for testing prime orders, ``num_checks=1`` is sufficient:: + + sage: p = random_prime(1000) + sage: E = EllipticCurve(GF(p), j=randrange(p)) + sage: q = random_prime(p + 20, lbound=p - 20) + sage: E.has_order(q, num_checks=20) == E.has_order(q, num_checks=1) + True + + AUTHORS: + + - Mariah Lenox (2011-02-16): Initial implementation + + - Gareth Ma (2024-01-21): Fix bug for small curves + """ + q = self.base_field().order() + a, b = Hasse_bounds(q, 1) + if not a <= value <= b: + return False + + # For really small values, the random tests are too weak to detect wrong + # orders So we go with computing directly instead. + # In #38341, the bound has been increased to a large value (2^64), but + # it should be decreased (to ~100) after bug #38617 is fixed. + if q <= 2**64 or hasattr(self, "_order"): + return self.order() == value + + # This might be slow + # if value.is_prime(): + # num_checks = 1 + + # Is value * random == identity? + for _ in range(num_checks): + while True: + G = self.random_point() + if not G.is_zero(): + break + + if not (value * G).is_zero(): + return False + + # TODO: uncomment this and remove the line in `set_order` after 38617 is fixed. + # self._order = value + + return True + def set_order(self, value, *, check=True, num_checks=8): r""" Set the value of ``self._order`` to ``value``. @@ -1355,7 +1464,7 @@ def set_order(self, value, *, check=True, num_checks=8): - ``check``-- boolean (default: ``True``); whether or not to run sanity checks on the input - - ``num_checks``-- integer (default: 8); if ``check`` is + - ``num_checks``-- integer (default: `8`); if ``check`` is ``True``, the number of times to check whether ``value`` times a random point on this curve equals the identity @@ -1401,42 +1510,41 @@ def set_order(self, value, *, check=True, num_checks=8): sage: E.set_order(0) Traceback (most recent call last): ... - ValueError: Value 0 illegal (not an integer in the Hasse range) + ValueError: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 does not have order 0 sage: E.set_order(1000) Traceback (most recent call last): ... - ValueError: Value 1000 illegal (not an integer in the Hasse range) + ValueError: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 does not have order 1000 - It is also very likely an error to pass a value which is not - the actual order of this curve. How unlikely is determined by - ``num_checks``, the factorization of the actual order, and the - actual group structure:: + It is also very likely an error to pass a value which is not the actual + order of this curve. How unlikely is determined by ``num_checks``, the + factorization of the actual order, and the actual group structure:: sage: E = EllipticCurve(GF(1009), [0, 1]) # This curve has order 948 sage: E.set_order(947) Traceback (most recent call last): ... - ValueError: Value 947 illegal (multiple of random point not the identity) + ValueError: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 1009 does not have order 947 - For curves over small finite fields, the order is cheap to compute, so it is computed - directly and compared:: + For curves over small finite fields, the order is cheap to compute, so + it is computed directly and compared:: sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12 sage: E.set_order(11) Traceback (most recent call last): ... - ValueError: Value 11 illegal (correct order is 12) + ValueError: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 does not have order 11 TESTS: - The previous version's random tests are not strong enough. In particular, the following used - to work:: + The previous version's random tests are not strong enough. In particular, + the following used to work:: sage: E = EllipticCurve(GF(2), [0, 0, 1, 1, 1]) # This curve has order 1 sage: E.set_order(3) Traceback (most recent call last): ... - ValueError: Value 3 illegal (correct order is 1) + ValueError: Elliptic Curve defined by y^2 + y = x^3 + x + 1 over Finite Field of size 2 does not have order 3 :: @@ -1444,12 +1552,12 @@ def set_order(self, value, *, check=True, num_checks=8): sage: E.set_order(4, num_checks=0) Traceback (most recent call last): ... - ValueError: Value 4 illegal (correct order is 12) + ValueError: Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7 does not have order 4 sage: E.order() 12 - .. TODO:: Add provable correctness check by computing the abelian group structure and - comparing. + .. TODO:: Add provable correctness check by computing the abelian group + structure and comparing. AUTHORS: @@ -1459,24 +1567,8 @@ def set_order(self, value, *, check=True, num_checks=8): """ value = Integer(value) - if check: - # Is value in the Hasse range? - q = self.base_field().order() - a,b = Hasse_bounds(q,1) - if not a <= value <= b: - raise ValueError(f"Value {value} illegal (not an integer in the Hasse range)") - - # For really small values, the random tests are too weak to detect wrong orders - # So we go with computing directly instead. - if q <= 100: - if self.order() != value: - raise ValueError(f"Value {value} illegal (correct order is {self.order()})") - - # Is value*random == identity? - for _ in range(num_checks): - G = self.random_point() - if value * G != self(0): - raise ValueError(f"Value {value} illegal (multiple of random point not the identity)") + if check and not self.has_order(value, num_checks=num_checks): + raise ValueError(f"{self} does not have order {value}") # TODO: It might help some of PARI's algorithms if we # could copy this over to the .pari_curve() as well. @@ -2676,7 +2768,7 @@ def EllipticCurve_with_order(m, *, D=None): Return an iterator for elliptic curves over finite fields with the given order. The curves are computed using the Complex Multiplication (CM) method. - A `:sage:`~sage.structure.factorization.Factorization` can be passed for ``m``, in which case + A :class:`~sage.structure.factorization.Factorization` can be passed for ``m``, in which case the algorithm is more efficient. If ``D`` is specified, it is used as the discriminant. @@ -2723,8 +2815,8 @@ def EllipticCurve_with_order(m, *, D=None): sage: all(E.order() == 21 for E in Es) True - Indeed, we can verify that this is correct. Hasse's bounds tell us that $p \leq 50$ - (approximately), and the rest can be checked via bruteforce:: + Indeed, we can verify that this is correct. Hasse's bounds tell us that + `p \leq 50` (approximately), and the rest can be checked via bruteforce:: sage: for p in prime_range(50): ....: for j in range(p): @@ -2760,7 +2852,7 @@ def find_q(m, m4_fac, D): m_val = m if D is None: - Ds = (D for D in range(-4 * m_val, 0) if D % 4 in [0, 1]) + Ds = (D for D in range(-1, -4 * m_val - 1, -1) if D % 4 in [0, 1]) else: assert D < 0 and D % 4 in [0, 1] Ds = [D] @@ -2772,17 +2864,325 @@ def find_q(m, m4_fac, D): continue H = hilbert_class_polynomial(D) - K = GF(q) - roots = H.roots(ring=K) - for j0, _ in roots: + for j0 in H.roots(ring=GF(q), multiplicities=False): E = EllipticCurve(j=j0) for Et in E.twists(): if any(Et.is_isomorphic(E) for E in seen): continue - try: - # This tests whether the curve has given order - Et.set_order(m_val) + # This tests whether the curve has given order + if Et.has_order(m_val): + # TODO: remove after 38617 + Et.set_order(m_val, check=False) seen.add(Et) yield Et - except ValueError: - pass + +def EllipticCurve_with_prime_order(N): + r""" + Given a prime number ``N``, find another prime number `p` and construct an + elliptic curve `E` defined over `\mathbb F_p` such that + `\#E(\mathbb F_p) = N`. + + INPUT: + + - ``N`` -- integer; the order for which we seek an elliptic curve. Must be a + prime number. + + OUTPUT: an iterator of (some) elliptic curves `E/\mathbb F_p` of order ``N`` + + ALGORITHM: + + Our algorithm is based on [BS2007]_, Algorithm 2.2, but we deviate for + several key steps. Firstly, the authors in the paper perform the search for + a suitable `D` *incrementally*, by enlarging the table `S` by `log(N)`-size + interval of primes `p` and testing all products of distinct primes `p` (or + rather `p^*`). We find this difficult to implement without testing + duplicate `D`s, so we instead enlarge the table one prime at a time + (effectively replacing `[r\log(N), (r + 1)\log(N)]` in the paper by `[r, + r]`). To compensate for the speed loss, we begin the algorithm by + prefilling `S` with the primes below `1000` (satisfying quadratic + reciprocity properties). The constant `1000` is determined experimentally + to be fast for many purposes, and for most `N` we tested we are able to + find a suitable small `D` without increasing the size of `S`. + + The paper also doesn't specify how to enumerate such `D`s, which recall + should be product of distinct values in the table `S`. We implement this + with a priority queue (min heap), which also allows us to search for the + suitable `D`s in increasing (absolute value) order. This is suitable for + the algorithm because smaller `D` means the Hilbert class polynomial is + computed quicker. + + Finally, to avoid repeatedly testing the same `D`s, we require the latest + prime to be added to the table to be included as a factor of `D` (see code + for more explanation). As we need to find integers `x, y` such that `x^2 + + (-D)y^2 = 4N` with `D < 0` and `N` prime, we actually need `|D| \leq 4N`, + so we terminate the algorithm when the primes in the table are larger than + that bound. This makes the iterator return all curves it can find in finite + time. + + ALGORITHM: Based on [BS2007]_, Algorithm 2.2 + + EXAMPLES:: + + sage: N = 8314040072427107567 + sage: E = next(EllipticCurve_with_prime_order(N)) + sage: E + Elliptic Curve defined by y^2 = x^3 + 4757897140353078952*x + 1841350074072114366 + over Finite Field of size 8314040074357871443 + sage: E.has_order(N) + True + + The returned curves are sometimes random because + :meth:`~sage.schemes.elliptic_curves.ell_finite_field.EllipticCurve_finite_field.twists` + is not deterministic. However, it's always isomorphic:: + + sage: E = next(EllipticCurve_with_prime_order(23)); E # random + Elliptic Curve defined by y^2 = x^3 + 12*x + 6 over Finite Field of size 17 + sage: E.is_isomorphic(EllipticCurve(GF(17), [3, 5])) + True + + You can directly iterate over the iterator; here only on the first 10 + curves:: + + sage: N = 54675917 + sage: for _, E in zip(range(10), EllipticCurve_with_prime_order(N)): + ....: assert E.has_order(N) + + It works for large primes:: + + sage: N = 2666207849820848272386538889527600954292544013630953455833 + sage: E = next(EllipticCurve_with_prime_order(N)); E + Elliptic Curve defined by y^2 = x^3 + 2666207849820848272386538889427721639173508298483739490459*x + + 77986137112576 over Finite Field of size 2666207849820848272386538889427721639173508298487130585243 + sage: E.has_order(N) + True + + :: + + sage: N = next_prime(2^256) + sage: E = next(EllipticCurve_with_prime_order(N)); E # random + Elliptic Curve defined by y^2 = x^3 + 6056521267553273205988520276135607487700943205131813669424576873701361709521*x + + 86942739955486781674010637133214195706465136689012129911736706024465988573567 over Finite Field of size + 115792089237316195423570985008687907853847329310253429036565151476471048389761 + sage: E.j_invariant() + 111836223967433630316209796253554285080540088646141285337487360944738698436350 + sage: E.has_order(N) + True + + Note that the iterator does *not* return all curves with the given order:: + + sage: any(E.base_ring() is GF(7) for E in EllipticCurve_with_prime_order(7)) + False + sage: EllipticCurve(GF(7), [0, 5]).order() + 7 + + However, experimentally it returns many of them. Here it returns all of + them:: + + sage: N = 23 + sage: set_random_seed(1337) # as the function returns random twists of curves + sage: curves = list(EllipticCurve_with_prime_order(N)); curves # random + [Elliptic Curve defined by y^2 = x^3 + 3*x + 5 over Finite Field of size 17, + Elliptic Curve defined by y^2 = x^3 + 19*x + 14 over Finite Field of size 31, + Elliptic Curve defined by y^2 = x^3 + 2*x + 9 over Finite Field of size 19, + Elliptic Curve defined by y^2 = x^3 + 7*x + 18 over Finite Field of size 29, + Elliptic Curve defined by y^2 = x^3 + 20*x + 20 over Finite Field of size 23, + Elliptic Curve defined by y^2 = x^3 + 10*x + 16 over Finite Field of size 23] + sage: import itertools + sage: # These are the only primes, by the Hasse-Weil bound + sage: for q in prime_range(17, 35): + ....: K = GF(q) + ....: for u in itertools.product(range(q), repeat=2): + ....: try: E = EllipticCurve(GF(q), u) + ....: except ArithmeticError: continue + ....: if E.has_order(N): + ....: assert any(E.is_isomorphic(E_) for E_ in curves) + + The algorithm is efficient for small ``N`` due to the low number of suitable + discriminants (see the ``abs_products_under`` internal function of the code + for details):: + + sage: len(list(EllipticCurve_with_prime_order(next_prime(5000)))) + 534 + sage: len(list(EllipticCurve_with_prime_order(next_prime(50000)))) # long time (6s) + 3841 + + There is different verbose data for level `2` to `4`, though level `3` + rarely logs anything (it logs when a new prime `p` is added to the + smoothness bound):: + + sage: from sage.misc.verbose import set_verbose + sage: set_random_seed(1337) # as the function returns random twists of curves + sage: for _, E in zip(range(3), EllipticCurve_with_prime_order(10^9 + 7)): + ....: print(E) + Elliptic Curve defined by y^2 = x^3 + 265977778*x + 120868502 over Finite Field of size 1000041437 + Elliptic Curve defined by y^2 = x^3 + 689795416*x + 188156157 over Finite Field of size 999969307 + Elliptic Curve defined by y^2 = x^3 + 999178436*x + 900579394 over Finite Field of size 999969307 + sage: set_verbose(2) + sage: set_random_seed(1337) + sage: for _, E in zip(range(3), EllipticCurve_with_prime_order(10^9 + 7)): + ....: print(E) + verbose 2 (...: ell_finite_field.py, EllipticCurve_with_prime_order) Computing the Hilbert class polynomial H_-163 + Elliptic Curve defined by y^2 = x^3 + 265977778*x + 120868502 over Finite Field of size 1000041437 + verbose 2 (...: ell_finite_field.py, EllipticCurve_with_prime_order) Computing the Hilbert class polynomial H_-667 + Elliptic Curve defined by y^2 = x^3 + 689795416*x + 188156157 over Finite Field of size 999969307 + Elliptic Curve defined by y^2 = x^3 + 999178436*x + 900579394 over Finite Field of size 999969307 + sage: set_verbose(4) + sage: set_random_seed(1337) + sage: for _, E in zip(range(3), EllipticCurve_with_prime_order(10^9 + 7)): + ....: print(E) + verbose 4 (...: ell_finite_field.py, EllipticCurve_with_prime_order) Testing D=-19 + ... + verbose 4 (...: ell_finite_field.py, EllipticCurve_with_prime_order) Testing D=-163 + verbose 2 (...: ell_finite_field.py, EllipticCurve_with_prime_order) Computing the Hilbert class polynomial H_-163 + Elliptic Curve defined by y^2 = x^3 + 265977778*x + 120868502 over Finite Field of size 1000041437 + verbose 4 (...: ell_finite_field.py, EllipticCurve_with_prime_order) Testing D=-179 + ... + verbose 4 (...: ell_finite_field.py, EllipticCurve_with_prime_order) Testing D=-667 + verbose 2 (...: ell_finite_field.py, EllipticCurve_with_prime_order) Computing the Hilbert class polynomial H_-667 + Elliptic Curve defined by y^2 = x^3 + 689795416*x + 188156157 over Finite Field of size 999969307 + Elliptic Curve defined by y^2 = x^3 + 999178436*x + 900579394 over Finite Field of size 999969307 + + TESTS:: + + sage: list(EllipticCurve_with_prime_order(2)) + [Elliptic Curve defined by y^2 + x*y + y = x^3 + 1 over Finite Field of size 2, + Elliptic Curve defined by y^2 = x^3 + 2*x^2 + 2 over Finite Field of size 3, + Elliptic Curve defined by y^2 = x^3 + 2*x over Finite Field of size 5] + + sage: set_verbose(0) + sage: for N in prime_range(3, 100): + ....: E = next(EllipticCurve_with_prime_order(N)) + ....: assert E.has_order(N) + + sage: N = 113 + sage: for _, E in zip(range(30), EllipticCurve_with_prime_order(N)): + ....: assert E.has_order(N) + + sage: N = 15175980689839334471 + sage: E = next(EllipticCurve_with_prime_order(N)) + sage: E.has_order(N) + True + + sage: N = next_prime(123456789) + sage: E = next(EllipticCurve_with_prime_order(N)) + sage: E.has_order(N) + True + + sage: N = 123456789 + sage: E = next(EllipticCurve_with_prime_order(N)) + Traceback (most recent call last): + ... + ValueError: input order is not a prime + + sage: E = next(EllipticCurve_with_prime_order(0)) + Traceback (most recent call last): + ... + ValueError: input order is not a prime + + sage: E = next(EllipticCurve_with_prime_order(-7)) + Traceback (most recent call last): + ... + ValueError: input order is not a prime + + AUTHORS: + + - Martin Grenouilloux, Gareth Ma (2024-09): initial implementation + """ + import itertools + from sage.arith.misc import is_prime, legendre_symbol + from sage.misc.verbose import verbose + from sage.quadratic_forms.binary_qf import BinaryQF + from sage.rings.fast_arith import prime_range + from sage.schemes.elliptic_curves.cm import hilbert_class_polynomial + from sage.sets.primes import Primes + + if not is_prime(N): + raise ValueError("input order is not a prime") + + if N == 2: + yield from [ + EllipticCurve(GF(2), [1, 0, 1, 0, 1]), + EllipticCurve(GF(3), [0, 2, 0, 0, 2]), + EllipticCurve(GF(5), [2, 0]) + ] + return + + # We start with small primes directly to accelerate the search. Note that + # 1000 is a magic constant, it's just fast enough to compute without + # sacrificing much speed. + # The if-then-else term is (-1)^((p - 1) / 2) * p in [BS2007]_ page 5. + S = [(-p if p % 4 == 3 else p) for p in prime_range(3, min(1000, 4 * N)) + if legendre_symbol(N, p) == 1] + + def abs_products_under(bound): + """ + This function returns an iterator of all numbers with absolute value not + exceeding ``bound`` expressable as product of distinct elements in ``S`` + in ascending order. + """ + import heapq + hq = [(1, 1, -1)] + while len(hq): + abs_n, n, idx = heapq.heappop(hq) + yield n + for nxt in range(idx + 1, len(S)): + if abs_n * abs(S[nxt]) <= bound: + heapq.heappush(hq, (abs_n * abs(S[nxt]), n * S[nxt], nxt)) + else: + break + + # We add p = 1 to process the small primes. + for p in itertools.chain([1], Primes()): + if p != 1: + if p < abs(S[-1]): + continue + + if legendre_symbol(N, p) != 1: + continue + + # Later we need x^2 + (-D)y^2 = 4N, and since y = 0 has no + # solution, we need p = |p_star| <= |-D| <= 4N. This is a stopping + # condition for the algorithm. + if p > 4 * N: + break + + verbose(f"Considering {len(S) + 1}th valid prime {p}", level=3) + + p_star = -p if p % 4 == 3 else p + + for e in abs_products_under(4 * N // p): + # According to the paper, the expected minimum D to work is + # O(log(N)^2) + D = p_star * e + assert abs(D) <= 4 * N + + if D % 8 != 5 or D >= 0: + continue + + verbose(f"Testing {D=}", level=4) + + Q = BinaryQF([1, 0, -D]) + sol = Q.solve_integer(4 * N, algorithm='cornacchia') + if sol is None: + continue + + x, _ = sol + for p_i in [N + 1 - x, N + 1 + x]: + if is_prime(p_i): + verbose(f"Computing the Hilbert class polynomial H_{D}", + level=2) + H = hilbert_class_polynomial(D) + K = GF(p_i) + for j0 in H.roots(ring=K, multiplicities=False): + E = EllipticCurve(K, j=j0) + # `E.twists()` also contains E. + for Et in E.twists(): + # `num_checks=1` is sufficient for prime order + if Et.has_order(N, num_checks=1): + # TODO: remove after 38617 + Et.set_order(N, check=False) + yield Et + + if p != 1: + # Extending our prime list and continuing onto the next round. + S.append(p_star) diff --git a/src/sage/schemes/elliptic_curves/mod_sym_num.pyx b/src/sage/schemes/elliptic_curves/mod_sym_num.pyx index 927df5e5a42..e55500f0d0f 100755 --- a/src/sage/schemes/elliptic_curves/mod_sym_num.pyx +++ b/src/sage/schemes/elliptic_curves/mod_sym_num.pyx @@ -256,9 +256,9 @@ cdef llong llxgcd(llong a, llong b, llong *ss, llong *tt) except -1: q = 0 r = 0 s = 1 - while (b): + while b: c = a % b - quot = a/b + quot = a // b a = b b = c new_r = p - quot*r @@ -365,9 +365,9 @@ cdef int proj_normalise(llong N, llong u, llong v, # Now g = s*u + t*N, so s is a "pseudo-inverse" of u mod N # Adjust s modulo N/g so it is coprime to N. if g != 1: - d = N / g + d = N // g while llgcd(s, N) != 1: - s = (s+d) % N + s = (s + d) % N # verbose(" now g=%s, s=%s, t=%s" % (g,s,t), level=5) # Multiply [u,v] by s; then [s*u,s*v] = [g,s*v] (mod N) @@ -376,7 +376,7 @@ cdef int proj_normalise(llong N, llong u, llong v, min_v = v min_t = 1 if g != 1: - Ng = N / g + Ng = N // g vNg = (v * Ng) % N t = 1 k = 2 @@ -456,24 +456,24 @@ cdef int best_proj_point(llong u, llong v, llong N, else: # cases like (p:q) mod p*q drop here p = llgcd(u, N) q = llgcd(v, N) - Nnew = N / p / q - w = ((u/p) * llinvmod(v/q, Nnew)) % Nnew - y0 = N/q + Nnew = (N // p) // q + w = ((u // p) * llinvmod(v // q, Nnew)) % Nnew + y0 = N // q y1 = 0 x0 = w*p x1 = q # y will always be the longer and x the shorter - while llabs(x0) + llabs(x1) < llabs(y0)+llabs(y1): + while llabs(x0) + llabs(x1) < llabs(y0) + llabs(y1): if llsign(x0) == llsign(x1): - r = (y0+y1) / (x0+x1) + r = (y0+y1) // (x0+x1) else: - r = (y0-y1) / (x0-x1) + r = (y0-y1) // (x0-x1) t0 = y0 - r * x0 t1 = y1 - r * x1 s0 = t0 - x0 s1 = t1 - x1 - if llabs(s0)+llabs(s1) < llabs(t0)+llabs(t1): + if llabs(s0) + llabs(s1) < llabs(t0) + llabs(t1): t0 = s0 t1 = s1 # t is now the shortest vector on the line y + RR x @@ -593,7 +593,7 @@ cdef class _CuspsForModularSymbolNumerical: a -= m self._r = Rational((a, m)) B = llgcd(m, N) - self._width = N / B + self._width = N // B self._a = a self._m = m self._N_level = N @@ -625,7 +625,7 @@ cdef class _CuspsForModularSymbolNumerical: # verbose(" enter atkin_lehner for cusp r=%s" % self._r, level=5) Q = self._width B = llgcd(self._m, self._N_level) - c = self._m / B + c = self._m // B if llgcd(Q, B) != 1: raise ValueError("This cusp is not in the Atkin-Lehner " "orbit of oo.") @@ -2125,12 +2125,12 @@ cdef class ModularSymbolNumerical: verbose(" yields %s " % int2c, level=3) ans = int2c + int1c else: # use_partials - g = llgcd(Q,QQ) + g = llgcd(Q, QQ) D = Q * QQ D /= g D *= llabs(a*mm-aa*m) - xi = (Q*aa*u+v*mm) * QQ /g * llsign(a*mm-aa*m) - xixi = (QQ*a*uu+vv*m) * Q /g * llsign(aa*m-a*mm) + xi = (Q*aa*u+v*mm) * (QQ // g) * llsign(a*mm-aa*m) + xixi = (QQ*a*uu+vv*m) * (Q // g) * llsign(aa*m-a*mm) z = Q * QQ * (a*mm-aa*m)**2 ka = self._kappa(D, z, eps/2) twopii = TWOPI * complex("j") @@ -2787,33 +2787,33 @@ cdef class ModularSymbolNumerical: else: # (c:d) = (u:v) but c and d are fairly small # in absolute value - Mu = llgcd(u,N) - Qu = N/Mu - Mv = llgcd(v,N) - Qv = N/Mv - isunitary = (llgcd(Qu,Mu) == 1 and llgcd(Qv,Mv) == 1) + Mu = llgcd(u, N) + Qu = N // Mu + Mv = llgcd(v, N) + Qv = N // Mv + isunitary = (llgcd(Qu, Mu) == 1 and llgcd(Qv, Mv) == 1) if isunitary: # unitary case _ = best_proj_point(u, v, self._N_E, &c, &d) else: # at least one of the two cusps is not unitary du = llgcd(Qu,Mu) - dv = llgcd(Qv,Mv) - NMM = N/Mv/Mu + dv = llgcd(Qv, Mv) + NMM = N // Mv // Mu if dv == 1: c = Mu - d = llinvmod(u/Mu, NMM) + d = llinvmod(u // Mu, NMM) d *= v - d = d % (N/Mu) + d = d % (N // Mu) while llgcd(c,d) != 1: - d += N/Mu + d += N // Mu d = d % N # now (u:v) = (c:d) with c as small as possible. else: d = Mv - c = llinvmod(v/Mv, NMM) + c = llinvmod(v // Mv, NMM) c *= u - c = c % (N/Mv) - while llgcd(c,d) != 1: - c += N/Mv + c = c % (N // Mv) + while llgcd(c, d) != 1: + c += N // Mv c = c % N # now (u:v) = (c:d) with d as small as possible. # verbose(" better representant on P^1: " @@ -3170,8 +3170,8 @@ cdef class ModularSymbolNumerical: RR = RealField(53) N = self._N_E - Q = N / llgcd(m,N) - if llgcd(m,Q) > 1: + Q = N // llgcd(m, N) + if llgcd(m, Q) > 1: raise NotImplementedError("Only implemented for cusps that are " "in the Atkin-Lehner orbit of oo") # verbose(" compute all partial sums with denominator m=%s" % m, diff --git a/src/sage/schemes/hyperelliptic_curves/constructor.py b/src/sage/schemes/hyperelliptic_curves/constructor.py index a39b94b030b..b3c006b2c19 100755 --- a/src/sage/schemes/hyperelliptic_curves/constructor.py +++ b/src/sage/schemes/hyperelliptic_curves/constructor.py @@ -202,7 +202,7 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): # rather than f and h, one of which might be constant. F = h**2 + 4 * f if not isinstance(F, Polynomial): - raise TypeError(f"arguments {f = } and {h = } must be polynomials") + raise TypeError(f"arguments f = {f} and h = {h} must be polynomials") P = F.parent() f = P(f) h = P(h) @@ -220,7 +220,7 @@ def HyperellipticCurve(f, h=0, names=None, PP=None, check_squarefree=True): # characteristic 2 if h == 0: raise ValueError( - f"for characteristic 2, argument {h = } must be nonzero" + f"for characteristic 2, argument h = {h} must be nonzero" ) if h[g + 1] == 0 and f[2 * g + 1] ** 2 == f[2 * g + 2] * h[g] ** 2: raise ValueError( diff --git a/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx b/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx index 4fa3280f6f9..2b14032ffc9 100755 --- a/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx +++ b/src/sage/schemes/hyperelliptic_curves/hypellfrob.pyx @@ -127,8 +127,10 @@ def interval_products(M0, M1, target): cdef long dim = M0.nrows() sig_on() c.restore_c() + sig_off() set_ntl_matrix_modn_dense(mm0, c, M0) set_ntl_matrix_modn_dense(mm1, c, M1) + sig_on() for t in target: targ.push_back(ntl_ZZ(t).x) numintervals = len(target)/2 diff --git a/src/sage/schemes/toric/ideal.py b/src/sage/schemes/toric/ideal.py index 41b69d05aca..93a28beafe8 100755 --- a/src/sage/schemes/toric/ideal.py +++ b/src/sage/schemes/toric/ideal.py @@ -354,8 +354,8 @@ def _naive_ideal(self, ring): x = ring.gens() binomials = [] for row in self.ker().matrix().rows(): - xpos = prod(x[i]**max( row[i],0) for i in range(0,len(x))) - xneg = prod(x[i]**max(-row[i],0) for i in range(0,len(x))) + xpos = prod(x[i]**max(row[i], 0) for i in range(len(x))) + xneg = prod(x[i]**max(-row[i], 0) for i in range(len(x))) binomials.append(xpos - xneg) return ring.ideal(binomials) @@ -445,6 +445,6 @@ def _ideal_HostenSturmfels(self): J = self._naive_ideal(ring) if J.is_zero(): return J - for i in range(0,self.nvariables()): + for i in range(self.nvariables()): J = self._ideal_quotient_by_variable(ring, J, i) return J diff --git a/src/sage/schemes/toric/library.py b/src/sage/schemes/toric/library.py index 1e1ff4d38d8..6f833c5c5d2 100755 --- a/src/sage/schemes/toric/library.py +++ b/src/sage/schemes/toric/library.py @@ -58,44 +58,44 @@ # The combinatorial data of the toric varieties is stored separately here # since we might want to use it later on to do the reverse lookup. toric_varieties_rays_cones = { - 'dP6':[ + 'dP6': [ [(0, 1), (-1, 0), (-1, -1), (0, -1), (1, 0), (1, 1)], - [[0,1],[1,2],[2,3],[3,4],[4,5],[5,0]] ], - 'dP7':[ + [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0]]], + 'dP7': [ [(0, 1), (-1, 0), (-1, -1), (0, -1), (1, 0)], - [[0,1],[1,2],[2,3],[3,4],[4,0]] ], - 'dP8':[ - [(1,1), (0, 1), (-1, -1), (1, 0)], - [[0,1],[1,2],[2,3],[3,0]] + [[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]]], + 'dP8': [ + [(1, 1), (0, 1), (-1, -1), (1, 0)], + [[0, 1], [1, 2], [2, 3], [3, 0]] ], - 'P1xP1':[ + 'P1xP1': [ [(1, 0), (-1, 0), (0, 1), (0, -1)], - [[0,2],[2,1],[1,3],[3,0]] ], - 'P1xP1_Z2':[ + [[0, 2], [2, 1], [1, 3], [3, 0]]], + 'P1xP1_Z2': [ [(1, 1), (-1, -1), (-1, 1), (1, -1)], - [[0,2],[2,1],[1,3],[3,0]] ], - 'P1':[ + [[0, 2], [2, 1], [1, 3], [3, 0]]], + 'P1': [ [(1,), (-1,)], - [[0],[1]] ], - 'P2':[ - [(1,0), (0, 1), (-1, -1)], - [[0,1],[1,2],[2,0]] ], - 'A1':[ + [[0], [1]]], + 'P2': [ + [(1, 0), (0, 1), (-1, -1)], + [[0, 1], [1, 2], [2, 0]]], + 'A1': [ [(1,)], - [[0]] ], - 'A2':[ + [[0]]], + 'A2': [ [(1, 0), (0, 1)], - [[0,1]] ], - 'A2_Z2':[ + [[0, 1]]], + 'A2_Z2': [ [(1, 0), (1, 2)], - [[0,1]] ], - 'P1xA1':[ + [[0, 1]]], + 'P1xA1': [ [(1, 0), (-1, 0), (0, 1)], - [[0,2],[2,1]] ], - 'Conifold':[ + [[0, 2], [2, 1]]], + 'Conifold': [ [(0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 1)], - [[0,1,2,3]] ], - 'dP6xdP6':[ + [[0, 1, 2, 3]]], + 'dP6xdP6': [ [(0, 1, 0, 0), (-1, 0, 0, 0), (-1, -1, 0, 0), (0, -1, 0, 0), (1, 0, 0, 0), (1, 1, 0, 0), (0, 0, 0, 1), (0, 0, -1, 0), (0, 0, -1, -1), @@ -108,74 +108,74 @@ [3, 4, 8, 9], [3, 4, 9, 10], [3, 4, 10, 11], [3, 4, 6, 11], [4, 5, 6, 7], [4, 5, 7, 8], [4, 5, 8, 9], [4, 5, 9, 10], [4, 5, 10, 11], [4, 5, 6, 11], [0, 5, 6, 7], [0, 5, 7, 8], - [0, 5, 8, 9], [0, 5, 9, 10], [0, 5, 10, 11], [0, 5, 6, 11]] ], - 'Cube_face_fan':[ + [0, 5, 8, 9], [0, 5, 9, 10], [0, 5, 10, 11], [0, 5, 6, 11]]], + 'Cube_face_fan': [ [(1, 1, 1), (1, -1, 1), (-1, 1, 1), (-1, -1, 1), (-1, -1, -1), (-1, 1, -1), (1, -1, -1), (1, 1, -1)], - [[0,1,2,3], [4,5,6,7], [0,1,7,6], [4,5,3,2], [0,2,5,7], [4,6,1,3]] ], - 'Cube_sublattice':[ + [[0, 1, 2, 3], [4, 5, 6, 7], [0, 1, 7, 6], [4, 5, 3, 2], [0, 2, 5, 7], [4, 6, 1, 3]]], + 'Cube_sublattice': [ [(1, 0, 0), (0, 1, 0), (0, 0, 1), (-1, 1, 1), (-1, 0, 0), (0, -1, 0), (0, 0, -1), (1, -1, -1)], - [[0,1,2,3],[4,5,6,7],[0,1,7,6],[4,5,3,2],[0,2,5,7],[4,6,1,3]] ], - 'Cube_nonpolyhedral':[ + [[0, 1, 2, 3], [4, 5, 6, 7], [0, 1, 7, 6], [4, 5, 3, 2], [0, 2, 5, 7], [4, 6, 1, 3]]], + 'Cube_nonpolyhedral': [ [(1, 2, 3), (1, -1, 1), (-1, 1, 1), (-1, -1, 1), (-1, -1, -1), (-1, 1, -1), (1, -1, -1), (1, 1, -1)], - [[0,1,2,3],[4,5,6,7],[0,1,7,6],[4,5,3,2],[0,2,5,7],[4,6,1,3]] ], - 'BCdlOG':[ + [[0, 1, 2, 3], [4, 5, 6, 7], [0, 1, 7, 6], [4, 5, 3, 2], [0, 2, 5, 7], [4, 6, 1, 3]]], + 'BCdlOG': [ [(-1, 0, 0, 2, 3), # 0 - ( 0,-1, 0, 2, 3), # 1 - ( 0, 0,-1, 2, 3), # 2 - ( 0, 0,-1, 1, 2), # 3 - ( 0, 0, 0,-1, 0), # 4 - ( 0, 0, 0, 0,-1), # 5 - ( 0, 0, 0, 2, 3), # 6 - ( 0, 0, 1, 2, 3), # 7 - ( 0, 0, 2, 2, 3), # 8 - ( 0, 0, 1, 1, 1), # 9 - ( 0, 1, 2, 2, 3), # 10 - ( 0, 1, 3, 2, 3), # 11 - ( 1, 0, 4, 2, 3)], # 12 - [ [0,6,7,1,4], [0,6,10,2,4], [0,6,1,2,4], [0,9,7,1,5], [0,6,7,1,5], - [0,6,10,2,5], [0,6,1,2,5], [0,9,1,4,5], [0,6,10,4,11],[0,6,7,4,11], - [0,6,10,5,11], [0,9,7,5,11], [0,6,7,5,11], [0,9,4,5,11], [0,10,4,5,11], - [0,9,7,1,8], [0,9,1,4,8], [0,7,1,4,8], [0,9,7,11,8], [0,9,4,11,8], - [0,7,4,11,8], [0,10,2,4,3], [0,1,2,4,3], [0,10,2,5,3], [0,1,2,5,3], - [0,10,4,5,3], [0,1,4,5,3], [12,6,7,1,4], [12,6,10,2,4],[12,6,1,2,4], - [12,9,7,1,5], [12,6,7,1,5], [12,6,10,2,5], [12,6,1,2,5], [12,9,1,4,5], - [12,6,10,4,11],[12,6,7,4,11], [12,6,10,5,11],[12,9,7,5,11],[12,6,7,5,11], - [12,9,4,5,11], [12,10,4,5,11],[12,9,7,1,8], [12,9,1,4,8], [12,7,1,4,8], - [12,9,7,11,8], [12,9,4,11,8], [12,7,4,11,8], [12,10,2,4,3],[12,1,2,4,3], - [12,10,2,5,3], [12,1,2,5,3], [12,10,4,5,3], [12,1,4,5,3] ] ], - 'BCdlOG_base':[ + (0, -1, 0, 2, 3), # 1 + (0, 0, -1, 2, 3), # 2 + (0, 0, -1, 1, 2), # 3 + (0, 0, 0, -1, 0), # 4 + (0, 0, 0, 0, -1), # 5 + (0, 0, 0, 2, 3), # 6 + (0, 0, 1, 2, 3), # 7 + (0, 0, 2, 2, 3), # 8 + (0, 0, 1, 1, 1), # 9 + (0, 1, 2, 2, 3), # 10 + (0, 1, 3, 2, 3), # 11 + (1, 0, 4, 2, 3)], # 12 + [[0, 6, 7, 1, 4], [0, 6, 10, 2, 4], [0, 6, 1, 2, 4], [0, 9, 7, 1, 5], [0, 6, 7, 1, 5], + [0, 6, 10, 2, 5], [0, 6, 1, 2, 5], [0, 9, 1, 4, 5], [0, 6, 10, 4, 11], [0, 6, 7, 4, 11], + [0, 6, 10, 5, 11], [0, 9, 7, 5, 11], [0, 6, 7, 5, 11], [0, 9, 4, 5, 11], [0, 10, 4, 5, 11], + [0, 9, 7, 1, 8], [0, 9, 1, 4, 8], [0, 7, 1, 4, 8], [0, 9, 7, 11, 8], [0, 9, 4, 11, 8], + [0, 7, 4, 11, 8], [0, 10, 2, 4, 3], [0, 1, 2, 4, 3], [0, 10, 2, 5, 3], [0, 1, 2, 5, 3], + [0, 10, 4, 5, 3], [0, 1, 4, 5, 3], [12, 6, 7, 1, 4], [12, 6, 10, 2, 4], [12, 6, 1, 2, 4], + [12, 9, 7, 1, 5], [12, 6, 7, 1, 5], [12, 6, 10, 2, 5], [12, 6, 1, 2, 5], [12, 9, 1, 4, 5], + [12, 6, 10, 4, 11], [12, 6, 7, 4, 11], [12, 6, 10, 5, 11], [12, 9, 7, 5, 11], [12, 6, 7, 5, 11], + [12, 9, 4, 5, 11], [12, 10, 4, 5, 11], [12, 9, 7, 1, 8], [12, 9, 1, 4, 8], [12, 7, 1, 4, 8], + [12, 9, 7, 11, 8], [12, 9, 4, 11, 8], [12, 7, 4, 11, 8], [12, 10, 2, 4, 3], [12, 1, 2, 4, 3], + [12, 10, 2, 5, 3], [12, 1, 2, 5, 3], [12, 10, 4, 5, 3], [12, 1, 4, 5, 3]]], + 'BCdlOG_base': [ [(-1, 0, 0), - ( 0,-1, 0), - ( 0, 0,-1), - ( 0, 0, 1), - ( 0, 1, 2), - ( 0, 1, 3), - ( 1, 0, 4)], - [[0,4,2],[0,4,5],[0,5,3],[0,1,3],[0,1,2], - [6,4,2],[6,4,5],[6,5,3],[6,1,3],[6,1,2]] ], - 'P2_112':[ - [(1,0), (0, 1), (-1, -2)], - [[0,1],[1,2],[2,0]] ], - 'P2_123':[ - [(1,0), (0, 1), (-2, -3)], - [[0,1],[1,2],[2,0]] ], - 'P4_11169':[ + (0, -1, 0), + (0, 0, -1), + (0, 0, 1), + (0, 1, 2), + (0, 1, 3), + (1, 0, 4)], + [[0, 4, 2], [0, 4, 5], [0, 5, 3], [0, 1, 3], [0, 1, 2], + [6, 4, 2], [6, 4, 5], [6, 5, 3], [6, 1, 3], [6, 1, 2]]], + 'P2_112': [ + [(1, 0), (0, 1), (-1, -2)], + [[0, 1], [1, 2], [2, 0]]], + 'P2_123': [ + [(1, 0), (0, 1), (-2, -3)], + [[0, 1], [1, 2], [2, 0]]], + 'P4_11169': [ [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1), (-9, -6, -1, -1)], - [[0,1,2,3],[0,1,2,4],[0,1,3,4],[0,2,3,4],[1,2,3,4]] ], - 'P4_11169_resolved':[ + [[0, 1, 2, 3], [0, 1, 2, 4], [0, 1, 3, 4], [0, 2, 3, 4], [1, 2, 3, 4]]], + 'P4_11169_resolved': [ [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1), (-9, -6, -1, -1), (-3, -2, 0, 0)], [[0, 1, 2, 3], [0, 1, 3, 4], [0, 1, 2, 4], [1, 3, 4, 5], [0, 3, 4, 5], - [1, 2, 4, 5], [0, 2, 4, 5], [1, 2, 3, 5], [0, 2, 3, 5]] ], - 'P4_11133':[ + [1, 2, 4, 5], [0, 2, 4, 5], [1, 2, 3, 5], [0, 2, 3, 5]]], + 'P4_11133': [ [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1), (-3, -3, -1, -1)], - [[0,1,2,3],[0,1,2,4],[0,1,3,4],[0,2,3,4],[1,2,3,4]] ], - 'P4_11133_resolved':[ + [[0, 1, 2, 3], [0, 1, 2, 4], [0, 1, 3, 4], [0, 2, 3, 4], [1, 2, 3, 4]]], + 'P4_11133_resolved': [ [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1), (-3, -3, -1, -1), (-1, -1, 0, 0)], [[0, 1, 2, 3], [0, 1, 3, 4], [0, 1, 2, 4], [1, 3, 4, 5], [0, 3, 4, 5], - [1, 2, 4, 5], [0, 2, 4, 5], [1, 2, 3, 5], [0, 2, 3, 5]] ] + [1, 2, 4, 5], [0, 2, 4, 5], [1, 2, 3, 5], [0, 2, 3, 5]]] } @@ -264,7 +264,7 @@ def _make_CPRFanoToricVariety(self, name, coordinate_names, base_ring): polytope = LatticePolytope(rays, lattice=ToricLattice(len(rays[0]))) points = [tuple(_) for _ in polytope.points()] ray2point = [points.index(r) for r in rays] - charts = [ [ray2point[i] for i in c] for c in cones ] + charts = [[ray2point[i] for i in c] for c in cones] self.__dict__[dict_key] = \ CPRFanoToricVariety(Delta_polar=polytope, coordinate_points=ray2point, @@ -868,7 +868,7 @@ def Cube_nonpolyhedral(self, names='z+', base_ring=QQ): """ return self._make_ToricVariety('Cube_nonpolyhedral', names, base_ring) - def Cube_deformation(self,k, names=None, base_ring=QQ): + def Cube_deformation(self, k, names=None, base_ring=QQ): r""" Construct, for each `k\in\ZZ_{\geq 0}`, a toric variety with `\ZZ_k`-torsion in the Chow group. @@ -1268,7 +1268,7 @@ def WP(self, *q, **kw): rays = rays + [v] w_c = w[:i] + w[i + 1:] cones = cones + [tuple(w_c)] - fan = Fan(cones,rays) + fan = Fan(cones, rays) return ToricVariety(fan, coordinate_names=names, base_ring=base_ring) def torus(self, n, names='z+', base_ring=QQ): diff --git a/src/sage/schemes/toric/sheaf/klyachko.py b/src/sage/schemes/toric/sheaf/klyachko.py index 94f465860b7..a2e80dd7fa6 100755 --- a/src/sage/schemes/toric/sheaf/klyachko.py +++ b/src/sage/schemes/toric/sheaf/klyachko.py @@ -605,7 +605,7 @@ def cohomology_complex(self, m): C = fan.complex() CV = [] F = self.base_ring() - for dim in range(1,fan.dim()+1): + for dim in range(1, fan.dim()+1): codim = fan.dim() - dim d_C = C.differential(codim) d_V = [] @@ -616,7 +616,7 @@ def cohomology_complex(self, m): sigma = fan(dim-1)[i] if sigma.is_face_of(tau): pr = self.E_quotient_projection(sigma, tau, m) - d = d_C[i,j] * pr.matrix().transpose() + d = d_C[i, j] * pr.matrix().transpose() else: E_sigma = self.E_quotient(sigma, m) E_tau = self.E_quotient(tau, m) @@ -695,7 +695,7 @@ def cohomology(self, degree=None, weight=None, dim=False): except KeyError: HH[d] = FreeModule(self.base_ring(), 0) if dim: - HH = vector(ZZ, [HH[i].rank() for i in range(space_dim+1) ]) + HH = vector(ZZ, [HH[i].rank() for i in range(space_dim+1)]) return HH def __richcmp__(self, other, op): diff --git a/src/sage/schemes/toric/toric_subscheme.py b/src/sage/schemes/toric/toric_subscheme.py index 33806674d28..908ef979984 100755 --- a/src/sage/schemes/toric/toric_subscheme.py +++ b/src/sage/schemes/toric/toric_subscheme.py @@ -328,14 +328,14 @@ def pullback_polynomial(p): result = R.zero() for coefficient, monomial in p: exponent = monomial.exponents()[0] - exponent = [ exponent[i] for i in cone.ambient_ray_indices() ] - exponent = vector(ZZ,exponent) + exponent = [exponent[i] for i in cone.ambient_ray_indices()] + exponent = vector(ZZ, exponent) m = n_rho_matrix.solve_right(exponent) assert all(x in ZZ for x in m), \ - 'The polynomial '+str(p)+' does not define a ZZ-divisor!' + f'The polynomial {p} does not define a ZZ-divisor!' m_coeffs = dualcone.Hilbert_coefficients(m) result += coefficient * prod(R.gen(i)**m_coeffs[i] - for i in range(0,R.ngens())) + for i in range(R.ngens())) return result # construct the affine algebraic scheme to use as patch @@ -353,7 +353,7 @@ def pullback_polynomial(p): if cone.is_smooth(): x = ambient.coordinate_ring().gens() phi = [] - for i in range(0,fan.nrays()): + for i in range(fan.nrays()): if i in cone.ambient_ray_indices(): phi.append(pullback_polynomial(x[i])) else: @@ -371,11 +371,10 @@ def pullback_polynomial(p): # it remains to find the preimage of point # map m to the monomial x^{D_m}, see reference. F = ambient.coordinate_ring().fraction_field() - image = [] - for m in dualcone.Hilbert_basis(): - x_Dm = prod([ F.gen(i)**(m*n) for i,n in enumerate(fan.rays()) ]) - image.append(x_Dm) - patch._embedding_center = tuple( f(list(point)) for f in image ) + image = [prod([F.gen(i)**(m * n) + for i, n in enumerate(fan.rays())]) + for m in dualcone.Hilbert_basis()] + patch._embedding_center = tuple(f(list(point)) for f in image) return patch def _best_affine_patch(self, point): @@ -487,7 +486,7 @@ def neighborhood(self, point): phi_reduced = [S(t) for t in phi] patch._embedding_center = patch(point_preimage) - patch._embedding_morphism = patch.hom(phi_reduced,self) + patch._embedding_morphism = patch.hom(phi_reduced, self) return patch def dimension(self): @@ -513,7 +512,7 @@ def dimension(self): if '_dimension' in self.__dict__: return self._dimension npatches = self.ambient_space().fan().ngenerating_cones() - dims = [ self.affine_patch(i).dimension() for i in range(0,npatches) ] + dims = [self.affine_patch(i).dimension() for i in range(npatches)] self._dimension = max(dims) return self._dimension @@ -582,7 +581,8 @@ def is_smooth(self, point=None): if '_smooth' in self.__dict__: return self._smooth npatches = self.ambient_space().fan().ngenerating_cones() - self._smooth = all(self.affine_patch(i).is_smooth() for i in range(0,npatches)) + self._smooth = all(self.affine_patch(i).is_smooth() + for i in range(npatches)) return self._smooth def is_nondegenerate(self): @@ -692,7 +692,7 @@ def restrict(cone): enumerate(SR.subs(divide).gens())]) return ideal, Jac_patch + SR_patch - for dim in range(0, fan.dim() + 1): + for dim in range(fan.dim() + 1): for cone in fan(dim): ideal1, ideal2 = restrict(cone) if ideal1.is_zero() or ideal2.dimension() != -1: diff --git a/src/sage/topology/simplicial_set_examples.py b/src/sage/topology/simplicial_set_examples.py index 498284e9816..2df0d9a1bf0 100644 --- a/src/sage/topology/simplicial_set_examples.py +++ b/src/sage/topology/simplicial_set_examples.py @@ -30,7 +30,7 @@ # **************************************************************************** import re -import os +from pathlib import Path from sage.env import SAGE_ENV from sage.misc.cachefunc import cached_method, cached_function @@ -48,6 +48,8 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.categories.simplicial_sets', 'SimplicialSets') +kenzo_path = Path(SAGE_ENV['SAGE_EXTCODE']) / 'kenzo' + # ###################################################################### # The nerve of a finite monoid, used in sage.categories.finite_monoid. @@ -96,7 +98,7 @@ def __init__(self, monoid): # of monoid elements). Omit the base point. self._simplex_data = () - def __eq__(self, other): + def __eq__(self, other) -> bool: """ Return ``True`` if ``self`` and ``other`` are equal. @@ -119,7 +121,7 @@ def __eq__(self, other): and self._monoid == other._monoid and self.base_point() == other.base_point()) - def __ne__(self, other): + def __ne__(self, other) -> bool: """ Return the negation of `__eq__`. @@ -214,13 +216,13 @@ def n_skeleton(self, n): face_dict[(g,)] = x start = 1 - for d in range(start+1, n+1): + for d in range(start + 1, n + 1): for g in monoid: if g == one: continue new_faces = {} for t in face_dict.keys(): - if len(t) != d-1: + if len(t) != d - 1: continue # chain: chain of group elements to multiply, # as a tuple. @@ -235,8 +237,8 @@ def n_skeleton(self, n): # Compute faces of x. faces = [face_dict[chain[1:]]] - for i in range(d-1): - product = chain[i] * chain[i+1] + for i in range(d - 1): + product = chain[i] * chain[i + 1] if product == one: # Degenerate. if d == 2: @@ -294,11 +296,11 @@ def Sphere(n): w_0 = AbstractSimplex(0, name='w_0') return SimplicialSet_finite({v_0: None, w_0: None}, base_point=v_0, name='S^0') - degens = range(n-2, -1, -1) + degens = range(n - 2, -1, -1) degen_v = v_0.apply_degeneracies(*degens) sigma = AbstractSimplex(n, name='sigma_{}'.format(n), latex_name='\\sigma_{}'.format(n)) - return SimplicialSet_finite({sigma: [degen_v] * (n+1)}, base_point=v_0, + return SimplicialSet_finite({sigma: [degen_v] * (n + 1)}, base_point=v_0, name='S^{}'.format(n), latex_name='S^{{{}}}'.format(n)) @@ -616,22 +618,20 @@ def ComplexProjectiveSpace(n): latex_name='CP^{2}') return K if n == 3: - file = os.path.join(SAGE_ENV['SAGE_EXTCODE'], 'kenzo', 'CP3.txt') + file = kenzo_path / 'CP3.txt' data = simplicial_data_from_kenzo_output(file) - v = [_ for _ in data.keys() if _.dimension() == 0][0] - K = SimplicialSet_finite(data, base_point=v, name='CP^3', - latex_name='CP^{3}') - return K + v = [sigma for sigma in data if sigma.dimension() == 0][0] + return SimplicialSet_finite(data, base_point=v, name='CP^3', + latex_name='CP^{3}') if n == 4: - file = os.path.join(SAGE_ENV['SAGE_EXTCODE'], 'kenzo', 'CP4.txt') + file = kenzo_path / 'CP4.txt' data = simplicial_data_from_kenzo_output(file) - v = [_ for _ in data.keys() if _.dimension() == 0][0] - K = SimplicialSet_finite(data, base_point=v, name='CP^4', - latex_name='CP^{4}') - return K + v = [sigma for sigma in data if sigma.dimension() == 0][0] + return SimplicialSet_finite(data, base_point=v, name='CP^4', + latex_name='CP^{4}') -def simplicial_data_from_kenzo_output(filename): +def simplicial_data_from_kenzo_output(filename) -> dict: """ Return data to construct a simplicial set, given Kenzo output. @@ -649,7 +649,8 @@ def simplicial_data_from_kenzo_output(filename): sage: from sage.topology.simplicial_set_examples import simplicial_data_from_kenzo_output sage: from sage.topology.simplicial_set import SimplicialSet - sage: sphere = os.path.join(SAGE_ENV['SAGE_EXTCODE'], 'kenzo', 'S4.txt') + sage: from pathlib import Path + sage: sphere = Path(SAGE_ENV['SAGE_EXTCODE']) / 'kenzo' /'S4.txt' sage: S4 = SimplicialSet(simplicial_data_from_kenzo_output(sphere)) # needs pyparsing sage: S4.homology(reduced=False) # needs pyparsing {0: Z, 1: 0, 2: 0, 3: 0, 4: Z} @@ -667,7 +668,7 @@ def simplicial_data_from_kenzo_output(filename): dim_idx = data.find('Dimension = {}:'.format(dim), start) while dim_idx != -1: start = dim_idx + len('Dimension = {}:'.format(dim)) - new_dim_idx = data.find('Dimension = {}:'.format(dim+1), start) + new_dim_idx = data.find('Dimension = {}:'.format(dim + 1), start) if new_dim_idx == -1: end = len(data) else: diff --git a/src/sage/version.py b/src/sage/version.py index bf2f786a4bf..a8d3c7fd130 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.5.beta3' -date = '2024-09-03' -banner = 'SageMath version 10.5.beta3, Release Date: 2024-09-03' +version = '10.5.beta4' +date = '2024-09-15' +banner = 'SageMath version 10.5.beta4, Release Date: 2024-09-15' diff --git a/src/sage_docbuild/conf.py b/src/sage_docbuild/conf.py index 0f0333338e3..18ce9f7663e 100644 --- a/src/sage_docbuild/conf.py +++ b/src/sage_docbuild/conf.py @@ -187,9 +187,6 @@ def sphinx_plot(graphics, **kwds): # Add any paths that contain templates here, relative to this directory. templates_path = [os.path.join(SAGE_DOC_SRC, 'common', 'templates'), 'templates'] -# The suffix of source filenames. -source_suffix = '.rst' - # The master toctree document. master_doc = 'index' @@ -495,6 +492,7 @@ def linkcode_resolve(domain, info): 'custom-furo.css', 'custom-jupyter-sphinx.css', 'custom-codemirror-monokai.css', + 'custom-tabs.css', ] html_js_files = [ @@ -956,12 +954,13 @@ class SagecodeTransform(SphinxTransform): def apply(self): if self.app.builder.tags.has('html') or self.app.builder.tags.has('inventory'): - for node in self.document.traverse(nodes.literal_block): + for node in self.document.findall(nodes.literal_block): if node.get('language') is None and node.astext().startswith('sage:'): from docutils.nodes import container as Container, label as Label, literal_block as LiteralBlock, Text from sphinx_inline_tabs._impl import TabContainer parent = node.parent index = parent.index(node) + prev_node = node.previous_sibling() if isinstance(node.previous_sibling(), TabContainer): # Make sure not to merge inline tabs for adjacent literal blocks parent.insert(index, Text('')) @@ -976,6 +975,10 @@ def apply(self): content += node container += content parent.insert(index, container) + index += 1 + if isinstance(prev_node, nodes.paragraph): + prev_node['classes'].append('with-sage-tab') + if SAGE_PREPARSED_DOC == 'yes': # Tab for preparsed version from sage.repl.preparse import preparse @@ -1006,7 +1009,10 @@ def apply(self): preparsed_node = LiteralBlock(preparsed, preparsed, language='ipycon') content += preparsed_node container += content - parent.insert(index + 1, container) + parent.insert(index, container) + index += 1 + if isinstance(prev_node, nodes.paragraph): + prev_node['classes'].append('with-python-tab') if SAGE_LIVE_DOC == 'yes': # Tab for Jupyter-sphinx cell from jupyter_sphinx.ast import JupyterCellNode, CellInputNode @@ -1038,7 +1044,10 @@ def apply(self): content = Container("", is_div=True, classes=["tab-content"]) content += cell_node container += content - parent.insert(index + 1, container) + parent.insert(index, container) + index += 1 + if isinstance(prev_node, nodes.paragraph): + prev_node['classes'].append('with-sage-live-tab') class Ignore(SphinxDirective): diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 220067f125f..87e4e69d7bd 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -33,12 +33,15 @@ - Kwankyu Lee (2024-02-14): rebased on Sphinx 7.2.6 - François Bissey (2024-08-24): rebased on Sphinx 8.0.2 + +- François Bissey (2024-09-10): Tweaks to support python 3.9 (and older sphinx) as well """ from __future__ import annotations import functools import operator +import sys import re from inspect import Parameter, Signature from typing import TYPE_CHECKING, Any, ClassVar, NewType, TypeVar @@ -670,7 +673,7 @@ def add_content(self, more_content: StringList | None) -> None: # add additional content (e.g. from document), if present if more_content: - for line, src in zip(more_content.data, more_content.items, strict=True): + for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) def get_object_members(self, want_all: bool) -> tuple[bool, list[ObjectMember]]: @@ -1041,7 +1044,7 @@ def add_content(self, more_content: StringList | None) -> None: super().add_content(None) self.indent = old_indent if more_content: - for line, src in zip(more_content.data, more_content.items, strict=True): + for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) @classmethod @@ -1576,8 +1579,14 @@ def __init__(self, *args: Any) -> None: def can_document_member( cls: type[Documenter], member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: - return isinstance(member, type) or ( - isattr and isinstance(member, NewType | TypeVar)) + # support both sphinx 8 and py3.9/older sphinx + try: + result_bool = isinstance(member, type) or ( + isattr and isinstance(member, NewType | TypeVar)) + except: + result_bool = isinstance(member, type) or ( + isattr and (inspect.isNewType(member) or isinstance(member, TypeVar))) + return result_bool def import_object(self, raiseerror: bool = False) -> bool: ret = super().import_object(raiseerror) @@ -1650,7 +1659,12 @@ def import_object(self, raiseerror: bool = False) -> bool: # ------------------------------------------------------------------- else: self.doc_as_attr = True - if isinstance(self.object, NewType | TypeVar): + # support both sphinx 8 and py3.9/older sphinx + try: + test_bool = isinstance(self.object, NewType | TypeVar) + except: + test_bool = inspect.isNewType(self.object) or isinstance(self.object, TypeVar) + if test_bool: modname = getattr(self.object, '__module__', self.modname) if modname != self.modname and self.modname.startswith(modname): bases = self.modname[len(modname):].strip('.').split('.') @@ -1659,7 +1673,12 @@ def import_object(self, raiseerror: bool = False) -> bool: return ret def _get_signature(self) -> tuple[Any | None, str | None, Signature | None]: - if isinstance(self.object, NewType | TypeVar): + # support both sphinx 8 and py3.9/older sphinx + try: + test_bool = isinstance(self.object, NewType | TypeVar) + except: + test_bool = inspect.isNewType(self.object) or isinstance(self.object, TypeVar) + if test_bool: # Suppress signature return None, None, None @@ -1844,14 +1863,24 @@ def add_directive_header(self, sig: str) -> None: self.directivetype = 'attribute' super().add_directive_header(sig) - if isinstance(self.object, NewType | TypeVar): + # support both sphinx 8 and py3.9/older sphinx + try: + test_bool = isinstance(self.object, NewType | TypeVar) + except: + test_bool = inspect.isNewType(self.object) or isinstance(self.object, TypeVar) + if test_bool: return if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: self.add_line(' :final:', sourcename) canonical_fullname = self.get_canonical_fullname() - if (not self.doc_as_attr and not isinstance(self.object, NewType) + # support both sphinx 8 and py3.9/older sphinx + try: + newtype_test = isinstance(self.object, NewType) + except: + newtype_test = inspect.isNewType(self.object) + if (not self.doc_as_attr and not newtype_test and canonical_fullname and self.fullname != canonical_fullname): self.add_line(' :canonical: %s' % canonical_fullname, sourcename) @@ -1903,6 +1932,28 @@ def get_doc(self) -> list[list[str]] | None: if isinstance(self.object, TypeVar): if self.object.__doc__ == TypeVar.__doc__: return [] + # ------------------------------------------------------------------ + # This section is kept for compatibility with python 3.9 + # see https://github.com/sagemath/sage/pull/38549#issuecomment-2327790930 + if sys.version_info[:2] < (3, 10): + if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): + parts = self.modname.strip('.').split('.') + orig_objpath = self.objpath + for i in range(len(parts)): + new_modname = '.'.join(parts[:len(parts) - i]) + new_objpath = parts[len(parts) - i:] + orig_objpath + try: + analyzer = ModuleAnalyzer.for_module(new_modname) + analyzer.analyze() + key = ('', new_objpath[-1]) + comment = list(analyzer.attr_docs.get(key, [])) + if comment: + self.objpath = new_objpath + self.modname = new_modname + return [comment] + except PycodeError: + pass + # ------------------------------------------------------------------ if self.doc_as_attr: # Don't show the docstring of the class when it is an alias. if self.get_variable_comment(): @@ -1966,7 +2017,12 @@ def get_variable_comment(self) -> list[str] | None: return None def add_content(self, more_content: StringList | None) -> None: - if isinstance(self.object, NewType): + # support both sphinx 8 and py3.9/older sphinx + try: + newtype_test = isinstance(self.object, NewType) + except: + newtype_test = inspect.isNewType(self.object) + if newtype_test: if self.config.autodoc_typehints_format == "short": supertype = restify(self.object.__supertype__, "smart") else: diff --git a/src/sage_docbuild/sphinxbuild.py b/src/sage_docbuild/sphinxbuild.py index 5621fe9e456..62b2d3cb112 100644 --- a/src/sage_docbuild/sphinxbuild.py +++ b/src/sage_docbuild/sphinxbuild.py @@ -307,6 +307,11 @@ def runsphinx(): saved_stdout = sys.stdout saved_stderr = sys.stderr + if not sys.warnoptions: + import warnings + original_filters = warnings.filters[:] + warnings.filterwarnings("ignore", category=DeprecationWarning, module='sphinx.util.inspect') + try: sys.stdout = SageSphinxLogger(sys.stdout, os.path.basename(output_dir)) sys.stderr = SageSphinxLogger(sys.stderr, os.path.basename(output_dir)) @@ -323,3 +328,6 @@ def runsphinx(): sys.stderr = saved_stderr sys.stdout.flush() sys.stderr.flush() + + if not sys.warnoptions: + warnings.filters = original_filters[:] diff --git a/src/setup.cfg.m4 b/src/setup.cfg.m4 index 76aa08f8833..969793209c8 100644 --- a/src/setup.cfg.m4 +++ b/src/setup.cfg.m4 @@ -33,7 +33,7 @@ dnl From Makefile.in: SAGERUNTIME SPKG_INSTALL_REQUIRES_ipython SPKG_INSTALL_REQUIRES_pexpect dnl From Makefile.in: DOC_DEPENDENCIES - SPKG_INSTALL_REQUIRES_sphinx + sphinx >=5.2, <9 SPKG_INSTALL_REQUIRES_networkx SPKG_INSTALL_REQUIRES_scipy SPKG_INSTALL_REQUIRES_sympy diff --git a/src/tox.ini b/src/tox.ini index 404b040b452..970b2330aec 100644 --- a/src/tox.ini +++ b/src/tox.ini @@ -181,7 +181,7 @@ description = # See https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes deps = pycodestyle commands = pycodestyle --select E111,E21,E221,E222,E225,E227,E228,E25,E271,E303,E305,E306,E401,E502,E701,E702,E703,E71,E72,W291,W293,W391,W605 {posargs:{toxinidir}/sage/} - pycodestyle --select E111,E271,E301,E302,E305,E306,E401,E502,E703,E712,E713,E714,E72,W29,W391,W605, --filename *.pyx {posargs:{toxinidir}/sage/} + pycodestyle --select E111,E271,E301,E302,E303,E305,E306,E401,E502,E703,E712,E713,E714,E72,W29,W391,W605, --filename *.pyx {posargs:{toxinidir}/sage/} [pycodestyle] max-line-length = 160