diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d17c9e85456..535c2948c39 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -589,6 +589,6 @@ jobs: - name: Upload coverage to codecov if: (success() || failure()) && steps.container.outcome == 'success' - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: directory: .coverage/coverage-report diff --git a/.github/workflows/ci-meson.yml b/.github/workflows/ci-meson.yml index ab874251d84..6681bf74225 100644 --- a/.github/workflows/ci-meson.yml +++ b/.github/workflows/ci-meson.yml @@ -58,7 +58,7 @@ jobs: key: ${{ runner.os }}-meson-${{ matrix.python }} - name: Setup Conda environment - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 with: python-version: ${{ matrix.python }} miniforge-version: latest diff --git a/CITATION.cff b/CITATION.cff index b3774971e4a..7f17ed5c0d8 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.6.beta0 +version: 10.6.beta1 doi: 10.5281/zenodo.8042260 -date-released: 2024-12-08 +date-released: 2024-12-15 repository-code: "https://github.com/sagemath/sage" url: "https://www.sagemath.org/" diff --git a/VERSION.txt b/VERSION.txt index 75a4802e431..6af82e4b429 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 10.6.beta0, Release Date: 2024-12-08 +SageMath version 10.6.beta1, Release Date: 2024-12-15 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index af25b3b9917..f2f6667ba09 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,3 +1,3 @@ tarball=configure-VERSION.tar.gz -sha1=ca942ace2e5104f3ae9e57946c8228b0bdb580c5 -sha256=99c0a76943170a85d2eb868d72dd673c6b0df529d99b808458bdb3b285dec8fe +sha1=ebc4bd50c332f06ad5b2a4ce6217ec65790655ab +sha256=a2fa7623b406a7937ebfbe3cc6d9e17bcf0c219dec2646320b7266326d789b56 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 036f9ad2756..2ae68fc2cca 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -d129e08e85a0c6530fa140dfc04c86ac0b74e05e +a72ae6d615ddfd3e49b36c200aaf14c24a265916 diff --git a/build/pkgs/execnet/distros/arch.txt b/build/pkgs/execnet/distros/arch.txt new file mode 100644 index 00000000000..3462eb00113 --- /dev/null +++ b/build/pkgs/execnet/distros/arch.txt @@ -0,0 +1 @@ +python-execnet diff --git a/build/pkgs/execnet/distros/debian.txt b/build/pkgs/execnet/distros/debian.txt new file mode 100644 index 00000000000..242410655f3 --- /dev/null +++ b/build/pkgs/execnet/distros/debian.txt @@ -0,0 +1 @@ +python3-execnet diff --git a/build/pkgs/execnet/distros/fedora.txt b/build/pkgs/execnet/distros/fedora.txt new file mode 100644 index 00000000000..242410655f3 --- /dev/null +++ b/build/pkgs/execnet/distros/fedora.txt @@ -0,0 +1 @@ +python3-execnet diff --git a/build/pkgs/execnet/distros/gentoo.txt b/build/pkgs/execnet/distros/gentoo.txt new file mode 100644 index 00000000000..ce0a8471ea6 --- /dev/null +++ b/build/pkgs/execnet/distros/gentoo.txt @@ -0,0 +1 @@ +dev-python/execnet diff --git a/build/pkgs/execnet/distros/void.txt b/build/pkgs/execnet/distros/void.txt new file mode 100644 index 00000000000..242410655f3 --- /dev/null +++ b/build/pkgs/execnet/distros/void.txt @@ -0,0 +1 @@ +python3-execnet diff --git a/build/pkgs/execnet/spkg-configure.m4 b/build/pkgs/execnet/spkg-configure.m4 new file mode 100644 index 00000000000..82f8824ba87 --- /dev/null +++ b/build/pkgs/execnet/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([execnet], [SAGE_PYTHON_PACKAGE_CHECK([execnet])]) diff --git a/build/pkgs/iniconfig/distros/arch.txt b/build/pkgs/iniconfig/distros/arch.txt new file mode 100644 index 00000000000..3b2e261329f --- /dev/null +++ b/build/pkgs/iniconfig/distros/arch.txt @@ -0,0 +1 @@ +python-iniconfig diff --git a/build/pkgs/iniconfig/distros/debian.txt b/build/pkgs/iniconfig/distros/debian.txt new file mode 100644 index 00000000000..db703500b14 --- /dev/null +++ b/build/pkgs/iniconfig/distros/debian.txt @@ -0,0 +1 @@ +python3-iniconfig diff --git a/build/pkgs/iniconfig/distros/fedora.txt b/build/pkgs/iniconfig/distros/fedora.txt new file mode 100644 index 00000000000..db703500b14 --- /dev/null +++ b/build/pkgs/iniconfig/distros/fedora.txt @@ -0,0 +1 @@ +python3-iniconfig diff --git a/build/pkgs/iniconfig/distros/gentoo.txt b/build/pkgs/iniconfig/distros/gentoo.txt new file mode 100644 index 00000000000..962de2510d3 --- /dev/null +++ b/build/pkgs/iniconfig/distros/gentoo.txt @@ -0,0 +1 @@ +dev-python/iniconfig diff --git a/build/pkgs/iniconfig/distros/void.txt b/build/pkgs/iniconfig/distros/void.txt new file mode 100644 index 00000000000..db703500b14 --- /dev/null +++ b/build/pkgs/iniconfig/distros/void.txt @@ -0,0 +1 @@ +python3-iniconfig diff --git a/build/pkgs/iniconfig/spkg-configure.m4 b/build/pkgs/iniconfig/spkg-configure.m4 new file mode 100644 index 00000000000..8c94fdf4986 --- /dev/null +++ b/build/pkgs/iniconfig/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([iniconfig], [SAGE_PYTHON_PACKAGE_CHECK([iniconfig])]) diff --git a/build/pkgs/planarity/spkg-configure.m4 b/build/pkgs/planarity/spkg-configure.m4 index 9ec8ddc64f6..355332a72e7 100644 --- a/build/pkgs/planarity/spkg-configure.m4 +++ b/build/pkgs/planarity/spkg-configure.m4 @@ -1,6 +1,6 @@ SAGE_SPKG_CONFIGURE([planarity], [ AC_LANG_PUSH([C]) - AC_CHECK_HEADER([planarity/planarity.h], [ + AC_CHECK_HEADER([planarity/graph.h], [ AC_CHECK_LIB([planarity], [gp_InitGraph], [ AC_MSG_CHECKING([for planarity version 3.0 or later]) AC_COMPILE_IFELSE( diff --git a/build/pkgs/pyproject_api/distros/arch.txt b/build/pkgs/pyproject_api/distros/arch.txt new file mode 100644 index 00000000000..7d778992044 --- /dev/null +++ b/build/pkgs/pyproject_api/distros/arch.txt @@ -0,0 +1 @@ +python-pyproject-api diff --git a/build/pkgs/pyproject_api/distros/debian.txt b/build/pkgs/pyproject_api/distros/debian.txt new file mode 100644 index 00000000000..0d2d5b563c5 --- /dev/null +++ b/build/pkgs/pyproject_api/distros/debian.txt @@ -0,0 +1 @@ +python3-pyproject-api diff --git a/build/pkgs/pyproject_api/distros/fedora.txt b/build/pkgs/pyproject_api/distros/fedora.txt new file mode 100644 index 00000000000..0d2d5b563c5 --- /dev/null +++ b/build/pkgs/pyproject_api/distros/fedora.txt @@ -0,0 +1 @@ +python3-pyproject-api diff --git a/build/pkgs/pyproject_api/distros/gentoo.txt b/build/pkgs/pyproject_api/distros/gentoo.txt new file mode 100644 index 00000000000..69d2b03e990 --- /dev/null +++ b/build/pkgs/pyproject_api/distros/gentoo.txt @@ -0,0 +1 @@ +dev-python/pyproject-api diff --git a/build/pkgs/pyproject_api/distros/void.txt b/build/pkgs/pyproject_api/distros/void.txt new file mode 100644 index 00000000000..0d2d5b563c5 --- /dev/null +++ b/build/pkgs/pyproject_api/distros/void.txt @@ -0,0 +1 @@ +python3-pyproject-api diff --git a/build/pkgs/pyproject_hooks/distros/arch.txt b/build/pkgs/pyproject_hooks/distros/arch.txt new file mode 100644 index 00000000000..71c493f0851 --- /dev/null +++ b/build/pkgs/pyproject_hooks/distros/arch.txt @@ -0,0 +1 @@ +python-pyproject-hooks diff --git a/build/pkgs/pyproject_hooks/distros/debian.txt b/build/pkgs/pyproject_hooks/distros/debian.txt new file mode 100644 index 00000000000..010de587955 --- /dev/null +++ b/build/pkgs/pyproject_hooks/distros/debian.txt @@ -0,0 +1 @@ +python3-pyproject-hooks diff --git a/build/pkgs/pyproject_hooks/distros/fedora.txt b/build/pkgs/pyproject_hooks/distros/fedora.txt new file mode 100644 index 00000000000..010de587955 --- /dev/null +++ b/build/pkgs/pyproject_hooks/distros/fedora.txt @@ -0,0 +1 @@ +python3-pyproject-hooks diff --git a/build/pkgs/pyproject_hooks/distros/gentoo.txt b/build/pkgs/pyproject_hooks/distros/gentoo.txt new file mode 100644 index 00000000000..4034186504e --- /dev/null +++ b/build/pkgs/pyproject_hooks/distros/gentoo.txt @@ -0,0 +1 @@ +dev-python/pyproject-hooks diff --git a/build/pkgs/pyproject_hooks/distros/void.txt b/build/pkgs/pyproject_hooks/distros/void.txt new file mode 100644 index 00000000000..010de587955 --- /dev/null +++ b/build/pkgs/pyproject_hooks/distros/void.txt @@ -0,0 +1 @@ +python3-pyproject-hooks diff --git a/build/pkgs/pyproject_hooks/spkg-configure.m4 b/build/pkgs/pyproject_hooks/spkg-configure.m4 new file mode 100644 index 00000000000..acec0fc4902 --- /dev/null +++ b/build/pkgs/pyproject_hooks/spkg-configure.m4 @@ -0,0 +1 @@ +SAGE_SPKG_CONFIGURE([pyproject_hooks], [SAGE_PYTHON_PACKAGE_CHECK([pyproject_hooks])]) diff --git a/build/pkgs/pyproject_metadata/distros/arch.txt b/build/pkgs/pyproject_metadata/distros/arch.txt new file mode 100644 index 00000000000..31f99621fe0 --- /dev/null +++ b/build/pkgs/pyproject_metadata/distros/arch.txt @@ -0,0 +1 @@ +python-pyproject-metadata diff --git a/build/pkgs/pyproject_metadata/distros/debian.txt b/build/pkgs/pyproject_metadata/distros/debian.txt new file mode 100644 index 00000000000..5180a07a0cc --- /dev/null +++ b/build/pkgs/pyproject_metadata/distros/debian.txt @@ -0,0 +1 @@ +python3-pyproject-metadata diff --git a/build/pkgs/pyproject_metadata/distros/void.txt b/build/pkgs/pyproject_metadata/distros/void.txt new file mode 100644 index 00000000000..5180a07a0cc --- /dev/null +++ b/build/pkgs/pyproject_metadata/distros/void.txt @@ -0,0 +1 @@ +python3-pyproject-metadata diff --git a/build/pkgs/sage_conf/version_requirements.txt b/build/pkgs/sage_conf/version_requirements.txt index 1528604151d..5fe4d966040 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.6b0 +sage-conf ~= 10.6b1 diff --git a/build/pkgs/sage_docbuild/version_requirements.txt b/build/pkgs/sage_docbuild/version_requirements.txt index 64078134018..8c3a08af6ba 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.6b0 +sage-docbuild ~= 10.6b1 diff --git a/build/pkgs/sage_setup/version_requirements.txt b/build/pkgs/sage_setup/version_requirements.txt index e5b0998d572..20373e5b24b 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.6b0 +sage-setup ~= 10.6b1 diff --git a/build/pkgs/sage_sws2rst/version_requirements.txt b/build/pkgs/sage_sws2rst/version_requirements.txt index 1aec16d1739..75d2dea8555 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.6b0 +sage-sws2rst ~= 10.6b1 diff --git a/build/pkgs/sagelib/version_requirements.txt b/build/pkgs/sagelib/version_requirements.txt index a01ffdf92ed..b9b66b303ce 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.6b0 +sagemath-standard ~= 10.6b1 diff --git a/build/pkgs/sagemath_bliss/version_requirements.txt b/build/pkgs/sagemath_bliss/version_requirements.txt index 637c4611451..66cfe981b11 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.6b0 +sagemath-bliss ~= 10.6b1 diff --git a/build/pkgs/sagemath_categories/version_requirements.txt b/build/pkgs/sagemath_categories/version_requirements.txt index 8e37771c3f9..012ced666ae 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.6b0 +sagemath-categories ~= 10.6b1 diff --git a/build/pkgs/sagemath_coxeter3/version_requirements.txt b/build/pkgs/sagemath_coxeter3/version_requirements.txt index 06dc600cfd7..d0efaa88af2 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.6b0 +sagemath-coxeter3 ~= 10.6b1 diff --git a/build/pkgs/sagemath_environment/version_requirements.txt b/build/pkgs/sagemath_environment/version_requirements.txt index 7e0f787af06..6cae42add68 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.6b0 +sagemath-environment ~= 10.6b1 diff --git a/build/pkgs/sagemath_mcqd/version_requirements.txt b/build/pkgs/sagemath_mcqd/version_requirements.txt index 3a2722024c4..17c1fefc2b9 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.6b0 +sagemath-mcqd ~= 10.6b1 diff --git a/build/pkgs/sagemath_meataxe/version_requirements.txt b/build/pkgs/sagemath_meataxe/version_requirements.txt index 3cb0d050fa5..2dc47f93620 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.6b0 +sagemath-meataxe ~= 10.6b1 diff --git a/build/pkgs/sagemath_objects/version_requirements.txt b/build/pkgs/sagemath_objects/version_requirements.txt index e8c44e2142e..aa457cf9337 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.6b0 +sagemath-objects ~= 10.6b1 diff --git a/build/pkgs/sagemath_repl/version_requirements.txt b/build/pkgs/sagemath_repl/version_requirements.txt index 3df75b7c62a..d89f0a710d3 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.6b0 +sagemath-repl ~= 10.6b1 diff --git a/build/pkgs/sagemath_sirocco/version_requirements.txt b/build/pkgs/sagemath_sirocco/version_requirements.txt index 2c8db8e59f7..3b31a56b658 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.6b0 +sagemath-sirocco ~= 10.6b1 diff --git a/build/pkgs/sagemath_tdlib/version_requirements.txt b/build/pkgs/sagemath_tdlib/version_requirements.txt index 1732fe39010..a1de5923196 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.6b0 +sagemath-tdlib ~= 10.6b1 diff --git a/pkgs/sage-conf/VERSION.txt b/pkgs/sage-conf/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sage-conf/VERSION.txt +++ b/pkgs/sage-conf/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sage-conf_conda/VERSION.txt b/pkgs/sage-conf_conda/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sage-conf_conda/VERSION.txt +++ b/pkgs/sage-conf_conda/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sage-conf_pypi/VERSION.txt b/pkgs/sage-conf_pypi/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sage-conf_pypi/VERSION.txt +++ b/pkgs/sage-conf_pypi/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sage-docbuild/VERSION.txt b/pkgs/sage-docbuild/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sage-docbuild/VERSION.txt +++ b/pkgs/sage-docbuild/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sage-setup/VERSION.txt b/pkgs/sage-setup/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sage-setup/VERSION.txt +++ b/pkgs/sage-setup/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sage-sws2rst/VERSION.txt b/pkgs/sage-sws2rst/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sage-sws2rst/VERSION.txt +++ b/pkgs/sage-sws2rst/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-bliss/VERSION.txt b/pkgs/sagemath-bliss/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-bliss/VERSION.txt +++ b/pkgs/sagemath-bliss/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-categories/VERSION.txt b/pkgs/sagemath-categories/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-categories/VERSION.txt +++ b/pkgs/sagemath-categories/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-coxeter3/VERSION.txt b/pkgs/sagemath-coxeter3/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-coxeter3/VERSION.txt +++ b/pkgs/sagemath-coxeter3/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-environment/VERSION.txt b/pkgs/sagemath-environment/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-environment/VERSION.txt +++ b/pkgs/sagemath-environment/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-mcqd/VERSION.txt b/pkgs/sagemath-mcqd/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-mcqd/VERSION.txt +++ b/pkgs/sagemath-mcqd/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-meataxe/VERSION.txt b/pkgs/sagemath-meataxe/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-meataxe/VERSION.txt +++ b/pkgs/sagemath-meataxe/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-objects/VERSION.txt b/pkgs/sagemath-objects/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-objects/VERSION.txt +++ b/pkgs/sagemath-objects/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-repl/VERSION.txt b/pkgs/sagemath-repl/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-repl/VERSION.txt +++ b/pkgs/sagemath-repl/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-sirocco/VERSION.txt b/pkgs/sagemath-sirocco/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-sirocco/VERSION.txt +++ b/pkgs/sagemath-sirocco/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/pkgs/sagemath-tdlib/VERSION.txt b/pkgs/sagemath-tdlib/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/pkgs/sagemath-tdlib/VERSION.txt +++ b/pkgs/sagemath-tdlib/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/src/VERSION.txt b/src/VERSION.txt index bf89b51e25f..6e3a857d06e 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -10.6.beta0 +10.6.beta1 diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 8f940ae430b..6877ac8ce0a 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.6.beta0' -SAGE_RELEASE_DATE='2024-12-08' -SAGE_VERSION_BANNER='SageMath version 10.6.beta0, Release Date: 2024-12-08' +SAGE_VERSION='10.6.beta1' +SAGE_RELEASE_DATE='2024-12-15' +SAGE_VERSION_BANNER='SageMath version 10.6.beta1, Release Date: 2024-12-15' diff --git a/src/doc/en/developer/coding_in_cython.rst b/src/doc/en/developer/coding_in_cython.rst index d0c7b0d521f..a912a75c544 100644 --- a/src/doc/en/developer/coding_in_cython.rst +++ b/src/doc/en/developer/coding_in_cython.rst @@ -32,13 +32,13 @@ up-to-date information or check out the to get started immediately. -Writing cython code in Sage +Writing Cython code in Sage =========================== There are several ways to create and build Cython code in Sage. -#. In the Sage Notebook, begin any cell with ``%cython``. When you - evaluate that cell, +#. In the Sage notebook or the Sage command line, begin any cell with + a line containing ``%%cython``. When you evaluate that cell, #. It is saved to a file. @@ -53,22 +53,21 @@ There are several ways to create and build Cython code in Sage. program that was compiled to create the ``.so`` file. #. A ``cpdef`` or ``def`` function, say ``testfunction``, defined in - a ``%cython`` cell in a worksheet can be imported and made available - in a different ``%cython`` cell within the same worksheet by + a ``%%cython`` cell in a worksheet can be imported and made available + in a different ``%%cython`` cell within the same worksheet by importing it as shown below:: - %cython + %%cython from __main__ import testfunction -#. Create an ``.spyx`` file and attach or load it from the command - line. This is similar to creating a ``%cython`` cell in the - notebook but works completely from the command line (and not from - the notebook). + Refer to :meth:`sage.repl.ipython_extension.SageMagics.cython`. + +#. Create an ``.spyx`` file and attach or load it + from the command line. #. Create a ``.pyx`` file and add it to the Sage library. Then run ``sage -b`` to rebuild Sage. - Attaching or loading .spyx files ================================ diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 7af6ae3bf7d..3ebb8fbe2f1 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -277,6 +277,10 @@ REFERENCES: .. [Ap1997] \T. Apostol, Modular functions and Dirichlet series in number theory, Springer, 1997 (2nd ed), section 3.7--3.9. +.. [AP2024] William Atherton, Dmitrii V. Pasechnik, *Decline and Fall of the + ICALP 2008 Modular Decomposition algorithm*, 2024. + :arxiv:`2404.14049`. + .. [APR2001] George E. Andrews, Peter Paule, Axel Riese, *MacMahon's partition analysis: the Omega package*, European J. Combin. 22 (2001), no. 7, 887--904. @@ -496,6 +500,9 @@ REFERENCES: .. [BeBo2009] Olivier Bernardi and Nicolas Bonichon, *Intervals in Catalan lattices and realizers of triangulations*, JCTA 116 (2009) +.. [Best2021] Alex J. Best: Tools and Techniques for Rational Points on Curves. + PhD Thesis, Boston University, 2021. + .. [BBGL2008] \A. Blondin Massé, S. Brlek, A. Garon, and S. Labbé, Combinatorial properties of f -palindromes in the Thue-Morse sequence. Pure Math. Appl., @@ -970,6 +977,10 @@ REFERENCES: Anal. Appl. 15 (1994) 804-823. :doi:`10.1137/S0895479892230031` +.. [BL1995] W. Bosma, H.W. Lenstra: Complete Systems of Two Addition Laws for + Elliptic Curves. Journal of Number Theory, volume 53, issue 2, + pages 229-240. 1995. + .. [BHMPW20a] Tom Braden, June Huh, Jacob P. Matherne, Nicholas Proudfoot, and Botong Wang, *A semi-small decomposition of the Chow ring of a matroid*, :arxiv:`2002.03341` (2020). diff --git a/src/doc/en/reference/repl/index.rst b/src/doc/en/reference/repl/index.rst index d12d8866dda..cf17bba8fb3 100644 --- a/src/doc/en/reference/repl/index.rst +++ b/src/doc/en/reference/repl/index.rst @@ -1,3 +1,5 @@ +.. _section-command-line: + The Sage Command Line ===================== @@ -5,8 +7,8 @@ The Sage Read-Eval-Print-Loop (REPL) is based on IPython. In this document, you'll find how the IPython integration works. You should also be familiar with the documentation for IPython. -For more details about using the Sage command line, see the Sage -tutorial. +For more details about using the Sage command line, see `the Sage +tutorial <../../tutorial/index.html>`_. Running Sage ------------ diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 5eeddc3a7f6..2339cc57f26 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', 'krull_dimension', - 'localization', 'ngens', 'one', 'order', diff --git a/src/doc/en/tutorial/index.rst b/src/doc/en/tutorial/index.rst index 9098e68c78e..7b1591cf621 100644 --- a/src/doc/en/tutorial/index.rst +++ b/src/doc/en/tutorial/index.rst @@ -2,8 +2,6 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -.. _tutorial: - ======================== Welcome to Sage Tutorial ======================== diff --git a/src/sage/algebras/finite_dimensional_algebras/meson.build b/src/sage/algebras/finite_dimensional_algebras/meson.build index 075f0b8cebd..85f04b2afcb 100644 --- a/src/sage/algebras/finite_dimensional_algebras/meson.build +++ b/src/sage/algebras/finite_dimensional_algebras/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'finite_dimensional_algebra.py', 'finite_dimensional_algebra_element.pxd', diff --git a/src/sage/algebras/fusion_rings/meson.build b/src/sage/algebras/fusion_rings/meson.build index 281460a066a..221cce6146e 100644 --- a/src/sage/algebras/fusion_rings/meson.build +++ b/src/sage/algebras/fusion_rings/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'f_matrix.py', 'fast_parallel_fmats_methods.pxd', diff --git a/src/sage/algebras/letterplace/meson.build b/src/sage/algebras/letterplace/meson.build index 1ada90927a7..3e429eb420a 100644 --- a/src/sage/algebras/letterplace/meson.build +++ b/src/sage/algebras/letterplace/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'free_algebra_element_letterplace.pxd', 'free_algebra_letterplace.pxd', diff --git a/src/sage/algebras/lie_algebras/meson.build b/src/sage/algebras/lie_algebras/meson.build index f50959cb44b..754d8729fbb 100644 --- a/src/sage/algebras/lie_algebras/meson.build +++ b/src/sage/algebras/lie_algebras/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'abelian.py', 'affine_lie_algebra.py', 'all.py', diff --git a/src/sage/algebras/meson.build b/src/sage/algebras/meson.build index a7e74474c6b..d3483851743 100644 --- a/src/sage/algebras/meson.build +++ b/src/sage/algebras/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'affine_nil_temperley_lieb.py', 'algebra.py', 'all.py', diff --git a/src/sage/algebras/quatalg/meson.build b/src/sage/algebras/quatalg/meson.build index 2ec5cd31f22..25c4adfc46c 100644 --- a/src/sage/algebras/quatalg/meson.build +++ b/src/sage/algebras/quatalg/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'quaternion_algebra.py', 'quaternion_algebra_element.pxd', diff --git a/src/sage/arith/meson.build b/src/sage/arith/meson.build index 3c3656c5738..500b49edf85 100644 --- a/src/sage/arith/meson.build +++ b/src/sage/arith/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'all__sagemath_objects.py', 'constants.pxd', diff --git a/src/sage/calculus/integration.pyx b/src/sage/calculus/integration.pyx index 643442ac714..f71695926aa 100644 --- a/src/sage/calculus/integration.pyx +++ b/src/sage/calculus/integration.pyx @@ -59,7 +59,13 @@ cdef double c_f(double t, void *params) noexcept: else: value = wrapper.the_function(t) except Exception as msg: - print(msg) + try: + if str(msg).strip(): + print(msg) + else: + print(f"Unable to evaluate function at {t}") + except Exception: + pass return 0 return value @@ -144,17 +150,19 @@ def numerical_integral(func, a, b=None, For a Python function with parameters:: sage: f(x,a) = 1/(a+x^2) - sage: [numerical_integral(f, 1, 2, max_points=100, params=[n]) for n in range(10)] # random output (architecture and os dependent) - [(0.49999999999998657, 5.5511151231256336e-15), - (0.32175055439664557, 3.5721487367706477e-15), - (0.24030098317249229, 2.6678768435816325e-15), - (0.19253082576711697, 2.1375215571674764e-15), - (0.16087527719832367, 1.7860743683853337e-15), - (0.13827545676349412, 1.5351659583939151e-15), - (0.12129975935702741, 1.3466978571966261e-15), - (0.10806674191683065, 1.1997818507228991e-15), - (0.09745444625548845, 1.0819617008493815e-15), - (0.088750683050217577, 9.8533051773561173e-16)] + sage: [numerical_integral(f, 1, 2, max_points=100, params=[n])[0] # abs tol 1.0e-6 + ....: for n in range(10)] + [0.5000000000000000, + 0.3217505543966422, + 0.24030098317248832, + 0.19253082576711372, + 0.1608752771983211, + 0.138275456763492, + 0.1212997593570257, + 0.10806674191683492, + 0.09745444625553161, + 0.08875068305030848] + sage: y = var('y') sage: numerical_integral(x*y, 0, 1) Traceback (most recent call last): @@ -323,6 +331,9 @@ def numerical_integral(func, a, b=None, if ell.is_numeric() and not ell.is_zero(): raise ValueError('integral does not converge at infinity') func = fast_callable(func, vars=[v], domain=float) + # `func` is now a function of one variable, + # so it no longer needs any parameters + params = [] if not isinstance(func, compiled_integrand): wrapper = PyFunctionWrapper() diff --git a/src/sage/calculus/meson.build b/src/sage/calculus/meson.build index 541d7d86d75..3bedeb2220a 100644 --- a/src/sage/calculus/meson.build +++ b/src/sage/calculus/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'calculus.py', 'desolvers.py', diff --git a/src/sage/calculus/transforms/meson.build b/src/sage/calculus/transforms/meson.build index 05d3fb59637..11ffa9f8ec1 100644 --- a/src/sage/calculus/transforms/meson.build +++ b/src/sage/calculus/transforms/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'dft.py', 'dwt.pxd', diff --git a/src/sage/categories/examples/meson.build b/src/sage/categories/examples/meson.build index ecb63c913ba..bf5926b1d84 100644 --- a/src/sage/categories/examples/meson.build +++ b/src/sage/categories/examples/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'algebras_with_basis.py', 'all.py', 'commutative_additive_monoids.py', diff --git a/src/sage/categories/integral_domains.py b/src/sage/categories/integral_domains.py index 4dd66a9277b..c66b609a5e3 100644 --- a/src/sage/categories/integral_domains.py +++ b/src/sage/categories/integral_domains.py @@ -143,6 +143,37 @@ def is_integral_domain(self, proof=True): """ return True + def is_field(self, proof=True): + r""" + Return ``True`` if this ring is a field. + + EXAMPLES:: + + sage: ZZ['x'].is_field() + False + """ + if self.is_finite(): + return True + if proof: + raise NotImplementedError(f"unable to determine whether or not {self} is a field.") + return False + + def localization(self, additional_units, names=None, normalize=True, category=None): + """ + Return the localization of ``self`` at the given additional units. + + EXAMPLES:: + + sage: R. = GF(3)[] + sage: R.localization((x*y, x**2 + y**2)) # needs sage.rings.finite_rings + Multivariate Polynomial Ring in x, y over Finite Field of size 3 + localized at (y, x, x^2 + y^2) + sage: ~y in _ # needs sage.rings.finite_rings + True + """ + from sage.rings.localization import Localization + return Localization(self, additional_units, names=names, normalize=normalize, category=category) + def _test_fraction_field(self, **options): r""" Test that the fraction field, if it is implemented, works diff --git a/src/sage/categories/meson.build b/src/sage/categories/meson.build index 132037fe7fd..affc2034df2 100644 --- a/src/sage/categories/meson.build +++ b/src/sage/categories/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'action.pxd', 'additive_groups.py', 'additive_magmas.py', diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index adfc75c00ec..e0769336c6f 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -554,6 +554,22 @@ def is_subring(self, other): except (TypeError, AttributeError): return False + def localization(self, *args, **kwds): + """ + Return the localization of ``self``. + + This only works for integral domains. + + EXAMPLES:: + + sage: R = Zmod(6) + sage: R.localization((4)) + Traceback (most recent call last): + ... + TypeError: self must be an integral domain + """ + raise TypeError("self must be an integral domain") + def bracket(self, x, y): """ Return the Lie bracket `[x, y] = x y - y x` of `x` and `y`. diff --git a/src/sage/coding/codecan/meson.build b/src/sage/coding/codecan/meson.build index 8749207de4b..1ccaca09b2a 100644 --- a/src/sage/coding/codecan/meson.build +++ b/src/sage/coding/codecan/meson.build @@ -1,4 +1,9 @@ -py.install_sources('all.py', 'codecan.pxd', subdir: 'sage/coding/codecan') +py.install_sources( + '__init__.py', + 'all.py', + 'codecan.pxd', + subdir: 'sage/coding/codecan', +) extension_data = { 'autgroup_can_label' : files('autgroup_can_label.pyx'), diff --git a/src/sage/coding/delsarte_bounds.py b/src/sage/coding/delsarte_bounds.py index a7cc35e058d..f3dedb7a720 100644 --- a/src/sage/coding/delsarte_bounds.py +++ b/src/sage/coding/delsarte_bounds.py @@ -201,9 +201,8 @@ def _delsarte_LP_building(n, d, d_star, q, isinteger, solver, maxc=0): constraint_1: 0 <= x_1 <= 0 constraint_2: 0 <= x_2 <= 0 constraint_3: -7 x_0 - 5 x_1 - 3 x_2 - x_3 + x_4 + 3 x_5 + 5 x_6 + 7 x_7 <= 0 - constraint_4: -7 x_0 - 5 x_1 - 3 x_2 - x_3 + x_4 + 3 x_5 + 5 x_6 + 7 x_7 <= 0 ... - constraint_16: - x_0 + x_1 - x_2 + x_3 - x_4 + x_5 - x_6 + x_7 <= 0 + constraint_9: - x_0 + x_1 - x_2 + x_3 - x_4 + x_5 - x_6 + x_7 <= 0 Variables: x_0 is a continuous variable (min=0, max=+oo) ... @@ -213,21 +212,20 @@ def _delsarte_LP_building(n, d, d_star, q, isinteger, solver, maxc=0): p = MixedIntegerLinearProgram(maximization=True, solver=solver) A = p.new_variable(integer=isinteger, nonnegative=True) - p.set_objective(sum([A[r] for r in range(n + 1)])) + p.set_objective(p.sum([A[r] for r in range(n + 1)])) p.add_constraint(A[0] == 1) for i in range(1, d): p.add_constraint(A[i] == 0) for j in range(1, n + 1): - rhs = sum([krawtchouk(n, q, j, r, check=False) * A[r] + rhs = p.sum([krawtchouk(n, q, j, r, check=False) * A[r] for r in range(n + 1)]) - p.add_constraint(0 <= rhs) if j >= d_star: p.add_constraint(0 <= rhs) else: # rhs is proportional to j-th weight of the dual code p.add_constraint(0 == rhs) if maxc > 0: - p.add_constraint(sum([A[r] for r in range(n + 1)]), max=maxc) + p.add_constraint(p.sum([A[r] for r in range(n + 1)]), max=maxc) return A, p @@ -275,7 +273,7 @@ def _delsarte_cwc_LP_building(n, d, w, solver, isinteger): p = MixedIntegerLinearProgram(maximization=True, solver=solver) A = p.new_variable(integer=isinteger, nonnegative=True) - p.set_objective(sum([A[2*r] for r in range(d//2, w+1)]) + 1) + p.set_objective(p.sum([A[2*r] for r in range(d//2, w+1)]) + 1) def _q(k, i): mu_i = 1 @@ -283,7 +281,7 @@ def _q(k, i): return mu_i*eberlein(n, w, i, k)/v_i for k in range(1, w+1): - p.add_constraint(sum([A[2*i]*_q(k, i) for i in range(d//2, w+1)]), + p.add_constraint(p.sum([A[2*i]*_q(k, i) for i in range(d//2, w+1)]), min=-1) return A, p @@ -622,7 +620,7 @@ def _delsarte_Q_LP_building(q, d, solver, isinteger): p = MixedIntegerLinearProgram(maximization=True, solver=solver) A = p.new_variable(integer=isinteger, nonnegative=True) - p.set_objective(sum([A[i] for i in range(n)])) + p.set_objective(p.sum([A[i] for i in range(n)])) p.add_constraint(A[0] == 1) @@ -634,7 +632,7 @@ def _delsarte_Q_LP_building(q, d, solver, isinteger): p.add_constraint(A[i] == 0) for k in range(1, n): - p.add_constraint(sum([q[k][i] * A[i] for i in range(n)]), min=0) + p.add_constraint(p.sum([q[k][i] * A[i] for i in range(n)]), min=0) return A, p diff --git a/src/sage/coding/grs_code.py b/src/sage/coding/grs_code.py index 2fada75c4a9..a37a5b15a7b 100644 --- a/src/sage/coding/grs_code.py +++ b/src/sage/coding/grs_code.py @@ -1915,6 +1915,12 @@ def decode_to_message(self, word_and_erasure_vector): sage: y = Chan(c) sage: D.connected_encoder().unencode(c) == D.decode_to_message(y) True + sage: n_era = C.minimum_distance() - 1 + sage: Chan = channels.ErrorErasureChannel(C.ambient_space(), + ....: D.decoding_radius(n_era), n_era) + sage: y = Chan(c) + sage: D.connected_encoder().unencode(c) == D.decode_to_message(y) + True TESTS: @@ -1958,14 +1964,14 @@ def decode_to_message(self, word_and_erasure_vector): [word[i] for i in range(len(word)) if not erasure_vector[i]]) C1_length = len(punctured_word) - if C1_length == k: - return self.connected_encoder().unencode_nocheck(word) C1_evaluation_points = [self.code().evaluation_points()[i] for i in range(n) if erasure_vector[i] != 1] C1_column_multipliers = [self.code().column_multipliers()[i] for i in range(n) if erasure_vector[i] != 1] C1 = GeneralizedReedSolomonCode(C1_evaluation_points, k, C1_column_multipliers) + if C1_length == k: + return C1.unencode(punctured_word, nocheck=True) return C1.decode_to_message(punctured_word) def decoding_radius(self, number_erasures): @@ -1997,7 +2003,7 @@ def decoding_radius(self, number_erasures): ValueError: The number of erasures exceed decoding capability """ diff = self.code().minimum_distance() - 1 - number_erasures - if diff <= 0: + if diff < 0: raise ValueError("The number of erasures exceed decoding capability") else: return diff // 2 diff --git a/src/sage/coding/meson.build b/src/sage/coding/meson.build index 65b2e0d8eb1..b311c8df5d5 100644 --- a/src/sage/coding/meson.build +++ b/src/sage/coding/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'abstract_code.py', 'ag_code.py', 'all.py', diff --git a/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py b/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py index c9f06a4761f..76be4d8bf02 100644 --- a/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +++ b/src/sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py @@ -1985,7 +1985,7 @@ def __init__(self, *args): """ data = args if len(data) < 2 or not all(isinstance(comp, QuiverMutationType_Irreducible) for comp in data): - return _mutation_type_error(data) + _mutation_type_error(data) # _info is initialized self._info = {} diff --git a/src/sage/combinat/crystals/meson.build b/src/sage/combinat/crystals/meson.build index 96ff9f4e19e..5c6b864d04a 100644 --- a/src/sage/combinat/crystals/meson.build +++ b/src/sage/combinat/crystals/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'affine.py', 'affine_factorization.py', 'affinization.py', diff --git a/src/sage/combinat/designs/incidence_structures.py b/src/sage/combinat/designs/incidence_structures.py index e9964af900c..3c6170e1ea3 100644 --- a/src/sage/combinat/designs/incidence_structures.py +++ b/src/sage/combinat/designs/incidence_structures.py @@ -1592,6 +1592,13 @@ def is_t_design(self, t=None, v=None, k=None, l=None, return_parameters=False): sage: I = IncidenceStructure(2, [[0],[0,1]]) sage: I.is_t_design(return_parameters=True) (False, (0, 0, 0, 0)) + + Verify that :issue:`38454` is fixed:: + + sage: I = IncidenceStructure(points=[0,1,2,3,4,5], + ....: blocks=[[0,1], [1,2], [0,2]]) + sage: I.is_t_design(return_parameters=True) + (True, (0, 6, 2, 3)) """ from sage.arith.misc import binomial @@ -1653,7 +1660,7 @@ def is_t_design(self, t=None, v=None, k=None, l=None, return_parameters=False): for i in combinations(block, tt): s[i] = s.get(i, 0) + 1 - if len(set(s.values())) != 1: + if (len(s) != binomial(v, tt)) or (len(set(s.values())) != 1): tt -= 1 break diff --git a/src/sage/combinat/designs/meson.build b/src/sage/combinat/designs/meson.build index fd3a8896bb3..019b62be54e 100644 --- a/src/sage/combinat/designs/meson.build +++ b/src/sage/combinat/designs/meson.build @@ -1,5 +1,6 @@ py.install_sources( 'MOLS_handbook_data.py', + '__init__.py', 'all.py', 'bibd.py', 'block_design.py', diff --git a/src/sage/combinat/finite_state_machine.py b/src/sage/combinat/finite_state_machine.py index 2750ea15e9c..ee5feb4815d 100644 --- a/src/sage/combinat/finite_state_machine.py +++ b/src/sage/combinat/finite_state_machine.py @@ -14717,7 +14717,7 @@ def __init__(self, *args, **kwargs): self.TapeCache = _FSMTapeCacheDetectEpsilon_ self.visited_states = {} kwargs['check_epsilon_transitions'] = False - return super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def _push_branch_(self, state, tape_cache, outputs): """ @@ -14842,7 +14842,7 @@ def __init__(self, *args, **kwargs): self.TapeCache = _FSMTapeCacheDetectAll_ self.visited_states = {} kwargs['check_epsilon_transitions'] = False - return super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # **************************************************************************** diff --git a/src/sage/combinat/matrices/meson.build b/src/sage/combinat/matrices/meson.build index 86021f8d376..d316252bca0 100644 --- a/src/sage/combinat/matrices/meson.build +++ b/src/sage/combinat/matrices/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'dlxcpp.py', 'hadamard_matrix.py', diff --git a/src/sage/combinat/meson.build b/src/sage/combinat/meson.build index 8c1aba5bd50..441caadca17 100644 --- a/src/sage/combinat/meson.build +++ b/src/sage/combinat/meson.build @@ -1,5 +1,6 @@ py.install_sources( 'SJT.py', + '__init__.py', 'abstract_tree.py', 'affine_permutation.py', 'algebraic_combinatorics.py', diff --git a/src/sage/combinat/posets/meson.build b/src/sage/combinat/posets/meson.build index 07837832519..9832967b4ff 100644 --- a/src/sage/combinat/posets/meson.build +++ b/src/sage/combinat/posets/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'cartesian_product.py', 'd_complete.py', diff --git a/src/sage/combinat/rigged_configurations/meson.build b/src/sage/combinat/rigged_configurations/meson.build index 6b12159dfda..40d0f4e42c5 100644 --- a/src/sage/combinat/rigged_configurations/meson.build +++ b/src/sage/combinat/rigged_configurations/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'bij_abstract_class.py', 'bij_infinity.py', diff --git a/src/sage/combinat/root_system/meson.build b/src/sage/combinat/root_system/meson.build index 35e7bfb1950..629c67bcdd6 100644 --- a/src/sage/combinat/root_system/meson.build +++ b/src/sage/combinat/root_system/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'ambient_space.py', 'associahedron.py', diff --git a/src/sage/combinat/words/meson.build b/src/sage/combinat/words/meson.build index bb12f65d28f..2333d8dc0cd 100644 --- a/src/sage/combinat/words/meson.build +++ b/src/sage/combinat/words/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'abstract_word.py', 'all.py', 'alphabet.py', diff --git a/src/sage/data_structures/meson.build b/src/sage/data_structures/meson.build index 8a94548917b..de25b78721d 100644 --- a/src/sage/data_structures/meson.build +++ b/src/sage/data_structures/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'binary_matrix.pxd', 'binary_search.pxd', diff --git a/src/sage/dynamics/arithmetic_dynamics/meson.build b/src/sage/dynamics/arithmetic_dynamics/meson.build index 9e26a72c874..7ba8b8b4068 100644 --- a/src/sage/dynamics/arithmetic_dynamics/meson.build +++ b/src/sage/dynamics/arithmetic_dynamics/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'affine_ds.py', 'all.py', 'berkovich_ds.py', diff --git a/src/sage/dynamics/complex_dynamics/meson.build b/src/sage/dynamics/complex_dynamics/meson.build index d3961275d3e..ccde4866dd1 100644 --- a/src/sage/dynamics/complex_dynamics/meson.build +++ b/src/sage/dynamics/complex_dynamics/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'mandel_julia.py', subdir: 'sage/dynamics/complex_dynamics', diff --git a/src/sage/dynamics/meson.build b/src/sage/dynamics/meson.build index 134cfd1a296..9ba62964e6c 100644 --- a/src/sage/dynamics/meson.build +++ b/src/sage/dynamics/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'finite_dynamical_system.py', 'finite_dynamical_system_catalog.py', diff --git a/src/sage/ext/meson.build b/src/sage/ext/meson.build index 73d0e85101d..dc3026cec34 100644 --- a/src/sage/ext/meson.build +++ b/src/sage/ext/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all__sagemath_objects.py', 'ccobject.h', 'cplusplus.pxd', diff --git a/src/sage/features/info.py b/src/sage/features/info.py index eeaf0118c0d..22580a42c00 100644 --- a/src/sage/features/info.py +++ b/src/sage/features/info.py @@ -5,6 +5,7 @@ from . import Executable + class Info(Executable): r""" A :class:`~sage.features.Feature` describing the presence of :ref:`info `. @@ -26,5 +27,6 @@ def __init__(self): Executable.__init__(self, 'info', executable='info', spkg='info', type='standard') + def all_features(): return [Info()] diff --git a/src/sage/functions/meson.build b/src/sage/functions/meson.build index c2a77f0e238..85b7b4afa49 100644 --- a/src/sage/functions/meson.build +++ b/src/sage/functions/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'airy.py', 'all.py', 'bessel.py', diff --git a/src/sage/games/meson.build b/src/sage/games/meson.build index d0776c0c71a..8852b13d9cf 100644 --- a/src/sage/games/meson.build +++ b/src/sage/games/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'hexad.py', 'quantumino.py', diff --git a/src/sage/geometry/meson.build b/src/sage/geometry/meson.build index 8906b859dde..3b48404564d 100644 --- a/src/sage/geometry/meson.build +++ b/src/sage/geometry/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'cone.py', 'cone_catalog.py', diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/meson.build b/src/sage/geometry/polyhedron/combinatorial_polyhedron/meson.build index 4b4ea8df4e7..a279309fc13 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/meson.build +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'base.pxd', 'combinatorial_face.pxd', diff --git a/src/sage/geometry/polyhedron/meson.build b/src/sage/geometry/polyhedron/meson.build index 3b07bbdd9a5..db589c39385 100644 --- a/src/sage/geometry/polyhedron/meson.build +++ b/src/sage/geometry/polyhedron/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'backend_cdd.py', 'backend_cdd_rdf.py', diff --git a/src/sage/geometry/triangulation/meson.build b/src/sage/geometry/triangulation/meson.build index dec407d83d5..e8361a9da09 100644 --- a/src/sage/geometry/triangulation/meson.build +++ b/src/sage/geometry/triangulation/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'data.pxd', 'element.py', diff --git a/src/sage/graphs/base/meson.build b/src/sage/graphs/base/meson.build index 92e205ceb81..badf69f0478 100644 --- a/src/sage/graphs/base/meson.build +++ b/src/sage/graphs/base/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'boost_graph.pxd', 'c_graph.pxd', diff --git a/src/sage/graphs/generators/meson.build b/src/sage/graphs/generators/meson.build index 44542f2631e..c98c2647c25 100644 --- a/src/sage/graphs/generators/meson.build +++ b/src/sage/graphs/generators/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'basic.py', 'chessboard.py', diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 38714a483fe..1e3025117df 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -88,6 +88,8 @@ - Jean-Florent Raymond (2019-04): is_redundant, is_dominating, private_neighbors +- Cyril Bouvier (2024-11): is_module + Graph Format ------------ @@ -7181,8 +7183,109 @@ def cores(self, k=None, with_labels=False): return core return list(core.values()) - @doc_index("Leftovers") - def modular_decomposition(self, algorithm=None, style='tuple'): + @doc_index("Modules") + def is_module(self, vertices): + r""" + Check whether ``vertices`` is a module of ``self``. + + A subset `M` of the vertices of a graph is a module if for every + vertex `v` outside of `M`, either all vertices of `M` are neighbors of + `v` or all vertices of `M` are not neighbors of `v`. + + INPUT: + + - ``vertices`` -- iterable; a subset of vertices of ``self`` + + EXAMPLES: + + The whole graph, the empty set and singletons are trivial modules:: + + sage: G = graphs.PetersenGraph() + sage: G.is_module([]) + True + sage: G.is_module([G.random_vertex()]) + True + sage: G.is_module(G) + True + + Prime graphs only have trivial modules:: + + sage: G = graphs.PathGraph(5) + sage: G.is_prime() + True + sage: all(not G.is_module(S) for S in subsets(G) + ....: if len(S) > 1 and len(S) < G.order()) + True + + For edgeless graphs and complete graphs, all subsets are modules:: + + sage: G = Graph(5) + sage: all(G.is_module(S) for S in subsets(G)) + True + sage: G = graphs.CompleteGraph(5) + sage: all(G.is_module(S) for S in subsets(G)) + True + + The modules of a graph and of its complements are the same:: + + sage: G = graphs.TuranGraph(10, 3) + sage: G.is_module([0,1,2]) + True + sage: G.complement().is_module([0,1,2]) + True + sage: G.is_module([3,4,5]) + True + sage: G.complement().is_module([3,4,5]) + True + sage: G.is_module([2,3,4]) + False + sage: G.complement().is_module([2,3,4]) + False + sage: G.is_module([3,4,5,6,7,8,9]) + True + sage: G.complement().is_module([3,4,5,6,7,8,9]) + True + + Elements of ``vertices`` must be in ``self``:: + + sage: G = graphs.PetersenGraph() + sage: G.is_module(['Terry']) + Traceback (most recent call last): + ... + LookupError: vertex (Terry) is not a vertex of the graph + sage: G.is_module([1, 'Graham']) + Traceback (most recent call last): + ... + LookupError: vertex (Graham) is not a vertex of the graph + """ + M = set(vertices) + + for v in M: + if v not in self: + raise LookupError(f"vertex ({v}) is not a vertex of the graph") + + if len(M) <= 1 or len(M) == self.order(): + return True + + N = None # will contains the neighborhood of M + for v in M: + if N is None: + # first iteration, the neighborhood N must be computed + N = {u for u in self.neighbor_iterator(v) if u not in M} + else: + # check that the neighborhood of v is N + n = 0 + for u in self.neighbor_iterator(v): + if u not in M: + n += 1 + if u not in N: + return False # u is a splitter + if n != len(N): + return False + return True + + @doc_index("Modules") + def modular_decomposition(self, algorithm=None, style="tuple"): r""" Return the modular decomposition of the current graph. @@ -7190,11 +7293,21 @@ def modular_decomposition(self, algorithm=None, style='tuple'): vertex outside the module is either connected to all members of the module or to none of them. Every graph that has a nontrivial module can be partitioned into modules, and the increasingly fine partitions into - modules form a tree. The ``modular_decomposition`` function returns - that tree, using an `O(n^3)` algorithm of [HM1979]_. + modules form a tree. The ``modular_decomposition`` method returns + that tree. INPUT: + - ``algorithm`` -- string (default: ``None``); the algorithm to use + among: + + - ``None`` or ``'corneil_habib_paul_tedder'`` -- will use the + Corneil-Habib-Paul-Tedder algorithm from [TCHP2008]_, its complexity + is linear in the number of vertices and edges. + + - ``'habib_maurer'`` -- will use the Habib-Maurer algorithm from + [HM1979]_, its complexity is cubic in the number of vertices. + - ``style`` -- string (default: ``'tuple'``); specifies the output format: @@ -7204,16 +7317,10 @@ def modular_decomposition(self, algorithm=None, style='tuple'): OUTPUT: - A pair of two values (recursively encoding the decomposition) : - - * The type of the current module : - - * ``'PARALLEL'`` - * ``'PRIME'`` - * ``'SERIES'`` - - * The list of submodules (as list of pairs ``(type, list)``, - recursively...) or the vertex's name if the module is a singleton. + The modular decomposition tree, either as nested tuples (if + ``style='tuple'``) or as an object of + :class:`~sage.combinat.rooted_tree.LabelledRootedTree` (if + ``style='tree'``) Crash course on modular decomposition: @@ -7266,7 +7373,19 @@ def modular_decomposition(self, algorithm=None, style='tuple'): The Petersen Graph too:: sage: graphs.PetersenGraph().modular_decomposition() - (PRIME, [1, 4, 5, 0, 2, 6, 3, 7, 8, 9]) + (PRIME, [1, 4, 5, 0, 6, 2, 3, 9, 7, 8]) + + Graph from the :wikipedia:`Modular_decomposition`:: + + sage: G = Graph('Jv\\zoKF@wN?', format='graph6') + sage: G.relabel([1..11]) + sage: G.modular_decomposition() + (PRIME, + [(SERIES, [4, (PARALLEL, [2, 3])]), + 1, + 5, + (PARALLEL, [6, 7]), + (SERIES, [(PARALLEL, [10, 11]), 9, 8])]) This a clique on 5 vertices with 2 pendant edges, though, has a more interesting decomposition:: @@ -7275,14 +7394,20 @@ def modular_decomposition(self, algorithm=None, style='tuple'): sage: g.add_edge(0,5) sage: g.add_edge(0,6) sage: g.modular_decomposition() - (SERIES, [(PARALLEL, [(SERIES, [1, 2, 3, 4]), 5, 6]), 0]) + (SERIES, [(PARALLEL, [(SERIES, [3, 4, 2, 1]), 5, 6]), 0]) + + Turán graphs are co-graphs:: + + sage: graphs.TuranGraph(11, 3).modular_decomposition() + (SERIES, + [(PARALLEL, [7, 8, 9, 10]), (PARALLEL, [3, 4, 5, 6]), (PARALLEL, [0, 1, 2])]) We can choose output to be a :class:`~sage.combinat.rooted_tree.LabelledRootedTree`:: sage: g.modular_decomposition(style='tree') SERIES[0[], PARALLEL[5[], 6[], SERIES[1[], 2[], 3[], 4[]]]] - sage: ascii_art(g.modular_decomposition(style='tree')) + sage: ascii_art(g.modular_decomposition(algorithm="habib_maurer",style='tree')) __SERIES / / 0 ___PARALLEL @@ -7293,7 +7418,9 @@ def modular_decomposition(self, algorithm=None, style='tuple'): ALGORITHM: - This function uses the algorithm of M. Habib and M. Maurer [HM1979]_. + This function can use either the algorithm of D. Corneil, M. Habib, C. + Paul and M. Tedder [TCHP2008]_ or the algorithm of M. Habib and M. + Maurer [HM1979]_. .. SEEALSO:: @@ -7301,10 +7428,16 @@ def modular_decomposition(self, algorithm=None, style='tuple'): - :class:`~sage.combinat.rooted_tree.LabelledRootedTree`. + - :func:`~sage.graphs.graph_decompositions.modular_decomposition.corneil_habib_paul_tedder_algorithm` + + - :func:`~sage.graphs.graph_decompositions.modular_decomposition.habib_maurer_algorithm` + .. NOTE:: - A buggy implementation of linear time algorithm from [TCHP2008]_ was - removed in Sage 9.7, see :issue:`25872`. + A buggy implementation of the linear time algorithm from [TCHP2008]_ + was removed in Sage 9.7, see :issue:`25872`. A new implementation + was reintroduced in Sage 10.6 after some corrections to the original + algorithm, see :issue:`39038`. TESTS: @@ -7343,41 +7476,33 @@ def modular_decomposition(self, algorithm=None, style='tuple'): sage: G2 = Graph('F@Nfg') sage: G1.is_isomorphic(G2) True - sage: G1.modular_decomposition() + sage: G1.modular_decomposition(algorithm="habib_maurer") (PRIME, [1, 2, 5, 6, 0, (PARALLEL, [3, 4])]) - sage: G2.modular_decomposition() + sage: G2.modular_decomposition(algorithm="habib_maurer") (PRIME, [5, 6, 3, 4, 2, (PARALLEL, [0, 1])]) + sage: G1.modular_decomposition(algorithm="corneil_habib_paul_tedder") + (PRIME, [6, 5, 1, 2, 0, (PARALLEL, [3, 4])]) + sage: G2.modular_decomposition(algorithm="corneil_habib_paul_tedder") + (PRIME, [6, 5, (PARALLEL, [0, 1]), 2, 3, 4]) Check that :issue:`37631` is fixed:: sage: G = Graph('GxJEE?') - sage: G.modular_decomposition(style='tree') + sage: G.modular_decomposition(algorithm="habib_maurer",style='tree') PRIME[2[], SERIES[0[], 1[]], PARALLEL[3[], 4[]], PARALLEL[5[], 6[], 7[]]] """ - from sage.graphs.graph_decompositions.modular_decomposition import (NodeType, - habib_maurer_algorithm, - create_prime_node, - create_normal_node) - - if algorithm is not None: - from sage.misc.superseded import deprecation - deprecation(25872, "algorithm=... parameter is obsolete and has no effect.") - self._scream_if_not_simple() + from sage.graphs.graph_decompositions.modular_decomposition import \ + modular_decomposition - if not self.order(): - D = None - elif self.order() == 1: - D = create_normal_node(next(self.vertex_iterator())) - else: - D = habib_maurer_algorithm(self) + D = modular_decomposition(self, algorithm=algorithm) if style == 'tuple': - if D is None: + if D.is_empty(): return tuple() def relabel(x): - if x.node_type == NodeType.NORMAL: + if x.is_leaf(): return x.children[0] return x.node_type, [relabel(y) for y in x.children] @@ -7385,11 +7510,11 @@ def relabel(x): elif style == 'tree': from sage.combinat.rooted_tree import LabelledRootedTree - if D is None: + if D.is_empty(): return LabelledRootedTree([]) def to_tree(x): - if x.node_type == NodeType.NORMAL: + if x.is_leaf(): return LabelledRootedTree([], label=x.children[0]) return LabelledRootedTree([to_tree(y) for y in x.children], label=x.node_type) @@ -7640,7 +7765,14 @@ def is_prime(self, algorithm=None): A graph is prime if all its modules are trivial (i.e. empty, all of the graph or singletons) -- see :meth:`modular_decomposition`. - Use the `O(n^3)` algorithm of [HM1979]_. + This method computes the modular decomposition tree using + :meth:`~sage.graphs.graph.Graph.modular_decomposition`. + + INPUT: + + - ``algorithm`` -- string (default: ``None``); the algorithm used to + compute the modular decomposition tree; the value is forwarded + directly to :meth:`~sage.graphs.graph.Graph.modular_decomposition`. EXAMPLES: @@ -7661,17 +7793,15 @@ def is_prime(self, algorithm=None): sage: graphs.EmptyGraph().is_prime() True """ - if algorithm is not None: - from sage.misc.superseded import deprecation - deprecation(25872, "algorithm=... parameter is obsolete and has no effect.") - from sage.graphs.graph_decompositions.modular_decomposition import NodeType + from sage.graphs.graph_decompositions.modular_decomposition import \ + modular_decomposition if self.order() <= 1: return True - D = self.modular_decomposition() + MD = modular_decomposition(self, algorithm=algorithm) - return D[0] == NodeType.PRIME and len(D[1]) == self.order() + return MD.is_prime() and len(MD.children) == self.order() @doc_index("Connectivity, orientations, trees") def gomory_hu_tree(self, algorithm=None, solver=None, verbose=0, diff --git a/src/sage/graphs/graph_decompositions/meson.build b/src/sage/graphs/graph_decompositions/meson.build index a3dee500e16..0f7a7ddf04d 100644 --- a/src/sage/graphs/graph_decompositions/meson.build +++ b/src/sage/graphs/graph_decompositions/meson.build @@ -8,10 +8,11 @@ endif rw = cc.find_library('rw', required: not is_windows, disabler: true) py.install_sources( + '__init__.py', 'all.py', 'all__sagemath_tdlib.py', 'fast_digraph.pxd', - 'modular_decomposition.py', + 'modular_decomposition.pxd', 'rankwidth.pxd', 'slice_decomposition.pxd', 'tree_decomposition.pxd', @@ -43,6 +44,7 @@ endforeach extension_data_cpp = { 'clique_separators': files('clique_separators.pyx'), 'slice_decomposition' : files('slice_decomposition.pyx'), + 'modular_decomposition' : files('modular_decomposition.pyx'), } foreach name, pyx : extension_data_cpp diff --git a/src/sage/graphs/graph_decompositions/modular_decomposition.hpp b/src/sage/graphs/graph_decompositions/modular_decomposition.hpp new file mode 100644 index 00000000000..4b14b03c1bf --- /dev/null +++ b/src/sage/graphs/graph_decompositions/modular_decomposition.hpp @@ -0,0 +1,857 @@ +/* + * 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/ + */ + +/* + * This file contains inline implementations of utility structs, classes and + * functions used in the implementation of the modular decomposition method + * + * AUTHORS: + * + * - Cyril Bouvier (2024): code for second implementation of the linear time + * algorithm of D. Corneil, M. Habib, C. Paul and M. Tedder [TCHP2008]_ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Labels attached to nodes of a partitive forest. For the meaning of the + * different values, see algorithms 3 and 4 of [TCHP2008]_. + */ +enum class Label : uint8_t { + EMPTY = 0b00, + HOMOGENEOUS = 0b01, + BROKEN = 0b10, + DEAD = 0b11 +}; + +/* + * Flags attached to nodes of a partitive forest. For the meaning of the flag, + * see algorithms 3 and 4 of [TCHP2008]_. + */ +enum class Flag : uint8_t { + UNFLAGGED = 0b00, + FLAGGED = 0b01 +}; + +/* + * A node of a modular decomposition tree is either a leaf or an internal node. + * An internal node can be prime, series or parallel. + */ +enum class Type : uint8_t { + PRIME = 0, + SERIES = 1, + PARALLEL = 2, + LEAF = 3 +}; + +/* + * This struct is used to have a nice interface to the data describing a slice + * decomposition. The slice decomposition is needed by the algorithm that + * compute the modular decomposition. + */ +struct SDData { + /* + * Set the members of the struct; it is useful to initialized the struct + * from the output of the extended_lex_BFS method. + */ + void set_from_data(size_t lex_label_offset_arg, + const int* sigma_arg, + const size_t *xslice_len_arg, + const std::vector *lex_label_arg) { + lex_label_offset = lex_label_offset_arg; + sigma = sigma_arg; + xslice_len = xslice_len_arg; + lex_label = lex_label_arg; + } + + /* + * Set the members of the struct to represent the subslice of sd starting at + * the given offset. + */ + void set_to_subslice(const SDData &sd, size_t offset) { + lex_label_offset = sd.lex_label[offset].size(); + sigma = sd.sigma + offset; + xslice_len = sd.xslice_len + offset; + lex_label = sd.lex_label + offset; + } + + /* + * Return the number of lexicographic labels of the ith vertex of the slice + * decomposition. + */ + size_t lex_label_size(size_t i) const { + if (lex_label[i].size() <= lex_label_offset) { + return 0; + } else { + return lex_label[i].size() - lex_label_offset; + } + } + + /* + * Return the pointer to the lexicographic labels of the ith vertex of the + * slice decomposition. + */ + const int * lex_label_ptr(size_t i) const { + if (lex_label[i].size() <= lex_label_offset) { + return nullptr; + } else { + return lex_label[i].data() + lex_label_offset; + } + } + + /* Return the index of the first slice (always 1). */ + size_t first_slice_index() const { + return 1; + } + + /* Return the index of the slice following the one starting at idx. */ + size_t next_slice_index(size_t idx) const { + return idx + xslice_len[idx]; + } + + /* Return the number of vertices in the slice decomposition. */ + size_t size() const { + return xslice_len[0]; + } + + /* + * Check whether the pivot has any neighbor (i.e., the first slice has no + * lexicographic labels). + */ + bool is_pivot_isolated () const { + return lex_label[1].size() <= lex_label_offset; + } + + size_t lex_label_offset; + const int *sigma; + const size_t *xslice_len; + const std::vector *lex_label; +}; + +/* + * This struct represents a node of a modular decomposition tree. It contains a + * pointer to its parent (or nullptr for the root), a list of children (must be + * empty for leaf) and a type (from the enum Type). + * A leaf of a modular decomposition tree corresponds to a vertex of the graph. + * The id of the vertex is stored in the vertex attribute of the struct. For + * internal nodes, the vertex attribute is used to store the id of any vertex + * belonging to the corresponding module. + * The attributes label, flag, slice, cc_tag are used by the different parts of + * the algorithm that computes the modular decomposition tree. + */ +struct md_tree_node { + /* ctor for non-leaf node */ + md_tree_node(Type type, Label label, Flag flag) + : parent(nullptr), vertex(INT_MAX), type(type), + label(label), flag(flag), + slice(SIZE_MAX), cc_tag(SIZE_MAX) { + } + + /* ctor for leaf */ + md_tree_node(int vertex) + : parent(nullptr), vertex(vertex), type(Type::LEAF), + label(Label::EMPTY), flag(Flag::UNFLAGGED), + slice(SIZE_MAX), cc_tag(SIZE_MAX) { + } + + /* ctor for non-leaf node, with default label and flag. */ + md_tree_node(Type type) + : md_tree_node(type, Label::EMPTY, Flag::UNFLAGGED) { + } + + /* check whether the node is a leaf. */ + bool is_leaf() const { + return type == Type::LEAF; + } + + /* check whether the node is prime. */ + bool is_prime() const { + return type == Type::PRIME; + } + + /* check whether the node is series. */ + bool is_series() const { + return type == Type::SERIES; + } + + /* check whether the node is parallel. */ + bool is_parallel() const { + return type == Type::PARALLEL; + } + + /* check whether the node is degenerate (i.e., series or parallel). */ + bool is_degenerate() const { + return type == Type::SERIES || type == Type::PARALLEL; + } + + /* + * The following methods are used to check the different possible status of + * the label of the node + */ + bool is_empty() const { + return label == Label::EMPTY; + } + + bool is_homogeneous() const { + return label == Label::HOMOGENEOUS; + } + + bool is_homogeneous_or_empty() const { + return !(static_cast(label) >> 1U); + } + + bool is_broken() const { + return label == Label::BROKEN; + } + + bool is_dead() const { + return label == Label::DEAD; + } + + bool is_dead_or_broken() const { + return static_cast(label) >> 1U; + } + + /* add the node c at the beginning of the list of children. */ + void prepend_new_child(md_tree_node *c) { + c->parent = this; + if (children.empty()) { + vertex = c->vertex; + } + children.push_front(c); + } + + /* add the node c at the end of the list of children. */ + void append_new_child(md_tree_node *c) { + c->parent = this; + if (children.empty()) { + vertex = c->vertex; + } + children.push_back(c); + } + + /* + * "Stole" the children from the node n and add them at the end of the list + * of children. ("stole" here means that at the end of this method, the list + * of children of n will be empty). + */ + void append_stolen_children_from(md_tree_node *n) { + if (!n->children.empty()) { + for (md_tree_node *c: n->children) { + c->parent = this; + } + if (children.empty()) { + vertex = n->children.front()->vertex; + } + children.splice(children.end(), n->children); + } + } + + /* Set the label and flag for all nodes of the tree. */ + void set_label_and_flag_recursively(Label l, Flag f) { + label = l; + flag = f; + for (md_tree_node *c: children) { + c->set_label_and_flag_recursively(l, f); + } + } + + md_tree_node *parent; + std::list children; + int vertex; + Type type; + Label label; + Flag flag; + size_t slice; + size_t cc_tag; +}; + +/* A forest is a list of tree */ +using md_forest = std::list; + +/* + * This struct is used to gather data structures needed by the different parts + * of the algorithm computing the modular decomposition tree. It is created + * at the beginning of the algorithm and pass along to any function that needs + * it. The hope is that it will reduce the number of allocations/deallocations + * because the number of creations and destructions of objects will be reduced. + */ +struct ScratchData { + /* + * Sub structure containing objects needed by the implementation of + * algorithms 3 and 4 of [TCHP2008]_. + */ + struct MDSequences { + std::unordered_map leaves; + std::unordered_set Marked; + std::unordered_set Full; + std::deque Explore; + }; + + /* + * Sub structure containing objects needed by the implementation of + * algorithms 5 and 6 of [TCHP2008]_. + */ + struct Clusters { + /* Assumes i < p < j where p is the index of the "cluster" {x}. */ + bool are_clusters_non_adjacent(size_t i, size_t j) const { + size_t sj = clusters[j].front()->slice; + auto e = std::make_pair(0, sj); + for (const md_tree_node *mi: clusters[i]) { + e.first = mi->vertex; + auto it = module_slice_adjacency.find(e); + if (it != module_slice_adjacency.end()) { + return false; + } + } + return true; + } + + struct pair_hash { + inline size_t operator()(const std::pair &v) const { + return ((size_t) v.first)*31+v.second; + } + }; + + std::vector> clusters; + /* adjacency list between a module (represented using the corresponding + * .vertex from the root node) and a slice. + */ + std::unordered_set, pair_hash> module_slice_adjacency; + std::vector Left; + std::vector Right; + std::unordered_map cluster_of_v; + }; + + /* Allocate the node corresponding to the vertex, and add it to the map */ + md_tree_node *new_leaf(int vertex) { + return mdseq.leaves[vertex] = new md_tree_node(vertex); + } + + MDSequences mdseq; + Clusters clusters; +}; + +/* + * This function deallocate (using delete) the node n and all of its + * descendants. It is needed to deallocate the modular decomposition tree + * computed by the function corneil_habib_paul_tedder_inner. + */ +void dealloc_md_tree_nodes_recursively(md_tree_node *n) { + for (md_tree_node *c: n->children) { + dealloc_md_tree_nodes_recursively(c); + } + delete n; +} + +/* Preprocess the trees in the forest: set the label to empty and the flag to + * unflagged for all the nodes, and set the cc_tag needed to compute the cluster + * later. + */ +void md_forest_preprocess(md_forest &MDi) { + Type one_cc_type = Type::PARALLEL; /* only for first iteration */ + size_t s = 0; + for (md_tree_node *md: MDi) { + md->set_label_and_flag_recursively(Label::EMPTY, Flag::UNFLAGGED); + md->slice = s; + if (md->type == Type::PRIME || md->type == one_cc_type) { + md->cc_tag = 0; + } else { + md->cc_tag = SIZE_MAX; + size_t i = 0; + for (md_tree_node *c: md->children) { + c->cc_tag = i; + i++; + } + } + one_cc_type = Type::SERIES; + s += 1; + } +} + +/* + * This function set to BROKEN the ancestors of DEAD nodes and gather into one + * node the HOMOGENEOUS and EMPTY childrend of a broken and degenerate node. + * Corresponds to the end of algorithm 3 of [TCHP2008]_. + */ +void mark_partitive_forest_finish_inner_rec(md_tree_node *r) { + size_t nb = 0; /* number of HOMOGENEOUS or EMPTY children */ + + /* Do a postorder visit: so we first visit the children */ + for (md_tree_node *c: r->children) { + mark_partitive_forest_finish_inner_rec(c); + nb += c->is_homogeneous_or_empty(); + } + + if (r->is_dead_or_broken()) { + if (r->parent != nullptr && !(r->parent->is_dead())) { + /* if parent.label is not DEAD set it to BROKEN */ + r->parent->label = Label::BROKEN; + } + if (r->is_broken() && r->is_degenerate() && nb > 1) { + md_tree_node *newnode = new md_tree_node(r->type, Label::EMPTY, + Flag::UNFLAGGED); + + /* Iterate over the children to gather HOMOGENEOUS and EMPTY + * child under newnode + * */ + auto it = r->children.begin(); + while (it != r->children.end()) { + if ((*it)->is_homogeneous_or_empty()) { + newnode->append_new_child(*it); + it = r->children.erase(it); /* get iterator to next child */ + } else { + ++it; + } + + } + r->append_new_child(newnode); + } + } +} + +/* This is an implementation of algorithm 3 of [TCHP2008]_. */ +void md_forest_mark_partitive_forest(md_forest &MDi, const SDData &sd, + ScratchData::MDSequences &scratch) { + size_t i = sd.first_slice_index(); + i = sd.next_slice_index(i); /* skip first slice */ + scratch.Explore.clear(); + for (; i < sd.size(); i = sd.next_slice_index(i)) { + scratch.Marked.clear(); + scratch.Full.clear(); + + for (auto it = sd.lex_label[i].begin() + sd.lex_label_offset; + it != sd.lex_label[i].end() ; ++it) { + scratch.Explore.push_back(scratch.leaves.at(*it)); + } + + while (scratch.Explore.size() > 0) { + md_tree_node *n = scratch.Explore.front(); + scratch.Explore.pop_front(); + md_tree_node *p = n->parent; + scratch.Full.insert(n); + if (n->is_empty()) { + n->label = Label::HOMOGENEOUS; + } + if (p) { + scratch.Marked.insert(p); + /* if all children of p are Full, move p to Explore */ + bool b = true; + for (md_tree_node *c: p->children) { + if (scratch.Full.find(c) == scratch.Full.end()) { + b = false; + break; + } + } + if (b) { + scratch.Marked.erase(p); + scratch.Explore.push_back(p); + } + } + } + + for (md_tree_node *n: scratch.Marked) { + /* If n is SERIES or PARALLEL => gather children of n in Full below + * the same new node A (needed only if there are >= 2 such children) + * and the children of n not in Full below the same new node B + * (needed only if there is >= 2 such children). + */ + if (n->is_degenerate() && n->children.size() > 2) { + Type t = n->type; + md_tree_node *newnodes[2] = { + new md_tree_node(t, Label::HOMOGENEOUS, Flag::FLAGGED), + new md_tree_node(t, Label::EMPTY, Flag::UNFLAGGED) + }; + auto end = n->children.end(); + for (auto it = n->children.begin(); it != end; ++it){ + bool notFull = scratch.Full.find(*it) == scratch.Full.end(); + newnodes[notFull]->append_new_child(*it); + } + n->children.clear(); + for (size_t i = 0; i < 2; i++) { + if (newnodes[i]->children.size() == 1) { + n->append_new_child(newnodes[i]->children.front()); + } else { + n->append_new_child(newnodes[i]); + } + } + } + + if (n->label != Label::DEAD) { + n->label = Label::DEAD; + /* Set flag to * for children of n that are in Full */ + for (md_tree_node *c: n->children) { + if (scratch.Full.find(c) != scratch.Full.end()) { + c->flag = Flag::FLAGGED; + } + } + } + } + } + + for (md_tree_node *md: MDi) { + mark_partitive_forest_finish_inner_rec(md); + } +} + +/* This function is needed by md_forest_extract_and_sort. */ +void sort_broken_nodes_recursively(md_tree_node *n, + bool dead_and_broken_first) { + if (n->is_dead_or_broken()) { + /* If the label is not DEAD or BROKEN, no need to go deeper: they + * will not be any DEAD or BROKEN nodes. + */ + for (md_tree_node *c: n->children) { + sort_broken_nodes_recursively(c, dead_and_broken_first); + } + + if (n->is_broken()) { + /* if dead_and_broken_first is true + * => put DEAD and BROKEN children at the beginning + * if dead_and_broken_first is false + * => put EMPTY and HOMOGENEOUS children at the beginning + */ + auto b = n->children.begin(); + auto end = n->children.end(); + for (auto it = n->children.begin(); it != end; ++it) { + if (dead_and_broken_first == (*it)->is_dead_or_broken()) { + std::iter_swap(it, b); + ++b; + } + } + } + } +} + +/* This function is needed by md_forest_extract_and_sort. */ +void sort_dead_nodes_recursively(md_tree_node *n, bool flagged_first) { + if (n->is_dead_or_broken()) { + /* If the label is not DEAD or BROKEN, no need to go deeper: they + * will not be any DEAD or BROKEN nodes. + */ + for (md_tree_node *c: n->children) { + sort_dead_nodes_recursively(c, flagged_first); + } + + if (n->is_dead()) { + /* if flagged_first is true => put flagged children at the beginning + * if flagged_first is talse => put unflagged children at the beginning + */ + auto b = n->children.begin(); + auto end = n->children.end(); + for (auto it = n->children.begin(); it != end; ++it) { + if (flagged_first == ((*it)->flag == Flag::FLAGGED)) { + std::iter_swap(it, b); + ++b; + } + } + } + } +} + +/* This is an implementation of algorithm 4 of [TCHP2008]_. */ +void md_forest_extract_and_sort(md_forest &MDi) { + bool is_first_slice = true; + auto it = MDi.begin(); + while (it != MDi.end()) { + md_tree_node *md = *it; + /* sort children of DEAD nodes */ + sort_dead_nodes_recursively(md, is_first_slice); + /* sort children of BROKEN nodes */ + sort_broken_nodes_recursively(md, is_first_slice); + + /* remove DEAD and BROKEN nodes */ + auto it_delete = it; + ++it; + while (it_delete != it) { + md_tree_node *r = *it_delete; + if (r->is_dead_or_broken()) { + for (md_tree_node *c: r->children) { + /* propagate cc tag and slice, if set */ + c->cc_tag = r->cc_tag != SIZE_MAX ? r->cc_tag : c->cc_tag; + c->slice = r->slice != SIZE_MAX ? r->slice : c->slice; + c->parent = nullptr; + } + auto insert_it = MDi.erase(it_delete); + it_delete = r->children.begin(); + MDi.splice(insert_it, r->children); + delete r; + } else { + it_delete++; + } + } + + is_first_slice = false; + } +} + +/* + * This function gathers the trees of the forest in clusters. A cluster is a set + * of trees that belongs to the same slice and (co)connected components. + * It also computes the Left and Right of the clusters (see section 5.2 of + * [TCHP2008]_. + */ +void md_forest_clusters_computation(const md_forest &MDi, const SDData &sd, + ScratchData::Clusters &scratch) { + scratch.clusters.clear(); + scratch.cluster_of_v.clear(); + size_t prev_cc = SIZE_MAX, prev_slice = SIZE_MAX; + for (md_tree_node *n: MDi) { + size_t cc = n->cc_tag; + size_t slice = n->slice; + int v = n->vertex; /* a vertex belonging to the module */ + + if (cc == SIZE_MAX) { /* n is alone in the cluster */ + scratch.clusters.emplace_back(1, n); /* new cluster */ + } else { + if (cc != prev_cc || slice != prev_slice) { + scratch.clusters.emplace_back(); /* start new cluster */ + } + scratch.clusters.back().push_back(n); + } + + prev_cc = cc; + prev_slice = slice; + scratch.cluster_of_v[v] = scratch.clusters.size()-1; + } + + size_t p = scratch.cluster_of_v[sd.sigma[0]]; + size_t q = scratch.clusters.size(); + + /* Left and Right computation. + * Left(i) == i if i <= p + * Left(i) == Left(j) for p < i,j if clusters Ki and Kj in same slice + * Right(i) = p for 0 <= i <= p + * Also compute adjacency between modules and slices (except the first one). + */ + scratch.Left.clear(); + scratch.Right.clear(); + scratch.module_slice_adjacency.clear(); + scratch.Left.reserve(q); + scratch.Right.reserve(q); + for (size_t i = 0; i <= p; i++) { + scratch.Left.push_back(i); + } + scratch.Right.resize(p+1, p); /* Right[i] = p for 0 <= i <= p */ + for (size_t i = p+1; i < q; i++) { + scratch.Right.push_back(i); + } + for (size_t i = sd.first_slice_index(), s = 0, j = 0; i < sd.size(); + i = sd.next_slice_index(i), s++) { + size_t j0 = j; + /* Compute j, the highest index of a cluster of the current slice s */ + for (; j+1 < q && scratch.clusters[j+1].front()->slice == s; j++); + + if (s == 0) { /* nothing to do for the first slice */ + j += 1; /* skip "cluster" {x} */ + } else { + /* for Right and module_slice_adjacency: iterate over the + * lexicographic labels of the slice. + */ + for (auto it = sd.lex_label[i].begin() + sd.lex_label_offset; + it != sd.lex_label[i].end() ; ++it) { + auto c = scratch.cluster_of_v.find(*it); + if (c != scratch.cluster_of_v.end()) { + scratch.module_slice_adjacency.emplace(*it, s); + /* cluster j is adjacent to cluster containing c so Right of + * the cluster c is >= j + */ + scratch.Right[c->second] = j; + } + } + + /* for Left: find the cluster of the first non adjacent module */ + size_t lp; /* lp will be the Left for all clusters of the slice */ + for (lp = 0; lp < p; lp++) { + bool adj = true; + for (const md_tree_node *m: scratch.clusters[lp]) { + if (scratch.module_slice_adjacency.find(std::make_pair(m->vertex, s)) == scratch.module_slice_adjacency.end()) { + adj = false; + break; + } + } + if (!adj) { + break; + } + } + /* Left is lp for all clusters of the slice s */ + std::fill_n(std::back_inserter(scratch.Left), j-j0, lp); + } + } + +} + +/* This is an implementation of algorithms 5 and 6 of [TCHP2008]_. */ +md_tree_node *md_forest_parse_and_assemble(md_tree_node *root, size_t p, + const ScratchData::Clusters &scratch) { + size_t q = scratch.clusters.size(); + size_t l = p; + size_t r = p; + while (l > 0 || r+1 < q) { + Type t; + size_t i; + size_t lp, old_l = l; + size_t rp, old_r = r; + + if (r+1 == q || (l>0 && scratch.are_clusters_non_adjacent(l-1, r+1))) { + lp = l-1; + rp = r; + t = Type::SERIES; + } else { + lp = l; + rp = r+1; + t = Type::PARALLEL; + } + + while (lp < l || r < rp) { + if (lp < l) { + i = l = l-1; + } else { + i = r = r+1; + } + lp = std::min(lp, scratch.Left[i]); + rp = std::max(rp, scratch.Right[i]); + } + + t = (r-l)-(old_r-old_l) > 1 ? Type::PRIME : t; + md_tree_node *old_root = root; + root = new md_tree_node(t); + + for (size_t i = l; i <= r; i++) { + if (i == old_l) { /* add the previous root */ + root->append_new_child(old_root); + i = old_r; + } else { + for (md_tree_node *m: scratch.clusters[i]) { + if (t != Type::PRIME && m->type == t) { + root->append_stolen_children_from(m); + delete m; + } else { + root->append_new_child(m); + } + } + } + } + } + return root; +} + +/* This is the main function: it corresponds to algorithms 7 of [TCHP2008]_. */ +md_tree_node *corneil_habib_paul_tedder_inner_rec(const SDData &sd, + ScratchData &scratch) { + if (sd.size() == 0) { /* empty graph */ + return nullptr; + } + + SDData sub_sd; + std::list MDi; + int x = sd.sigma[0]; + + /* First create a new leaf for x */ + md_tree_node *root = scratch.new_leaf(x); + + if (sd.size() == 1) { /* graph with one vertex */ + return root; + } else if (sd.size() == 2) { /* graph with two vertices */ + int y = sd.sigma[1]; + /* root is SERIES if there is an edge between x and y, else PARALLEL */ + Type t = sd.is_pivot_isolated() ? Type::PARALLEL : Type::SERIES; + root = new md_tree_node(t); + root->append_new_child(scratch.mdseq.leaves[x]); + root->append_new_child(scratch.new_leaf(y)); + return root; + } + + /* Now it is known that the graph has more than two vertices */ + + /* Recursive calls on all the slices */ + size_t first_of_last_slice = 1; /* set to 1 to remove warning */ + for (size_t i = sd.first_slice_index(); i < sd.size(); + i = sd.next_slice_index(i)) { + first_of_last_slice = i; + sub_sd.set_to_subslice(sd, i); + md_tree_node *md = corneil_habib_paul_tedder_inner_rec(sub_sd, scratch); + MDi.push_back(md); + } + + if (sd.is_pivot_isolated()) { /* x is isolated (i.e., has no neighbor) */ + md_tree_node *md = MDi.front(); /* only one slice in this case */ + if (md->type == Type::PARALLEL) { + root = md; + } else { + root = new md_tree_node(Type::PARALLEL); + root->append_new_child(md); + } + root->prepend_new_child(scratch.mdseq.leaves[x]); + return root; + } + + /* Now, it is known that x has at least one neighbor, so the first slice + * contains the neighborhood of x. + * If G is not connected, the last slice is all the connected components + * that do not contains x, it should be treated separately: the tree + * corresponding to the last slice is removed from MDi and will be added + * back before the clusters computation. + */ + md_tree_node *last_md = nullptr; + if (sd.lex_label_size(first_of_last_slice) == 0) { /* not connected */ + last_md = MDi.back(); + last_md->slice = MDi.size()-1; /* remember the slice */ + MDi.pop_back(); + } + + /* Preprocessing of the sub md trees: + * - set the slice attribute + * - set the connected components tag (for clusters computation later) + * - set label to EMPTY and flag to UNFLAGGED on all nodes + */ + md_forest_preprocess(MDi); + + /* Add the pivot {x} in MDi after the first slice (= the neighbors of x) */ + MDi.insert(++MDi.begin(), root); + + /* Mark partitive forest */ + md_forest_mark_partitive_forest(MDi, sd, scratch.mdseq); + + /* Extract and sort */ + md_forest_extract_and_sort(MDi); + + /* Add back the last slice if graph is not connected */ + if (last_md != nullptr) { + MDi.push_back(last_md); + } + + /* Postprocessing for connected (co-)components of slices: compute the + * factoring x-m-cluster sequence + */ + md_forest_clusters_computation(MDi, sd, scratch.clusters); + + size_t p = scratch.clusters.cluster_of_v[x]; + + /* Parse and assemble */ + root = md_forest_parse_and_assemble(root, p, scratch.clusters); + + return root; +} + +/* + * It is the function exported in the pxd file. It creates the ScratchData + * struct before calling the algorithm. + */ +md_tree_node *corneil_habib_paul_tedder_inner(const SDData &sd) { + ScratchData tmp; + return corneil_habib_paul_tedder_inner_rec(sd, tmp); +} diff --git a/src/sage/graphs/graph_decompositions/modular_decomposition.pxd b/src/sage/graphs/graph_decompositions/modular_decomposition.pxd new file mode 100644 index 00000000000..f8807be6266 --- /dev/null +++ b/src/sage/graphs/graph_decompositions/modular_decomposition.pxd @@ -0,0 +1,26 @@ +from libcpp cimport bool +from libcpp.list cimport list as cpplist +from libcpp.vector cimport vector + +cdef extern from "modular_decomposition.hpp": + cdef cppclass SDData: + void set_from_data(size_t lex_label_offset, const int* sigma, + const size_t *xslice_len, + const vector[int] *lex_label) + + cdef cppclass md_tree_node: + bool is_leaf() const + bool is_prime() const + bool is_parallel() const + bool is_series() const + cpplist[md_tree_node *] children + # If is_leaf() is true, the attribute 'vertex' contains the id of the + # corresponding vertex. If is_leaf() is false, the attribute 'vertex' + # contains the id of a vertex corresponding to any leaf below + # the node (i.e., 'vertex' contains the id of a vertex belonging to the + # module corresponding to the node). + int vertex + + void dealloc_md_tree_nodes_recursively(md_tree_node *) + + cdef md_tree_node * corneil_habib_paul_tedder_inner(const SDData &SD) diff --git a/src/sage/graphs/graph_decompositions/modular_decomposition.py b/src/sage/graphs/graph_decompositions/modular_decomposition.pyx similarity index 70% rename from src/sage/graphs/graph_decompositions/modular_decomposition.py rename to src/sage/graphs/graph_decompositions/modular_decomposition.pyx index 001f127d63d..3616f63259b 100644 --- a/src/sage/graphs/graph_decompositions/modular_decomposition.py +++ b/src/sage/graphs/graph_decompositions/modular_decomposition.pyx @@ -1,11 +1,25 @@ +# distutils: language = c++ +# distutils: extra_compile_args = -std=c++11 r""" Modular Decomposition This module implements the function for computing the modular decomposition of undirected graphs. + +AUTHORS: + +- Lokesh Jain (2017): first implementation of the linear time algorithm of + D. Corneil, M. Habib, C. Paul and M. Tedder [TCHP2008]_ + +- David Einstein (2018): added the algorithm of M. Habib and M. Maurer [HM1979]_ + +- Cyril Bouvier (2024): second implementation of the linear time algorithm + of D. Corneil, M. Habib, C. Paul and M. Tedder [TCHP2008]_ """ # **************************************************************************** # Copyright (C) 2017 Lokesh Jain +# 2018 David Einstein +# 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 @@ -13,15 +27,144 @@ # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** +from cython.operator cimport dereference as deref -from enum import Enum, IntEnum +from enum import IntEnum +from sage.graphs.base.c_graph cimport CGraph, CGraphBackend +from sage.graphs.graph_decompositions.slice_decomposition cimport \ + extended_lex_BFS +from sage.groups.perm_gps.permgroup_element import PermutationGroupElement from sage.misc.lazy_import import lazy_import from sage.misc.random_testing import random_testing -lazy_import('sage.groups.perm_gps.permgroup_element', 'PermutationGroupElement') + +################################################################################ +# Corneil-Habib-Paul-Tedder algorithm # +################################################################################ +def corneil_habib_paul_tedder_algorithm(G): + r""" + Compute the modular decomposition by the algorithm of Corneil, Habib, Paul + and Tedder. + + INPUT: + + - ``G`` -- the graph for which modular decomposition tree needs to be + computed + + OUTPUT: an object of type Node representing the modular decomposition tree + of the graph G + + This function computes the modular decomposition of the given graph by the + algorithm of Corneil, Habib, Paul and Tedder [TCHP2008]_. It is a recursive, + linear-time algorithm that first computes the slice decomposition of the + graph (via the extended lexBFS algorithm) and then computes the modular + decomposition by calling itself recursively on the slices of the previously + computed slice decomposition. + + This functions is based on the last version of the paper [TCHP2008]_. + Previous versions of the paper and previous implementations were found to + contains errors, see [AP2024]_. + + .. SEEALSO:: + + * :mod:`~sage.graphs.graph_decompositions.slice_decomposition` -- + compute a slice decomposition of the simple undirect graph + + This function should not be used directly, it should be called via the + ``modular_decomposition`` method of ``Graph`` with the parameter + ``algorithm='corneil_habib_paul_tedder'``. + + This functions assumes that ``graph`` is a object of the class ``Graph`` and + is a simple graph. + + TESTS:: + + sage: from sage.graphs.graph_decompositions.modular_decomposition import * + sage: recreate_decomposition(15, corneil_habib_paul_tedder_algorithm, + ....: 3, 4, 0.2) + sage: recreate_decomposition(10, corneil_habib_paul_tedder_algorithm, + ....: 4, 5, 0.2) + sage: recreate_decomposition(3, corneil_habib_paul_tedder_algorithm, + ....: 6, 5, 0.2) + + sage: H = Graph('Hv|mmjz', format='graph6') + sage: H.relabel('abcdefghi') # counter-exemple graph from [AP2024]_ + sage: H.modular_decomposition() + (SERIES, + [(PRIME, ['e', (PARALLEL, ['g', 'h']), 'b', 'c']), + (PARALLEL, [(SERIES, ['i', 'd', 'a']), 'f'])]) + """ + cdef CGraphBackend Gbackend = G._backend + cdef CGraph cg = Gbackend.cg() + + cdef vector[int] sigma + cdef vector[vector[int]] lex_label + cdef vector[size_t] xslice_len + + # Compute the slice decomposition using the extended lexBFS algorithm + extended_lex_BFS(cg, sigma, NULL, -1, NULL, &xslice_len, &lex_label) + + cdef SDData SD + SD.set_from_data(0, sigma.data(), xslice_len.data(), lex_label.data()) + + MD = corneil_habib_paul_tedder_inner(SD) + + r = md_tree_node_to_md_tree(MD, Gbackend) + dealloc_md_tree_nodes_recursively(MD) + return r + + +cdef object _md_tree_node_to_md_tree_inner_rec(const md_tree_node *n, + CGraphBackend Gb): + """ + Utility function for :func:`md_tree_node_to_md_tree`. + """ + cdef md_tree_node *c + if deref(n).is_leaf(): + return Node.create_leaf(Gb.vertex_label(deref(n).vertex)) + + if deref(n).is_series(): + node = Node(NodeType.SERIES) + elif deref(n).is_parallel(): + node = Node(NodeType.PARALLEL) + else: # is_prime + node = Node(NodeType.PRIME) + node.children.extend(_md_tree_node_to_md_tree_inner_rec(c, Gb) + for c in deref(n).children) + return node + + +cdef object md_tree_node_to_md_tree(const md_tree_node *n, CGraphBackend Gb): + """ + This function converts a modular decomposition tree (given as a pointer to a + md_tree_node) into an object of the Python class Node (which is then + converted into the correct output by :meth:`Graph.modular_decomposition`). + + The graph backend is needed to convert the int stored into the md_tree_node + into the corresponding vertex of the Python graph. + + This function deals with the case of an empty tree and then delegates the + actual conversion to :func:`_md_tree_node_to_md_tree_inner_rec`. + + TESTS: + + Indirect doctests:: + + sage: from sage.graphs.graph_decompositions.modular_decomposition import * + sage: corneil_habib_paul_tedder_algorithm(Graph(1)) + NORMAL [0] + sage: corneil_habib_paul_tedder_algorithm(Graph(2)) + PARALLEL [NORMAL [0], NORMAL [1]] + sage: corneil_habib_paul_tedder_algorithm(graphs.CompleteGraph(3)) + SERIES [NORMAL [1], NORMAL [2], NORMAL [0]] + """ + if n == NULL: + return Node(NodeType.EMPTY) + return _md_tree_node_to_md_tree_inner_rec(n, Gb) +################################################################################ class NodeType(IntEnum): """ NodeType is an enumeration class used to define the various types of nodes @@ -35,7 +178,7 @@ class NodeType(IntEnum): - ``PRIME`` -- indicates the node is a prime module - - ``FOREST`` -- indicates a forest containing trees + - ``EMPTY`` -- indicates a empty tree - ``NORMAL`` -- indicates the node is normal containing a vertex """ @@ -43,101 +186,33 @@ class NodeType(IntEnum): SERIES = 1 PARALLEL = 2 NORMAL = 3 - FOREST = -1 + EMPTY = -1 def __repr__(self) -> str: r""" - String representation of this node type. + Return a string representation of a ``NodeType`` object. - EXAMPLES:: + TESTS:: sage: from sage.graphs.graph_decompositions.modular_decomposition import NodeType sage: repr(NodeType.PARALLEL) 'PARALLEL' + sage: str(NodeType.PRIME) + 'PRIME' """ return self.name - def __str__(self): - """ - String representation of this node type. - - EXAMPLES:: - - sage: from sage.graphs.graph_decompositions.modular_decomposition import NodeType - sage: str(NodeType.PARALLEL) - 'PARALLEL' - """ - return repr(self) - - -class NodeSplit(Enum): - """ - Enumeration class used to specify the split that has occurred at the node or - at any of its descendants. - - ``NodeSplit`` is defined for every node in modular decomposition tree and is - required during the refinement and promotion phase of modular decomposition - tree computation. Various node splits defined are - - - ``LEFT_SPLIT`` -- indicates a left split has occurred - - - ``RIGHT_SPLIT`` -- indicates a right split has occurred - - - ``BOTH_SPLIT`` -- indicates both left and right split have occurred - - - ``NO_SPLIT`` -- indicates no split has occurred - """ - LEFT_SPLIT = 1 - RIGHT_SPLIT = 2 - BOTH_SPLIT = 3 - NO_SPLIT = 0 - - -class VertexPosition(Enum): - """ - Enumeration class used to define position of a vertex w.r.t source in - modular decomposition. - - For computing modular decomposition of connected graphs a source vertex is - chosen. The position of vertex is w.r.t this source vertex. The various - positions defined are - - - ``LEFT_OF_SOURCE`` -- indicates vertex is to left of source and is a - neighbour of source vertex - - - ``RIGHT_OF_SOURCE`` -- indicates vertex is to right of source and is - connected to but not a neighbour of source vertex - - - ``SOURCE`` -- indicates vertex is source vertex - """ - LEFT_OF_SOURCE = -1 - RIGHT_OF_SOURCE = 1 - SOURCE = 0 + __str__ = __repr__ class Node: """ - Node class stores information about the node type, node split and index of - the node in the parent tree. + Node class stores information about the node type. Node type can be ``PRIME``, ``SERIES``, ``PARALLEL``, ``NORMAL`` or - ``FOREST``. Node split can be ``NO_SPLIT``, ``LEFT_SPLIT``, ``RIGHT_SPLIT`` - or ``BOTH_SPLIT``. A node is split in the refinement phase and the split - used is propagated to the ancestors. + ``EMPTY``. - ``node_type`` -- is of type NodeType and specifies the type of node - - - ``node_split`` -- is of type NodeSplit and specifies the type of splits - which have occurred in the node and its descendants - - - ``index_in_root`` -- specifies the index of the node in the forest - obtained after promotion phase - - - ``comp_num`` -- specifies the number given to nodes in a (co)component - before refinement - - - ``is_separated`` -- specifies whether a split has occurred with the node - as the root """ def __init__(self, node_type): r""" @@ -152,83 +227,72 @@ def __init__(self, node_type): [] """ self.node_type = node_type - self.node_split = NodeSplit.NO_SPLIT - self.index_in_root = -1 - self.comp_num = -1 - self.is_separated = False self.children = [] - def set_node_split(self, node_split): - """ - Add node_split to the node split of ``self``. + def is_prime(self): + r""" + Check whether ``self`` is a prime node. - ``LEFT_SPLIT`` and ``RIGHT_SPLIT`` can exist together in ``self`` as - ``BOTH_SPLIT``. + EXAMPLES:: - INPUT: + sage: from sage.graphs.graph_decompositions.modular_decomposition import * + sage: n = Node(NodeType.PRIME) + sage: n.children.append(Node.create_leaf(1)) + sage: n.children.append(Node.create_leaf(2)) + sage: n.is_prime() + True + sage: (n.children[0].is_prime(), n.children[1].is_prime()) + (False, False) + """ + return self.node_type == NodeType.PRIME - - ``node_split`` -- ``node_split`` to be added to ``self`` + def is_series(self): + r""" + Check whether ``self`` is a series node. EXAMPLES:: sage: from sage.graphs.graph_decompositions.modular_decomposition import * - sage: node = Node(NodeType.PRIME) - sage: node.set_node_split(NodeSplit.LEFT_SPLIT) - sage: node.node_split == NodeSplit.LEFT_SPLIT - True - sage: node.set_node_split(NodeSplit.RIGHT_SPLIT) - sage: node.node_split == NodeSplit.BOTH_SPLIT + sage: n = Node(NodeType.SERIES) + sage: n.children.append(Node.create_leaf(1)) + sage: n.children.append(Node.create_leaf(2)) + sage: n.is_series() True - sage: node = Node(NodeType.PRIME) - sage: node.set_node_split(NodeSplit.BOTH_SPLIT) - sage: node.node_split == NodeSplit.BOTH_SPLIT - True - """ - if self.node_split == NodeSplit.NO_SPLIT: - self.node_split = node_split - elif ((self.node_split == NodeSplit.LEFT_SPLIT and - node_split == NodeSplit.RIGHT_SPLIT) or - (self.node_split == NodeSplit.RIGHT_SPLIT and - node_split == NodeSplit.LEFT_SPLIT)): - self.node_split = NodeSplit.BOTH_SPLIT - - def has_left_split(self): + sage: (n.children[0].is_series(), n.children[1].is_series()) + (False, False) """ - Check whether ``self`` has ``LEFT_SPLIT``. + return self.node_type == NodeType.SERIES + + def is_empty(self): + r""" + Check whether ``self`` is an empty node. EXAMPLES:: sage: from sage.graphs.graph_decompositions.modular_decomposition import * - sage: node = Node(NodeType.PRIME) - sage: node.set_node_split(NodeSplit.LEFT_SPLIT) - sage: node.has_left_split() - True - sage: node = Node(NodeType.PRIME) - sage: node.set_node_split(NodeSplit.BOTH_SPLIT) - sage: node.has_left_split() + sage: Node(NodeType.EMPTY).is_empty() True + sage: Node.create_leaf(1).is_empty() + False """ - return (self.node_split == NodeSplit.LEFT_SPLIT or - self.node_split == NodeSplit.BOTH_SPLIT) + return self.node_type == NodeType.EMPTY - def has_right_split(self): - """ - Check whether ``self`` has ``RIGHT_SPLIT``. + def is_leaf(self): + r""" + Check whether ``self`` is a leaf. EXAMPLES:: sage: from sage.graphs.graph_decompositions.modular_decomposition import * - sage: node = Node(NodeType.PRIME) - sage: node.set_node_split(NodeSplit.RIGHT_SPLIT) - sage: node.has_right_split() - True - sage: node = Node(NodeType.PRIME) - sage: node.set_node_split(NodeSplit.BOTH_SPLIT) - sage: node.has_right_split() + sage: n = Node(NodeType.PRIME) + sage: n.children.append(Node.create_leaf(1)) + sage: n.children.append(Node.create_leaf(2)) + sage: n.is_leaf() + False + sage: all(c.is_leaf() for c in n.children) True """ - return (self.node_split == NodeSplit.RIGHT_SPLIT or - self.node_split == NodeSplit.BOTH_SPLIT) + return self.node_type == NodeType.NORMAL def __repr__(self): r""" @@ -238,24 +302,12 @@ def __repr__(self): sage: from sage.graphs.graph_decompositions.modular_decomposition import * sage: n = Node(NodeType.PRIME) - sage: n.children.append(create_normal_node(1)) - sage: n.children.append(create_normal_node(2)) + sage: n.children.append(Node.create_leaf(1)) + sage: n.children.append(Node.create_leaf(2)) sage: str(n) 'PRIME [NORMAL [1], NORMAL [2]]' """ - if self.node_type == NodeType.SERIES: - s = "SERIES " - elif self.node_type == NodeType.PARALLEL: - s = "PARALLEL " - elif self.node_type == NodeType.PRIME: - s = "PRIME " - elif self.node_type == NodeType.FOREST: - s = "FOREST " - else: - s = "NORMAL " - - s += str(self.children) - return s + return f"{self.node_type} {self.children}" def __eq__(self, other): r""" @@ -273,82 +325,30 @@ def __eq__(self, other): False """ return (self.node_type == other.node_type and - self.node_split == other.node_split and - self.index_in_root == other.index_in_root and - self.comp_num == other.comp_num and - self.is_separated == other.is_separated and self.children == other.children) + @classmethod + def create_leaf(cls, v): + """ + Return Node object that is a leaf corresponding to the vertex ``v``. -def create_prime_node(): - """ - Return a prime node with no children. - - OUTPUT: a node object with ``node_type`` set as ``NodeType.PRIME`` - - EXAMPLES:: - - sage: from sage.graphs.graph_decompositions.modular_decomposition import create_prime_node - sage: node = create_prime_node() - sage: node - PRIME [] - """ - return Node(NodeType.PRIME) - - -def create_parallel_node(): - """ - Return a parallel node with no children. - - OUTPUT: a node object with ``node_type`` set as ``NodeType.PARALLEL`` - - EXAMPLES:: - - sage: from sage.graphs.graph_decompositions.modular_decomposition import create_parallel_node - sage: node = create_parallel_node() - sage: node - PARALLEL [] - """ - return Node(NodeType.PARALLEL) - - -def create_series_node(): - """ - Return a series node with no children. - - OUTPUT: a node object with ``node_type`` set as ``NodeType.SERIES`` - - EXAMPLES:: - - sage: from sage.graphs.graph_decompositions.modular_decomposition import create_series_node - sage: node = create_series_node() - sage: node - SERIES [] - """ - return Node(NodeType.SERIES) - - -def create_normal_node(vertex): - """ - Return a normal node with no children. - - INPUT: + INPUT: - - ``vertex`` -- vertex number + - ``vertex`` -- vertex number - OUTPUT: a node object representing the vertex with ``node_type`` set as - ``NodeType.NORMAL`` + OUTPUT: a node object representing the vertex with ``node_type`` set as + ``NodeType.NORMAL`` - EXAMPLES:: + EXAMPLES:: - sage: from sage.graphs.graph_decompositions.modular_decomposition import create_normal_node - sage: node = create_normal_node(2) - sage: node - NORMAL [2] - """ - node = Node(NodeType.NORMAL) - node.children.append(vertex) - return node + sage: from sage.graphs.graph_decompositions.modular_decomposition import Node + sage: node = Node.create_leaf(2) + sage: node + NORMAL [2] + """ + node = cls(NodeType.NORMAL) + node.children.append(v) + return node def print_md_tree(root): @@ -362,7 +362,7 @@ def print_md_tree(root): EXAMPLES:: sage: from sage.graphs.graph_decompositions.modular_decomposition import * - sage: print_md_tree(modular_decomposition(graphs.IcosahedralGraph())) + sage: print_md_tree(habib_maurer_algorithm(graphs.IcosahedralGraph())) PRIME 3 4 @@ -467,6 +467,13 @@ def habib_maurer_algorithm(graph, g_classes=None): classes for the current root and each of the submodules. See also [BM1983]_ for an equivalent algorithm described in greater detail. + This function should not be used directly, it should be called via the + ``modular_decomposition`` method of ``Graph`` with the parameter + ``algorithm='habib_maurer'``. + + This functions assumes that ``graph`` is a object of the class ``Graph``, is + a simple graph and has at least 1 vertex. + INPUT: - ``graph`` -- the graph for which modular decomposition tree needs to be @@ -546,16 +553,6 @@ def habib_maurer_algorithm(graph, g_classes=None): sage: test_modular_decomposition(habib_maurer_algorithm(g), g) True - Graph from the :wikipedia:`Modular_decomposition`:: - - sage: d2 = {1:[2,3,4], 2:[1,4,5,6,7], 3:[1,4,5,6,7], 4:[1,2,3,5,6,7], - ....: 5:[2,3,4,6,7], 6:[2,3,4,5,8,9,10,11], - ....: 7:[2,3,4,5,8,9,10,11], 8:[6,7,9,10,11], 9:[6,7,8,10,11], - ....: 10:[6,7,8,9], 11:[6,7,8,9]} - sage: g = Graph(d2) - sage: test_modular_decomposition(habib_maurer_algorithm(g), g) - True - Tetrahedral Graph is Series:: sage: print_md_tree(habib_maurer_algorithm(graphs.TetrahedralGraph())) @@ -581,39 +578,18 @@ def habib_maurer_algorithm(graph, g_classes=None): TESTS: - Bad Input:: - - sage: g = DiGraph() - sage: habib_maurer_algorithm(g) - Traceback (most recent call last): - ... - ValueError: Graph must be undirected - - Empty Graph is Prime:: - - sage: g = Graph() - sage: habib_maurer_algorithm(g) - PRIME [] - - Ensure that a random graph and an isomorphic graph have identical modular decompositions. :: sage: from sage.graphs.graph_decompositions.modular_decomposition import permute_decomposition sage: permute_decomposition(2, habib_maurer_algorithm, 20, 0.5) # needs sage.groups """ - if graph.is_directed(): - raise ValueError("Graph must be undirected") - - if not graph.order(): - return create_prime_node() - if graph.order() == 1: - root = create_normal_node(next(graph.vertex_iterator())) + root = Node.create_leaf(next(graph.vertex_iterator())) return root elif not graph.is_connected(): - root = create_parallel_node() + root = Node(NodeType.PARALLEL) root.children = [habib_maurer_algorithm(graph.subgraph(vertices=sg), g_classes) for sg in graph.connected_components(sort=False)] return root @@ -621,7 +597,7 @@ def habib_maurer_algorithm(graph, g_classes=None): g_comp = graph.complement() if g_comp.is_connected(): from collections import defaultdict - root = create_prime_node() + root = Node(NodeType.PRIME) if g_classes is None: g_classes = gamma_classes(graph) vertex_set = frozenset(graph) @@ -638,19 +614,97 @@ def habib_maurer_algorithm(graph, g_classes=None): for sg in d1.values()] return root - root = create_series_node() + root = Node(NodeType.SERIES) root.children = [habib_maurer_algorithm(graph.subgraph(vertices=sg), g_classes) for sg in g_comp.connected_components(sort=False)] return root -modular_decomposition = habib_maurer_algorithm +################################################################################ +# Exported modular_decomposition function # +################################################################################ +def modular_decomposition(G, algorithm=None): + r""" + Return the modular decomposition of the current graph. + + This function should not be used directly, it should be called via the + ``modular_decomposition`` method of ``Graph``. + + INPUT: + + - ``G`` -- graph whose modular decomposition tree is to be computed + + - ``algorithm`` -- string (default: ``None``); the algorithm to use among: + + - ``None`` or ``'corneil_habib_paul_tedder'`` -- will use the + Corneil-Habib-Paul-Tedder algorithm from [TCHP2008]_, its complexity + is linear in the number of vertices and edges. + + - ``'habib_maurer'`` -- will use the Habib-Maurer algorithm from + [HM1979]_, its complexity is cubic in the number of vertices. + + OUTPUT: The modular decomposition tree, as an object of type ``Node``. + + TESTS:: + + sage: from sage.graphs.graph_decompositions.modular_decomposition import * + + sage: modular_decomposition(Graph()) + EMPTY [] + + sage: modular_decomposition(Graph(1)) + NORMAL [0] + + sage: check_algos_are_equivalent(5,\ + ....: lambda : graphs.RandomProperIntervalGraph(100)) + + sage: check_algos_are_equivalent(5, lambda : graphs.RandomGNM(75, 1000)) + + sage: modular_decomposition(DiGraph()) + Traceback (most recent call last): + ... + TypeError: the input must be an undirected Sage graph + + sage: modular_decomposition(Graph(5, loops=True)) + Traceback (most recent call last): + ... + ValueError: This method is not known to work on graphs with loops... + + sage: modular_decomposition(Graph(5, multiedges=True)) + Traceback (most recent call last): + ... + ValueError: This method is not known to work on graphs with multiedges... + + sage: modular_decomposition(Graph(), algorithm='silly walk') + Traceback (most recent call last): + ... + ValueError: unknown algorithm "silly walk" + """ + from sage.graphs.graph import Graph + if not isinstance(G, Graph): + raise TypeError("the input must be an undirected Sage graph") + G._scream_if_not_simple() + + if algorithm is None: + algorithm = "corneil_habib_paul_tedder" + + if algorithm not in ("habib_maurer", "corneil_habib_paul_tedder"): + raise ValueError(f'unknown algorithm "{algorithm}"') + + if not G.order(): + return Node(NodeType.EMPTY) + if G.order() == 1: + D = Node(NodeType.NORMAL) + D.children.append(next(G.vertex_iterator())) + return D + if algorithm == "habib_maurer": + return habib_maurer_algorithm(G) + return corneil_habib_paul_tedder_algorithm(G) # ============================================================================ # Below functions are implemented to test the modular decomposition tree # ============================================================================ - # Function implemented for testing def test_modular_decomposition(tree_root, graph): """ @@ -758,19 +812,19 @@ def get_vertices(component_root): EXAMPLES:: sage: from sage.graphs.graph_decompositions.modular_decomposition import * - sage: forest = Node(NodeType.FOREST) - sage: forest.children = [create_normal_node(2), - ....: create_normal_node(3), create_normal_node(1)] + sage: forest = Node(NodeType.PRIME) + sage: forest.children = [Node.create_leaf(2), Node.create_leaf(0), + ....: Node.create_leaf(3), Node.create_leaf(1)] sage: series_node = Node(NodeType.SERIES) - sage: series_node.children = [create_normal_node(4), - ....: create_normal_node(5)] + sage: series_node.children = [Node.create_leaf(4), + ....: Node.create_leaf(5)] sage: parallel_node = Node(NodeType.PARALLEL) - sage: parallel_node.children = [create_normal_node(6), - ....: create_normal_node(7)] + sage: parallel_node.children = [Node.create_leaf(6), + ....: Node.create_leaf(7)] sage: forest.children.insert(1, series_node) sage: forest.children.insert(3, parallel_node) sage: get_vertices(forest) - [2, 4, 5, 3, 6, 7, 1] + [2, 4, 5, 0, 6, 7, 3, 1] """ vertices = [] @@ -963,17 +1017,17 @@ def children_node_type(module, node_type): sage: from sage.graphs.graph_decompositions.modular_decomposition import * sage: g = graphs.OctahedralGraph() sage: tree_root = modular_decomposition(g) - sage: print_md_tree(modular_decomposition(g)) + sage: print_md_tree(tree_root) SERIES PARALLEL - 0 - 5 + 2 + 3 PARALLEL 1 4 PARALLEL - 2 - 3 + 0 + 5 sage: children_node_type(tree_root, NodeType.SERIES) False sage: children_node_type(tree_root, NodeType.PARALLEL) @@ -1003,14 +1057,14 @@ def either_connected_or_not_connected(v, vertices_in_module, graph): sage: print_md_tree(modular_decomposition(g)) SERIES PARALLEL - 0 - 5 + 2 + 3 PARALLEL 1 4 PARALLEL - 2 - 3 + 0 + 5 sage: either_connected_or_not_connected(2, [1, 4], g) True sage: either_connected_or_not_connected(2, [3, 4], g) @@ -1043,7 +1097,7 @@ def tree_to_nested_tuple(root): sage: from sage.graphs.graph_decompositions.modular_decomposition import * sage: g = graphs.OctahedralGraph() sage: tree_to_nested_tuple(modular_decomposition(g)) - (SERIES, [(PARALLEL, [0, 5]), (PARALLEL, [1, 4]), (PARALLEL, [2, 3])]) + (SERIES, [(PARALLEL, [2, 3]), (PARALLEL, [1, 4]), (PARALLEL, [0, 5])]) """ if root.node_type == NodeType.NORMAL: return root.children[0] @@ -1075,7 +1129,7 @@ def nested_tuple_to_tree(nest): 4 """ if not isinstance(nest, tuple): - return create_normal_node(nest) + return Node.create_leaf(nest) root = Node(nest[0]) root.children = [nested_tuple_to_tree(n) for n in nest[1:]] @@ -1187,7 +1241,7 @@ def relabel_tree(root, perm): raise TypeError("type of perm is not supported for relabeling") if root.node_type == NodeType.NORMAL: - return create_normal_node(perm[root.children[0]]) + return Node.create_leaf(perm[root.children[0]]) else: new_root = Node(root.node_type) new_root.children = [relabel_tree(child, perm) for child in root.children] @@ -1304,7 +1358,7 @@ def rand_md_tree(max_depth, parent_type): is one less than its parent's. """ if random() < leaf_probability or max_depth == 1: - root = create_normal_node(current_leaf[0]) + root = Node.create_leaf(current_leaf[0]) current_leaf[0] += 1 return root if parent_type == NodeType.PRIME: @@ -1332,18 +1386,18 @@ def rand_md_tree(max_depth, parent_type): return root -def md_tree_to_graph(root): +def md_tree_to_graph(root, prime_node_generator=None): r""" Create a graph having the given MD tree. - For the prime nodes we use that every path of length 4 or more is prime. - - TODO: accept a function that generates prime graphs as a parameter and - use that in the prime nodes. + For the prime nodes, the parameter ``prime_node_generator`` is called with + the number of vertices as the only argument. If it is ``None``, the path + graph is used (it is prime when the length is 4 or more). EXAMPLES:: sage: from sage.graphs.graph_decompositions.modular_decomposition import * + sage: from sage.graphs.graph_generators import graphs sage: tup1 = (NodeType.PRIME, 1, (NodeType.SERIES, 2, 3), ....: (NodeType.PARALLEL, 4, 5), 6) sage: tree1 = nested_tuple_to_tree(tup1) @@ -1352,30 +1406,54 @@ def md_tree_to_graph(root): ....: 4: [2, 3, 6], 5: [2, 3, 6], 6: [4, 5]}) sage: g1.is_isomorphic(g2) True + + sage: G = md_tree_to_graph(Node(NodeType.EMPTY)) + sage: G.is_isomorphic(Graph()) + True + + sage: tree = Node(NodeType.SERIES) + sage: tree.children.extend(Node.create_leaf(i) for i in range(5)) + sage: G = md_tree_to_graph(tree) + sage: G.is_isomorphic(graphs.CompleteGraph(5)) + True + + sage: tree = Node(NodeType.PRIME) + sage: tree.children.extend(Node.create_leaf(i) for i in range(5)) + sage: png = lambda n: (graphs.PathGraph if n == 4 else graphs.CycleGraph)(n) + sage: G = md_tree_to_graph(tree, prime_node_generator=png) + sage: G.is_isomorphic(graphs.CycleGraph(5)) + True """ from itertools import product, combinations from sage.graphs.graph import Graph + if prime_node_generator is None: + from sage.graphs.graph_generators import graphs + prime_node_generator = graphs.PathGraph + def tree_to_vertices_and_edges(root): r""" Give the list of vertices and edges of the graph having the given md tree. """ - if root.node_type == NodeType.NORMAL: + if root.is_leaf(): return (root.children, []) children_ve = [tree_to_vertices_and_edges(child) for child in root.children] vertices = [v for vs, es in children_ve for v in vs] edges = [e for vs, es in children_ve for e in es] vertex_lists = [vs for vs, es in children_ve] - if root.node_type == NodeType.PRIME: - for vs1, vs2 in zip(vertex_lists, vertex_lists[1:]): - for v1, v2 in product(vs1, vs2): - edges.append((v1, v2)) - elif root.node_type == NodeType.SERIES: + if root.is_prime(): + G = prime_node_generator(len(vertex_lists)) + G.relabel(range(len(vertex_lists))) + for i1, i2 in G.edge_iterator(labels=False): + edges.extend(product(vertex_lists[i1], vertex_lists[i2])) + elif root.is_series(): for vs1, vs2 in combinations(vertex_lists, 2): - for v1, v2 in product(vs1, vs2): - edges.append((v1, v2)) + edges.extend(product(vs1, vs2)) + # else: no edge to be created for PARALLEL nodes return (vertices, edges) + if root.is_empty(): + return Graph() vs, es = tree_to_vertices_and_edges(root) return Graph([vs, es], format='vertices_and_edges') @@ -1410,3 +1488,45 @@ def recreate_decomposition(trials, algorithm, max_depth, max_fan_out, assert equivalent_trees(rand_tree, reconstruction) if verbose: print("Passes!") + + +@random_testing +def check_algos_are_equivalent(trials, graph_gen, verbose=False): + r""" + Verify that both algorithms compute the same tree (up to equivalence) for + random graphs. + + INPUT: + + - ``trials`` -- integer; the number of tests the function will run. + + - ``graph_gen`` -- function; a function that can be called without argument + and returns a random graph. + + - ``verbose`` -- boolean (defaul: ``False``); enable printing debug + information. + + OUTPUT: ``None``. Raises an ``AssertionError`` on failure. + + EXAMPLES:: + + sage: from sage.graphs.graph_decompositions.modular_decomposition import * + sage: check_algos_are_equivalent(3, lambda : graphs.RandomGNP(10, 0.1)) + sage: check_algos_are_equivalent(3, lambda : graphs.RandomGNP(10, 0.5)) + sage: check_algos_are_equivalent(3, lambda : graphs.RandomGNP(10, 0.9)) + """ + for _ in range(trials): + graph = graph_gen() + if verbose: + print(graph.graph6_string()) + print(graph.to_dictionary()) + MD = [] + for algo in ('habib_maurer', 'corneil_habib_paul_tedder'): + md = modular_decomposition(graph, algorithm=algo) + MD.append(md) + if verbose: + print(f'Using {algo}:') + print_md_tree(md) + assert equivalent_trees(MD[0], MD[1]) + if verbose: + print("Passes!") diff --git a/src/sage/graphs/meson.build b/src/sage/graphs/meson.build index f1c6df2b941..3426e16c285 100644 --- a/src/sage/graphs/meson.build +++ b/src/sage/graphs/meson.build @@ -11,6 +11,7 @@ cliquer = cc.find_library('cliquer', required: not is_windows, disabler: true) planarity = cc.find_library('planarity') py.install_sources( + '__init__.py', 'all.py', 'all__sagemath_bliss.py', 'all__sagemath_mcqd.py', diff --git a/src/sage/groups/matrix_gps/meson.build b/src/sage/groups/matrix_gps/meson.build index 30b8e2379c0..77c70adf7fa 100644 --- a/src/sage/groups/matrix_gps/meson.build +++ b/src/sage/groups/matrix_gps/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'binary_dihedral.py', 'catalog.py', diff --git a/src/sage/groups/meson.build b/src/sage/groups/meson.build index a1876172c4d..3c5f20a1214 100644 --- a/src/sage/groups/meson.build +++ b/src/sage/groups/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'artin.py', 'braid.py', diff --git a/src/sage/groups/misc_gps/argument_groups.py b/src/sage/groups/misc_gps/argument_groups.py index 61c1151452b..475f260c518 100644 --- a/src/sage/groups/misc_gps/argument_groups.py +++ b/src/sage/groups/misc_gps/argument_groups.py @@ -1006,7 +1006,7 @@ def __init__(self, category): Rational Field """ from sage.rings.rational_field import QQ - return super().__init__(base=QQ, category=category) + super().__init__(base=QQ, category=category) def _repr_(self): r""" @@ -1626,7 +1626,7 @@ def __init__(self, category): sage: S.base() # indirect doctest """ - return super().__init__(base=int, category=category) + super().__init__(base=int, category=category) def _repr_(self): r""" diff --git a/src/sage/groups/perm_gps/meson.build b/src/sage/groups/perm_gps/meson.build index e986fcd964a..6ad30d99fe2 100644 --- a/src/sage/groups/perm_gps/meson.build +++ b/src/sage/groups/perm_gps/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'constructor.py', 'cubegroup.py', diff --git a/src/sage/groups/perm_gps/partn_ref/data_structures.pxd b/src/sage/groups/perm_gps/partn_ref/data_structures.pxd index 1cbb95231d5..af6da5d606f 100644 --- a/src/sage/groups/perm_gps/partn_ref/data_structures.pxd +++ b/src/sage/groups/perm_gps/partn_ref/data_structures.pxd @@ -81,7 +81,10 @@ cdef inline int OP_copy_from_to(OrbitPartition *OP, OrbitPartition *OP2) noexcep - OP2.degree == OP.degree - OP2.num_cells == OP.num_cells """ - memcpy(OP2.parent, OP.parent, 4*OP.degree * sizeof(int) ) + memcpy(OP2.parent, OP.parent, OP.degree * sizeof(int)) + memcpy(OP2.rank, OP.rank, OP.degree * sizeof(int)) + memcpy(OP2.mcr, OP.mcr, OP.degree * sizeof(int)) + memcpy(OP2.size, OP.size, OP.degree * sizeof(int)) cdef inline OrbitPartition *OP_copy(OrbitPartition *OP) noexcept: """ @@ -138,38 +141,7 @@ cdef inline void OP_join(OrbitPartition *OP, int m, int n) noexcept: if m_root != n_root: OP.num_cells -= 1 - -cdef inline void OP_make_set(OrbitPartition *OP) noexcept: - cdef int i, n = OP.degree - cdef int *new_parent, *new_rank, *new_mcr, *new_size - - cdef int *int_array = sig_malloc(4*(n+1) * sizeof(int)) - if int_array is NULL: - raise MemoryError("MemoryError allocating int_array in make_set method") - - OP.degree = n + 1 - OP.num_cells = OP.num_cells + 1 - new_parent = int_array - new_rank = int_array + (n + 1) - new_mcr = int_array + (2*n + 2) - new_size = int_array + (3 * n + 3) - - memcpy(new_parent, OP.parent, n * sizeof(int)) - memcpy(new_rank, OP.rank, n * sizeof(int)) - memcpy(new_mcr, OP.mcr, n * sizeof(int)) - memcpy(new_size, OP.size, n * sizeof(int)) - - new_parent[n] = n - new_rank[n] = 0 - new_mcr[n] = n - new_size[n] = 1 - - sig_free(OP.parent) - - OP.parent = new_parent - OP.rank = new_rank - OP.mcr = new_mcr - OP.size = new_size +cdef void OP_make_set(OrbitPartition *OP) noexcept cdef inline int OP_merge_list_perm(OrbitPartition *OP, int *gamma) noexcept: """ diff --git a/src/sage/groups/perm_gps/partn_ref/data_structures.pyx b/src/sage/groups/perm_gps/partn_ref/data_structures.pyx index a3ef7193d3d..214e5fda627 100644 --- a/src/sage/groups/perm_gps/partn_ref/data_structures.pyx +++ b/src/sage/groups/perm_gps/partn_ref/data_structures.pyx @@ -46,25 +46,34 @@ cdef inline OrbitPartition *OP_new(int n) noexcept: """ cdef OrbitPartition *OP = \ sig_malloc(sizeof(OrbitPartition)) - cdef int *int_array = sig_malloc( 4*n * sizeof(int) ) - if OP is NULL or int_array is NULL: + if OP is NULL: + return NULL + OP.parent = sig_malloc(n * sizeof(int)) + OP.rank = sig_malloc(n * sizeof(int)) + OP.mcr = sig_malloc(n * sizeof(int)) + OP.size = sig_malloc(n * sizeof(int)) + if OP.parent is NULL or OP.rank is NULL or OP.mcr is NULL or OP.size is NULL: + sig_free(OP.parent) + sig_free(OP.rank) + sig_free(OP.mcr) + sig_free(OP.size) sig_free(OP) - sig_free(int_array) return NULL OP.degree = n OP.num_cells = n - OP.parent = int_array - OP.rank = int_array + n - OP.mcr = int_array + 2*n - OP.size = int_array + 3*n OP_clear(OP) return OP + cdef inline void OP_dealloc(OrbitPartition *OP) noexcept: if OP is not NULL: sig_free(OP.parent) + sig_free(OP.rank) + sig_free(OP.mcr) + sig_free(OP.size) sig_free(OP) + cdef OP_string(OrbitPartition *OP): """ Return a string representation of the OrbitPartition. @@ -78,6 +87,33 @@ cdef OP_string(OrbitPartition *OP): return s +cdef inline void OP_make_set(OrbitPartition *OP) noexcept: + """ + Increase the degree of the input partition by one. + + An error is raised in case of memory allocation failure. + """ + cdef int n = OP.degree + + OP.parent = sig_realloc(OP.parent, (n + 1) * sizeof(int)) + OP.rank = sig_realloc(OP.rank, (n + 1) * sizeof(int)) + OP.mcr = sig_realloc(OP.mcr, (n + 1) * sizeof(int)) + OP.size = sig_realloc(OP.size, (n + 1) * sizeof(int)) + if OP.parent is NULL or OP.rank is NULL or OP.mcr is NULL or OP.size is NULL: + sig_free(OP.parent) + sig_free(OP.rank) + sig_free(OP.mcr) + sig_free(OP.size) + raise MemoryError("unable to reallocate memory in OP_make_set method") + OP.degree = n + 1 + OP.num_cells = OP.num_cells + 1 + + OP.parent[n] = n + OP.rank[n] = 0 + OP.mcr[n] = n + OP.size[n] = 1 + + def OP_represent(int n, merges, perm): """ Demonstration and testing. @@ -198,6 +234,7 @@ cdef inline PartitionStack *PS_new(int n, bint unit_partition) noexcept: PS_unit_partition(PS) return PS + cdef void PS_unit_partition(PartitionStack *PS) noexcept: """ Set partition stack to a single partition with a single cell. @@ -210,6 +247,7 @@ cdef void PS_unit_partition(PartitionStack *PS) noexcept: PS.entries[n-1] = n - 1 PS.levels[n-1] = -1 + cdef inline PartitionStack *PS_copy(PartitionStack *PS) noexcept: """ Allocate and return a pointer to a copy of PartitionStack PS. Return a null @@ -229,11 +267,13 @@ cdef inline PartitionStack *PS_copy(PartitionStack *PS) noexcept: PS_copy_from_to(PS, PS2) return PS2 + cdef inline void PS_dealloc(PartitionStack *PS) noexcept: if PS is not NULL: sig_free(PS.entries) sig_free(PS) + cdef PartitionStack *PS_from_list(list L) noexcept: """ Allocate and return a pointer to a PartitionStack representing L. Return a @@ -259,6 +299,7 @@ cdef PartitionStack *PS_from_list(list L) noexcept: PS.degree = n return PS + cdef PS_print(PartitionStack *PS): """ Print a visual representation of PS. @@ -267,6 +308,7 @@ cdef PS_print(PartitionStack *PS): for i in range(PS.depth + 1): PS_print_partition(PS, i) + cdef PS_print_partition(PartitionStack *PS, int k): """ Print the partition at depth k. @@ -281,6 +323,7 @@ cdef PS_print_partition(PartitionStack *PS, int k): s = s[:-1] + ')' print(s) + cdef int PS_first_smallest(PartitionStack *PS, bitset_t b, int *second_pos=NULL) noexcept: """ Find the first occurrence of the smallest cell of size greater than one, @@ -345,7 +388,7 @@ cdef int PS_all_new_cells(PartitionStack *PS, bitset_t** nonsingletons_ptr) noex bitset_init(nonsingletons[count-1], n) bitset_copy(nonsingletons[count-1], scratch) else: - if beg==0: + if beg == 0: nonsingletons = sig_realloc(nonsingletons, sizeof(bitset_t)) if nonsingletons is NULL: raise MemoryError("Memory error in PS_all_new_cells") @@ -357,6 +400,7 @@ cdef int PS_all_new_cells(PartitionStack *PS, bitset_t** nonsingletons_ptr) noex nonsingletons_ptr[0] = nonsingletons return count + cdef int PS_find_element(PartitionStack *PS, bitset_t b, int x) except -1: """ Find the cell containing x, store its entries to b and return the location @@ -380,6 +424,7 @@ cdef int PS_find_element(PartitionStack *PS, bitset_t b, int x) except -1: i += 1 return location + cdef list PS_singletons(PartitionStack * part): """ Return the list of all singletons in the ``PartitionStack``. @@ -549,6 +594,7 @@ cdef enum: default_num_gens = 8 default_num_bits = 64 + cdef StabilizerChain *SC_new(int n, bint init_gens=True) noexcept: """ Allocate and return a pointer to a new StabilizerChain of degree n. Return @@ -619,6 +665,7 @@ cdef StabilizerChain *SC_new(int n, bint init_gens=True) noexcept: return SC + cdef inline int SC_realloc_gens(StabilizerChain *SC, int level, int size) noexcept: """ Reallocate generator array at level `level` to size `size`. @@ -641,6 +688,7 @@ cdef inline int SC_realloc_gens(StabilizerChain *SC, int level, int size) noexce SC.array_size[level] = size return 0 + cdef inline void SC_dealloc(StabilizerChain *SC) noexcept: cdef int i, n if SC is not NULL: @@ -649,13 +697,14 @@ cdef inline void SC_dealloc(StabilizerChain *SC) noexcept: for i in range(n): sig_free(SC.generators[i]) sig_free(SC.gen_inverses[i]) - sig_free(SC.generators) # frees int_ptrs - sig_free(SC.orbit_sizes) # frees int_array + sig_free(SC.generators) # frees int_ptrs + sig_free(SC.orbit_sizes) # frees int_array sig_free(SC.gen_used.bits) sig_free(SC.gen_is_id.bits) OP_dealloc(SC.OP_scratch) sig_free(SC) + cdef StabilizerChain *SC_symmetric_group(int n) noexcept: """ Return a stabilizer chain for the symmetric group on {0, 1, ..., n-1}. @@ -690,13 +739,14 @@ cdef StabilizerChain *SC_symmetric_group(int n) noexcept: SC.parents[i][i+j] = b SC.labels[i][i+j] = j for j in range(n - i - 1): - #j-th generator sends i+j+1 to b + # j-th generator sends i+j+1 to b memcpy(SC.generators[i] + n*j, id_perm, n * sizeof(int) ) SC.generators[i][n*j + i+j+1] = b SC.generators[i][n*j + b] = i+j+1 memcpy(SC.gen_inverses[i] + n*j, SC.generators[i] + n*j, n * sizeof(int) ) return SC + cdef StabilizerChain *SC_alternating_group(int n) noexcept: """ Return a stabilizer chain for the alternating group on {0, 1, ..., n-1}. @@ -733,7 +783,7 @@ cdef StabilizerChain *SC_alternating_group(int n) noexcept: SC.labels[i][i+j] = j SC.labels[i][n-1] = -(n-i-2) for j in range(n - i - 2): - #j-th generator sends i+j+1 to b, i+j+2 to i+j+1, and b to i+j+2 + # j-th generator sends i+j+1 to b, i+j+2 to i+j+1, and b to i+j+2 memcpy(SC.generators[i] + n*j, id_perm, n * sizeof(int) ) SC.generators[i][n*j + i+j+1] = b SC.generators[i][n*j + b ] = i+j+2 @@ -741,6 +791,7 @@ cdef StabilizerChain *SC_alternating_group(int n) noexcept: SC_invert_perm(SC.gen_inverses[i] + n*j, SC.generators[i] + n*j, n) return SC + cdef int SC_realloc_bitsets(StabilizerChain *SC, unsigned long size) noexcept: """ If size is larger than current allocation, double the size of the bitsets @@ -770,11 +821,14 @@ cdef int SC_realloc_bitsets(StabilizerChain *SC, unsigned long size) noexcept: SC.gen_used.size = new_size SC.gen_is_id.size = new_size SC.gen_used.bits[size_old >> index_shift] &= limb_lower_bits_down(size_old) - memset(SC.gen_used.bits + (size_old >> index_shift) + 1, 0, (limbs - (size_old >> index_shift) - 1) * sizeof(unsigned long)) + memset(SC.gen_used.bits + (size_old >> index_shift) + 1, 0, + (limbs - (size_old >> index_shift) - 1) * sizeof(unsigned long)) SC.gen_is_id.bits[size_old >> index_shift] &= limb_lower_bits_down(size_old) - memset(SC.gen_is_id.bits + (size_old >> index_shift) + 1, 0, (limbs - (size_old >> index_shift) - 1) * sizeof(unsigned long)) + memset(SC.gen_is_id.bits + (size_old >> index_shift) + 1, 0, + (limbs - (size_old >> index_shift) - 1) * sizeof(unsigned long)) return 0 + cdef StabilizerChain *SC_copy(StabilizerChain *SC, int level) noexcept: """ Create a copy of the first `level` levels of SC. Must have 0 < level. @@ -800,15 +854,16 @@ cdef StabilizerChain *SC_copy(StabilizerChain *SC, int level) noexcept: SC_dealloc(SCC) return NULL SCC.array_size[i] = default_num_gens - SC_copy_nomalloc(SCC, SC, level) # no chance for memory error here... + SC_copy_nomalloc(SCC, SC, level) # no chance for memory error here... return SCC + cdef int SC_copy_nomalloc(StabilizerChain *SC_dest, StabilizerChain *SC, int level) noexcept: cdef int i, n = SC.degree level = min(level, SC.base_size) SC_dest.base_size = level - memcpy(SC_dest.orbit_sizes, SC.orbit_sizes, 2*n * sizeof(int) ) # copies orbit_sizes, num_gens - memcpy(SC_dest.base_orbits[0], SC.base_orbits[0], 3*n*n * sizeof(int) ) # copies base_orbits, parents, labels + memcpy(SC_dest.orbit_sizes, SC.orbit_sizes, 2*n * sizeof(int) ) # copies orbit_sizes, num_gens + memcpy(SC_dest.base_orbits[0], SC.base_orbits[0], 3*n*n * sizeof(int) ) # copies base_orbits, parents, labels for i in range(level): if SC.num_gens[i] > SC_dest.array_size[i]: if SC_realloc_gens(SC_dest, i, max(SC.num_gens[i], 2*SC_dest.array_size[i])): @@ -817,6 +872,7 @@ cdef int SC_copy_nomalloc(StabilizerChain *SC_dest, StabilizerChain *SC, int lev memcpy(SC_dest.gen_inverses[i], SC.gen_inverses[i], SC.num_gens[i]*n * sizeof(int) ) return 0 + cdef SC_print_level(StabilizerChain *SC, int level): cdef int i, j, n = SC.degree if level < SC.base_size: @@ -856,6 +912,7 @@ cdef StabilizerChain *SC_new_base(StabilizerChain *SC, int *base, int base_len) return NULL return NEW + cdef int SC_new_base_nomalloc(StabilizerChain *SC_dest, StabilizerChain *SC, int *base, int base_len) noexcept: cdef int i SC_dest.base_size = 0 @@ -866,6 +923,7 @@ cdef int SC_new_base_nomalloc(StabilizerChain *SC_dest, StabilizerChain *SC, int return 1 return 0 + cdef int SC_update(StabilizerChain *dest, StabilizerChain *source, int level) noexcept: cdef mpz_t src_order, dst_order cdef int *perm = dest.perm_scratch @@ -900,6 +958,7 @@ cdef int SC_update(StabilizerChain *dest, StabilizerChain *source, int level) no mpz_clear(dst_order) return 0 + cdef StabilizerChain *SC_insert_base_point(StabilizerChain *SC, int level, int p) noexcept: """ Insert the point ``p`` as a base point on level ``level``. Return a new @@ -927,6 +986,7 @@ cdef StabilizerChain *SC_insert_base_point(StabilizerChain *SC, int level, int p return NULL return NEW + cdef int SC_insert_base_point_nomalloc(StabilizerChain *SC_dest, StabilizerChain *SC, int level, int p) noexcept: cdef int i, b SC_copy_nomalloc(SC_dest, SC, level) @@ -939,6 +999,7 @@ cdef int SC_insert_base_point_nomalloc(StabilizerChain *SC_dest, StabilizerChain return 1 return 0 + cdef int SC_re_tree(StabilizerChain *SC, int level, int *perm, int x) noexcept: """ Return values: @@ -980,6 +1041,7 @@ cdef int SC_re_tree(StabilizerChain *SC, int level, int *perm, int x) noexcept: i += 1 return 0 + cdef int SC_sift(StabilizerChain *SC, int level, int x, int *gens, int num_gens, int *new_gens) noexcept: """ Apply Schreier's subgroup lemma[1] as follows. Given a level, a point x, and @@ -1001,8 +1063,8 @@ cdef int SC_sift(StabilizerChain *SC, int level, int x, int *gens, int num_gens, # copy a representative taking base to the point x to each of these cdef int i - cdef int *temp = SC.gen_inverses[level] + n*SC.num_gens[level] # one more scratch space - # (available since num_gens > 0) + cdef int *temp = SC.gen_inverses[level] + n*SC.num_gens[level] # one more scratch space + # (available since num_gens > 0) cdef int *rep_inv = temp SC_identify(rep_inv, n) SC_compose_up_to_base(SC, level, x, rep_inv) @@ -1026,6 +1088,7 @@ cdef int SC_sift(StabilizerChain *SC, int level, int x, int *gens, int num_gens, SC_mult_perms(perm, perm, perm_rep_inv, n) return SC_insert(SC, level+1, new_gens, num_gens) + cdef int SC_insert_and_sift(StabilizerChain *SC, int level, int *pi, int num_perms, bint sift) noexcept: cdef int i, j, b, n = SC.degree cdef int perm_gen_index @@ -1075,7 +1138,7 @@ cdef int SC_insert_and_sift(StabilizerChain *SC, int level, int *pi, int num_per bitset_set(&SC.gen_used, perm_gen_index) if SC_re_tree(SC, level, perm, x): return 1 - start_over = 1 # we must look anew + start_over = 1 # we must look anew break if start_over: break @@ -1087,7 +1150,7 @@ cdef int SC_insert_and_sift(StabilizerChain *SC, int level, int *pi, int num_per # now we have an x which maps to a new point under perm, if SC_re_tree(SC, level, perm, x): return 1 - start_over = 1 # we must look anew + start_over = 1 # we must look anew break if start_over: break @@ -1098,7 +1161,7 @@ cdef int SC_insert_and_sift(StabilizerChain *SC, int level, int *pi, int num_per # now we have an x which maps to a new point under perm, if SC_re_tree(SC, level, perm, x): return 1 - start_over = 1 # we must look anew + start_over = 1 # we must look anew break if not sift: return 0 @@ -1133,6 +1196,7 @@ cdef int SC_insert_and_sift(StabilizerChain *SC, int level, int *pi, int num_per section += 1 return 0 + cdef bint SC_is_giant(int n, int num_perms, int *perms, float p, bitset_t support) noexcept: """ Test whether the group generated by the input permutations is a giant, i.e., @@ -1658,6 +1722,7 @@ cdef int sort_by_function(PartitionStack *PS, int start, int *degrees) noexcept: j += 1 return max_location + cdef int refine_by_orbits(PartitionStack *PS, StabilizerChain *SC, int *perm_stack, int *cells_to_refine_by, int *ctrb_len) noexcept: """ Given a stabilizer chain SC, refine the partition stack PS so that each cell @@ -1698,6 +1763,7 @@ cdef int refine_by_orbits(PartitionStack *PS, StabilizerChain *SC, int *perm_sta start += i return invariant + cdef int compute_relabeling(StabilizerChain *group, StabilizerChain *scratch_group, int *permutation, int *relabeling) noexcept: """ diff --git a/src/sage/groups/perm_gps/partn_ref/meson.build b/src/sage/groups/perm_gps/partn_ref/meson.build index 092b0d0b2c8..933d72d2729 100644 --- a/src/sage/groups/perm_gps/partn_ref/meson.build +++ b/src/sage/groups/perm_gps/partn_ref/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'automorphism_group_canonical_label.pxd', 'canonical_augmentation.pxd', diff --git a/src/sage/groups/perm_gps/partn_ref2/meson.build b/src/sage/groups/perm_gps/partn_ref2/meson.build index ef97195574e..69b9bee042d 100644 --- a/src/sage/groups/perm_gps/partn_ref2/meson.build +++ b/src/sage/groups/perm_gps/partn_ref2/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'refinement_generic.pxd', subdir: 'sage/groups/perm_gps/partn_ref2', diff --git a/src/sage/groups/semimonomial_transformations/meson.build b/src/sage/groups/semimonomial_transformations/meson.build index 402cb5244e5..f7958976271 100644 --- a/src/sage/groups/semimonomial_transformations/meson.build +++ b/src/sage/groups/semimonomial_transformations/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'semimonomial_transformation.pxd', 'semimonomial_transformation_group.py', diff --git a/src/sage/interacts/library.py b/src/sage/interacts/library.py index a41ba32b393..a58a0748477 100644 --- a/src/sage/interacts/library.py +++ b/src/sage/interacts/library.py @@ -2,7 +2,7 @@ Sage Interacts Sage interacts are applications of the `@interact decorator <../../sagenb/notebook/interact.html>`_. -They are conveniently accessible in the Sage Notebook via ``interacts.[TAB].[TAB]()``. +They are conveniently accessible in the Sage notebook via ``interacts.[TAB].[TAB]()``. The first ``[TAB]`` lists categories and the second ``[TAB]`` reveals the interact examples. EXAMPLES: diff --git a/src/sage/interacts/meson.build b/src/sage/interacts/meson.build index 4889c06f9fa..b0542f38853 100644 --- a/src/sage/interacts/meson.build +++ b/src/sage/interacts/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'algebra.py', 'all.py', 'calculus.py', diff --git a/src/sage/interfaces/meson.build b/src/sage/interfaces/meson.build index 92677c45730..70ce7ce88c8 100644 --- a/src/sage/interfaces/meson.build +++ b/src/sage/interfaces/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'abc.py', 'all.py', 'all__sagemath_polyhedra.py', diff --git a/src/sage/lfunctions/meson.build b/src/sage/lfunctions/meson.build index cf0ffe05e17..24e2f156f5e 100644 --- a/src/sage/lfunctions/meson.build +++ b/src/sage/lfunctions/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'dokchitser.py', 'lcalc.py', diff --git a/src/sage/libs/gap/meson.build b/src/sage/libs/gap/meson.build index 2302a169cb2..def07898f4c 100644 --- a/src/sage/libs/gap/meson.build +++ b/src/sage/libs/gap/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'all_documented_functions.py', 'assigned_names.py', diff --git a/src/sage/libs/meson.build b/src/sage/libs/meson.build index eeaa9293cff..e64a26e69b8 100644 --- a/src/sage/libs/meson.build +++ b/src/sage/libs/meson.build @@ -11,6 +11,7 @@ homfly = cc.find_library( ) py.install_sources( + '__init__.py', 'all.py', 'all__sagemath_coxeter3.py', 'all__sagemath_meataxe.py', diff --git a/src/sage/libs/mpfi/__init__.pxd b/src/sage/libs/mpfi/__init__.pxd index b55a5129d18..6b81af16593 100644 --- a/src/sage/libs/mpfi/__init__.pxd +++ b/src/sage/libs/mpfi/__init__.pxd @@ -26,7 +26,7 @@ cdef extern from "mpfi.h": int mpfi_set_z(mpfi_ptr, mpz_t) int mpfi_set_q(mpfi_ptr, mpq_t) int mpfi_set_fr(mpfi_ptr, mpfr_srcptr) - int mpfi_set_str(mpfi_ptr, char *, int) + int mpfi_set_str(mpfi_ptr, const char *, int) # combined initialization and assignment functions int mpfi_init_set(mpfi_ptr, mpfi_srcptr) @@ -36,7 +36,7 @@ cdef extern from "mpfi.h": int mpfi_init_set_z(mpfi_ptr, mpz_srcptr) int mpfi_init_set_q(mpfi_ptr, mpq_srcptr) int mpfi_init_set_fr(mpfi_ptr, mpfr_srcptr) - int mpfi_init_set_str(mpfi_ptr, char *, int) + int mpfi_init_set_str(mpfi_ptr, const char *, int) # swapping two intervals void mpfi_swap(mpfi_ptr, mpfi_ptr) diff --git a/src/sage/libs/mpfr/__init__.pxd b/src/sage/libs/mpfr/__init__.pxd index facac9aa6c7..8bb85ff3c1a 100644 --- a/src/sage/libs/mpfr/__init__.pxd +++ b/src/sage/libs/mpfr/__init__.pxd @@ -27,7 +27,7 @@ cdef extern from "mpfr.h": # int mpfr_set_f(mpfr_t rop, mpf_t op, mpfr_rnd_t rnd) int mpfr_set_ui_2exp(mpfr_t rop, unsigned long int op, mp_exp_t e, mpfr_rnd_t rnd) int mpfr_set_si_2exp(mpfr_t rop, long int op, mp_exp_t e, mpfr_rnd_t rnd) - int mpfr_set_str(mpfr_t rop, char *s, int base, mpfr_rnd_t rnd) + int mpfr_set_str(mpfr_t rop, const char *s, int base, mpfr_rnd_t rnd) int mpfr_strtofr(mpfr_t rop, char *nptr, char **endptr, int base, mpfr_rnd_t rnd) void mpfr_set_inf(mpfr_t x, int sign) void mpfr_set_nan(mpfr_t x) @@ -43,7 +43,7 @@ cdef extern from "mpfr.h": int mpfr_init_set_z(mpfr_t rop, mpz_t op, mpfr_rnd_t rnd) int mpfr_init_set_q(mpfr_t rop, mpq_t op, mpfr_rnd_t rnd) # int mpfr_init_set_f(mpfr_t rop, mpf_t op, mpfr_rnd_t rnd) - int mpfr_init_set_str(mpfr_t x, char *s, int base, mpfr_rnd_t rnd) + int mpfr_init_set_str(mpfr_t x, const char *s, int base, mpfr_rnd_t rnd) # Conversion Functions double mpfr_get_d(mpfr_t op, mpfr_rnd_t rnd) diff --git a/src/sage/libs/mpmath/ext_main.pyx b/src/sage/libs/mpmath/ext_main.pyx index 96111f7b942..03840b3d8df 100644 --- a/src/sage/libs/mpmath/ext_main.pyx +++ b/src/sage/libs/mpmath/ext_main.pyx @@ -580,8 +580,7 @@ cdef class Context: s = (x).re.special t = (x).im.special return s == S_NAN or t == S_NAN - if type(x) is int or type(x) is long or isinstance(x, Integer) \ - or isinstance(x, rationallib.mpq): + if isinstance(x, (int, Integer, rationallib.mpq)): return False typ = MPF_set_any(&tmp_opx_re, &tmp_opx_im, x, global_opts, 0) if typ == 1: @@ -622,8 +621,7 @@ cdef class Context: s = (x).re.special t = (x).im.special return s == S_INF or s == S_NINF or t == S_INF or t == S_NINF - if type(x) is int or type(x) is long or isinstance(x, Integer) \ - or isinstance(x, rationallib.mpq): + if isinstance(x, (int, Integer, rationallib.mpq)): return False typ = MPF_set_any(&tmp_opx_re, &tmp_opx_im, x, global_opts, 0) if typ == 1: @@ -671,8 +669,7 @@ cdef class Context: if re == libmp.fzero: return im_normal if im == libmp.fzero: return re_normal return re_normal and im_normal - if type(x) is int or type(x) is long or isinstance(x, Integer) \ - or isinstance(x, rationallib.mpq): + if isinstance(x, (int, Integer, rationallib.mpq)): return bool(x) x = ctx.convert(x) if hasattr(x, '_mpf_') or hasattr(x, '_mpc_'): @@ -708,7 +705,7 @@ cdef class Context: cdef MPF v cdef MPF w cdef int typ - if type(x) is int or type(x) is long or isinstance(x, Integer): + if isinstance(x, (int, Integer)): return True if isinstance(x, mpf): v = (x).value diff --git a/src/sage/libs/singular/singular.pyx b/src/sage/libs/singular/singular.pyx index 9c7b4078583..e2ca2b02e75 100644 --- a/src/sage/libs/singular/singular.pyx +++ b/src/sage/libs/singular/singular.pyx @@ -1535,7 +1535,7 @@ cdef inline number *sa2si_ZZmod(IntegerMod_abstract d, ring *_ring) noexcept: cdef nMapFunc nMapFuncPtr = NULL if _ring.cf.type == n_Z2m: - _d = long(d) + _d = d return nr2mMapZp(_d, currRing.cf, _ring.cf) elif _ring.cf.type == n_Zn or _ring.cf.type == n_Znm: lift = d.lift() diff --git a/src/sage/matrix/matrix_modn_dense_template.pxi b/src/sage/matrix/matrix_modn_dense_template.pxi index 96ea3b69ad8..03e60d56394 100644 --- a/src/sage/matrix/matrix_modn_dense_template.pxi +++ b/src/sage/matrix/matrix_modn_dense_template.pxi @@ -73,7 +73,7 @@ We test corner cases for multiplication:: ....: pass """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2004,2005,2006 William Stein # Copyright (C) 2011 Burcin Erocal # Copyright (C) 2011 Martin Albrecht @@ -83,8 +83,8 @@ We test corner cases for multiplication:: # 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 libc.stdint cimport uint64_t from cpython.bytes cimport * @@ -100,7 +100,7 @@ from sage.parallel.parallelism import Parallelism cimport sage.rings.fast_arith cdef sage.rings.fast_arith.arith_int ArithIntObj -ArithIntObj = sage.rings.fast_arith.arith_int() +ArithIntObj = sage.rings.fast_arith.arith_int() # for copying/pickling from libc.string cimport memcpy @@ -110,7 +110,7 @@ from sage.modules.vector_modn_dense cimport Vector_modn_dense from sage.arith.misc import is_prime from sage.structure.element cimport (Element, Vector, Matrix, - ModuleElement, RingElement) + ModuleElement, RingElement) from sage.matrix.matrix_dense cimport Matrix_dense from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense from sage.rings.finite_rings.integer_mod cimport IntegerMod_int, IntegerMod_abstract @@ -146,7 +146,7 @@ cdef inline celement_invert(celement a, celement n): # always: gcd (n,residue) = gcd (x_int,y_int) # sx*n + tx*residue = x_int # sy*n + ty*residue = y_int - q = x_int / y_int # integer quotient + q = x_int / y_int # integer quotient temp = y_int y_int = x_int - q * y_int x_int = temp @@ -155,7 +155,7 @@ cdef inline celement_invert(celement a, celement n): tx = temp if tx < 0: - tx += n + tx += n # now x_int = gcd (n,residue) return tx @@ -175,9 +175,8 @@ cdef inline linbox_echelonize(celement modulus, celement* entries, Py_ssize_t nr """ Return the reduced row echelon form of this matrix. """ - if linbox_is_zero(modulus, entries, nrows, ncols): - return 0,[] + return 0, [] cdef Py_ssize_t i, j cdef ModField *F = new ModField(modulus) @@ -217,12 +216,12 @@ cdef inline linbox_echelonize_efd(celement modulus, celement* entries, Py_ssize_ # which would yield a segfault in Sage's debug version. TODO: Fix # that bug upstream. if nrows == 0 or ncols == 0: - return 0,[] + return 0, [] cdef ModField *F = new ModField(modulus) cdef DenseMatrix *A = new DenseMatrix(F[0], nrows, ncols) - cdef Py_ssize_t i,j + cdef Py_ssize_t i, j for i in range(nrows): for j in range(ncols): A.setEntry(i, j, entries[i*ncols+j]) @@ -230,12 +229,12 @@ cdef inline linbox_echelonize_efd(celement modulus, celement* entries, Py_ssize_ cdef Py_ssize_t r = reducedRowEchelonize(A[0]) for i in range(nrows): for j in range(ncols): - entries[i*ncols+j] = A.getEntry(i,j) + entries[i*ncols+j] = A.getEntry(i, j) cdef Py_ssize_t ii = 0 cdef list pivots = [] for i in range(r): - for j in range(ii,ncols): + for j in range(ii, ncols): if entries[i*ncols+j] == 1: pivots.append(j) ii = j+1 @@ -316,10 +315,10 @@ cdef inline celement linbox_matrix_matrix_multiply(celement modulus, celement* a pfgemm(F[0], FflasNoTrans, FflasNoTrans, m, n, k, one, A, k, B, n, zero, ans, n, nbthreads) - else : + else: fgemm(F[0], FflasNoTrans, FflasNoTrans, m, n, k, one, - A, k, B, n, zero, - ans, n) + A, k, B, n, zero, + ans, n) if m*n*k > 100000: sig_off() @@ -339,7 +338,7 @@ cdef inline int linbox_matrix_vector_multiply(celement modulus, celement* C, cel sig_on() fgemv(F[0], trans, m, n, one, A, n, b, 1, - zero, C, 1) + zero, C, 1) if m*n > 100000: sig_off() @@ -360,9 +359,7 @@ cdef inline linbox_minpoly(celement modulus, Py_ssize_t nrows, celement* entries if nrows*nrows > 1000: sig_off() - l = [] - for i in range(minP.size()): - l.append( minP.at(i) ) + l = [minP.at(i) for i in range(minP.size())] del F return l @@ -420,10 +417,10 @@ cpdef __matrix_from_rows_of_matrices(X): ``Matrix_modn_dense_float/double._matrix_from_rows_of_matrices`` """ # The code below is just a fast version of the following: - ## from constructor import matrix - ## K = X[0].base_ring() - ## v = sum([y.list() for y in X],[]) - ## return matrix(K, len(X), X[0].nrows()*X[0].ncols(), v) + # from constructor import matrix + # K = X[0].base_ring() + # v = sum([y.list() for y in X],[]) + # return matrix(K, len(X), X[0].nrows()*X[0].ncols(), v) cdef Matrix_modn_dense_template T cdef Py_ssize_t i, n, m @@ -444,7 +441,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): cdef long p = self._base_ring.characteristic() self.p = p if p >= MAX_MODULUS: - raise OverflowError("p (=%s) must be < %s."%(p, MAX_MODULUS)) + raise OverflowError("p (=%s) must be < %s." % (p, MAX_MODULUS)) if zeroed_alloc: self._entries = check_calloc(self._nrows * self._ncols, sizeof(celement)) @@ -729,7 +726,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): PyBytes_AsStringAndSize(s, &buf, &buflen) if buflen != expectedlen: - raise ValueError("incorrect size in matrix pickle (expected %d, got %d)"%(expectedlen, buflen)) + raise ValueError("incorrect size in matrix pickle (expected %d, got %d)" % (expectedlen, buflen)) sig_on() try: @@ -746,7 +743,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): for i from 0 <= i < self._nrows: row_self = self._matrix[i] for j from 0 <= j < self._ncols: - v = (us[0]) + v = (us[0]) v += (us[1]) << 8 v += (us[2]) << 16 v += (us[3]) << 24 @@ -758,7 +755,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): for i from 0 <= i < self._nrows: row_self = self._matrix[i] for j from 0 <= j < self._ncols: - v = (us[word_size-1]) + v = (us[word_size-1]) v += (us[word_size-2]) << 8 v += (us[word_size-3]) << 16 v += (us[word_size-4]) << 24 @@ -790,7 +787,8 @@ cdef class Matrix_modn_dense_template(Matrix_dense): cdef Matrix_modn_dense_template M cdef celement p = self.p - M = self.__class__.__new__(self.__class__, self._parent,None,None,None, zeroed_alloc=False) + M = self.__class__.__new__(self.__class__, self._parent, + None, None, None, zeroed_alloc=False) sig_on() for i in range(self._nrows*self._ncols): @@ -824,12 +822,13 @@ cdef class Matrix_modn_dense_template(Matrix_dense): sage: 3*A + 9*A == 12*A True """ - cdef Py_ssize_t i,j + cdef Py_ssize_t i, j cdef Matrix_modn_dense_template M cdef celement p = self.p cdef celement a = left - M = self.__class__.__new__(self.__class__, self._parent,None,None,None,zeroed_alloc=False) + M = self.__class__.__new__(self.__class__, self._parent, + None, None, None, zeroed_alloc=False) sig_on() for i in range(self._nrows*self._ncols): @@ -848,7 +847,8 @@ cdef class Matrix_modn_dense_template(Matrix_dense): False """ cdef Matrix_modn_dense_template A - A = self.__class__.__new__(self.__class__,self._parent,None,None,None,zeroed_alloc=False) + A = self.__class__.__new__(self.__class__, self._parent, + None, None, None, zeroed_alloc=False) memcpy(A._entries, self._entries, sizeof(celement)*self._nrows*self._ncols) if self._subdivisions is not None: A.subdivide(*self.subdivisions()) @@ -886,7 +886,8 @@ cdef class Matrix_modn_dense_template(Matrix_dense): cdef celement k, p cdef Matrix_modn_dense_template M - M = self.__class__.__new__(self.__class__, self._parent,None,None,None,zeroed_alloc=False) + M = self.__class__.__new__(self.__class__, self._parent, + None, None, None, zeroed_alloc=False) p = self.p cdef celement* other_ent = (right)._entries @@ -1125,7 +1126,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): True """ if get_verbose() >= 2: - verbose('mod-p multiply of %s x %s matrix by %s x %s matrix modulo %s'%( + verbose('mod-p multiply of %s x %s matrix by %s x %s matrix modulo %s' % ( self._nrows, self._ncols, right._nrows, right._ncols, self.p)) if self._ncols != right._nrows: @@ -1174,7 +1175,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): True """ if not isinstance(v, Vector_modn_dense): - return (self.new_matrix(1,self._nrows, entries=v.list()) * self)[0] + return (self.new_matrix(1, self._nrows, entries=v.list()) * self)[0] M = self.row_ambient_module() cdef Vector_modn_dense c = M.zero_vector() @@ -1385,7 +1386,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): return g.change_variable_name(var) if algorithm == 'linbox' and (self.p == 2 or not self.base_ring().is_field()): - algorithm = 'generic' # LinBox only supports Z/pZ (p prime) + algorithm = 'generic' # LinBox only supports Z/pZ (p prime) if algorithm == 'linbox': g = self._charpoly_linbox(var) @@ -1512,7 +1513,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): proof = get_proof_flag(proof, "linear_algebra") if algorithm == 'linbox' and (self.p == 2 or not self.base_ring().is_field()): - algorithm='generic' # LinBox only supports fields + algorithm='generic' # LinBox only supports fields if algorithm == 'linbox': if self._nrows != self._ncols: @@ -1533,9 +1534,9 @@ cdef class Matrix_modn_dense_template(Matrix_dense): raise NotImplementedError("Minimal polynomials are not implemented for Z/nZ.") else: - raise ValueError("no algorithm '%s'"%algorithm) + raise ValueError("no algorithm '%s'" % algorithm) - self.cache('minpoly_%s_%s'%(algorithm, var), g) + self.cache('minpoly_%s_%s' % (algorithm, var), g) return g def _charpoly_linbox(self, var='x'): @@ -1729,7 +1730,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): return # already known to be in echelon form if not self.base_ring().is_field(): - raise NotImplementedError("Echelon form not implemented over '%s'."%self.base_ring()) + raise NotImplementedError("Echelon form not implemented over '%s'." % self.base_ring()) if algorithm == 'linbox': self._echelonize_linbox(efd=True) @@ -1747,7 +1748,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): if A != self or A != B: raise ArithmeticError("Bug in echelon form.") else: - raise ValueError("Algorithm '%s' not known"%algorithm) + raise ValueError("Algorithm '%s' not known" % algorithm) def _echelonize_linbox(self, efd=True): """ @@ -1778,13 +1779,15 @@ cdef class Matrix_modn_dense_template(Matrix_dense): self.check_mutability() self.clear_cache() - t = verbose('Calling echelonize mod %d.'%self.p) + t = verbose('Calling echelonize mod %d.' % self.p) if efd: - r, pivots = linbox_echelonize_efd(self.p, self._entries, self._nrows, self._ncols) + r, pivots = linbox_echelonize_efd(self.p, self._entries, + self._nrows, self._ncols) else: - r, pivots = linbox_echelonize(self.p, self._entries, self._nrows, self._ncols) - verbose('done with echelonize',t) - self.cache('in_echelon_form',True) + r, pivots = linbox_echelonize(self.p, self._entries, + self._nrows, self._ncols) + verbose('done with echelonize', t) + self.cache('in_echelon_form', True) self.cache('rank', r) self.cache('pivots', tuple(pivots)) @@ -1821,11 +1824,11 @@ cdef class Matrix_modn_dense_template(Matrix_dense): fifth = self._ncols / 10 + 1 do_verb = (get_verbose() >= 2) for c from 0 <= c < nc: - if do_verb and (c % fifth == 0 and c>0): - tm = verbose('on column %s of %s'%(c, self._ncols), + if do_verb and (c % fifth == 0 and c > 0): + tm = verbose('on column %s of %s' % (c, self._ncols), level = 2, caller_name = 'matrix_modn_dense echelon') - #end if + # end if sig_check() for r from start_row <= r < nr: a = m[r][c] @@ -1842,7 +1845,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): start_row = start_row + 1 break self.cache('pivots', tuple(pivots)) - self.cache('in_echelon_form',True) + self.cache('in_echelon_form', True) def right_kernel_matrix(self, algorithm='linbox', basis='echelon'): r""" @@ -1990,38 +1993,38 @@ cdef class Matrix_modn_dense_template(Matrix_dense): i = -1 for r from m+1 <= r < n: if h[r][m-1]: - i = r - break + i = r + break if i != -1: - # Found a nonzero entry in column m-1 that is strictly - # below row m. Now set i to be the first nonzero position >= - # m in column m-1. - if h[m][m-1]: - i = m - t = h[i][m-1] - t_inv = celement_invert(t,p) - if i > m: - self.swap_rows_c(i,m) - self.swap_columns_c(i,m) - - # Now the nonzero entry in position (m,m-1) is t. - # Use t to clear the entries in column m-1 below m. - for j from m+1 <= j < n: - if h[j][m-1]: - u = (h[j][m-1] * t_inv) % p - self.add_multiple_of_row_c(j, m, p - u, 0) # h[j] -= u*h[m] - # To maintain charpoly, do the corresponding - # column operation, which doesn't mess up the - # matrix, since it only changes column m, and - # we're only worried about column m-1 right - # now. Add u*column_j to column_m. - self.add_multiple_of_column_c(m, j, u, 0) - # end for + # Found a nonzero entry in column m-1 that is strictly + # below row m. Now set i to be the first nonzero position >= + # m in column m-1. + if h[m][m-1]: + i = m + t = h[i][m-1] + t_inv = celement_invert(t, p) + if i > m: + self.swap_rows_c(i, m) + self.swap_columns_c(i, m) + + # Now the nonzero entry in position (m,m-1) is t. + # Use t to clear the entries in column m-1 below m. + for j from m+1 <= j < n: + if h[j][m-1]: + u = (h[j][m-1] * t_inv) % p + self.add_multiple_of_row_c(j, m, p - u, 0) # h[j] -= u*h[m] + # To maintain charpoly, do the corresponding + # column operation, which doesn't mess up the + # matrix, since it only changes column m, and + # we're only worried about column m-1 right + # now. Add u*column_j to column_m. + self.add_multiple_of_column_c(m, j, u, 0) + # end for # end if # end for sig_off() - self.cache('in_hessenberg_form',True) + self.cache('in_hessenberg_form', True) def _charpoly_hessenberg(self, var): """ @@ -2073,7 +2076,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): # Algorithm 2.2.9. cdef Matrix_modn_dense_template c - c = self.new_matrix(nrows=n+1,ncols=n+1) # the 0 matrix + c = self.new_matrix(nrows=n+1, ncols=n+1) # the 0 matrix c._matrix[0][0] = 1 for m from 1 <= m <= n: # Set the m-th row of c to (x - H[m-1,m-1])*c[m-1] = x*c[m-1] - H[m-1,m-1]*c[m-1] @@ -2088,7 +2091,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): for i from 1 <= i < m: t = (t*H._matrix[m-i][m-i-1]) % p # Set the m-th row of c to c[m] - t*H[m-i-1,m-1]*c[m-i-1] - c.add_multiple_of_row_c(m, m-i-1, p - (t*H._matrix[m-i-1][m-1])%p, 0) + c.add_multiple_of_row_c(m, m-i-1, p - (t*H._matrix[m-i-1][m-1]) % p, 0) # The answer is now the n-th row of c. v = [] @@ -2306,13 +2309,13 @@ cdef class Matrix_modn_dense_template(Matrix_dense): cdef Py_ssize_t nc, i cdef int a = row1[start_col] cdef int b = row2[start_col] - g = ArithIntObj.c_xgcd_int (a,b,&s,&t) + g = ArithIntObj.c_xgcd_int(a, b, &s, &t) v = a/g w = -b/g nc = self.ncols() for i from start_col <= i < nc: - tmp = ( s * row1[i] + t * row2[i]) % p + tmp = (s * row1[i] + t * row2[i]) % p row2[i] = (w* row1[i] + v*row2[i]) % p row1[i] = tmp return g @@ -2446,7 +2449,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): cdef Py_ssize_t i, nc nc = self._ncols for i from start_col <= i < nc: - v_to[i] = ((multiple) * v_from[i] + v_to[i]) % p + v_to[i] = ((multiple) * v_from[i] + v_to[i]) % p cdef add_multiple_of_column_c(self, Py_ssize_t col_to, Py_ssize_t col_from, multiple, Py_ssize_t start_row): """ @@ -2665,7 +2668,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): True """ s = self.base_ring()._magma_init_(magma) - return 'Matrix(%s,%s,%s,StringToIntegerSequence("%s"))'%( + return 'Matrix(%s,%s,%s,StringToIntegerSequence("%s"))' % ( s, self._nrows, self._ncols, self._export_as_string()) cpdef _export_as_string(self): @@ -2758,8 +2761,9 @@ cdef class Matrix_modn_dense_template(Matrix_dense): cdef Py_ssize_t i, j cdef Matrix_integer_dense L - cdef object P = matrix_space.MatrixSpace(ZZ, self._nrows, self._ncols, sparse=False) - L = Matrix_integer_dense(P,ZZ(0),False,False) + cdef object P = matrix_space.MatrixSpace(ZZ, self._nrows, + self._ncols, sparse=False) + L = Matrix_integer_dense(P, ZZ(0), False, False) cdef celement* A_row for i in range(self._nrows): A_row = self._matrix[i] @@ -2808,7 +2812,7 @@ cdef class Matrix_modn_dense_template(Matrix_dense): cdef Py_ssize_t ncols = self._ncols cdef Matrix_modn_dense_template M = self.new_matrix(nrows=ncols, ncols=nrows) - cdef Py_ssize_t i,j + cdef Py_ssize_t i, j for i from 0 <= i < ncols: for j from 0 <= j < nrows: @@ -2955,8 +2959,9 @@ cdef class Matrix_modn_dense_template(Matrix_dense): memcpy(M._entries+selfsize, other._entries, sizeof(celement)*other._ncols*other._nrows) return M - def submatrix(self, Py_ssize_t row=0, Py_ssize_t col=0, - Py_ssize_t nrows=-1, Py_ssize_t ncols=-1): + def submatrix(self, + Py_ssize_t row=0, Py_ssize_t col=0, + Py_ssize_t nrows=-1, Py_ssize_t ncols=-1): r""" Return the matrix constructed from ``self`` using the specified range of rows and columns. @@ -3024,8 +3029,8 @@ cdef class Matrix_modn_dense_template(Matrix_dense): memcpy(M._entries, self._matrix[row], sizeof(celement)*ncols*nrows) return M - cdef Py_ssize_t i,r - for i,r in enumerate(range(row, row+nrows)) : + cdef Py_ssize_t i, r + for i, r in enumerate(range(row, row+nrows)) : memcpy(M._matrix[i], self._matrix[r]+col, sizeof(celement)*ncols) return M diff --git a/src/sage/matrix/meson.build b/src/sage/matrix/meson.build index 440c9ffaaf8..2f053bef05b 100644 --- a/src/sage/matrix/meson.build +++ b/src/sage/matrix/meson.build @@ -2,6 +2,7 @@ iml = cc.find_library('iml', required: not is_windows, disabler: true) py.install_sources( + '__init__.py', 'action.pxd', 'all.py', 'all__sagemath_meataxe.py', diff --git a/src/sage/matroids/chow_ring_ideal.py b/src/sage/matroids/chow_ring_ideal.py index d0ac04a23ee..f2a6c6cf477 100644 --- a/src/sage/matroids/chow_ring_ideal.py +++ b/src/sage/matroids/chow_ring_ideal.py @@ -13,6 +13,7 @@ from sage.combinat.posets.posets import Poset from itertools import product + class ChowRingIdeal(MPolynomialIdeal): def matroid(self): r""" @@ -55,6 +56,7 @@ def _lattice_flats(self): chains = lattice_flats.chains() #Only chains return (ranks, chains) + class ChowRingIdeal_nonaug(ChowRingIdeal): r""" The Chow ring ideal of a matroid `M`. @@ -797,4 +799,4 @@ def normal_basis(self, algorithm='', *args, **kwargs): for val, c in zip(subset, combination): expression *= flats_gen[val] ** c monomial_basis.append(expression) - return PolynomialSequence(R, [monomial_basis]) \ No newline at end of file + return PolynomialSequence(R, [monomial_basis]) diff --git a/src/sage/matroids/meson.build b/src/sage/matroids/meson.build index f60970da5b9..8fd8ae12895 100644 --- a/src/sage/matroids/meson.build +++ b/src/sage/matroids/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'advanced.py', 'all.py', 'basis_exchange_matroid.pxd', diff --git a/src/sage/meson.build b/src/sage/meson.build index a1192dd8267..b6f8426484c 100644 --- a/src/sage/meson.build +++ b/src/sage/meson.build @@ -89,6 +89,7 @@ foreach package : no_processing endforeach py.install_sources( + '__init__.py', 'all.py', 'all__sagemath_bliss.py', 'all__sagemath_categories.py', diff --git a/src/sage/misc/binary_tree.pxd b/src/sage/misc/binary_tree.pxd index 4e54f74a71a..1200b4b3d95 100644 --- a/src/sage/misc/binary_tree.pxd +++ b/src/sage/misc/binary_tree.pxd @@ -4,24 +4,24 @@ cdef struct binary_tree_node: binary_tree_node *right void *value -#cdef binary_tree_node *BinaryTreeNode(int, object) -#cdef void free_binary_tree_node(binary_tree_node *) -#cdef void binary_tree_dealloc(binary_tree_node *) -#cdef void binary_tree_insert(binary_tree_node *self, int, object) -#cdef object binary_tree_get(binary_tree_node *, int) -#cdef object binary_tree_delete(binary_tree_node *, int) -#cdef binary_tree_node *binary_tree_left_excise(binary_tree_node *) -#cdef binary_tree_node *binary_tree_right_excise(binary_tree_node *) -#cdef binary_tree_node *binary_tree_head_excise(binary_tree_node *) -#cdef object binary_tree_list(binary_tree_node *, int) +# cdef binary_tree_node *BinaryTreeNode(int, object) +# cdef void free_binary_tree_node(binary_tree_node *) +# cdef void binary_tree_dealloc(binary_tree_node *) +# cdef void binary_tree_insert(binary_tree_node *self, int, object) +# cdef object binary_tree_get(binary_tree_node *, int) +# cdef object binary_tree_delete(binary_tree_node *, int) +# cdef binary_tree_node *binary_tree_left_excise(binary_tree_node *) +# cdef binary_tree_node *binary_tree_right_excise(binary_tree_node *) +# cdef binary_tree_node *binary_tree_head_excise(binary_tree_node *) +# cdef object binary_tree_list(binary_tree_node *, int) -#cdef int LIST_PREORDER, LIST_POSTORDER, LIST_INORDER, LIST_KEYS, LIST_VALUES -#LIST_PREORDER = 1 -#LIST_INORDER = 2 -#LIST_POSTORDER = 4 -#LIST_KEYS = 8 -#LIST_VALUES = 16 +# cdef int LIST_PREORDER, LIST_POSTORDER, LIST_INORDER, LIST_KEYS, LIST_VALUES +# LIST_PREORDER = 1 +# LIST_INORDER = 2 +# LIST_POSTORDER = 4 +# LIST_KEYS = 8 +# LIST_VALUES = 16 cdef class BinaryTree: diff --git a/src/sage/misc/binary_tree.pyx b/src/sage/misc/binary_tree.pyx index 0756218e53c..14111178271 100644 --- a/src/sage/misc/binary_tree.pyx +++ b/src/sage/misc/binary_tree.pyx @@ -145,7 +145,7 @@ cdef binary_tree_node *binary_tree_head_excise(binary_tree_node *self) noexcept: cdef int LIST_PREORDER, LIST_POSTORDER, LIST_INORDER, LIST_KEYS, LIST_VALUES -LIST_PREORDER = 1 +LIST_PREORDER = 1 LIST_INORDER = 2 LIST_POSTORDER = 4 LIST_KEYS = 8 diff --git a/src/sage/misc/c3.pyx b/src/sage/misc/c3.pyx index 8dc7e9a9feb..9329065a27b 100644 --- a/src/sage/misc/c3.pyx +++ b/src/sage/misc/c3.pyx @@ -192,9 +192,10 @@ cpdef list C3_algorithm(object start, str bases, str attribute, bint proper): cdef object O, X cdef list tails = [getattr(obj, attribute) for obj in args] tails.append(args) - tails = [list(reversed(tail)) for tail in tails] - cdef list heads = [tail.pop() for tail in tails] - cdef list tailsets = [set([O for O in tail]) for tail in tails] + tails = [list(reversed(tail)) for tail in tails] + cdef list heads = [tail.pop() for tail in tails] + cdef list tailsets = [set([O for O in tail]) + for tail in tails] cdef int i, j, nbheads nbheads = len(heads) diff --git a/src/sage/misc/c3_controlled.pyx b/src/sage/misc/c3_controlled.pyx index 885dcf673f0..765e83fbec2 100644 --- a/src/sage/misc/c3_controlled.pyx +++ b/src/sage/misc/c3_controlled.pyx @@ -380,7 +380,7 @@ cdef tuple atoms = ("FacadeSets", "MagmasAndAdditiveMagmas", "Rngs", "Domains", "HopfAlgebras") -cdef dict flags = { atom: 1 << i for i,atom in enumerate(atoms) } +cdef dict flags = {atom: 1 << i for i, atom in enumerate(atoms)} cdef class CmpKey: r""" @@ -574,7 +574,7 @@ cdef class CmpKeyNamed: return result except KeyError: pass - result = _cmp_key.__get__(inst,cls) + result = _cmp_key.__get__(inst, cls) D[key] = result return result @@ -615,9 +615,9 @@ def C3_merge(list lists): cdef list tail, l cdef set tailset - cdef list tails = [l[::-1] for l in lists if l] - cdef list heads = [tail.pop() for tail in tails] - cdef list tailsets = [set(O for O in tail) for tail in tails] # + cdef list tails = [l[::-1] for l in lists if l] + cdef list heads = [tail.pop() for tail in tails] + cdef list tailsets = [set(O for O in tail) for tail in tails] # cdef int i, j, nbheads nbheads = len(heads) @@ -632,7 +632,7 @@ def C3_merge(list lists): if j == i: continue tailset = tailsets[j] - if O in tailset: # O + if O in tailset: # O next_item_found = False break if next_item_found: @@ -641,13 +641,13 @@ def C3_merge(list lists): # if the tail is already empty. # j goes down so that ``del heads[j]`` does not screw up the numbering for j in range(nbheads-1, -1, -1): - if heads[j] == O: # is O + if heads[j] == O: # is O tail = tails[j] if tail: X = tail.pop() heads[j] = X tailset = tailsets[j] - tailset.remove(X) # X) + tailset.remove(X) # X) else: del heads[j] del tails[j] @@ -792,7 +792,7 @@ cpdef tuple C3_sorted_merge(list lists, key=identity): lists = list(lists) if not lists: raise ValueError("The input should be a non empty list of lists (or iterables)") - #for l in lists: + # for l in lists: # assert sorted(l, key = key, reverse=True) == l,\ # "Each input list should be sorted %s"%l @@ -819,10 +819,10 @@ cpdef tuple C3_sorted_merge(list lists, key=identity): cdef list tail, l cdef set tailset - cdef list tails = [l[::-1] for l in lists if l] - cdef list heads = [tail.pop() for tail in tails] + cdef list tails = [l[::-1] for l in lists if l] + cdef list heads = [tail.pop() for tail in tails] cdef set tmp_set - cdef list tailsets = [] # remove closure [set(key(O) for O in tail) for tail in tails] + cdef list tailsets = [] # remove closure [set(key(O) for O in tail) for tail in tails] for tail in tails: tmp_set = set() for O in tail: @@ -855,10 +855,10 @@ cpdef tuple C3_sorted_merge(list lists, key=identity): # "keys should be distinct"%(tails[i]) while nbheads: - #print_state() - #check_state() + # print_state() + # check_state() # Find the position of the largest head which will become the next item - max_i = 0 + max_i = 0 max_key = key(heads[0]) for i in range(1, nbheads): O = heads[i] @@ -915,10 +915,10 @@ cpdef tuple C3_sorted_merge(list lists, key=identity): # Use a heap or something for fast sorted insertion? # Since Python uses TimSort, that's probably not so bad. tails[-1].append(O) - tails[-1].sort(key = key) + tails[-1].sort(key=key) tailsets[-1].add(O_key) suggestion.add(O) - #check_state() + # check_state() # Insert max_value in the last list, if needed to hold off the bad items if max_bad is not None: @@ -927,10 +927,10 @@ cpdef tuple C3_sorted_merge(list lists, key=identity): if last_head is not None and last_head != max_bad: tails[-1].append(last_head) tailsets[-1].add(key(last_head)) - #check_state() + # check_state() heads[-1] = max_value holder[max_bad] = max_value - #check_state() + # check_state() out.append(max_value) # Clear O from other heads, removing the line altogether @@ -951,10 +951,10 @@ cpdef tuple C3_sorted_merge(list lists, key=identity): nbheads -= 1 if last_list_non_empty and j == nbheads: last_list_non_empty = False - #check_state() + # check_state() suggestion.update(holder.values()) cdef list suggestion_list = sorted(suggestion, key=key, reverse=True) - #assert C3_merge(lists[:-1]+[suggestion_list]) == out + # assert C3_merge(lists[:-1]+[suggestion_list]) == out return (out, suggestion_list) @@ -1373,7 +1373,7 @@ class HierarchyElement(object, metaclass=ClasscallMetaclass): sage: sorted([x.value for x in HierarchyElement(10, P).all_bases()]) [1, 2, 5, 10] """ - return {self} | { x for base in self._bases for x in base.all_bases() } + return {self} | {x for base in self._bases for x in base.all_bases()} def all_bases_len(self): """ @@ -1386,7 +1386,7 @@ class HierarchyElement(object, metaclass=ClasscallMetaclass): sage: HierarchyElement(30, P).all_bases_len() # needs sage.graphs 12 """ - return sum( len(x._bases) for x in self.all_bases()) + return sum(len(x._bases) for x in self.all_bases()) def all_bases_controlled_len(self): """ @@ -1399,4 +1399,4 @@ class HierarchyElement(object, metaclass=ClasscallMetaclass): sage: HierarchyElement(30, P).all_bases_controlled_len() # needs sage.graphs 13 """ - return sum( len(x._bases_controlled) for x in self.all_bases()) + return sum(len(x._bases_controlled) for x in self.all_bases()) diff --git a/src/sage/misc/cachefunc.pyx b/src/sage/misc/cachefunc.pyx index f800cfd27f1..df4fa8d4457 100644 --- a/src/sage/misc/cachefunc.pyx +++ b/src/sage/misc/cachefunc.pyx @@ -442,20 +442,21 @@ from inspect import isfunction from sage.misc.weak_dict cimport CachedWeakValueDictionary from sage.misc.decorators import decorator_keywords -cdef frozenset special_method_names = frozenset(['__abs__', '__add__', - '__and__', '__call__', '__cmp__', '__coerce__', '__complex__', '__contains__', '__del__', - '__delattr__', '__delete__', '__delitem__', '__delslice__', '__dir__', '__div__', - '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__get__', '__getattr__', - '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__hex__', - '__iadd__', '__iand__', '__idiv__', '__ifloordiv__', '__ilshift__', '__imod__', '__imul__', - '__index__', '__init__', '__instancecheck__', '__int__', '__invert__', '__ior__', '__ipow__', - '__irshift__', '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__', - '__length_hint__', '__long__', '__lshift__', '__lt__', '__missing__', '__mod__', '__mul__', - '__ne__', '__neg__', '__new__', '__oct__', '__or__', '__pos__', '__pow__', - '__radd__', '__rand__', '__rdiv__', '__repr__', '__reversed__', '__rfloordiv__', '__rlshift__', - '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', - '__rtruediv__', '__rxor__', '__set__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', - '__str__', '__sub__', '__subclasscheck__', '__truediv__', '__unicode__', '__xor__', 'next']) +cdef frozenset special_method_names = frozenset( + ['__abs__', '__add__', + '__and__', '__call__', '__cmp__', '__coerce__', '__complex__', '__contains__', '__del__', + '__delattr__', '__delete__', '__delitem__', '__delslice__', '__dir__', '__div__', + '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__get__', '__getattr__', + '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__hex__', + '__iadd__', '__iand__', '__idiv__', '__ifloordiv__', '__ilshift__', '__imod__', '__imul__', + '__index__', '__init__', '__instancecheck__', '__int__', '__invert__', '__ior__', '__ipow__', + '__irshift__', '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__', + '__length_hint__', '__long__', '__lshift__', '__lt__', '__missing__', '__mod__', '__mul__', + '__ne__', '__neg__', '__new__', '__oct__', '__or__', '__pos__', '__pow__', + '__radd__', '__rand__', '__rdiv__', '__repr__', '__reversed__', '__rfloordiv__', '__rlshift__', + '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', + '__rtruediv__', '__rxor__', '__set__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', + '__str__', '__sub__', '__subclasscheck__', '__truediv__', '__unicode__', '__xor__', 'next']) def _cached_function_unpickle(module, name, cache=None): @@ -500,7 +501,7 @@ def _cached_function_unpickle(module, name, cache=None): sage: f(0) 0 """ - ret = getattr(__import__(module, fromlist=['']),name) + ret = getattr(__import__(module, fromlist=['']), name) if cache is not None: ret.cache.update(cache) return ret @@ -777,8 +778,9 @@ cdef class CachedFunction(): self.__cached_module__ = f.__module__ except AttributeError: self.__cached_module__ = f.__objclass__.__module__ - if argument_fixer is not None: # it is None unless the argument fixer - # was known previously. See #15038. + if argument_fixer is not None: + # it is None unless the argument fixer + # was known previously. See #15038. self._argument_fixer = argument_fixer @property @@ -811,7 +813,7 @@ cdef class CachedFunction(): -1 """ self._argument_fixer = ArgumentFixer(self.f, - classmethod=self.is_classmethod) + classmethod=self.is_classmethod) cdef fix_args_kwds(self, tuple args, dict kwds): r""" @@ -848,10 +850,10 @@ cdef class CachedFunction(): return _cached_function_unpickle, (self.__cached_module__, self.__name__, self.cache) ######### - ## Introspection - ## - ## We provide some methods explicitly, and - ## forward other questions to the cached function. + # Introspection + # + # We provide some methods explicitly, and + # forward other questions to the cached function. def _instancedoc_(self): """ @@ -894,15 +896,15 @@ cdef class CachedFunction(): try: sourcelines = sage_getsourcelines(f) filename = sage_getfile_relative(f) - #this is a rather expensive way of getting the line number, because - #retrieving the source requires reading the source file and in many - #cases this is not required (in cython it's embedded in the docstring, - #on code objects you'll find it in co_filename and co_firstlineno) - #however, this hasn't been factored out yet in sageinspect - #and the logic in sage_getsourcelines is rather intricate. - file_info = "File: {} (starting at line {})".format(filename,sourcelines[1])+os.linesep - - doc = file_info+doc + # this is a rather expensive way of getting the line number, because + # retrieving the source requires reading the source file and in many + # cases this is not required (in cython it's embedded in the docstring, + # on code objects you'll find it in co_filename and co_firstlineno) + # however, this hasn't been factored out yet in sageinspect + # and the logic in sage_getsourcelines is rather intricate. + file_info = "File: {} (starting at line {})".format(filename, sourcelines[1]) + os.linesep + + doc = file_info + doc except IOError: pass return doc @@ -1249,7 +1251,7 @@ cdef class CachedFunction(): ak = normalize_input(a) if self.get_key_args_kwds(ak[0], ak[1]) not in self.cache: arglist2.append(ak) - for ((args,kwargs), val) in P(arglist2): + for ((args, kwargs), val) in P(arglist2): self.set_cache(val, *args, **kwargs) @@ -1605,7 +1607,7 @@ class CachedMethodPickle(): x^2*y*z^3 - x*y^2*z^3 + 2*y^3*z^3 + z^6, x*y^3 + y^4 + x*z^3, x^3 + y^3 + z^3]} """ - return CachedMethodPickle,(self._instance,self._name,self._cache) + return CachedMethodPickle, (self._instance, self._name, self._cache) def __call__(self, *args, **kwds): """ @@ -1629,7 +1631,7 @@ class CachedMethodPickle(): Cached version of """ self._instance.__dict__.__delitem__(self._name) - CM = getattr(self._instance,self._name) + CM = getattr(self._instance, self._name) if self._cache is not None: if isinstance(CM, CachedMethodCallerNoArgs): CM.cache = self._cache @@ -1667,14 +1669,14 @@ class CachedMethodPickle(): Cached version of """ self._instance.__dict__.__delitem__(self._name) - CM = getattr(self._instance,self._name) + CM = getattr(self._instance, self._name) if self._cache is not None: if isinstance(CM, CachedMethodCallerNoArgs): CM.cache = self._cache else: for k, v in self._cache: CM.cache[k] = v - return getattr(CM,s) + return getattr(CM, s) cdef class CachedMethodCaller(CachedFunction): @@ -1786,10 +1788,10 @@ cdef class CachedMethodCaller(CachedFunction): sage: J.groebner_basis Cached version of """ - if isinstance(self._cachedmethod, CachedInParentMethod) or hasattr(self._instance,self._cachedmethod._cache_name): - return CachedMethodPickle,(self._instance,self.__name__) + if isinstance(self._cachedmethod, CachedInParentMethod) or hasattr(self._instance, self._cachedmethod._cache_name): + return CachedMethodPickle, (self._instance, self.__name__) else: - return CachedMethodPickle,(self._instance,self.__name__,self.cache) + return CachedMethodPickle, (self._instance, self.__name__, self.cache) def _instance_call(self, *args, **kwds): """ @@ -2086,11 +2088,12 @@ cdef class CachedMethodCaller(CachedFunction): cls = type(self) Caller = cls(self._cachedmethod, inst, - cache=self._cachedmethod._get_instance_cache(inst), - name=self._cachedmethod._cachedfunc.__name__, key=self.key, do_pickle=self.do_pickle) + cache=self._cachedmethod._get_instance_cache(inst), + name=self._cachedmethod._cachedfunc.__name__, + key=self.key, do_pickle=self.do_pickle) try: - setattr(inst,self._cachedmethod._cachedfunc.__name__, Caller) + setattr(inst, self._cachedmethod._cachedfunc.__name__, Caller) return Caller except AttributeError: pass @@ -2139,7 +2142,7 @@ cdef class CachedMethodCaller(CachedFunction): ak = normalize_input(a) if self.get_key_args_kwds(ak[0], ak[1]) not in self.cache: arglist2.append(ak) - for ((args,kwargs), val) in P(arglist2): + for ((args, kwargs), val) in P(arglist2): self.set_cache(val, *args, **kwargs) @@ -2227,12 +2230,12 @@ cdef class CachedMethodCallerNoArgs(CachedFunction): 4 """ # initialize CachedFunction - if isinstance(f,str): + if isinstance(f, str): try: - F = getattr(inst.__class__,f) + F = getattr(inst.__class__, f) except AttributeError: - F = getattr(inst,f) - if isinstance(F,CachedFunction): + F = getattr(inst, f) + if isinstance(F, CachedFunction): f = F.f else: f = F @@ -2277,9 +2280,8 @@ cdef class CachedMethodCallerNoArgs(CachedFunction): Cached version of """ if self.do_pickle: - return CachedMethodPickle,(self._instance, self.__name__, self.cache) - else: - return CachedMethodPickle,(self._instance, self.__name__) + return CachedMethodPickle, (self._instance, self.__name__, self.cache) + return CachedMethodPickle, (self._instance, self.__name__) def _instance_call(self): """ @@ -2465,7 +2467,7 @@ cdef class CachedMethodCallerNoArgs(CachedFunction): pass Caller = CachedMethodCallerNoArgs(inst, self.f, name=self.__name__, do_pickle=self.do_pickle) try: - setattr(inst,self.__name__, Caller) + setattr(inst, self.__name__, Caller) return Caller except AttributeError: pass @@ -2827,7 +2829,7 @@ cdef class CachedMethod(): if self.nargs == 0: if isinstance(f, object) and not isfunction(f): try: - if METH_NOARGS&PyCFunction_GetFlags(f.__get__(inst,cls)): + if METH_NOARGS&PyCFunction_GetFlags(f.__get__(inst, cls)): self.nargs = 1 except Exception: pass @@ -2971,7 +2973,7 @@ cdef class CachedSpecialMethod(CachedMethod): self.nargs = 1 Caller = CachedMethodCallerNoArgs(inst, f, name=name, do_pickle=self._cachedfunc.do_pickle) else: - self.nargs = 2 # don't need the exact number + self.nargs = 2 # don't need the exact number Caller = CachedMethodCaller(self, inst, cache=self._get_instance_cache(inst), name=name, @@ -2987,7 +2989,7 @@ cdef class CachedSpecialMethod(CachedMethod): do_pickle=self._cachedfunc.do_pickle) if inst is not None: try: - setattr(inst,name, Caller) + setattr(inst, name, Caller) return Caller except AttributeError: pass @@ -3280,7 +3282,7 @@ cdef class CachedInParentMethod(CachedMethod): return P.__dict__.setdefault(self._cache_name, default) except AttributeError: pass - if not hasattr(P,'_cached_methods'): + if not hasattr(P, '_cached_methods'): raise TypeError("The parent of this element does not allow attribute assignment\n" + " and does not descend from the Parent base class.\n" + " Cannot use CachedInParentMethod.") @@ -3295,7 +3297,7 @@ cdef class CachedInParentMethod(CachedMethod): """ Caller = GloballyCachedMethodCaller(self, inst, cache=self._get_instance_cache(inst), key=self._cachedfunc.key, do_pickle=self._cachedfunc.do_pickle) try: - setattr(inst,self._cachedfunc.__name__, Caller) + setattr(inst, self._cachedfunc.__name__, Caller) except AttributeError: pass return Caller @@ -3382,7 +3384,7 @@ class FileCache(): l = len(prefix) for f in os.listdir(dir): if f[:l] == prefix: - files.append( dir + f ) + files.append(dir + f) return files def items(self): @@ -3601,7 +3603,7 @@ class FileCache(): f = self._filename(key) save(key, f+'.key.sobj') - save((key,value), f + '.sobj') + save((key, value), f + '.sobj') if self._cache is not None: self._cache[key] = value diff --git a/src/sage/misc/citation.pyx b/src/sage/misc/citation.pyx index fbe932ba49a..96fe00e98c4 100644 --- a/src/sage/misc/citation.pyx +++ b/src/sage/misc/citation.pyx @@ -97,12 +97,12 @@ def get_systems(cmd): from sage.repl.preparse import preparse cmd = preparse(cmd) - #Run the command and get the stats + # Run the command and get the stats filename = tmp_filename() cProfile.runctx(cmd, globals(), {}, filename) stats = pstats.Stats(filename) - #Strings is a list of method names and modules which get run + # Strings is a list of method names and modules which get run def string_from_stat(a): s = a[0] if SAGE_LOCAL: @@ -114,7 +114,7 @@ def get_systems(cmd): strings = [string_from_stat(a) for a in stats.stats] - #Remove trivial functions + # Remove trivial functions bad_res = [re.compile(r'is_.*Element'), re.compile("is_[a-z_]*_type")] for bad_re in bad_res: i = 0 diff --git a/src/sage/misc/classcall_metaclass.pxd b/src/sage/misc/classcall_metaclass.pxd index 1ae792f074b..24022139808 100644 --- a/src/sage/misc/classcall_metaclass.pxd +++ b/src/sage/misc/classcall_metaclass.pxd @@ -1,10 +1,10 @@ # sage_setup: distribution = sagemath-objects -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2012 Florent Hivert # # Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.misc.nested_class cimport NestedClassMetaclass diff --git a/src/sage/misc/constant_function.pyx b/src/sage/misc/constant_function.pyx index 2e9c3d9fe44..044b83ad24f 100644 --- a/src/sage/misc/constant_function.pyx +++ b/src/sage/misc/constant_function.pyx @@ -3,15 +3,15 @@ r""" Constant functions """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009 Nicolas M. Thiery # # 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.richcmp cimport richcmp from sage.structure.sage_object cimport SageObject diff --git a/src/sage/misc/cython.py b/src/sage/misc/cython.py index 95491a5c623..087d3069f88 100644 --- a/src/sage/misc/cython.py +++ b/src/sage/misc/cython.py @@ -226,17 +226,6 @@ def cython(filename, verbose=0, compile_message=False, ....: from sage.misc.cachefunc cimport cache_key ....: ''') - In Cython 0.29.33 using `from PACKAGE cimport MODULE` is broken - when `PACKAGE` is a namespace package, see :issue:`35322`:: - - sage: cython(''' - ....: from sage.misc cimport cachefunc - ....: ''') - Traceback (most recent call last): - ... - RuntimeError: Error compiling Cython file: - ... - ...: 'sage/misc.pxd' not found """ if not filename.endswith('pyx'): print("Warning: file (={}) should have extension .pyx".format(filename), file=sys.stderr) @@ -578,6 +567,9 @@ def cython_import_all(filename, globals, **kwds): - ``filename`` -- string; name of a file that contains Cython code + + See the function :func:`sage.misc.cython.cython` for documentation + for the other inputs. """ m = cython_import(filename, **kwds) for k, x in m.__dict__.items(): @@ -621,6 +613,9 @@ def compile_and_load(code, **kwds): - ``code`` -- string containing code that could be in a .pyx file that is attached or put in a %cython block in the notebook + See the function :func:`sage.misc.cython.cython` for documentation + for the other inputs. + OUTPUT: a module, which results from compiling the given code and importing it diff --git a/src/sage/misc/decorators.py b/src/sage/misc/decorators.py index f93f1227448..004e7b51922 100644 --- a/src/sage/misc/decorators.py +++ b/src/sage/misc/decorators.py @@ -51,6 +51,18 @@ def sage_wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES): the special attribute ``_sage_argspec_`` of the wrapping function (for an example, see e.g. ``@options`` decorator in this module). + Note that in ``.pyx`` files which is compiled by Cython, because Sage uses + ``binding=False`` compiler directive by default, you need to explicitly + specify ``binding=True`` for all functions decorated with ``sage_wraps``:: + + sage: import cython + sage: def square(f): + ....: @sage_wraps(f) + ....: @cython.binding(True) + ....: def new_f(x): + ....: return f(x)*f(x) + ....: return new_f + EXAMPLES: Demonstrate that documentation string and source are retained from the diff --git a/src/sage/misc/derivative.pyx b/src/sage/misc/derivative.pyx index 24ceb09a1db..24bb401e4be 100644 --- a/src/sage/misc/derivative.pyx +++ b/src/sage/misc/derivative.pyx @@ -1,4 +1,4 @@ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 William Stein # # Distributed under the terms of the GNU General Public License (GPL) @@ -10,8 +10,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** r""" Utility functions for making derivative() behave uniformly across Sage. diff --git a/src/sage/misc/fast_methods.pyx b/src/sage/misc/fast_methods.pyx index d38c1802c45..7c939a5f810 100644 --- a/src/sage/misc/fast_methods.pyx +++ b/src/sage/misc/fast_methods.pyx @@ -18,15 +18,15 @@ AUTHOR: - Simon King (2013-10): Add :class:`Singleton` """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2013 Simon A. King # # 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.misc.classcall_metaclass import ClasscallMetaclass, typecall from sage.misc.constant_function import ConstantFunction diff --git a/src/sage/misc/function_mangling.pyx b/src/sage/misc/function_mangling.pyx index e410a49a900..6eded73fc63 100644 --- a/src/sage/misc/function_mangling.pyx +++ b/src/sage/misc/function_mangling.pyx @@ -146,7 +146,7 @@ cdef class ArgumentFixer: cdef dict default_map self._defaults = default_map = {} - for k,v in zip(self._arg_names[-self._ndefault:], defaults): + for k, v in zip(self._arg_names[-self._ndefault:], defaults): default_map[k] = v def __repr__(self): @@ -221,10 +221,10 @@ cdef class ArgumentFixer: val = defaults[name] else: val = args[i] - ARGS.append((name,val)) + ARGS.append((name, val)) extra_args = args[self._nargs:] for k in sorted(kwargs_.keys()): - ARGS.append((k,kwargs_[k])) + ARGS.append((k, kwargs_[k])) return tuple(extra_args), tuple(ARGS) def fix_to_pos(self, *args, **kwds): @@ -289,7 +289,7 @@ cdef class ArgumentFixer: if lenargs >= nargs: return args, () # we take the given arguments, plus the default arguments - return args + self._default_tuple[-nargs+lenargs:],() + return args + self._default_tuple[-nargs+lenargs:], () cdef list Largs = list(args) cdef dict kwargs = dict(kwds) cdef Py_ssize_t i diff --git a/src/sage/misc/inherit_comparison.pyx b/src/sage/misc/inherit_comparison.pyx index 9758d25574d..5455cfe53a5 100644 --- a/src/sage/misc/inherit_comparison.pyx +++ b/src/sage/misc/inherit_comparison.pyx @@ -26,7 +26,7 @@ AUTHOR: - Jeroen Demeyer (2015-05-22): initial version, see :issue:`18329` """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2015 Jeroen Demeyer # # This program is free software: you can redistribute it and/or modify @@ -34,7 +34,7 @@ AUTHOR: # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # http://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from cpython.object cimport PyTypeObject from sage.misc.classcall_metaclass cimport ClasscallMetaclass diff --git a/src/sage/misc/instancedoc.pyx b/src/sage/misc/instancedoc.pyx index 4641b53b13d..0661ac43dd1 100644 --- a/src/sage/misc/instancedoc.pyx +++ b/src/sage/misc/instancedoc.pyx @@ -108,7 +108,7 @@ Check that inheritance works (after passing the subclass to 'Instance docstring' """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2017 Jeroen Demeyer # # This program is free software: you can redistribute it and/or modify @@ -116,7 +116,7 @@ Check that inheritance works (after passing the subclass to # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from cpython.object cimport PyObject, PyTypeObject diff --git a/src/sage/misc/lazy_attribute.pyx b/src/sage/misc/lazy_attribute.pyx index 7514aee1569..7c2eaffa86e 100644 --- a/src/sage/misc/lazy_attribute.pyx +++ b/src/sage/misc/lazy_attribute.pyx @@ -107,7 +107,7 @@ cdef class _lazy_attribute(): """ cdef CM cdef result - if a is None: # when doing cls.x for cls a class and x a lazy attribute + if a is None: # when doing cls.x for cls a class and x a lazy attribute return self try: # _cached_methods is supposed to be a public Cython attribute. @@ -132,7 +132,7 @@ cdef class _lazy_attribute(): for supercls in cls.__mro__: if self.__name__ in supercls.__dict__ and self is supercls.__dict__[self.__name__]: cls = supercls - return getattr(super(cls, a),self.__name__) + return getattr(super(cls, a), self.__name__) try: setattr(a, self.__name__, result) except AttributeError: @@ -497,11 +497,11 @@ class lazy_attribute(_lazy_attribute): self.f = f if hasattr(f, "__doc__"): self.__doc__ = f.__doc__ - elif hasattr(f, "__doc__"): # Needed to handle Cython methods + elif hasattr(f, "__doc__"): # Needed to handle Cython methods self.__doc__ = f.__doc__ if hasattr(f, "__name__"): self.__name__ = f.__name__ - elif hasattr(f, "__name__"): # Needed to handle Cython methods + elif hasattr(f, "__name__"): # Needed to handle Cython methods self.__name__ = f.__name__ if hasattr(f, "__module__"): self.__module__ = f.__module__ diff --git a/src/sage/misc/lazy_import.pyx b/src/sage/misc/lazy_import.pyx index cde9be93d7e..f309429537b 100644 --- a/src/sage/misc/lazy_import.pyx +++ b/src/sage/misc/lazy_import.pyx @@ -990,7 +990,8 @@ cdef class LazyImport(): def lazy_import(module, names, as_=None, *, - at_startup=False, namespace=None, deprecation=None, feature=None): + at_startup=False, namespace=None, + deprecation=None, feature=None): """ Create a lazy import object and inject it into the caller's global namespace. For the purposes of introspection and calling, this is diff --git a/src/sage/misc/lazy_list.pyx b/src/sage/misc/lazy_list.pyx index 65d397996fc..3f48dce9831 100644 --- a/src/sage/misc/lazy_list.pyx +++ b/src/sage/misc/lazy_list.pyx @@ -117,7 +117,7 @@ empty_lazy_list.cache = [] def lazy_list(data=None, initial_values=None, start=None, stop=None, step=None, - update_function=None): + update_function=None): r""" Return a lazy list. @@ -224,7 +224,7 @@ def lazy_list(data=None, initial_values=None, start=None, stop=None, step=None, assert callable(update_function) return lazy_list_from_update_function(update_function, cache) - if isinstance(data, (tuple,list)): + if isinstance(data, (tuple, list)): data = cache + list(data) l = lazy_list_generic(data, start=0, stop=len(data), step=1) elif isinstance(data, lazy_list_generic): @@ -1137,7 +1137,7 @@ cdef class lazy_list_from_update_function(lazy_list_generic): stop 2147483647 # 32-bit step 1 """ - cdef Py_ssize_t l,ll + cdef Py_ssize_t l, ll l = len(self.cache) while l <= i: self.update_function(self.cache) diff --git a/src/sage/misc/lazy_string.pyx b/src/sage/misc/lazy_string.pyx index 48a26c9b609..553664d639f 100644 --- a/src/sage/misc/lazy_string.pyx +++ b/src/sage/misc/lazy_string.pyx @@ -25,13 +25,13 @@ Note that the function is recomputed each time:: l'2' """ -#Copyright (c) 2009 by Armin Ronacher. +# Copyright (c) 2009 by Armin Ronacher. # -#Some rights reserved. +# Some rights reserved. # -#Redistribution and use in source and binary forms, with or without -#modification, are permitted provided that the following conditions are -#met: +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. @@ -45,17 +45,17 @@ Note that the function is recomputed each time:: # promote products derived from this software without specific # prior written permission. # -#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -#"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -#LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -#A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -#OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -#SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -#LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from cpython.object cimport PyObject_Call, PyObject_RichCompare diff --git a/src/sage/misc/meson.build b/src/sage/misc/meson.build index d9f03dd7ffd..964867fa167 100644 --- a/src/sage/misc/meson.build +++ b/src/sage/misc/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'abstract_method.py', 'all.py', 'all__sagemath_environment.py', diff --git a/src/sage/misc/nested_class.pyx b/src/sage/misc/nested_class.pyx index e42569abe3e..1f661b8240a 100644 --- a/src/sage/misc/nested_class.pyx +++ b/src/sage/misc/nested_class.pyx @@ -91,7 +91,7 @@ cdef dict sys_modules = sys.modules __all__ = ['modify_for_nested_pickle', 'nested_pickle', 'NestedClassMetaclass', 'MainClass' # Comment out to silence Sphinx warning about nested classes. - #, 'SubClass', 'CopiedClass', 'A1' + # , 'SubClass', 'CopiedClass', 'A1' ] cpdef modify_for_nested_pickle(cls, str name_prefix, module, first_run=True): @@ -325,7 +325,7 @@ class MainClass(object, metaclass=NestedClassMetaclass): sage: MainClass.NestedClass.NestedSubClass.__name__ 'MainClass.NestedClass.NestedSubClass' """ - def dummy(self, x, *args, r=(1,2,3.4), **kwds): + def dummy(self, x, *args, r=(1, 2, 3.4), **kwds): """ A dummy method to demonstrate the embedding of method signature for nested classes. diff --git a/src/sage/misc/persist.pyx b/src/sage/misc/persist.pyx index 15476a3ec37..f7f83a696b8 100644 --- a/src/sage/misc/persist.pyx +++ b/src/sage/misc/persist.pyx @@ -72,8 +72,8 @@ from sage.misc.sage_unittest import TestSuite # `already_unpickled` and finally returns the element. # # For a working example, see sage.rings.padics.lazy_template.LazyElement_unknown -already_pickled = { } -already_unpickled = { } +already_pickled = {} +already_unpickled = {} cdef _normalize_filename(s): @@ -92,7 +92,7 @@ def load(*filename, compress=True, verbose=True, **kwargs): an ``.sobj`` extension added if it doesn't have one. Or, if the input is a filename ending in ``.py``, ``.pyx``, ``.sage``, ``.spyx``, ``.f``, ``.f90`` or ``.m``, load that file into the current running - session. + session using :func:`sage.repl.load.load`. Loaded files are not loaded into their own namespace, i.e., this is much more like Python's ``execfile`` than Python's ``import``. @@ -176,7 +176,7 @@ def load(*filename, compress=True, verbose=True, **kwargs): sage.repl.load.load(filename, globals()) return - ## Check if filename starts with "http://" or "https://" + # Check if filename starts with "http://" or "https://" lower = filename.lower() if lower.startswith("http://") or lower.startswith("https://"): from sage.misc.remote_file import get_remote_file @@ -186,7 +186,7 @@ def load(*filename, compress=True, verbose=True, **kwargs): tmpfile_flag = False filename = _normalize_filename(filename) - ## Load file by absolute filename + # Load file by absolute filename with open(filename, 'rb') as fobj: X = loads(fobj.read(), compress=compress, **kwargs) try: @@ -194,7 +194,7 @@ def load(*filename, compress=True, verbose=True, **kwargs): except AttributeError: pass - ## Delete the tempfile, if it exists + # Delete the tempfile, if it exists if tmpfile_flag: os.unlink(filename) @@ -303,7 +303,7 @@ def _base_dumps(obj, compress=True): global already_pickled gherkin = SagePickler.dumps(obj) - already_pickled = { } + already_pickled = {} if compress: return comp.compress(gherkin) @@ -334,7 +334,7 @@ def dumps(obj, compress=True): ans = obj.dumps(compress) except (AttributeError, RuntimeError, TypeError): ans = _base_dumps(obj, compress=compress) - already_pickled = { } + already_pickled = {} return ans @@ -588,8 +588,7 @@ def unpickle_global(module, name): def error(): raise ImportError("cannot import {1} from {0}, call " - "register_unpickle_override({0!r}, {1!r}, ...) to fix this".format( - module, name)) + "register_unpickle_override({0!r}, {1!r}, ...) to fix this".format(module, name)) mod = sys.modules.get(module) if mod is not None: @@ -624,9 +623,9 @@ class _BasePickler(pickle.Pickler): """ def __init__(self, file_obj, protocol=None, persistent_id=None, *, - fix_imports=True): + fix_imports=True): super(_BasePickler, self).__init__(file_obj, protocol, - fix_imports=fix_imports) + fix_imports=fix_imports) self._persistent_id = persistent_id def persistent_id(self, obj): @@ -825,7 +824,7 @@ class SagePickler(_BasePickler): buf = io.BytesIO() pickler = cls(buf, **kwargs) pickler.dump(obj) - already_pickled = { } + already_pickled = {} return buf.getvalue() @@ -988,7 +987,7 @@ def loads(s, compress=True, **kwargs): unpickler = SageUnpickler(io.BytesIO(s), **kwargs) global already_unpickled ans = unpickler.load() - already_unpickled = { } + already_unpickled = {} return ans @@ -1060,7 +1059,7 @@ def picklejar(obj, dir=None): global already_pickled s = comp.compress(SagePickler.dumps(obj)) - already_pickled = { } + already_pickled = {} typ = str(type(obj)) name = ''.join([x if (x.isalnum() or x == '_') else '_' for x in typ]) diff --git a/src/sage/misc/randstate.pyx b/src/sage/misc/randstate.pyx index 4ff95658d0d..023b413566f 100644 --- a/src/sage/misc/randstate.pyx +++ b/src/sage/misc/randstate.pyx @@ -544,11 +544,11 @@ cdef class randstate: if seed is None: if use_urandom: - seed = long(binascii.hexlify(os.urandom(16)), 16) + seed = int(binascii.hexlify(os.urandom(16)), 16) else: - seed = long(time.time() * 256) + seed = int(time.time() * 256) else: - seed = long(seed) + seed = int(seed) # If seed==0, leave it at the default seed used by # gmp_randinit_default() @@ -620,9 +620,9 @@ cdef class randstate: from sage.rings.integer_ring import ZZ rand = cls() if seed is None: - rand.seed(long(ZZ.random_element(long(1)<<128))) + rand.seed(int(ZZ.random_element(1<<128))) else: - rand.seed(long(seed)) + rand.seed(int(seed)) self._python_random = rand return rand @@ -639,7 +639,7 @@ cdef class randstate: 48314508034782595865062786044921182484 """ from sage.rings.integer_ring import ZZ - return ZZ.random_element(long(1)<<128) + return ZZ.random_element(1<<128) cpdef long_seed(self): r""" @@ -653,7 +653,7 @@ cdef class randstate: 256056279774514099508607350947089272595 """ from sage.rings.integer_ring import ZZ - return long(ZZ.random_element(long(1)<<128)) + return int(ZZ.random_element(1<<128)) cpdef set_seed_libc(self, bint force): r""" @@ -703,7 +703,7 @@ cdef class randstate: if force or _ntl_seed_randstate is not self: import sage.libs.ntl.ntl_ZZ as ntl_ZZ from sage.rings.integer_ring import ZZ - ntl_ZZ.ntl_setSeed(ZZ.random_element(long(1)<<128)) + ntl_ZZ.ntl_setSeed(ZZ.random_element(1<<128)) _ntl_seed_randstate = self def set_seed_gap(self): @@ -730,7 +730,7 @@ cdef class randstate: mersenne_seed, classic_seed = self._gap_saved_seed else: from sage.rings.integer_ring import ZZ - seed = ZZ.random_element(long(1)<<128) + seed = ZZ.random_element(1<<128) classic_seed = seed mersenne_seed = seed @@ -853,8 +853,8 @@ cdef class randstate: sage: current_randstate().c_rand_double() 0.22437207488974298 """ - cdef double a = gmp_urandomb_ui(self.gmp_state, 25) * (1.0 / 33554432.0) # divide by 2^25 - cdef double b = gmp_urandomb_ui(self.gmp_state, 28) * (1.0 / 9007199254740992.0) # divide by 2^53 + cdef double a = gmp_urandomb_ui(self.gmp_state, 25) * (1.0 / 33554432.0) # divide by 2^25 + cdef double b = gmp_urandomb_ui(self.gmp_state, 28) * (1.0 / 9007199254740992.0) # divide by 2^53 return a+b def __dealloc__(self): @@ -1019,9 +1019,7 @@ def benchmark_libc(): sage: timeit('benchmark_mt()') # random 125 loops, best of 3: 2.12 ms per loop """ - cdef int i - cdef randstate rstate = _current_randstate - for i from 0 <= i < 100000: + for _ in range(100000): c_libc_random() diff --git a/src/sage/misc/search.pxd b/src/sage/misc/search.pxd index 8cc43ba1b0f..f73e9061b8d 100644 --- a/src/sage/misc/search.pxd +++ b/src/sage/misc/search.pxd @@ -1 +1 @@ -cpdef search(object v, object x) \ No newline at end of file +cpdef search(object v, object x) diff --git a/src/sage/misc/stopgap.pyx b/src/sage/misc/stopgap.pyx index e6f626ae9b4..ccf18faf22b 100644 --- a/src/sage/misc/stopgap.pyx +++ b/src/sage/misc/stopgap.pyx @@ -2,7 +2,7 @@ Stopgaps """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2012 William Stein # # This program is free software: you can redistribute it and/or modify @@ -10,7 +10,7 @@ Stopgaps # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # https://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** import warnings diff --git a/src/sage/misc/weak_dict.pyx b/src/sage/misc/weak_dict.pyx index ce69b6a840a..c288d312bd9 100644 --- a/src/sage/misc/weak_dict.pyx +++ b/src/sage/misc/weak_dict.pyx @@ -404,7 +404,7 @@ cdef class WeakValueDictionary(dict): True """ out = WeakValueDictionary() - for k,v in self.items(): + for k, v in self.items(): out[deepcopy(k, memo)] = v return out @@ -624,7 +624,7 @@ cdef class WeakValueDictionary(dict): ... KeyError: 'popitem(): weak value dictionary is empty' """ - for k,v in self.items(): + for k, v in self.items(): del self[k] return k, v raise KeyError('popitem(): weak value dictionary is empty') diff --git a/src/sage/modular/arithgroup/congroup_generic.py b/src/sage/modular/arithgroup/congroup_generic.py index b94568522f6..48ec86783e1 100644 --- a/src/sage/modular/arithgroup/congroup_generic.py +++ b/src/sage/modular/arithgroup/congroup_generic.py @@ -464,7 +464,7 @@ def __init__(self, *args, **kwds): sage: sage.modular.arithgroup.congroup_generic.CongruenceSubgroup(5) # indirect doctest Generic congruence subgroup of level 5 """ - return CongruenceSubgroupBase.__init__(self, *args, **kwds) + CongruenceSubgroupBase.__init__(self, *args, **kwds) def _repr_(self): """ diff --git a/src/sage/modular/arithgroup/meson.build b/src/sage/modular/arithgroup/meson.build index c4a68af3217..52475097b34 100644 --- a/src/sage/modular/arithgroup/meson.build +++ b/src/sage/modular/arithgroup/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'arithgroup_generic.py', 'arithgroup_perm.py', diff --git a/src/sage/modular/meson.build b/src/sage/modular/meson.build index d334cf975c8..8b7e48c94aa 100644 --- a/src/sage/modular/meson.build +++ b/src/sage/modular/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'buzzard.py', 'congroup.py', diff --git a/src/sage/modular/modform/meson.build b/src/sage/modular/modform/meson.build index 7276059448d..541227d9511 100644 --- a/src/sage/modular/modform/meson.build +++ b/src/sage/modular/modform/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'ambient.py', 'ambient_R.py', diff --git a/src/sage/modular/modsym/meson.build b/src/sage/modular/modsym/meson.build index f05d0776246..15851710402 100644 --- a/src/sage/modular/modsym/meson.build +++ b/src/sage/modular/modsym/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'ambient.py', 'apply.pxd', diff --git a/src/sage/modular/pollack_stevens/meson.build b/src/sage/modular/pollack_stevens/meson.build index d22947db12c..0506a90ac83 100644 --- a/src/sage/modular/pollack_stevens/meson.build +++ b/src/sage/modular/pollack_stevens/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'dist.pxd', 'distributions.py', diff --git a/src/sage/modules/meson.build b/src/sage/modules/meson.build index 5212b65d0da..fcf360ee292 100644 --- a/src/sage/modules/meson.build +++ b/src/sage/modules/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'complex_double_vector.py', 'diamond_cutting.py', @@ -16,6 +17,7 @@ py.install_sources( 'module.pxd', 'module_functors.py', 'multi_filtered_vector_space.py', + 'numpy_util.pxd', 'quotient_module.py', 'real_double_vector.py', 'submodule.py', diff --git a/src/sage/modules/with_basis/meson.build b/src/sage/modules/with_basis/meson.build index 1956c6ac99c..7dc1dda551b 100644 --- a/src/sage/modules/with_basis/meson.build +++ b/src/sage/modules/with_basis/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'cell_module.py', 'indexed_element.pxd', diff --git a/src/sage/monoids/meson.build b/src/sage/monoids/meson.build index df2a4ae36be..5f785b5b257 100644 --- a/src/sage/monoids/meson.build +++ b/src/sage/monoids/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'automatic_semigroup.py', 'free_abelian_monoid.py', diff --git a/src/sage/numerical/backends/meson.build b/src/sage/numerical/backends/meson.build index a6a53e97033..57eeaeb10b0 100644 --- a/src/sage/numerical/backends/meson.build +++ b/src/sage/numerical/backends/meson.build @@ -2,6 +2,7 @@ glpk = cc.find_library('glpk') py.install_sources( + '__init__.py', 'all.py', 'all__sagemath_polyhedra.py', 'cvxopt_backend_test.py', diff --git a/src/sage/numerical/meson.build b/src/sage/numerical/meson.build index 222deff834e..91257af0880 100644 --- a/src/sage/numerical/meson.build +++ b/src/sage/numerical/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'all__sagemath_polyhedra.py', 'interactive_simplex_method.py', diff --git a/src/sage/plot/animate.py b/src/sage/plot/animate.py index c37a17531d8..84433c492e2 100644 --- a/src/sage/plot/animate.py +++ b/src/sage/plot/animate.py @@ -839,7 +839,7 @@ def show(self, delay=None, iterations=None, **kwds): sage: a.show(delay=50) # long time # optional -- ImageMagick - You can also make use of the HTML5 video element in the Sage Notebook:: + You can also make use of the HTML5 video element in the Sage notebook:: sage: # long time, optional -- FFmpeg sage: a.show(format='ogg') diff --git a/src/sage/plot/meson.build b/src/sage/plot/meson.build index 96a337faf78..8cb44114959 100644 --- a/src/sage/plot/meson.build +++ b/src/sage/plot/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'animate.py', 'arc.py', diff --git a/src/sage/plot/misc.py b/src/sage/plot/misc.py index ca19ef887cb..e5bccb0ffad 100644 --- a/src/sage/plot/misc.py +++ b/src/sage/plot/misc.py @@ -63,10 +63,10 @@ def setup_for_eval_on_grid(funcs, EXAMPLES:: - sage: x,y,z=var('x,y,z') - sage: f(x,y)=x+y-z - sage: g(x,y)=x+y - sage: h(y)=-y + sage: x,y,z = var('x,y,z') + sage: f(x,y) = x+y-z + sage: g(x,y) = x+y + sage: h(y) = -y sage: sage.plot.misc.setup_for_eval_on_grid(f, [(0, 2),(1,3),(-4,1)], plot_points=5) (, [(0.0, 2.0, 0.5), (1.0, 3.0, 0.5), (-4.0, 1.0, 1.25)]) sage: sage.plot.misc.setup_for_eval_on_grid([g,h], [(0, 2),(-1,1)], plot_points=5) @@ -149,7 +149,7 @@ def setup_for_eval_on_grid(funcs, # pad the variables if we don't have enough nargs = len(ranges) if len(vars) < nargs: - vars += ('_',)*(nargs-len(vars)) + vars += ('_',) * (nargs - len(vars)) ranges = [[float(z) for z in r] for r in ranges] @@ -157,12 +157,13 @@ def setup_for_eval_on_grid(funcs, plot_points = 2 if not isinstance(plot_points, (list, tuple)): - plot_points = [plot_points]*len(ranges) + plot_points = [plot_points] * len(ranges) elif len(plot_points) != nargs: raise ValueError("plot_points must be either an integer or a list of integers, one for each range") plot_points = [int(p) if p >= 2 else 2 for p in plot_points] - range_steps = [abs(range[1] - range[0])/(p-1) for range, p in zip(ranges, plot_points)] + range_steps = [abs(range[1] - range[0]) / (p - 1) + for range, p in zip(ranges, plot_points)] if min(range_steps) == float(0): raise ValueError("plot start point and end point must be different") @@ -206,11 +207,12 @@ def try_make_fast(f): # Handle vectors, lists, tuples, etc. if isinstance(funcs, Iterable): - funcs = tuple( try_make_fast(f) for f in funcs ) + funcs = tuple(try_make_fast(f) for f in funcs) else: funcs = try_make_fast(funcs) - #TODO: raise an error if there is a function/method in funcs that takes more values than we have ranges + # TODO: raise an error if there is a function/method in funcs that + # takes more values than we have ranges if return_vars: return (funcs, @@ -242,10 +244,10 @@ def unify_arguments(funcs): EXAMPLES:: - sage: x,y,z=var('x,y,z') - sage: f(x,y)=x+y-z - sage: g(x,y)=x+y - sage: h(y)=-y + sage: x,y,z = var('x,y,z') + sage: f(x,y) = x+y-z + sage: g(x,y) = x+y + sage: h(y) = -y sage: sage.plot.misc.unify_arguments((f,g,h)) ((x, y, z), (z,)) sage: sage.plot.misc.unify_arguments((g,h)) @@ -284,7 +286,9 @@ def _multiple_of_constant(n, pos, const): r""" Function for internal use in formatting ticks on axes with nice-looking multiples of various symbolic constants, such - as `\pi` or `e`. Should only be used via keyword argument + as `\pi` or `e`. + + This should only be used via keyword argument ``tick_formatter`` in :meth:`plot.show`. See documentation for the matplotlib.ticker module for more details. @@ -311,11 +315,11 @@ def _multiple_of_constant(n, pos, const): from sage.misc.latex import latex from sage.rings.continued_fraction import continued_fraction from sage.rings.infinity import Infinity - cf = continued_fraction(n/const) + cf = continued_fraction(n / const) k = 1 while cf.quotient(k) != Infinity and cf.denominator(k) < 12: k += 1 - return '$%s$' % latex(cf.convergent(k-1)*const) + return '$%s$' % latex(cf.convergent(k - 1) * const) def get_matplotlib_linestyle(linestyle, return_type): @@ -401,10 +405,14 @@ def get_matplotlib_linestyle(linestyle, return_type): {'solid', 'dashed', 'dotted', dashdot', 'None'}, respectively {'-', '--', ':', '-.', ''} """ - long_to_short_dict = {'solid' : '-','dashed' : '--', 'dotted' : ':', - 'dashdot':'-.'} - short_to_long_dict = {'-' : 'solid','--' : 'dashed', ':' : 'dotted', - '-.':'dashdot'} + long_to_short_dict = {'solid': '-', + 'dashed': '--', + 'dotted': ':', + 'dashdot': '-.'} + short_to_long_dict = {'-': 'solid', + '--': 'dashed', + ':': 'dotted', + '-.': 'dashdot'} # We need this to take care of region plot. Essentially, if None is # passed, then we just return back the same thing. @@ -416,16 +424,16 @@ def get_matplotlib_linestyle(linestyle, return_type): elif linestyle.startswith("steps"): if linestyle.startswith("steps-mid"): return "steps-mid" + get_matplotlib_linestyle( - linestyle.strip("steps-mid"), "short") + linestyle.strip("steps-mid"), "short") elif linestyle.startswith("steps-post"): return "steps-post" + get_matplotlib_linestyle( - linestyle.strip("steps-post"), "short") + linestyle.strip("steps-post"), "short") elif linestyle.startswith("steps-pre"): return "steps-pre" + get_matplotlib_linestyle( - linestyle.strip("steps-pre"), "short") + linestyle.strip("steps-pre"), "short") else: return "steps" + get_matplotlib_linestyle( - linestyle.strip("steps"), "short") + linestyle.strip("steps"), "short") if return_type == 'short': if linestyle in short_to_long_dict.keys(): diff --git a/src/sage/plot/plot3d/meson.build b/src/sage/plot/plot3d/meson.build index 46cc4a25ffc..ae1dd2a6b41 100644 --- a/src/sage/plot/plot3d/meson.build +++ b/src/sage/plot/plot3d/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'base.pxd', 'implicit_plot3d.py', diff --git a/src/sage/plot/plot3d/parametric_plot3d.py b/src/sage/plot/plot3d/parametric_plot3d.py index 6a3da2b830f..c0eebb5a160 100644 --- a/src/sage/plot/plot3d/parametric_plot3d.py +++ b/src/sage/plot/plot3d/parametric_plot3d.py @@ -989,8 +989,8 @@ def g(x, y): return x, y+sin(y), x**2 + y**2 if isinstance(f, Vector): f = tuple(f) - if isinstance(f, (list, tuple)) and len(f) > 0 and isinstance(f[0], (list, tuple)): - return sum([parametric_plot3d(v, urange, vrange, plot_points=plot_points, **kwds) for v in f]) + if isinstance(f, (list, tuple)) and f and isinstance(f[0], (list, tuple)): + return sum(parametric_plot3d(v, urange, vrange, plot_points=plot_points, **kwds) for v in f) if not isinstance(f, (tuple, list)) or len(f) != 3: raise ValueError("f must be a list, tuple, or vector of length 3") @@ -1121,7 +1121,9 @@ def _parametric_plot3d_surface(f, urange, vrange, plot_points, boundary_style, * if boundary_style is not None: for u in (urange[0], urange[-1]): - G += line3d([(g[0](u,v), g[1](u,v), g[2](u,v)) for v in vrange], **boundary_style) + G += line3d([(g[0](u, v), g[1](u, v), g[2](u, v)) for v in vrange], + **boundary_style) for v in (vrange[0], vrange[-1]): - G += line3d([(g[0](u,v), g[1](u,v), g[2](u,v)) for u in urange], **boundary_style) + G += line3d([(g[0](u, v), g[1](u, v), g[2](u, v)) for u in urange], + **boundary_style) return G diff --git a/src/sage/plot/plot3d/plot3d.py b/src/sage/plot/plot3d/plot3d.py index 9f5b9a8d94d..f3fd2078d64 100644 --- a/src/sage/plot/plot3d/plot3d.py +++ b/src/sage/plot/plot3d/plot3d.py @@ -155,7 +155,7 @@ def f(x, y): return math.exp(x/5)*math.cos(y) from sage.plot.plot3d.shapes import arrow3d from sage.plot.plot3d.texture import Texture from sage.plot.plot3d.tri_plot import TrianglePlot - +from . import parametric_plot3d lazy_import("sage.functions.trig", ["cos", "sin"]) @@ -190,8 +190,12 @@ def __init__(self, dep_var, indep_vars): Arbitrary Coordinates coordinate transform (z in terms of x, y) """ all_vars = sage_getargspec(self.transform).args[1:] - if set(all_vars) != set(indep_vars + [dep_var]): - raise ValueError('variables were specified incorrectly for this coordinate system; incorrect variables were %s' % list(set(all_vars).symmetric_difference(set(indep_vars+[dep_var])))) + A = set(all_vars) + B = set(indep_vars + [dep_var]) + if A != B: + raise ValueError('variables were specified incorrectly for this ' + 'coordinate system; incorrect variables ' + 'were %s' % list(A.symmetric_difference(B))) self.dep_var = dep_var self.indep_vars = indep_vars @@ -790,10 +794,7 @@ def smooth_triangle(self, a, b, c, da, db, dc, color=None): sage: sm_tri [[0, 0, 0], [0, 0, 1], [1, 1, 0]] """ - return [a,b,c] - - -from . import parametric_plot3d + return [a, b, c] def plot3d(f, urange, vrange, adaptive=False, transformation=None, **kwds): @@ -1083,7 +1084,7 @@ def plot3d(f, urange, vrange, adaptive=False, transformation=None, **kwds): params = f.variables() from sage.modules.vector_callable_symbolic_dense import Vector_callable_symbolic_dense - if isinstance(transformation, (tuple, list,Vector_callable_symbolic_dense)): + if isinstance(transformation, (tuple, list, Vector_callable_symbolic_dense)): if len(transformation) == 3: if params is None: raise ValueError("must specify independent variable names in the ranges when using generic transformation") @@ -1095,7 +1096,7 @@ def plot3d(f, urange, vrange, adaptive=False, transformation=None, **kwds): raise ValueError("unknown transformation type") # find out which variable is the function variable by # eliminating the parameter variables. - all_vars = set(sum([list(s.variables()) for s in transformation],[])) + all_vars = set(sum([list(s.variables()) for s in transformation], [])) dep_var = all_vars - set(indep_vars) if len(dep_var) == 1: dep_var = dep_var.pop() @@ -1173,11 +1174,11 @@ def plot3d_adaptive(f, x_range, y_range, color='automatic', max_depth = max(max_depth, initial_depth) from sage.plot.misc import setup_for_eval_on_grid - g, ranges = setup_for_eval_on_grid(f, [x_range,y_range], plot_points=2) - xmin,xmax = ranges[0][:2] - ymin,ymax = ranges[1][:2] + g, ranges = setup_for_eval_on_grid(f, [x_range, y_range], plot_points=2) + xmin, xmax = ranges[0][:2] + ymin, ymax = ranges[1][:2] - opacity = float(kwds.get('opacity',1)) + opacity = float(kwds.get('opacity', 1)) if color == "automatic": texture = rainbow(num_colors, 'rgbtuple') @@ -1197,9 +1198,9 @@ def plot3d_adaptive(f, x_range, y_range, color='automatic', if isinstance(texture, (list, tuple)): if len(texture) == 2: # do a grid coloring - xticks = (xmax - xmin)/2**initial_depth - yticks = (ymax - ymin)/2**initial_depth - parts = P.partition(lambda x,y,z: (int((x-xmin)/xticks) + int((y-ymin)/yticks)) % 2) + xticks = (xmax - xmin) / 2**initial_depth + yticks = (ymax - ymin) / 2**initial_depth + parts = P.partition(lambda x, y, z: (int((x - xmin) / xticks) + int((y - ymin) / yticks)) % 2) else: # do a topo coloring bounds = P.bounding_box() @@ -1208,8 +1209,8 @@ def plot3d_adaptive(f, x_range, y_range, color='automatic', if max_z == min_z: span = 0 else: - span = (len(texture)-1) / (max_z - min_z) # max to avoid dividing by 0 - parts = P.partition(lambda x, y, z: int((z-min_z)*span)) + span = (len(texture) - 1) / (max_z - min_z) # max to avoid dividing by 0 + parts = P.partition(lambda x, y, z: int((z - min_z) * span)) all = [] for k, G in parts.items(): G.set_texture(texture[k], opacity=opacity) diff --git a/src/sage/plot/plot3d/plot_field3d.py b/src/sage/plot/plot3d/plot_field3d.py index 99f9449e2f8..a4f415935d2 100644 --- a/src/sage/plot/plot3d/plot_field3d.py +++ b/src/sage/plot/plot3d/plot_field3d.py @@ -2,7 +2,7 @@ """ Plotting 3D fields """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009 Jason Grout # # Distributed under the terms of the GNU General Public License (GPL) @@ -14,8 +14,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.arith.srange import srange from sage.plot.misc import setup_for_eval_on_grid @@ -143,16 +143,19 @@ def plot_vector_field3d(functions, xrange, yrange, zrange, from matplotlib.colors import LinearSegmentedColormap cm = LinearSegmentedColormap.from_list('mymap', colors) else: - cm = lambda x: colors + def cm(x): + return colors max_len = max(v.norm() for v in vectors) - scaled_vectors = [v/max_len for v in vectors] + scaled_vectors = [v / max_len for v in vectors] if center_arrows: - G = sum([plot(v, color=cm(v.norm()), **kwds).translate(p-v/2) for v, p in zip(scaled_vectors, points)]) + G = sum(plot(v, color=cm(v.norm()), **kwds).translate(p - v / 2) + for v, p in zip(scaled_vectors, points)) G._set_extra_kwds(kwds) return G else: - G = sum([plot(v, color=cm(v.norm()), **kwds).translate(p) for v, p in zip(scaled_vectors, points)]) + G = sum(plot(v, color=cm(v.norm()), **kwds).translate(p) + for v, p in zip(scaled_vectors, points)) G._set_extra_kwds(kwds) return G diff --git a/src/sage/plot/plot3d/revolution_plot3d.py b/src/sage/plot/plot3d/revolution_plot3d.py index 721573516a5..a98f2ddcc32 100644 --- a/src/sage/plot/plot3d/revolution_plot3d.py +++ b/src/sage/plot/plot3d/revolution_plot3d.py @@ -7,7 +7,7 @@ - Oscar Gerardo Lazo Arjona (2010): initial version. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2010 Oscar Gerardo Lazo Arjona algebraicamente@gmail.com # # Distributed under the terms of the GNU General Public License (GPL) @@ -19,8 +19,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.misc.decorators import rename_keyword from sage.plot.plot3d.parametric_plot3d import parametric_plot3d @@ -253,10 +253,10 @@ def cf(u, phi): return float(2 * u / pi) % 1 phirange = (phi, phirange[0], phirange[1]) if isinstance(curve, (tuple, list)): - #this if-else provides a vector v to be plotted - #if curve is a tuple or a list of length 2, it is interpreted as a parametric curve - #in the x-z plane. - #if it is of length 3 it is interpreted as a parametric curve in 3d space + # this if-else provides a vector v to be plotted + # if curve is a tuple or a list of length 2, it is interpreted as a parametric curve + # in the x-z plane. + # if it is of length 3 it is interpreted as a parametric curve in 3d space if len(curve) == 2: x = curve[0] diff --git a/src/sage/plot/plot3d/shapes2.py b/src/sage/plot/plot3d/shapes2.py index 05d6bb6e583..08077c89f2e 100644 --- a/src/sage/plot/plot3d/shapes2.py +++ b/src/sage/plot/plot3d/shapes2.py @@ -7,7 +7,7 @@ - William Stein and Robert Bradshaw (2008-01): Many improvements """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 William Stein # Copyright (C) 2008 Robert Bradshaw # @@ -20,10 +20,11 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** import math + from . import shapes from .base import PrimitiveObject, point_list_bounding_box @@ -34,11 +35,10 @@ from sage.arith.srange import srange from .texture import Texture - -TACHYON_PIXEL = 1/200.0 - from .shapes import Text, Sphere +TACHYON_PIXEL = 1 / 200.0 + @rename_keyword(alpha='opacity') def line3d(points, thickness=1, radius=None, arrow_head=False, **kwds): @@ -516,10 +516,12 @@ def frame_labels(lower_left, upper_right, # Helper function for formatting the frame labels from math import log log10 = log(10) - nd = lambda a: int(log(a)/log10) + + def nd(a): + return int(log(a) / log10) def fmt_string(a): - b = a/2.0 + b = a / 2.0 if b >= 1: return "%.1f" n = max(0, 2 - nd(a/2.0)) @@ -527,7 +529,7 @@ def fmt_string(a): # Slightly faster than mean for this situation def avg(a, b): - return (a+b)/2.0 + return (a + b) / 2.0 color = (0.3, 0.3, 0.3) @@ -722,8 +724,8 @@ def ruler_frame(lower_left, upper_right, ticks=4, sub_ticks=4, **kwds): sphinx_plot(ruler_frame([1,2,3],vector([2,3,4]),ticks=6, sub_ticks=2, color='red')) """ return ruler(lower_left, (upper_right[0], lower_left[1], lower_left[2]), ticks=ticks, sub_ticks=sub_ticks, absolute=True, **kwds) \ - + ruler(lower_left, (lower_left[0], upper_right[1], lower_left[2]), ticks=ticks, sub_ticks=sub_ticks, absolute=True, **kwds) \ - + ruler(lower_left, (lower_left[0], lower_left[1], upper_right[2]), ticks=ticks, sub_ticks=sub_ticks, absolute=True, **kwds) + + ruler(lower_left, (lower_left[0], upper_right[1], lower_left[2]), ticks=ticks, sub_ticks=sub_ticks, absolute=True, **kwds) \ + + ruler(lower_left, (lower_left[0], lower_left[1], upper_right[2]), ticks=ticks, sub_ticks=sub_ticks, absolute=True, **kwds) ########################### @@ -1143,8 +1145,8 @@ def tachyon_repr(self, render_params): cmd = ('FCylinder base {pos[0]!r} {pos[1]!r} {pos[2]!r} ' 'apex {apex[0]!r} {apex[1]!r} {apex[2]!r} ' 'rad {radius!r} {texture}').format( - pos=(px, py, pz), apex=(x, y, z), radius=radius, - texture=self.texture.id) + pos=(px, py, pz), apex=(x, y, z), radius=radius, + texture=self.texture.id) cmds.append(cmd) px, py, pz = x, y, z return cmds diff --git a/src/sage/plot/plot3d/tachyon.py b/src/sage/plot/plot3d/tachyon.py index adb1d80b7be..d04b4332a8c 100644 --- a/src/sage/plot/plot3d/tachyon.py +++ b/src/sage/plot/plot3d/tachyon.py @@ -371,8 +371,8 @@ def __init__(self, antialiasing=False, aspectratio=1.0, raydepth=8, - camera_position=None, # default value (-3, 0, 0), - camera_center=None, # alternative equivalent name + camera_position=None, # default value (-3, 0, 0), + camera_center=None, # alternative equivalent name updir=[0, 0, 1], look_at=[0, 0, 0], viewdir=None, @@ -397,10 +397,10 @@ def __init__(self, self._raydepth = raydepth if camera_position is not None: self._camera_position = camera_position - elif camera_center is not None: # make sure that old programs continue to work + elif camera_center is not None: # make sure that old programs continue to work self._camera_position = camera_center else: - self._camera_position = (-3, 0, 0) # default value + self._camera_position = (-3, 0, 0) # default value self._updir = updir self._projection = projection self._focallength = focallength @@ -689,8 +689,8 @@ def str(self): {} {} end_scene""".format(self._res(), - self._camera(), - '\n'.join(x.str() for x in self._objects)) + self._camera(), + '\n'.join(x.str() for x in self._objects)) def light(self, center, radius, color): r""" @@ -1249,15 +1249,15 @@ def str(self): phong {} {} phong_size {} color {} texfunc {} """.format(self._name, - self._ambient, - self._diffuse, - self._specular, - self._opacity, - self._phongtype, - self._phong, - self._phongsize, - tostr(self._color), - self._texfunc) + self._ambient, + self._diffuse, + self._specular, + self._opacity, + self._phongtype, + self._phong, + self._phongsize, + tostr(self._color), + self._texfunc) class Sphere: @@ -1338,7 +1338,7 @@ def str(self): return r""" ring center {} normal {} inner {} outer {} {} """.format(tostr(self._center), tostr(self._normal), - self._inner, self._outer, self._texture) + self._inner, self._outer, self._texture) class FractalLandscape: @@ -1380,7 +1380,7 @@ def str(self): return r""" scape res {} scale {} center {} {} """.format(tostr(self._res, 2, int), tostr(self._scale, 2, int), - tostr(self._center), self._texture) + tostr(self._center), self._texture) class Cylinder: diff --git a/src/sage/plot/plot3d/texture.py b/src/sage/plot/plot3d/texture.py index 561b8efbc3a..21c901fc0c3 100644 --- a/src/sage/plot/plot3d/texture.py +++ b/src/sage/plot/plot3d/texture.py @@ -421,10 +421,10 @@ def mtl_str(self): illum {illumination} Ns {shininess!r} d {opacity!r}""" - ).format(id=self.id, ambient=self.ambient, diffuse=self.diffuse, - specular=self.specular, - illumination=(2 if sum(self.specular) > 0 else 1), - shininess=self.shininess, opacity=self.opacity) + ).format(id=self.id, ambient=self.ambient, diffuse=self.diffuse, + specular=self.specular, + illumination=(2 if sum(self.specular) > 0 else 1), + shininess=self.shininess, opacity=self.opacity) def jmol_str(self, obj): r""" @@ -447,6 +447,6 @@ def jmol_str(self, obj): """ translucent = "translucent %s" % float(1 - self.opacity) if self.opacity < 1 else "" return "color {} {} [{},{},{}]".format(obj, translucent, - int(255 * self.color[0]), - int(255 * self.color[1]), - int(255 * self.color[2])) + int(255 * self.color[0]), + int(255 * self.color[1]), + int(255 * self.color[2])) diff --git a/src/sage/plot/plot3d/tri_plot.py b/src/sage/plot/plot3d/tri_plot.py index a2e843778eb..4011995a0a7 100644 --- a/src/sage/plot/plot3d/tri_plot.py +++ b/src/sage/plot/plot3d/tri_plot.py @@ -241,7 +241,7 @@ def str(self): return "".join(o.str() for o in self._objects) def __init__(self, triangle_factory, f, min_x__max_x, min_y__max_y, g=None, - min_depth=4, max_depth=8, num_colors=None, max_bend=.3): + min_depth=4, max_depth=8, num_colors=None, max_bend=.3): """ TESTS:: @@ -345,28 +345,28 @@ def plot_block(self, min_x, mid_x, max_x, min_y, mid_y, max_y, sw_z, nw_z, se_z, se_depth = next_depth ne_depth = next_depth else: - #compute the midpoint-to-corner vectors + # compute the midpoint-to-corner vectors sw_v = (min_x - mid_x, min_y - mid_y, sw_z[0] - mid_z[0]) nw_v = (min_x - mid_x, max_y - mid_y, nw_z[0] - mid_z[0]) se_v = (max_x - mid_x, min_y - mid_y, se_z[0] - mid_z[0]) ne_v = (max_x - mid_x, max_y - mid_y, ne_z[0] - mid_z[0]) - #compute triangle normal unit vectors by taking the cross-products - #of the midpoint-to-corner vectors. always go around clockwise - #so we're guaranteed to have a positive value near 1 when neighboring - #triangles are parallel - #However -- crossunit doesn't really return a unit vector. It returns - #the length of the vector to avoid numerical instability when the - #length is nearly zero -- rather than divide by nearly zero, we multiply - #the other side of the inequality by nearly zero -- in general, this - #should work a bit better because of the density of floating-point - #numbers near zero. + # compute triangle normal unit vectors by taking the cross-products + # of the midpoint-to-corner vectors. always go around clockwise + # so we're guaranteed to have a positive value near 1 when neighboring + # triangles are parallel + # However -- crossunit doesn't really return a unit vector. It returns + # the length of the vector to avoid numerical instability when the + # length is nearly zero -- rather than divide by nearly zero, we multiply + # the other side of the inequality by nearly zero -- in general, this + # should work a bit better because of the density of floating-point + # numbers near zero. norm_w = crossunit(sw_v, nw_v) norm_n = crossunit(nw_v, ne_v) norm_e = crossunit(ne_v, se_v) norm_s = crossunit(se_v, sw_v) - #compute the dot products of the triangle unit norms + # compute the dot products of the triangle unit norms e_sw = norm_w[0]*norm_s[0] + norm_w[1]*norm_s[1] + norm_w[2]*norm_s[2] e_nw = norm_w[0]*norm_n[0] + norm_w[1]*norm_n[1] + norm_w[2]*norm_n[2] e_se = norm_e[0]*norm_s[0] + norm_e[1]*norm_s[1] + norm_e[2]*norm_s[2] diff --git a/src/sage/probability/meson.build b/src/sage/probability/meson.build index 83b6a7e091e..2981a7496c4 100644 --- a/src/sage/probability/meson.build +++ b/src/sage/probability/meson.build @@ -1,4 +1,9 @@ -py.install_sources('all.py', 'random_variable.py', subdir: 'sage/probability') +py.install_sources( + '__init__.py', + 'all.py', + 'random_variable.py', + subdir: 'sage/probability', +) extension_data = { 'probability_distribution' : files('probability_distribution.pyx'), diff --git a/src/sage/quadratic_forms/meson.build b/src/sage/quadratic_forms/meson.build index 0e352ed72be..3208c9f15f6 100644 --- a/src/sage/quadratic_forms/meson.build +++ b/src/sage/quadratic_forms/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'binary_qf.py', 'bqf_class_group.py', diff --git a/src/sage/quivers/meson.build b/src/sage/quivers/meson.build index cdefdce952b..aa6d757721d 100644 --- a/src/sage/quivers/meson.build +++ b/src/sage/quivers/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'algebra.py', 'algebra_elements.pxd', 'all.py', diff --git a/src/sage/repl/attach.py b/src/sage/repl/attach.py index 89b6a789fc4..b3e20fe61d8 100644 --- a/src/sage/repl/attach.py +++ b/src/sage/repl/attach.py @@ -318,8 +318,12 @@ def attach(*files): .. SEEALSO:: - :meth:`~sage.repl.load.load` is the same as :func:`attach`, but - does not automatically reload a file when it changes. + :func:`~sage.repl.load.load` is the same as :func:`attach`, but + does not automatically reload a file when it changes unless + ``attach=True`` is passed. + + ``%attach`` magic can also be used, see + :meth:`~sage.repl.ipython_extension.SageMagics.attach`. EXAMPLES: diff --git a/src/sage/repl/interpreter.py b/src/sage/repl/interpreter.py index 83d22127cd3..664fa9a8444 100644 --- a/src/sage/repl/interpreter.py +++ b/src/sage/repl/interpreter.py @@ -75,8 +75,7 @@ sage: from sage.repl.interpreter import get_test_shell sage: shell = get_test_shell() - sage: print("dummy line"); shell.run_cell('1/0') # known bug (meson doesn't include the Cython source code) # see #25320 for the reason of the `...` and the dummy line in this test - dummy line + sage: shell.run_cell('1/0') # known bug (meson doesn't include the Cython source code) ... ZeroDivisionError...Traceback (most recent call last) ... diff --git a/src/sage/repl/ipython_extension.py b/src/sage/repl/ipython_extension.py index d7b1821d7d0..329a7b9b95a 100644 --- a/src/sage/repl/ipython_extension.py +++ b/src/sage/repl/ipython_extension.py @@ -107,6 +107,10 @@ def runfile(self, s): - ``s`` -- string; the file to be loaded + .. SEEALSO:: + + This is the same as :func:`~sage.repl.load.load`. + EXAMPLES:: sage: import os @@ -133,6 +137,10 @@ def attach(self, s): - ``s`` -- string. The file to be attached + .. SEEALSO:: + + This is the same as :func:`~sage.repl.attach.attach`. + EXAMPLES:: sage: from sage.repl.interpreter import get_test_shell @@ -395,14 +403,13 @@ def cython(self, line, cell): ....: ''') UsageError: unrecognized arguments: --help - Test invalid quotes (see :mod:`sage.repl.interpreter` for explanation of the dummy line):: + Test invalid quotes:: sage: # needs sage.misc.cython - sage: print("dummy line"); shell.run_cell(''' + sage: shell.run_cell(''' ....: %%cython --a=' ....: print(1) ....: ''') - dummy line ... ValueError...Traceback (most recent call last) ... diff --git a/src/sage/repl/ipython_kernel/widgets.py b/src/sage/repl/ipython_kernel/widgets.py index 42d674b7c9a..6b32b3a1ded 100644 --- a/src/sage/repl/ipython_kernel/widgets.py +++ b/src/sage/repl/ipython_kernel/widgets.py @@ -119,7 +119,7 @@ def __init__(self, *args, **kwds): <... 'dict'> """ self.__transform = kwds.pop("transform", None) - return super().__init__(*args, **kwds) + super().__init__(*args, **kwds) def get_value(self): """ diff --git a/src/sage/repl/load.py b/src/sage/repl/load.py index a1a1451f1c2..6cd0c792c76 100644 --- a/src/sage/repl/load.py +++ b/src/sage/repl/load.py @@ -87,6 +87,14 @@ def load(filename, globals, attach=False): from t import * + .. NOTE:: + + The global ``load`` function is :func:`sage.misc.persist.load`, + which delegates to this function for code file formats. + + ``%runfile`` magic can also be used, see + :meth:`~sage.repl.ipython_extension.SageMagics.runfile`. + INPUT: - ``filename`` -- string (denoting a filename or URL) or a :class:`Path` object @@ -97,13 +105,15 @@ def load(filename, globals, attach=False): - ``attach`` -- boolean (default: ``False``); whether to add the file to the list of attached files - Loading an executable Sage script from the command prompt will run whatever - code is inside an + Loading an executable Sage script from the :ref:`command line ` + will run whatever code is inside an + + :: if __name__ == "__main__": section, as the condition on ``__name__`` will hold true (code run from the - command prompt is considered to be running in the ``__main__`` module.) + command line is considered to be running in the ``__main__`` module.) EXAMPLES: @@ -159,7 +169,8 @@ def load(filename, globals, attach=False): sage: sage.repl.load.load('https://raw.githubusercontent.com/sagemath/sage-patchbot/3.0.0/sage_patchbot/util.py', globals()) # optional - internet - We attach a file:: + We attach a file (note that :func:`~sage.repl.attach.attach` + is equivalent, but available at the global scope by default):: sage: t = tmp_filename(ext='.py') sage: with open(t, 'w') as f: diff --git a/src/sage/rings/abc.pyx b/src/sage/rings/abc.pyx index e8078f97743..ef74bf34d4a 100644 --- a/src/sage/rings/abc.pyx +++ b/src/sage/rings/abc.pyx @@ -1,7 +1,6 @@ """ Abstract base classes for rings """ -from sage.rings.ring import IntegralDomain class NumberField_quadratic(Field): @@ -419,7 +418,7 @@ class Order: pass -class pAdicRing(IntegralDomain): +class pAdicRing(CommutativeRing): r""" Abstract base class for :class:`~sage.rings.padics.generic_nodes.pAdicRingGeneric`. diff --git a/src/sage/rings/convert/meson.build b/src/sage/rings/convert/meson.build index 0b485247bf1..04b9e285593 100644 --- a/src/sage/rings/convert/meson.build +++ b/src/sage/rings/convert/meson.build @@ -1,4 +1,9 @@ -py.install_sources('all.py', 'mpfi.pxd', subdir: 'sage/rings/convert') +py.install_sources( + '__init__.py', + 'all.py', + 'mpfi.pxd', + subdir: 'sage/rings/convert', +) extension_data = {'mpfi' : files('mpfi.pyx')} diff --git a/src/sage/rings/convert/mpfi.pyx b/src/sage/rings/convert/mpfi.pyx index 106a94c5aca..0c95ff6af94 100644 --- a/src/sage/rings/convert/mpfi.pyx +++ b/src/sage/rings/convert/mpfi.pyx @@ -11,11 +11,16 @@ Convert Sage/Python objects to real/complex intervals # http://www.gnu.org/licenses/ #***************************************************************************** +import re + from cpython.float cimport PyFloat_AS_DOUBLE from cpython.complex cimport PyComplex_RealAsDouble, PyComplex_ImagAsDouble +from libc.stdio cimport printf + from sage.libs.mpfr cimport * from sage.libs.mpfi cimport * +from sage.libs.gmp.mpz cimport * from sage.libs.gsl.complex cimport * from sage.arith.long cimport integer_check_long @@ -45,6 +50,243 @@ cdef inline int return_real(mpfi_ptr im) noexcept: return 0 +NUMBER = re.compile(rb'([+-]?(0[XxBb])?[0-9A-Za-z]+)\.([0-9A-Za-z]*)\?([0-9]*)(?:([EePp@])([+-]?[0-9]+))?') +# example: -0xABC.DEF?12@5 +# match groups: (-0xABC) (0x) (DEF) (12) (@) (5) + +cdef int _from_str_question_style(mpfi_ptr x, bytes s, int base) except -1: + """ + Convert a string in question style to an MPFI interval. + + INPUT: + + - ``x`` -- a pre-initialized MPFI interval + + - ``s`` -- the string to convert + + - ``base`` -- base to use for string conversion + + OUTPUT: + + - if conversion is possible: set ``x`` and return 0. + + - in all other cases: return some nonzero value, or raise an exception. + + TESTS: + + Double check that ``ZZ``, ``RR`` and ``RIF`` follows the string + conversion rule for base different from `10` (except ``ZZ`` + which only allows base up to `36`):: + + sage: ZZ("0x123", base=0) + 291 + sage: RR("0x123.e1", base=0) # rel tol 1e-12 + 291.878906250000 + sage: RR("0x123.@1", base=0) # rel tol 1e-12 + 4656.00000000000 + sage: RIF("0x123.4@1", base=0) + 4660 + sage: ZZ("1Xx", base=36) # case insensitive + 2517 + sage: ZZ("1Xx", base=62) + Traceback (most recent call last): + ... + ValueError: base (=62) must be 0 or between 2 and 36 + sage: RR("1Xx", base=36) # rel tol 1e-12 + 2517.00000000000 + sage: RR("0x123", base=36) # rel tol 1e-12 + 1.54101900000000e6 + sage: RR("-1Xx@-1", base=62) # rel tol 1e-12 + -95.9516129032258 + sage: RIF("1Xx@-1", base=62) # rel tol 1e-12 + 95.95161290322580? + sage: RIF("1aE1", base=11) + Traceback (most recent call last): + ... + TypeError: unable to convert '1aE1' to real interval + sage: RIF("1aE1", base=11) + Traceback (most recent call last): + ... + TypeError: unable to convert '1aE1' to real interval + + General checks:: + + sage: RIF("123456.?2").endpoints() # rel tol 1e-12 + (123454.0, 123458.0) + sage: RIF("1234.56?2").endpoints() # rel tol 1e-12 + (1234.54, 1234.58) + sage: RIF("1234.56?2e2").endpoints() # rel tol 1e-12 + (123454.0, 123458.0) + sage: x = RIF("-1234.56?2e2"); x.endpoints() # rel tol 1e-12 + (-123458.0, -123454.0) + sage: x + -1.2346?e5 + sage: x.str(style="question", error_digits=1) + '-123456.?2' + sage: RIF("1.?100").endpoints() # rel tol 1e-12 + (-99.0, 101.0) + sage: RIF("1.?100").str(style="question", error_digits=3) + '1.?100' + + Large exponent (ensure precision is not lost):: + + sage: x = RIF("1.123456?2e100000000"); x + 1.12346?e100000000 + sage: x.str(style="question", error_digits=3) + '1.12345600?201e100000000' + + Large precision:: + + sage: F = RealIntervalField(1000) + sage: x = F(sqrt(2)); x.endpoints() # rel tol 1e-290 + (1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605585073721264412149709993583141322266592750559275579995050115278206057147010955997160597027453459686201472851741864088919860955232923048430871432145083976260362799525140798, + 1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605585073721264412149709993583141322266592750559275579995050115278206057147010955997160597027453459686201472851741864088919860955232923048430871432145083976260362799525140799) + sage: x in F(x.str(style="question", error_digits=3)) + True + sage: x in F(x.str(style="question", error_digits=0)) + True + sage: F("1.123456789123456789123456789123456789123456789123456789123456789123456789?987654321987654321987654321e500").endpoints() # rel tol 1e-290 + (1.123456789123456789123456789123456789123456788135802467135802467135802468e500, + 1.12345678912345678912345678912345678912345679011111111111111111111111111e500) + + Stress test:: + + sage: for F in [RealIntervalField(15), RIF, RealIntervalField(100), RealIntervalField(1000)]: + ....: for i in range(1000): + ....: a, b = randint(-10^9, 10^9), randint(0, 50) + ....: c, d = randint(-2^b, 2^b), randint(2, 5) + ....: x = a * F(d)^c + ....: assert x in F(x.str(style="question", error_digits=3)), (x, a, c, d) + ....: assert x in F(x.str(style="question", error_digits=0)), (x, a, c, d) + + Base different from `10` (note that the error and exponent are specified in decimal):: + + sage: RIF("10000.?0", base=2).endpoints() # rel tol 1e-12 + (16.0, 16.0) + sage: RIF("10000.?0e10", base=2).endpoints() # rel tol 1e-12 + (16384.0, 16384.0) + sage: x = RIF("10000.?10", base=2); x.endpoints() # rel tol 1e-12 + (6.0, 26.0) + sage: x.str(base=2, style="question", error_digits=2) + '10000.000?80' + sage: x = RIF("10000.000?80", base=2); x.endpoints() # rel tol 1e-12 + (6.0, 26.0) + sage: x = RIF("12a.?", base=16); x.endpoints() # rel tol 1e-12 + (297.0, 299.0) + sage: x = RIF("12a.BcDeF?", base=16); x.endpoints() # rel tol 1e-12 + (298.737775802611, 298.737777709962) + sage: x = RIF("12a.BcDeF?@10", base=16); x.endpoints() # rel tol 1e-12 + (3.28465658150911e14, 3.28465660248065e14) + sage: x = RIF("12a.BcDeF?p10", base=16); x.endpoints() # rel tol 1e-12 + (305907.482421875, 305907.484375000) + sage: x = RIF("0x12a.BcDeF?p10", base=0); x.endpoints() # rel tol 1e-12 + (305907.482421875, 305907.484375000) + + Space is allowed:: + + sage: RIF("-1234.56?2").endpoints() # rel tol 1e-12 + (-1234.58, -1234.54) + sage: RIF("- 1234.56 ?2").endpoints() # rel tol 1e-12 + (-1234.58, -1234.54) + + Erroneous input:: + + sage: RIF("1234.56?2e2.3") + Traceback (most recent call last): + ... + TypeError: unable to convert '1234.56?2e2.3' to real interval + sage: RIF("1234?2") # decimal point required + Traceback (most recent call last): + ... + TypeError: unable to convert '1234?2' to real interval + sage: RIF("1234.?2e") + Traceback (most recent call last): + ... + TypeError: unable to convert '1234.?2e' to real interval + sage: RIF("1.?e999999999999999999999999") + [-infinity .. +infinity] + sage: RIF("0X1.?", base=33) # X is not valid digit in base 33 + Traceback (most recent call last): + ... + TypeError: unable to convert '0X1.?' to real interval + sage: RIF("1.a?1e10", base=12) + Traceback (most recent call last): + ... + TypeError: unable to convert '1.a?1e10' to real interval + sage: RIF("1.1?a@10", base=12) + Traceback (most recent call last): + ... + TypeError: unable to convert '1.1?a@10' to real interval + sage: RIF("0x1?2e1", base=0) # e is not allowed in base > 10, use @ instead + Traceback (most recent call last): + ... + TypeError: unable to convert '0x1?2e1' to real interval + sage: RIF("0x1?2p1", base=36) + Traceback (most recent call last): + ... + TypeError: unable to convert '0x1?2p1' to real interval + """ + cdef mpz_t error_part + cdef mpfi_t error + cdef mpfr_t radius, neg_radius + cdef bytes int_part_string, base_prefix, frac_part_string, error_string, e, sci_expo_string, optional_expo, tmp + + match = NUMBER.fullmatch(s) + if match is None: + return 1 + int_part_string, base_prefix, frac_part_string, error_string, e, sci_expo_string = match.groups() + + if (base > 10 or (base == 0 and base_prefix in (b'0X', b'0X'))) and e in (b'e', b'E'): + return 1 + if base > 16 and e in (b'p', b'P'): + return 1 + if base > 16 or not base_prefix: + base_prefix = b'' + + if error_string: + if mpz_init_set_str(error_part, error_string, 10): + mpz_clear(error_part) + return 1 + else: + mpz_init_set_ui(error_part, 1) + + optional_expo = e + sci_expo_string if e else b'' + if mpfi_set_str(x, int_part_string + b'.' + frac_part_string + optional_expo, base): + mpz_clear(error_part) + return 1 + + mpfr_init2(radius, mpfi_get_prec(x)) + tmp = base_prefix + ( + b'0.' + b'0'*(len(frac_part_string)-1) + b'1' + optional_expo + if frac_part_string else + b'1.' + optional_expo) + # if base = 0: + # when s = '-0x123.456@7', tmp = '0x0.001@7' + # when s = '-0x123.@7', tmp = '0x1.@7' + # if base = 36: + # when s = '-0x123.456@7', tmp = '0.001@7' + if mpfr_set_str(radius, tmp, base, MPFR_RNDU): + mpfr_clear(radius) + mpz_clear(error_part) + return 1 + + mpfr_mul_z(radius, radius, error_part, MPFR_RNDU) + mpz_clear(error_part) + + mpfr_init2(neg_radius, mpfi_get_prec(x)) + mpfr_neg(neg_radius, radius, MPFR_RNDD) + + mpfi_init2(error, mpfi_get_prec(x)) + mpfi_interv_fr(error, neg_radius, radius) + mpfr_clear(radius) + mpfr_clear(neg_radius) + + mpfi_add(x, x, error) + mpfi_clear(error) + + return 0 + + cdef int mpfi_set_sage(mpfi_ptr re, mpfi_ptr im, x, field, int base) except -1: """ Convert any object ``x`` to an MPFI interval or a pair of @@ -186,6 +428,11 @@ cdef int mpfi_set_sage(mpfi_ptr re, mpfi_ptr im, x, field, int base) except -1: if isinstance(x, unicode): x = x.encode("ascii") if isinstance(x, bytes): + if b"?" in x: + if _from_str_question_style(re, (x).replace(b' ', b''), base): + x = bytes_to_str(x) + raise TypeError(f"unable to convert {x!r} to real interval") + return return_real(im) s = (x).replace(b'..', b',').replace(b' ', b'').replace(b'+infinity', b'@inf@').replace(b'-infinity', b'-@inf@') if mpfi_set_str(re, s, base): x = bytes_to_str(x) diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index c1d0c58a4d3..332fedc4713 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -1358,7 +1358,7 @@ cdef class FiniteField(Field): False """ from sage.rings.integer_ring import ZZ - if R is int or R is long or R is ZZ: + if R is int or R is ZZ: return True if isinstance(R, sage.rings.abc.IntegerModRing) and self.characteristic().divides(R.characteristic()): return R.hom((self.one(),), check=False) diff --git a/src/sage/rings/finite_rings/integer_mod.pyx b/src/sage/rings/finite_rings/integer_mod.pyx index cf2d43ca59a..19525b40937 100644 --- a/src/sage/rings/finite_rings/integer_mod.pyx +++ b/src/sage/rings/finite_rings/integer_mod.pyx @@ -2039,8 +2039,12 @@ cdef class IntegerMod_gmp(IntegerMod_abstract): sage: e = Mod(19, 10^10) sage: e << 102 9443608576 + sage: e << (2^200) + Traceback (most recent call last): + ... + OverflowError: Python int too large to convert to C long """ - return self.shift(long(k)) + return self.shift(k) def __rshift__(IntegerMod_gmp self, k): r""" @@ -2053,8 +2057,12 @@ cdef class IntegerMod_gmp(IntegerMod_abstract): sage: e = Mod(19, 10^10) sage: e >> 1 9 + sage: e << (2^200) + Traceback (most recent call last): + ... + OverflowError: Python int too large to convert to C long """ - return self.shift(-long(k)) + return self.shift(-k) cdef shift(IntegerMod_gmp self, long k): r""" diff --git a/src/sage/rings/finite_rings/meson.build b/src/sage/rings/finite_rings/meson.build index 7f7a5744a1f..7e6c338636f 100644 --- a/src/sage/rings/finite_rings/meson.build +++ b/src/sage/rings/finite_rings/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'conway_polynomials.py', 'element_base.pxd', diff --git a/src/sage/rings/function_field/meson.build b/src/sage/rings/function_field/meson.build index 16e5fa6fa6f..0ed61989167 100644 --- a/src/sage/rings/function_field/meson.build +++ b/src/sage/rings/function_field/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'constructor.py', 'derivations.py', diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index ffc5ee16e0f..c9d1ff65bc6 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -645,7 +645,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): mpz_set_pylong(self.value, x) elif isinstance(x, float): - n = long(x) + n = int(x) if n == x: mpz_set_pylong(self.value, n) else: @@ -7434,7 +7434,7 @@ cdef class int_to_Z(Morphism): def __init__(self): import sage.categories.homset from sage.sets.pythonclass import Set_PythonType - Morphism.__init__(self, sage.categories.homset.Hom(Set_PythonType(long), integer_ring.ZZ)) + Morphism.__init__(self, sage.categories.homset.Hom(Set_PythonType(int), integer_ring.ZZ)) cpdef Element _call_(self, a): cdef Integer r diff --git a/src/sage/rings/localization.py b/src/sage/rings/localization.py index ab6916d36c8..e5015e95a7a 100644 --- a/src/sage/rings/localization.py +++ b/src/sage/rings/localization.py @@ -180,7 +180,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.categories.integral_domains import IntegralDomains -from sage.rings.ring import IntegralDomain +from sage.structure.parent import Parent from sage.structure.element import IntegralDomainElement @@ -193,7 +193,7 @@ def normalize_extra_units(base_ring, add_units, warning=True): INPUT: - - ``base_ring`` -- an instance of :class:`IntegralDomain` + - ``base_ring`` -- a ring in the category of :class:`IntegralDomains` - ``add_units`` -- list of elements from base ring - ``warning`` -- boolean (default: ``True``); to suppress a warning which is thrown if no normalization was possible @@ -561,7 +561,7 @@ def _integer_(self, Z=None): return self._value._integer_(Z=Z) -class Localization(IntegralDomain, UniqueRepresentation): +class Localization(Parent, UniqueRepresentation): r""" The localization generalizes the construction of the field of fractions of an integral domain to an arbitrary ring. Given a (not necessarily @@ -580,21 +580,18 @@ class Localization(IntegralDomain, UniqueRepresentation): this class relies on the construction of the field of fraction and is therefore restricted to integral domains. - Accordingly, this class is inherited from :class:`IntegralDomain` and can - only be used in that context. Furthermore, the base ring should support + Accordingly, the base ring must be in the category of ``IntegralDomains``. + Furthermore, the base ring should support :meth:`sage.structure.element.CommutativeRingElement.divides` and the exact division operator ``//`` (:meth:`sage.structure.element.Element.__floordiv__`) in order to guarantee a successful application. INPUT: - - ``base_ring`` -- an instance of :class:`Ring` allowing the construction - of :meth:`fraction_field` (that is an integral domain) + - ``base_ring`` -- a ring in the category of ``IntegralDomains`` - ``extra_units`` -- tuple of elements of ``base_ring`` which should be turned into units - - ``names`` -- passed to :class:`IntegralDomain` - - ``normalize`` -- boolean (default: ``True``); passed to :class:`IntegralDomain` - - ``category`` -- (default: ``None``) passed to :class:`IntegralDomain` + - ``category`` -- (default: ``None``) passed to :class:`Parent` - ``warning`` -- boolean (default: ``True``); to suppress a warning which is thrown if ``self`` cannot be represented uniquely @@ -712,7 +709,7 @@ def __init__(self, base_ring, extra_units, names=None, normalize=True, category= # since by construction the base ring must contain non units self must be infinite category = IntegralDomains().Infinite() - IntegralDomain.__init__(self, base_ring, names=names, normalize=normalize, category=category) + Parent.__init__(self, base=base_ring, names=names, normalize=normalize, category=category) self._extra_units = tuple(extra_units) self._fraction_field = base_ring.fraction_field() self._populate_coercion_lists_() diff --git a/src/sage/rings/meson.build b/src/sage/rings/meson.build index 9d39ca95188..bab6721d43b 100644 --- a/src/sage/rings/meson.build +++ b/src/sage/rings/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'abc.pxd', 'algebraic_closure_finite_field.py', 'all.py', diff --git a/src/sage/rings/number_field/meson.build b/src/sage/rings/number_field/meson.build index 46162077eb8..5b612679d40 100644 --- a/src/sage/rings/number_field/meson.build +++ b/src/sage/rings/number_field/meson.build @@ -1,5 +1,6 @@ py.install_sources( 'S_unit_solver.py', + '__init__.py', 'all.py', 'bdd_height.py', 'class_group.py', diff --git a/src/sage/rings/number_field/number_field_base.pyx b/src/sage/rings/number_field/number_field_base.pyx index 910f6ff7904..1c8259a8250 100644 --- a/src/sage/rings/number_field/number_field_base.pyx +++ b/src/sage/rings/number_field/number_field_base.pyx @@ -65,7 +65,7 @@ cdef class NumberField(Field): +Infinity """ # This token docstring is mostly there to prevent Sphinx from pasting in - # the docstring of the __init__ method inherited from IntegralDomain, which + # the docstring of the __init__ method inherited from Field, which # is rather confusing. def _pushout_(self, other): r""" diff --git a/src/sage/rings/padics/meson.build b/src/sage/rings/padics/meson.build index 9cb64492095..f589881042e 100644 --- a/src/sage/rings/padics/meson.build +++ b/src/sage/rings/padics/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'common_conversion.pxd', 'eisenstein_extension_generic.py', diff --git a/src/sage/rings/padics/relaxed_template.pxi b/src/sage/rings/padics/relaxed_template.pxi index 1b6b4fb0833..79869e9b7a1 100644 --- a/src/sage/rings/padics/relaxed_template.pxi +++ b/src/sage/rings/padics/relaxed_template.pxi @@ -1659,7 +1659,7 @@ cdef class RelaxedElement(pAdicGenericElement): 964*997^4 + 572*997^5 + 124*997^6 + ... """ cdef long start - cdef long shift = long(s) + cdef long shift = s if shift: if (self)._parent.is_field(): start = -maxordp diff --git a/src/sage/rings/polynomial/meson.build b/src/sage/rings/polynomial/meson.build index cbd48976335..a74efed061a 100644 --- a/src/sage/rings/polynomial/meson.build +++ b/src/sage/rings/polynomial/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'binary_form_reduce.py', 'commutative_polynomial.pxd', diff --git a/src/sage/rings/polynomial/weil/meson.build b/src/sage/rings/polynomial/weil/meson.build index 2c9bd006919..77432ffef30 100644 --- a/src/sage/rings/polynomial/weil/meson.build +++ b/src/sage/rings/polynomial/weil/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'power_sums.h', subdir: 'sage/rings/polynomial/weil', diff --git a/src/sage/rings/power_series_ring.py b/src/sage/rings/power_series_ring.py index e6c932aadff..512ab8d6e20 100644 --- a/src/sage/rings/power_series_ring.py +++ b/src/sage/rings/power_series_ring.py @@ -1323,7 +1323,9 @@ def laurent_series_ring(self): return self.__laurent_series_ring -class PowerSeriesRing_domain(PowerSeriesRing_generic, ring.IntegralDomain): +class PowerSeriesRing_domain(PowerSeriesRing_generic): + _default_category = _IntegralDomains + def fraction_field(self): """ Return the Laurent series ring over the fraction field of the base diff --git a/src/sage/rings/puiseux_series_ring_element.pyx b/src/sage/rings/puiseux_series_ring_element.pyx index 902ce96bd7c..3533c9c4c85 100644 --- a/src/sage/rings/puiseux_series_ring_element.pyx +++ b/src/sage/rings/puiseux_series_ring_element.pyx @@ -201,7 +201,7 @@ cdef class PuiseuxSeries(AlgebraElement): l = l.add_bigoh(prec / d) self._l = l - self._e = long(abs(e)) + self._e = int(abs(e)) def __reduce__(self): """ diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index bc29e952b1a..68ee004a251 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -3030,7 +3030,7 @@ cdef class Rational(sage.structure.element.FieldElement): Convert this rational to a Python ``int``. This truncates ``self`` if ``self`` has a denominator (which is - consistent with Python's ``long(floats)``). + consistent with Python's ``int(floats)``). EXAMPLES:: @@ -4243,7 +4243,7 @@ cdef class int_to_Q(Morphism): import sage.categories.homset from sage.sets.pythonclass import Set_PythonType Morphism.__init__(self, sage.categories.homset.Hom( - Set_PythonType(long), rational_field.QQ)) + Set_PythonType(int), rational_field.QQ)) cpdef Element _call_(self, a): """ diff --git a/src/sage/rings/real_lazy.pyx b/src/sage/rings/real_lazy.pyx index 8fe3a8281d0..b2212871dfa 100644 --- a/src/sage/rings/real_lazy.pyx +++ b/src/sage/rings/real_lazy.pyx @@ -174,7 +174,7 @@ cdef class LazyField(Field): True """ if isinstance(R, type): - if R in [int, long]: + if R is int: from sage.sets.pythonclass import Set_PythonType return LazyWrapperMorphism(Set_PythonType(R), self) elif R.is_exact(): diff --git a/src/sage/rings/real_mpfi.pyx b/src/sage/rings/real_mpfi.pyx index 11953a50ab5..ce9958ce7e7 100644 --- a/src/sage/rings/real_mpfi.pyx +++ b/src/sage/rings/real_mpfi.pyx @@ -120,6 +120,26 @@ satisfying, but we have chosen the latter. sage: a == 2 False +Some default printing options can be set by modifying module globals:: + + sage: from sage.rings import real_mpfi + sage: x = RIF(sqrt(2), sqrt(2)+1e-10); x + 1.4142135624? + sage: real_mpfi.printing_error_digits = 2 + sage: x + 1.414213562424?51 + sage: real_mpfi.printing_style = 'brackets' + sage: x + [1.4142135623730949 .. 1.4142135624730952] + sage: real_mpfi.printing_style = 'question'; real_mpfi.printing_error_digits = 0 # revert to default + +The default value of using scientific notation can be configured at field construction instead:: + + sage: RealIntervalField(53, sci_not=False)(0.5) + 0.50000000000000000? + sage: RealIntervalField(53, sci_not=True)(0.5) + 5.0000000000000000?e-1 + COMPARISONS: Comparison operations (``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``) @@ -816,9 +836,7 @@ cdef class RealIntervalField_class(sage.rings.abc.RealIntervalField): prec = self._prec # Direct and efficient conversions - if S is ZZ or S is QQ: - return True - if S is int or S is long: + if S is ZZ or S is QQ or S is int: return True if isinstance(S, RealIntervalField_class): return (S)._prec >= prec diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index a870c6bf0c7..31d0bb96aed 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -753,8 +753,6 @@ cdef class RealField_class(sage.rings.abc.RealField): return QQtoRR(QQ, self) elif (S is RDF or S is float) and self._prec <= 53: return double_toRR(S, self) - elif S is long: - return int_toRR(long, self) elif S is int: return int_toRR(int, self) elif isinstance(S, RealField_class) and S.prec() >= self._prec: diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index c4137a6974f..36b84c399fe 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -773,25 +773,6 @@ cdef class CommutativeRing(Ring): Ring.__init__(self, base_ring, names=names, normalize=normalize, category=category) - def localization(self, additional_units, names=None, normalize=True, category=None): - """ - Return the localization of ``self`` at the given additional units. - - EXAMPLES:: - - sage: R. = GF(3)[] - sage: R.localization((x*y, x**2 + y**2)) # needs sage.rings.finite_rings - Multivariate Polynomial Ring in x, y over Finite Field of size 3 - localized at (y, x, x^2 + y^2) - sage: ~y in _ # needs sage.rings.finite_rings - True - """ - if not self.is_integral_domain(): - raise TypeError("self must be an integral domain.") - - from sage.rings.localization import Localization - return Localization(self, additional_units, names=names, normalize=normalize, category=category) - def fraction_field(self): """ Return the fraction field of ``self``. @@ -1018,29 +999,6 @@ cdef class IntegralDomain(CommutativeRing): CommutativeRing.__init__(self, base_ring, names=names, normalize=normalize, category=category) - def is_field(self, proof=True): - r""" - Return ``True`` if this ring is a field. - - EXAMPLES:: - - sage: GF(7).is_field() - True - - The following examples have their own ``is_field`` implementations:: - - sage: ZZ.is_field(); QQ.is_field() - False - True - sage: R. = PolynomialRing(QQ); R.is_field() - False - """ - if self.is_finite(): - return True - if proof: - raise NotImplementedError("unable to determine whether or not is a field.") - else: - return False cdef class NoetherianRing(CommutativeRing): _default_category = NoetherianRings() diff --git a/src/sage/rings/semirings/meson.build b/src/sage/rings/semirings/meson.build index e1e4e627ba3..6cbfce4f84c 100644 --- a/src/sage/rings/semirings/meson.build +++ b/src/sage/rings/semirings/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'non_negative_integer_semiring.py', 'tropical_mpolynomial.py', diff --git a/src/sage/rings/semirings/tropical_variety.py b/src/sage/rings/semirings/tropical_variety.py index dbc1b85cd52..63d92e6509f 100644 --- a/src/sage/rings/semirings/tropical_variety.py +++ b/src/sage/rings/semirings/tropical_variety.py @@ -32,6 +32,7 @@ from sage.rings.rational_field import QQ from sage.rings.infinity import infinity + class TropicalVariety(UniqueRepresentation, SageObject): r""" A tropical variety in `\RR^n`. diff --git a/src/sage/sat/meson.build b/src/sage/sat/meson.build index a1a0246dfce..85d9be286e2 100644 --- a/src/sage/sat/meson.build +++ b/src/sage/sat/meson.build @@ -1,4 +1,9 @@ -py.install_sources('all.py', 'boolean_polynomials.py', subdir: 'sage/sat') +py.install_sources( + '__init__.py', + 'all.py', + 'boolean_polynomials.py', + subdir: 'sage/sat', +) install_subdir('converters', install_dir: sage_install_dir / 'sat') subdir('solvers') diff --git a/src/sage/schemes/elliptic_curves/addition_formulas_ring.py b/src/sage/schemes/elliptic_curves/addition_formulas_ring.py new file mode 100644 index 00000000000..3846f9f0c7d --- /dev/null +++ b/src/sage/schemes/elliptic_curves/addition_formulas_ring.py @@ -0,0 +1,89 @@ + +def _add(E, P, Q): + r""" + Addition formulas for elliptic curves over general rings + with trivial Picard group. + + This function returns a generator which yields tuples of projective + coordinates. Some linear combination of those coordinate tuples is + guaranteed to form a valid projective point. + + .. NOTE:: + + This function is for internal use. To add two elliptic-curve + points, users should simply use the `+` operator. + + REFERENCES: + + These formulas were derived by Bosma and Lenstra [BL1995]_, + with corrections by Best [Best2021]_ (Appendix A). + + EXAMPLES:: + + sage: from sage.schemes.elliptic_curves.addition_formulas_ring import _add + sage: M = Zmod(13*17*19) + sage: R. = M[] + sage: S. = R.quotient(U*V - 17) + sage: E = EllipticCurve(S, [1,2,3,4,5]) + sage: P = E(817, 13, 19) + sage: Q = E(425, 123, 17) + sage: PQ1, PQ2 = _add(E, P, Q) + sage: PQ1 + (1188, 1674, 540) + sage: PQ2 + (582, 2347, 1028) + sage: E(PQ1) == E(PQ2) + True + + TESTS: + + We ensure that these formulas return the same result as the ones over a field:: + + sage: from sage.schemes.elliptic_curves.addition_formulas_ring import _add + sage: F = GF(2^127-1) + sage: E = EllipticCurve(j=F.random_element()) + sage: E = choice(E.twists()) + sage: P = E.random_point() + sage: Q = E.random_point() + sage: PQ1, PQ2 = _add(E, P, Q) + sage: assert E(*PQ1) == P + Q + sage: assert E(*PQ2) == P + Q + """ + a1, a2, a3, a4, a6 = E.a_invariants() + b2, b4, b6, b8 = E.b_invariants() + + if P not in E: + raise ValueError('P must be in E') + if Q not in E: + raise ValueError('Q must be in E') + X1, Y1, Z1 = P + X2, Y2, Z2 = Q + + # TODO: I've made a half-hearted attempt at simplifying the formulas + # by caching common subexpressions. This could almost certainly be + # sped up significantly with some more serious optimization effort. + + XYdif = X1*Y2 - X2*Y1 + XYsum = X1*Y2 + X2*Y1 + XZdif = X1*Z2 - X2*Z1 + XZsum = X1*Z2 + X2*Z1 + YZdif = Y1*Z2 - Y2*Z1 + YZsum = Y1*Z2 + Y2*Z1 + + a1sq, a2sq, a3sq, a4sq = (a**2 for a in (a1, a2, a3, a4)) + + X31 = XYdif*YZsum+XZdif*Y1*Y2+a1*X1*X2*YZdif+a1*XYdif*XZsum-a2*X1*X2*XZdif+a3*XYdif*Z1*Z2+a3*XZdif*YZsum-a4*XZsum*XZdif-3*a6*XZdif*Z1*Z2 + + Y31 = -3*X1*X2*XYdif-Y1*Y2*YZdif-2*a1*XZdif*Y1*Y2+(a1sq+3*a2)*X1*X2*YZdif-(a1sq+a2)*XYsum*XZdif+(a1*a2-3*a3)*X1*X2*XZdif-(2*a1*a3+a4)*XYdif*Z1*Z2+a4*XZsum*YZdif+(a1*a4-a2*a3)*XZsum*XZdif+(a3sq+3*a6)*YZdif*Z1*Z2+(3*a1*a6-a3*a4)*XZdif*Z1*Z2 + + Z31 = 3*X1*X2*XZdif-YZsum*YZdif+a1*XYdif*Z1*Z2-a1*XZdif*YZsum+a2*XZsum*XZdif-a3*YZdif*Z1*Z2+a4*XZdif*Z1*Z2 + + yield (X31, Y31, Z31) + + X32 = Y1*Y2*XYsum+a1*(2*X1*Y2+X2*Y1)*X2*Y1+a1sq*X1*X2**2*Y1-a2*X1*X2*XYsum-a1*a2*X1**2*X2**2+a3*X2*Y1*(YZsum+Y2*Z1)+a1*a3*X1*X2*YZdif-a1*a3*XYsum*XZdif-a4*X1*X2*YZsum-a4*XYsum*XZsum-a1sq*a3*X1**2*X2*Z2-a1*a4*X1*X2*(X1*Z2+XZsum)-a2*a3*X1*X2**2*Z1-a3sq*X1*Z2*(Y2*Z1+YZsum)-3*a6*XYsum*Z1*Z2-3*a6*XZsum*YZsum-a1*a3sq*X1*Z2*(XZsum+X2*Z1)-3*a1*a6*X1*Z2*(XZsum+X2*Z1)-a3*a4*(X1*Z2+XZsum)*X2*Z1-b8*YZsum*Z1*Z2-a1*b8*X1*Z1*Z2**2-a3**3*XZsum*Z1*Z2-3*a3*a6*(XZsum+X2*Z1)*Z1*Z2-a3*b8*Z1**2*Z2**2 + + Y32 = Y1**2*Y2**2+a1*X2*Y1**2*Y2+(a1*a2-3*a3)*X1*X2**2*Y1+a3*Y1**2*Y2*Z2-(a2sq-3*a4)*X1**2*X2**2+(a1*a4-a2*a3)*(2*X1*Z2+X2*Z1)*X2*Y1+(a1sq*a4-2*a1*a2*a3+3*a3sq)*X1**2*X2*Z2-(a2*a4-9*a6)*X1*X2*XZsum+(3*a1*a6-a3*a4)*(XZsum+X2*Z1)*Y1*Z2+(3*a1sq*a6-2*a1*a3*a4+a2*a3sq+3*a2*a6-a4sq)*X1*Z2*(XZsum+X2*Z1)+(3*a2*a6-a4sq)*X2*Z1*(2*X1*Z2+Z1*X2)+(a1**3*a6-a1sq*a3*a4+a1*a2*a3sq-a1*a4sq+4*a1*a2*a6-a3**3-3*a3*a6)*Y1*Z1*Z2**2+(a1**4*a6-a1**3*a3*a4+5*a1sq*a2*a6+a1sq*a2*a3sq-a1*a2*a3*a4-a1*a3**3-3*a1*a3*a6-a1sq*a4sq+a2sq*a3sq-a2*a4sq+4*a2sq*a6-a3**2*a4-3*a4*a6)*X1*Z1*Z2**2+(a1sq*a2*a6-a1*a2*a3*a4+3*a1*a3*a6+a2sq*a3sq-a2*a4sq+4*a2sq*a6-2*a3sq*a4-3*a4*a6)*X2*Z1**2*Z2+(a1**3*a3*a6-a1sq*a3sq*a4+a1sq*a4*a6+a1*a2*a3**3+4*a1*a2*a3*a6-2*a1*a3*a4sq+a2*a3sq*a4+4*a2*a4*a6-a3**4-6*a3**2*a6-a4**3-9*a6**2)*Z1**2*Z2**2 + + Z32 = 3*X1*X2*XYsum+Y1*Y2*YZsum+3*a1*X1**2*X2**2+a1*(2*X1*Y2+Y1*X2)*Y1*Z2+a1sq*X1*Z2*(2*X2*Y1+X1*Y2)+a2*X1*X2*YZsum+a2*XYsum*XZsum+a1**3*X1**2*X2*Z2+a1*a2*X1*X2*(2*X1*Z2+X2*Z1)+3*a3*X1*X2**2*Z1+a3*Y1*Z2*(YZsum+Y2*Z1)+2*a1*a3*X1*Z2*YZsum+2*a1*a3*X2*Y1*Z1*Z2+a4*XYsum*Z1*Z2+a4*XZsum*YZsum+(a1sq*a3+a1*a4)*X1*Z2*(XZsum+X2*Z1)+a2*a3*X2*Z1*(2*X1*Z2+X2*Z1)+a3sq*Y1*Z1*Z2**2+(a3sq+3*a6)*YZsum*Z1*Z2+a1*a3sq*(2*X1*Z2+X2*Z1)*Z1*Z2+3*a1*a6*X1*Z1*Z2**2+a3*a4*(XZsum+X2*Z1)*Z1*Z2+(a3**3+3*a3*a6)*Z1**2*Z2**2 + + yield (X32, Y32, Z32) diff --git a/src/sage/schemes/elliptic_curves/ell_field.py b/src/sage/schemes/elliptic_curves/ell_field.py index 9bed4936f7b..3ff2826f3e7 100755 --- a/src/sage/schemes/elliptic_curves/ell_field.py +++ b/src/sage/schemes/elliptic_curves/ell_field.py @@ -1529,6 +1529,55 @@ def isogeny_codomain(self, kernel): E._fetch_cached_order(self) return E + def period_lattice(self): + r""" + Return the period lattice of the elliptic curve for the given + embedding of its base field with respect to the differential + `dx/(2y + a_1x + a_3)`. + + Only supported for some base rings. + + EXAMPLES:: + + sage: EllipticCurve(RR, [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.00000000000000*x + 6.00000000000000 over Real Field with 53 bits of precision + + TESTS:: + + sage: EllipticCurve(QQ, [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + x + 6 over Rational Field + sage: EllipticCurve(RR, [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.00000000000000*x + 6.00000000000000 over Real Field with 53 bits of precision + sage: EllipticCurve(RealField(100), [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0000000000000000000000000000*x + 6.0000000000000000000000000000 over Real Field with 100 bits of precision + sage: EllipticCurve(CC, [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.00000000000000*x + 6.00000000000000 over Complex Field with 53 bits of precision + sage: EllipticCurve(ComplexField(100), [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + 1.0000000000000000000000000000*x + 6.0000000000000000000000000000 over Complex Field with 100 bits of precision + sage: EllipticCurve(AA, [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + x + 6 over Algebraic Real Field + sage: EllipticCurve(QQbar, [1, 6]).period_lattice() + Period lattice associated to Elliptic Curve defined by y^2 = x^3 + x + 6 over Algebraic Field + + Unsupported cases (the exact error being raised may change in the future):: + + sage: EllipticCurve(ZZ, [1, 6]).period_lattice() + Traceback (most recent call last): + ... + AttributeError: 'EllipticCurve_generic_with_category' object has no attribute 'period_lattice' + sage: QQt. = QQ[] + sage: EllipticCurve(QQt.fraction_field(), [1, 6]).period_lattice() + Traceback (most recent call last): + ... + AttributeError: 'FractionField_1poly_field_with_category' object has no attribute ... + sage: EllipticCurve(GF(7), [1, 6]).period_lattice() + Traceback (most recent call last): + ... + IndexError: list index out of range + """ + from sage.schemes.elliptic_curves.period_lattice import PeriodLattice_ell + return PeriodLattice_ell(self) + def kernel_polynomial_from_point(self, P, *, algorithm=None): r""" Given a point `P` on this curve which generates a rational subgroup, diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index 6f2532e2b3b..44f552ae4af 100755 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -55,8 +55,6 @@ import math from sage.arith.misc import valuation -import sage.rings.abc -from sage.rings.finite_rings.integer_mod import mod from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.polynomial.polynomial_ring import polygen, polygens from sage.rings.polynomial.polynomial_element import polynomial_is_variable @@ -175,15 +173,49 @@ def __init__(self, K, ainvs, category=None): self.__divpolys = ({}, {}, {}) - # See #1975: we deliberately set the class to - # EllipticCurvePoint_finite_field for finite rings, so that we - # can do some arithmetic on points over Z/NZ, for teaching - # purposes. - if isinstance(K, sage.rings.abc.IntegerModRing): - self._point = ell_point.EllipticCurvePoint_finite_field - _point = ell_point.EllipticCurvePoint + def assume_base_ring_is_field(self, flag=True): + r""" + Set a flag to pretend that this elliptic curve is defined over a + field while doing arithmetic, which is useful in some algorithms. + + + .. WARNING:: + + The flag affects all points created while the flag is set. Note + that elliptic curves are unique parents, hence setting this flag + may break seemingly unrelated parts of Sage. + + .. NOTE:: + + This method is a **hack** provided for educational purposes. + + EXAMPLES:: + + sage: E = EllipticCurve(Zmod(35), [1,1]) + sage: P = E(-5, 9) + sage: 4*P + (23 : 26 : 1) + sage: 9*P + (10 : 11 : 5) + sage: E.assume_base_ring_is_field() + sage: P = E(-5, 9) + sage: 4*P + (23 : 26 : 1) + sage: 9*P + Traceback (most recent call last): + ... + ZeroDivisionError: Inverse of 5 does not exist (characteristic = 35 = 5*7) + """ + if flag: + if self.__base_ring.is_finite(): + self._point = ell_point.EllipticCurvePoint_finite_field + else: + self._point = ell_point.EllipticCurvePoint_field + else: + self._point = ell_point.EllipticCurvePoint + def _defining_params_(self): r""" Internal function. Return a tuple of the base ring of this @@ -582,7 +614,7 @@ def __call__(self, *args, **kwds): # infinity. characteristic = self.base_ring().characteristic() if characteristic != 0 and isinstance(args[0][0], Rational) and isinstance(args[0][1], Rational): - if mod(args[0][0].denominator(),characteristic) == 0 or mod(args[0][1].denominator(),characteristic) == 0: + if characteristic.divides(args[0][0].denominator()) or characteristic.divides(args[0][1].denominator()): return self._reduce_point(args[0], characteristic) args = tuple(args[0]) diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 6aa63fc6370..d05196240a1 100755 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -1,14 +1,16 @@ r""" Points on elliptic curves -The base class :class:`EllipticCurvePoint` currently provides little -functionality of its own. Its derived class -:class:`EllipticCurvePoint_field` provides support for points on -elliptic curves over general fields. The derived classes -:class:`EllipticCurvePoint_number_field` and -:class:`EllipticCurvePoint_finite_field` provide further support for -points on curves over number fields (including the rational field -`\QQ`) and over finite fields. +The base class :class:`EllipticCurvePoint` provides support for +points on elliptic curves defined over general rings, including +generic addition formulas. + +The derived classes :class:`EllipticCurvePoint_field` and its +child classes :class:`EllipticCurvePoint_number_field` and +:class:`EllipticCurvePoint_finite_field` provide further support +for points on curves defined over arbitrary fields, as well as +specialized functionality for points on curves over number fields +(including the rational field `\QQ`) and finite fields. EXAMPLES: @@ -73,15 +75,24 @@ sage: P*(n+1)-P*n == P True -Arithmetic over `\ZZ/N\ZZ` with composite `N` is supported. When an -operation tries to invert a non-invertible element, a -:exc:`ZeroDivisionError` is raised and a factorization of the modulus appears -in the error message:: +Arithmetic over `\Zmod{N}` with composite `N` is supported:: sage: N = 1715761513 sage: E = EllipticCurve(Integers(N), [3,-13]) sage: P = E(2,1) sage: LCM([2..60])*P + (1643112467 : 9446995 : 26927) + +However, some algorithms (e.g., toy examples of ECM) involve performing +elliptic-curve operations as if the base ring were a field even when it +is not, and exploit the failures when attempting to invert a non-unit. +Sage provides a *hack* to support such educational examples via the +:meth:`EllipticCurve_generic.assume_base_ring_is_field` method. +Example:: + + sage: E.assume_base_ring_is_field() + sage: P = E(2,1) + sage: LCM([2..60])*P Traceback (most recent call last): ... ZeroDivisionError: Inverse of 26927 does not exist @@ -104,6 +115,8 @@ - Mariah Lenox (March 2011) -- Added ``tate_pairing`` and ``ate_pairing`` functions to ``EllipticCurvePoint_finite_field`` class + +- Lorenz Panny (2022): point addition over general rings """ # **************************************************************************** @@ -127,15 +140,21 @@ from sage.rings.integer_ring import ZZ from sage.rings.padics.precision_error import PrecisionError from sage.rings.rational_field import QQ +from sage.rings.finite_rings.integer_mod import Mod from sage.rings.real_mpfr import RealField, RR +from sage.rings.quotient_ring import QuotientRing_generic +import sage.groups.generic as generic + +from sage.structure.element import AdditiveGroupElement +from sage.structure.sequence import Sequence +from sage.structure.richcmp import richcmp + +from sage.structure.coerce_actions import IntegerMulAction + from sage.schemes.curves.projective_curve import Hasse_bounds from sage.schemes.elliptic_curves.constructor import EllipticCurve from sage.schemes.projective.projective_point import (SchemeMorphism_point_projective_ring, SchemeMorphism_point_abelian_variety_field) -from sage.structure.coerce_actions import IntegerMulAction -from sage.structure.element import AdditiveGroupElement -from sage.structure.richcmp import richcmp -from sage.structure.sequence import Sequence lazy_import('sage.rings.padics.factory', 'Qp') lazy_import('sage.schemes.generic.morphism', 'SchemeMorphism') @@ -151,6 +170,28 @@ class EllipticCurvePoint(AdditiveGroupElement, """ A point on an elliptic curve. """ + def __init__(self, *args, **kwds): + r""" + Initialize this elliptic-curve point. + + EXAMPLES:: + + sage: E = EllipticCurve(Zmod(77), [1,1,1,1,1]) + sage: E(0) + (0 : 1 : 0) + sage: E(3, 9) + (3 : 9 : 1) + sage: E(6, 18, 2) + (3 : 9 : 1) + sage: E(66, 23, 22) + (33 : 50 : 11) + """ + super().__init__(*args, **kwds) + try: + self.normalize_coordinates() + except NotImplementedError: + pass + def curve(self): """ Return the curve that this point is on. @@ -179,6 +220,259 @@ def curve(self): """ return self.scheme() + def _add_(self, other): + r""" + Add this point to another point on the same elliptic curve. + + This method computes point additions for fairly general rings. + + ALGORITHM: + + Over quotient rings of Euclidean domains modulo principal ideals: + The standard formulas for fields, extended to non-fields via the + Chinese remainder theorem. + + In more general rings: Formulas due to Bosma and Lenstra [BL1995]_ + with corrections by Best [Best2021]_ (Appendix A). + See :mod:`sage.schemes.elliptic_curves.addition_formulas_ring`. + + EXAMPLES:: + + sage: N = 1113121 + sage: E = EllipticCurve(Zmod(N), [1,0]) + sage: R1 = E(301098, 673883, 644675) + sage: R2 = E(411415, 758555, 255837) + sage: R3 = E(983009, 342673, 207687) + sage: R1 + R2 == R3 + True + + TESTS: + + We check on random examples that the results are compatible modulo + all divisors of the characteristic. (In particular, this includes + prime divisors, for which the result is computed using the "old", + much simpler formulas for fields.) :: + + sage: N = ZZ(randrange(2, 10**5)) + sage: E = None + sage: while True: + ....: try: + ....: E = EllipticCurve(list((Zmod(N)^5).random_element())) + ....: except ArithmeticError: + ....: pass + ....: else: + ....: if E.discriminant().is_unit(): + ....: break + sage: pts = [] + sage: X = polygen(Zmod(N^2)) + sage: while len(pts) < 2: + ....: y, z = (Zmod(N)^2).random_element() + ....: f = E.defining_polynomial()(X, y, z) + ....: xs = f.roots(multiplicities=False) + ....: xs = [x for x in xs if 1 in Zmod(N).ideal([x,y,z])] + ....: if xs: + ....: pts.append(E(choice(xs), y, z)) + sage: P, Q = pts + sage: R = P + Q + sage: for d in N.divisors(): + ....: if d > 1: + ....: assert R.change_ring(Zmod(d)) == P.change_ring(Zmod(d)) + Q.change_ring(Zmod(d)) + """ + if self.is_zero(): + return other + if other.is_zero(): + return self + + E = self.curve() + R = E.base_ring() + + # We handle Euclidean domains modulo principal ideals separately. + # Important special cases of this include quotient rings of the + # integers as well as of univariate polynomial rings over fields. + if isinstance(R, QuotientRing_generic): + from sage.categories.euclidean_domains import EuclideanDomains + if R.cover_ring() in EuclideanDomains(): + I = R.defining_ideal() + if I.ngens() == 1: + mod, = I.gens() + + a1, a2, a3, a4, a6 = E.ainvs() + x1, y1, z1 = map(R, self) + x2, y2, z2 = map(R, other) + + mod_1st = mod.gcd(z2.lift()) + mod //= mod_1st + mod_2nd = mod.gcd(z1.lift()) + mod //= mod_2nd + + xz, zx = x1*z2, x2*z1 + yz, zy = y1*z2, y2*z1 + zz = z1*z2 + + # addition + num_add = yz - zy + den_add = xz - zx + mod_dbl = mod.gcd(num_add.lift()).gcd(den_add.lift()) + mod_add = mod // mod_dbl + + # doubling + if not mod_dbl.is_one(): + num_dbl = (3*x1 + 2*a2*z1) * x1 + (a4*z1 - a1*y1) * z1 + den_dbl = (2*y1 + a1*x1 + a3*z1) * z1 + else: + num_dbl = den_dbl = 0 + + if mod_dbl.gcd(mod_add).is_one(): + from sage.arith.misc import CRT_vectors + if mod_dbl.is_one(): + num, den = num_add, den_add + elif mod_add.is_one(): + num, den = num_dbl, den_dbl + else: + num, den = CRT_vectors([(num_add, den_add), (num_dbl, den_dbl)], [mod_add, mod_dbl]) + + den2 = den**2 + x3 = ((num + a1*den)*zz*num - (xz + zx + a2*zz)*den2) * den + y3 = ((2*xz + zx + (a2 - a1**2)*zz)*num + (a1*(xz + zx + a2*zz) - a3*zz - yz)*den) * den2 - (num + 2*a1*den)*zz*num**2 + z3 = zz * den * den2 + + pt = x3.lift(), y3.lift(), z3.lift() + if not mod_1st.is_one(): + pt = CRT_vectors([pt, [x1.lift(), y1.lift(), z1.lift()]], [mod, mod_1st]) + mod = mod.lcm(mod_1st) + if not mod_2nd.is_one(): + pt = CRT_vectors([pt, [x2.lift(), y2.lift(), z2.lift()]], [mod, mod_2nd]) + + return E.point(Sequence(pt, E.base_ring()), check=False) + + from sage.schemes.elliptic_curves.addition_formulas_ring import _add + from sage.modules.free_module_element import vector + + pts = [] + for pt in filter(any, _add(E, self, other)): + if R.one() in R.ideal(pt): + return E.point(pt) + pts.append(pt) + assert len(pts) == 2, 'bug in elliptic-curve point addition' + + #TODO: If the base ring has trivial Picard group, it is known + # that some linear combination of the two vectors is a valid + # projective point (whose coordinates generate the unit ideal). + # Below, we simply try random linear combinations until we + # find a good choice. Is there a general method that doesn't + # involve guessing? + + pts = [vector(R, pt) for pt in pts] + for _ in range(1000): + result = tuple(sum(R.random_element() * pt for pt in pts)) + if R.one() in R.ideal(result): + return E.point(result, check=False) + + assert False, 'bug: failed to compute elliptic-curve point addition' + + def _neg_(self): + """ + Return the negative of this elliptic-curve point, over a general ring. + + EXAMPLES:: + + sage: E = EllipticCurve('389a') + sage: P = E([-1,1]) + sage: Q = -P; Q + (-1 : -2 : 1) + sage: Q + P + (0 : 1 : 0) + + :: + + sage: N = 1113121 + sage: E = EllipticCurve(Zmod(N), [1,0]) + sage: R = E(301098, 673883, 644675) + sage: -R + (136211 : 914033 : 107) + sage: ((-R) + R) == 0 + True + """ + if self.is_zero(): + return self + E = self.curve() + a1, _, a3, _, _ = E.a_invariants() + x, y, z = self + return E.point([x, -y - a1*x - a3*z, z], check=False) + + def _sub_(self, other): + """ + Subtract ``other`` from ``self``. + + ALGORITHM: :meth:`_add_` and :meth:`_neg_`. + + EXAMPLES:: + + sage: E = EllipticCurve('389a') + sage: P = E([-1,1]); Q = E([0,0]) + sage: P - Q + (4 : 8 : 1) + sage: P - Q == P._sub_(Q) + True + sage: (P - Q) + Q + (-1 : 1 : 1) + sage: P + (-1 : 1 : 1) + + :: + + sage: N = 1113121 + sage: E = EllipticCurve(Zmod(N), [1,0]) + sage: R1 = E(301098, 673883, 644675) + sage: R2 = E(411415, 758555, 255837) + sage: R3 = E(983009, 342673, 207687) + sage: R1 == R3 - R2 + True + """ + return self + (-other) + + def _acted_upon_(self, other, side): + r""" + We implement ``_acted_upon_`` to provide scalar multiplications. + + EXAMPLES:: + + sage: # needs sage.rings.finite_rings + sage: N = 1113121 + sage: E = EllipticCurve(Zmod(N), [1,0]) + sage: R = E(301098, 673883, 644675) + sage: 123*R + (703739 : 464106 : 107) + sage: 70200*R + (0 : 1 : 0) + """ + return IntegerMulAction(ZZ, self.parent())._act_(other, self) + + def __bool__(self): + r""" + Test whether this elliptic-curve point equals the neutral + element of the group (i.e., the point at infinity). + + EXAMPLES:: + + sage: E = EllipticCurve(GF(7), [1,1]) + sage: bool(E(0)) + False + sage: bool(E.lift_x(2)) + True + + sage: + + sage: E = EllipticCurve(Zmod(77), [1,1,1,1,1]) + sage: bool(E(0)) + False + sage: P = E(66, 23, 22); P + (33 : 50 : 11) + sage: bool(P) + True + """ + return bool(self[2]) + class EllipticCurvePoint_field(EllipticCurvePoint, SchemeMorphism_point_abelian_variety_field): @@ -680,9 +974,11 @@ def plot(self, **args): else: return point((self[0], self[1]), **args) - def _add_(self, right): - """ - Add ``self`` to ``right``. + def _add_(self, other): + r""" + Add this point to another point on the same elliptic curve. + + This method is specialized to elliptic curves over fields. EXAMPLES:: @@ -693,6 +989,8 @@ def _add_(self, right): sage: P._add_(Q) == P + Q True + TESTS: + Example to show that bug :issue:`4820` is fixed:: sage: [type(c) for c in 2*EllipticCurve('37a1').gen(0)] @@ -704,6 +1002,7 @@ def _add_(self, right): sage: N = 1715761513 sage: E = EllipticCurve(Integers(N), [3,-13]) + sage: E.assume_base_ring_is_field() sage: P = E(2,1) sage: LCM([2..60])*P Traceback (most recent call last): @@ -713,6 +1012,7 @@ def _add_(self, right): sage: N = 35 sage: E = EllipticCurve(Integers(N), [5,1]) + sage: E.assume_base_ring_is_field() sage: P = E(0,1) sage: 4*P Traceback (most recent call last): @@ -727,72 +1027,52 @@ def _add_(self, right): sage: 2*P (15 : 14 : 1) """ - # Use Prop 7.1.7 of Cohen "A Course in Computational Algebraic - # Number Theory" + # Use Prop 7.1.7 of Cohen "A Course in Computational Algebraic Number Theory" + if self.is_zero(): - return right - if right.is_zero(): + return other + if other.is_zero(): return self + E = self.curve() a1, a2, a3, a4, a6 = E.ainvs() - x1, y1 = self[0], self[1] - x2, y2 = right[0], right[1] + x1, y1 = self.xy() + x2, y2 = other.xy() + if x1 == x2 and y1 == -y2 - a1*x2 - a3: return E(0) # point at infinity - if x1 == x2 and y1 == y2: - try: + try: + if x1 == x2 and y1 == y2: m = (3*x1*x1 + 2*a2*x1 + a4 - a1*y1) / (2*y1 + a1*x1 + a3) - except ZeroDivisionError: - R = E.base_ring() - if R.is_finite(): - N = R.characteristic() - N1 = N.gcd(Integer(2*y1 + a1*x1 + a3)) - N2 = N//N1 - raise ZeroDivisionError("Inverse of %s does not exist (characteristic = %s = %s*%s)" % (2*y1 + a1*x1 + a3, N, N1, N2)) - else: - raise ZeroDivisionError("Inverse of %s does not exist" % (2*y1 + a1*x1 + a3)) - else: + else: + m = (y1 - y2) / (x1 - x2) + except ZeroDivisionError as ex: try: - m = (y1-y2)/(x1-x2) - except ZeroDivisionError: - R = E.base_ring() - if R.is_finite(): - N = R.characteristic() - N1 = N.gcd(Integer(x1-x2)) - N2 = N//N1 - raise ZeroDivisionError("Inverse of %s does not exist (characteristic = %s = %s*%s)" % (x1-x2, N, N1, N2)) - else: - raise ZeroDivisionError("Inverse of %s does not exist" % (x1-x2)) + d = next(d for d in (x1 - x2, 2*y1 + a1*x1 + a3) if d and not d.is_unit()) + m, = d.parent().defining_ideal().gens() + f1 = d.lift().gcd(m) + f2 = m // f1 + assert m == f1 * f2 + except Exception: + raise ex + else: + raise ZeroDivisionError(f'Inverse of {d} does not exist (characteristic = {m} = {f1}*{f2})') x3 = -x1 - x2 - a2 + m*(m+a1) y3 = -y1 - a3 - a1*x3 + m*(x1-x3) # See trac #4820 for why we need to coerce 1 into the base ring here: return E.point([x3, y3, E.base_ring().one()], check=False) - def _sub_(self, right): - """ - Subtract ``right`` from ``self``. + _sub_ = EllipticCurvePoint._sub_ - EXAMPLES:: - - sage: E = EllipticCurve('389a') - sage: P = E([-1,1]); Q = E([0,0]) - sage: P - Q - (4 : 8 : 1) - sage: P - Q == P._sub_(Q) - True - sage: (P - Q) + Q - (-1 : 1 : 1) - sage: P - (-1 : 1 : 1) - """ - return self + (-right) - - def __neg__(self): + def _neg_(self): """ Return the additive inverse of this point. + Same as :meth:`EllipticCurvePoint._neg_`, but specialized + to points over fields, which are normalized to satisfy `z=1`. + EXAMPLES:: sage: E = EllipticCurve('389a') @@ -2575,7 +2855,7 @@ def _has_order_at_least(self, bound, *, attempts=999): sage: P = next(filter(bool, E.torsion_points())) sage: P._has_order_at_least(5) True - sage: P._has_order_at_least(6) + sage: P._has_order_at_least(6) # long time -- 5s sage: P.order() 5 sage: Q = E.lift_x(10^42, extend=True) @@ -3992,8 +4272,9 @@ def _magma_init_(self, magma): def _acted_upon_(self, other, side): r""" - We implement ``_acted_upon_`` to keep track of cached - point orders when scalar multiplications are applied. + We implement ``_acted_upon_`` to make use of the specialized faster + scalar multiplication from PARI, and to keep track of cached point + orders when scalar multiplications are applied. EXAMPLES:: diff --git a/src/sage/schemes/elliptic_curves/meson.build b/src/sage/schemes/elliptic_curves/meson.build index 3448c5d1c0a..b2a3dda08c9 100644 --- a/src/sage/schemes/elliptic_curves/meson.build +++ b/src/sage/schemes/elliptic_curves/meson.build @@ -1,6 +1,8 @@ py.install_sources( 'BSD.py', 'Qcurves.py', + '__init__.py', + 'addition_formulas_ring.py', 'all.py', 'cardinality.py', 'cm.py', diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index e6008a09279..54cd135dea9 100755 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -111,11 +111,12 @@ from sage.misc.cachefunc import cached_method from sage.misc.lazy_import import lazy_import from sage.modules.free_module import FreeModule_generic_pid -from sage.rings.complex_mpfr import ComplexField, ComplexNumber +from sage.rings.complex_mpfr import ComplexField, ComplexNumber, ComplexField_class from sage.rings.infinity import Infinity from sage.rings.integer_ring import ZZ +from sage.rings.qqbar import AA, QQbar from sage.rings.rational_field import QQ -from sage.rings.real_mpfr import RealField, RealNumber +from sage.rings.real_mpfr import RealField, RealField_class, RealNumber from sage.schemes.elliptic_curves.constructor import EllipticCurve from sage.structure.richcmp import richcmp_method, richcmp, richcmp_not_equal @@ -212,9 +213,16 @@ def __init__(self, E, embedding=None): sage: L = PeriodLattice_ell(E,emb) sage: L == loads(dumps(L)) True - """ - from sage.rings.qqbar import AA, QQbar + Elliptic curve over imaginary number field without ``embedding`` specified:: + + sage: E = EllipticCurve(QQ[I], [5, -3*I]) + sage: L = PeriodLattice_ell(E, embedding=None) + sage: L.elliptic_logarithm(E(I+1, I+2)) # abs tol 1e-15 + -0.773376784700140 - 0.177736018028666*I + sage: L.elliptic_exponential(_) # abs tol 1e-15 + (1.00000000000000 - 1.00000000000000*I : 2.00000000000000 - 1.00000000000000*I : 1.00000000000000) + """ # First we cache the elliptic curve with this period lattice: self.E = E @@ -223,12 +231,20 @@ def __init__(self, E, embedding=None): # the given embedding: K = E.base_field() + self.is_approximate = isinstance(K, (RealField_class, ComplexField_class)) if embedding is None: - embs = K.embeddings(AA) - real = len(embs) > 0 - if not real: - embs = K.embeddings(QQbar) - embedding = embs[0] + if K in (AA, QQbar): + embedding = K.hom(QQbar) + real = K == AA + elif self.is_approximate: + embedding = K.hom(K) + real = isinstance(K, RealField_class) + else: + embs = K.embeddings(AA) + real = len(embs) > 0 + if not real: + embs = K.embeddings(QQbar) + embedding = embs[0] else: embedding = refine_embedding(embedding, Infinity) real = embedding(K.gen()).imag().is_zero() @@ -255,20 +271,24 @@ def __init__(self, E, embedding=None): # The ei are used both for period computation and elliptic # logarithms. - self.Ebar = self.E.change_ring(self.embedding) - self.f2 = self.Ebar.two_division_polynomial() + if self.is_approximate: + self.f2 = self.E.two_division_polynomial() + else: + self.Ebar = self.E.change_ring(self.embedding) + self.f2 = self.Ebar.two_division_polynomial() if self.real_flag == 1: # positive discriminant - self._ei = self.f2.roots(AA,multiplicities=False) + self._ei = self.f2.roots(K if self.is_approximate else AA,multiplicities=False) self._ei.sort() # e1 < e2 < e3 e1, e2, e3 = self._ei elif self.real_flag == -1: # negative discriminant - self._ei = self.f2.roots(QQbar, multiplicities=False) + self._ei = self.f2.roots(ComplexField(K.precision()) if self.is_approximate else QQbar, multiplicities=False) self._ei = sorted(self._ei, key=lambda z: z.imag()) e1, e3, e2 = self._ei # so e3 is real - e3 = AA(e3) + if not self.is_approximate: + e3 = AA(e3) self._ei = [e1, e2, e3] else: - self._ei = self.f2.roots(QQbar, multiplicities=False) + self._ei = self.f2.roots(ComplexField(K.precision()) if self.is_approximate else QQbar, multiplicities=False) e1, e2, e3 = self._ei # The quantities sqrt(e_i-e_j) are cached (as elements of @@ -329,7 +349,8 @@ def __repr__(self): To: Algebraic Real Field Defn: a |--> 1.259921049894873? """ - if self.E.base_field() is QQ: + K = self.E.base_field() + if K in (QQ, AA, QQbar) or isinstance(K, (RealField_class, ComplexField_class)): return "Period lattice associated to %s" % (self.E) return "Period lattice associated to %s with respect to the embedding %s" % (self.E, self.embedding) @@ -364,8 +385,8 @@ def __call__(self, P, prec=None): sage: P = E([-1,1]) sage: P.is_on_identity_component () False - sage: L(P, prec=96) - 0.4793482501902193161295330101 + 0.985868850775824102211203849...*I + sage: L(P, prec=96) # abs tol 1e-27 + 0.4793482501902193161295330101 + 0.985868850775824102211203849*I sage: Q = E([3,5]) sage: Q.is_on_identity_component() True @@ -630,6 +651,13 @@ def tau(self, prec=None, algorithm='sage'): w1, w2 = self.normalised_basis(prec=prec, algorithm=algorithm) return w1/w2 + @cached_method + def _compute_default_prec(self): + r""" + Internal function to compute the default precision to be used if nothing is passed in. + """ + return self.E.base_field().precision() if self.is_approximate else RealField().precision() + @cached_method def _compute_periods_real(self, prec=None, algorithm='sage'): r""" @@ -670,13 +698,13 @@ def _compute_periods_real(self, prec=None, algorithm='sage'): 1.9072648860892725468182549468 - 1.3404778596244020196600112394*I) """ if prec is None: - prec = 53 + prec = self._compute_default_prec() R = RealField(prec) C = ComplexField(prec) if algorithm == 'pari': ainvs = self.E.a_invariants() - if self.E.base_field() is not QQ: + if self.E.base_field() is not QQ and not self.is_approximate: ainvs = [C(self.embedding(ai)).real() for ai in ainvs] # The precision for omega() is determined by ellinit() @@ -688,9 +716,8 @@ def _compute_periods_real(self, prec=None, algorithm='sage'): raise ValueError("invalid value of 'algorithm' parameter") pi = R.pi() - # Up to now everything has been exact in AA or QQbar, but now - # we must go transcendental. Only now is the desired - # precision used! + # Up to now everything has been exact in AA or QQbar (unless self.is_approximate), + # but now we must go transcendental. Only now is the desired precision used! if self.real_flag == 1: # positive discriminant a, b, c = (R(x) for x in self._abc) w1 = R(pi/a.agm(b)) # least real period @@ -758,12 +785,11 @@ def _compute_periods_complex(self, prec=None, normalise=True): 0.692321964451917 """ if prec is None: - prec = RealField().precision() + prec = self._compute_default_prec() C = ComplexField(prec) - # Up to now everything has been exact in AA, but now we - # must go transcendental. Only now is the desired - # precision used! + # Up to now everything has been exact in AA or QQbar (unless self.is_approximate), + # but now we must go transcendental. Only now is the desired precision used! pi = C.pi() a, b, c = (C(x) for x in self._abc) if (a+b).abs() < (a-b).abs(): @@ -1107,7 +1133,7 @@ def sigma(self, z, prec=None, flag=0): 2.60912163570108 - 0.200865080824587*I """ if prec is None: - prec = RealField().precision() + prec = self._compute_default_prec() try: return self.E.pari_curve().ellsigma(z, flag, precision=prec) except AttributeError: @@ -1211,14 +1237,14 @@ def coordinates(self, z, rounding=None): sage: L = E.period_lattice() sage: w1, w2 = L.basis(prec=100) sage: P = E([-1,1]) - sage: zP = P.elliptic_logarithm(precision=100); zP + sage: zP = P.elliptic_logarithm(precision=100); zP # abs tol 1e-28 0.47934825019021931612953301006 + 0.98586885077582410221120384908*I - sage: L.coordinates(zP) + sage: L.coordinates(zP) # abs tol 1e-28 (0.19249290511394227352563996419, 0.50000000000000000000000000000) - sage: sum([x*w for x, w in zip(L.coordinates(zP), L.basis(prec=100))]) + sage: sum([x*w for x, w in zip(L.coordinates(zP), L.basis(prec=100))]) # abs tol 1e-28 0.47934825019021931612953301006 + 0.98586885077582410221120384908*I - sage: L.coordinates(12*w1 + 23*w2) + sage: L.coordinates(12*w1 + 23*w2) # abs tol 1e-28 (12.000000000000000000000000000, 23.000000000000000000000000000) sage: L.coordinates(12*w1 + 23*w2, rounding='floor') (11, 22) @@ -1276,17 +1302,17 @@ def reduce(self, z): sage: L = E.period_lattice() sage: w1, w2 = L.basis(prec=100) sage: P = E([-1,1]) - sage: zP = P.elliptic_logarithm(precision=100); zP + sage: zP = P.elliptic_logarithm(precision=100); zP # abs tol 1e-28 0.47934825019021931612953301006 + 0.98586885077582410221120384908*I - sage: z = zP + 10*w1 - 20*w2; z + sage: z = zP + 10*w1 - 20*w2; z # abs tol 1e-28 25.381473858740770069343110929 - 38.448885180257139986236950114*I - sage: L.reduce(z) + sage: L.reduce(z) # abs tol 1e-28 0.47934825019021931612953301006 + 0.98586885077582410221120384908*I - sage: L.elliptic_logarithm(2*P) + sage: L.elliptic_logarithm(2*P) # abs tol 1e-15 0.958696500380439 - sage: L.reduce(L.elliptic_logarithm(2*P)) + sage: L.reduce(L.elliptic_logarithm(2*P)) # abs tol 1e-15 0.958696500380439 - sage: L.reduce(L.elliptic_logarithm(2*P) + 10*w1 - 20*w2) + sage: L.reduce(L.elliptic_logarithm(2*P) + 10*w1 - 20*w2) # abs tol 1e-15 0.958696500380444 """ C = z.parent() @@ -1366,12 +1392,12 @@ def e_log_RC(self, xP, yP, prec=None, reduce=True): The elliptic log from the real coordinates:: - sage: L.e_log_RC(xP, yP) + sage: L.e_log_RC(xP, yP) # abs tol 1e-15 0.479348250190219 + 0.985868850775824*I The same elliptic log from the algebraic point:: - sage: L(P) + sage: L(P) # abs tol 1e-15 0.479348250190219 + 0.985868850775824*I A number field example:: @@ -1383,10 +1409,10 @@ def e_log_RC(self, xP, yP, prec=None, reduce=True): sage: v = K.real_places()[0] sage: L = E.period_lattice(v) sage: P = E.lift_x(1/3*a^2 + a + 5/3) - sage: L(P) + sage: L(P) # abs tol 1e-15 3.51086196882538 sage: xP, yP = [v(c) for c in P.xy()] - sage: L.e_log_RC(xP, yP) + sage: L.e_log_RC(xP, yP) # abs tol 1e-15 3.51086196882538 Elliptic logs of real points which do not come from algebraic @@ -1396,11 +1422,11 @@ def e_log_RC(self, xP, yP, prec=None, reduce=True): sage: ER = EllipticCurve([v(ai) for ai in E.a_invariants()]) sage: P = ER.lift_x(12.34) sage: xP, yP = P.xy() - sage: xP, yP + sage: xP, yP # abs tol 1e-15 (12.3400000000000, -43.3628968710567) - sage: L.e_log_RC(xP, yP) + sage: L.e_log_RC(xP, yP) # abs tol 1e-15 0.284656841192041 - sage: xP, yP = ER.lift_x(0).xy() + sage: xP, yP = ER.lift_x(0).xy() # abs tol 1e-15 sage: L.e_log_RC(xP, yP) 1.34921304541057 @@ -1410,18 +1436,18 @@ def e_log_RC(self, xP, yP, prec=None, reduce=True): sage: v = K.complex_embeddings()[0] sage: L = E.period_lattice(v) sage: P = E.lift_x(1/3*a^2 + a + 5/3) - sage: L(P) + sage: L(P) # abs tol 1e-15 1.68207104397706 - 1.87873661686704*I sage: xP, yP = [v(c) for c in P.xy()] - sage: L.e_log_RC(xP, yP) + sage: L.e_log_RC(xP, yP) # abs tol 1e-15 1.68207104397706 - 1.87873661686704*I sage: EC = EllipticCurve([v(ai) for ai in E.a_invariants()]) sage: xP, yP = EC.lift_x(0).xy() - sage: L.e_log_RC(xP, yP) + sage: L.e_log_RC(xP, yP) # abs tol 1e-15 2.06711431204080 - 1.73451485683471*I """ if prec is None: - prec = RealField().precision() + prec = self._compute_default_prec() # Note: using log2(prec) + 3 guard bits is usually enough. # To avoid computing a logarithm, we use 40 guard bits which # should be largely enough in practice. @@ -1709,11 +1735,61 @@ def elliptic_logarithm(self, P, prec=None, reduce=True): 1.17058357737548897849026170185581196033579563441850967539191867385734983296504066660506637438866628981886518901958717288150400849746892393771983141354 - 1.13513899565966043682474529757126359416758251309237866586896869548539516543734207347695898664875799307727928332953834601460994992792519799260968053875*I sage: L.elliptic_logarithm(P, prec=1000) 1.17058357737548897849026170185581196033579563441850967539191867385734983296504066660506637438866628981886518901958717288150400849746892393771983141354014895386251320571643977497740116710952913769943240797618468987304985625823413440999754037939123032233879499904283600304184828809773650066658885672885 - 1.13513899565966043682474529757126359416758251309237866586896869548539516543734207347695898664875799307727928332953834601460994992792519799260968053875387282656993476491590607092182964878750169490985439873220720963653658829712494879003124071110818175013453207439440032582917366703476398880865439217473*I + + Elliptic curve over ``QQbar``:: + + sage: E = EllipticCurve(QQbar, [sqrt(2), I]) + sage: L = E.period_lattice() + sage: P = E.lift_x(3) + sage: L.elliptic_logarithm(P) + -1.97657221097437 - 1.05021415535949*I + sage: L.elliptic_exponential(_) # abs tol 1e-15 + (3.00000000000000 + 9.20856947066460e-16*I : -5.59022723358798 - 0.0894418024719718*I : 1.00000000000000) + sage: L.elliptic_logarithm(P, prec=100) # abs tol 1e-15 + -3.4730631218714889933426781799 + 0.44627675553762761312098773197*I + sage: L.elliptic_exponential(_) # abs tol 1e-28 + (3.0000000000000000000000000000 - 1.4773628579202938936348512161e-30*I : -5.5902272335879800026836302686 - 0.089441802471969391005702381090*I : 1.0000000000000000000000000000) + + Real approximate field, negative discriminant. Note that the output precision uses the precision of the base field:: + + sage: E = EllipticCurve(RealField(100), [1, 6]) + sage: L = E.period_lattice() + sage: L.real_flag + -1 + sage: P = E(3, 6) + sage: L.elliptic_logarithm(P) + 2.4593388737550379526023682666 + sage: L.elliptic_exponential(_) + (3.0000000000000000000000000000 : 5.9999999999999999999999999999 : 1.0000000000000000000000000000) + + Real approximate field, positive discriminant:: + + sage: E = EllipticCurve(RealField(100), [-4, 3]) + sage: L = E.period_lattice() + sage: L.real_flag + 1 + sage: P = E.lift_x(4) + sage: L.elliptic_logarithm(P) + 0.51188849089267627141925354967 + sage: L.elliptic_exponential(_) + (4.0000000000000000000000000000 : -7.1414284285428499979993998114 : 1.0000000000000000000000000000) + + Complex approximate field:: + + sage: E = EllipticCurve(ComplexField(100), [I, 3*I+4]) + sage: L = E.period_lattice() + sage: L.real_flag + 0 + sage: P = E.lift_x(4) + sage: L.elliptic_logarithm(P) + -1.1447032790074574712147458157 - 0.72429843602171875396186134806*I + sage: L.elliptic_exponential(_) + (4.0000000000000000000000000000 + 1.2025589033682610849950210280e-30*I : -8.2570982991257407680322611854 - 0.42387771989714340809597881586*I : 1.0000000000000000000000000000) """ if P.curve() is not self.E: raise ValueError("Point is on the wrong curve") if prec is None: - prec = RealField().precision() + prec = self._compute_default_prec() if P.is_zero(): return ComplexField(prec)(0) @@ -1826,8 +1902,8 @@ def elliptic_exponential(self, z, to_curve=True): sage: E = EllipticCurve('37a') sage: K. = QuadraticField(-5) sage: L = E.change_ring(K).period_lattice(K.places()[0]) - sage: L.elliptic_exponential(CDF(.1,.1)) - (0.0000142854026029... - 49.9960001066650*I + sage: L.elliptic_exponential(CDF(.1,.1)) # abs tol 1e-15 + (0.0000142854026029 - 49.9960001066650*I : 249.520141250950 + 250.019855549131*I : 1.00000000000000) sage: L.elliptic_exponential(CDF(.1,.1), to_curve=False) (0.0000142854026029447 - 49.9960001066650*I, @@ -1926,7 +2002,10 @@ def elliptic_exponential(self, z, to_curve=True): if to_curve: K = x.parent() - v = refine_embedding(self.embedding, Infinity) + if self.is_approximate: + v = self.embedding + else: + v = refine_embedding(self.embedding, Infinity) a1, a2, a3, a4, a6 = (K(v(a)) for a in self.E.ainvs()) b2 = K(v(self.E.b2())) x = x - b2 / 12 diff --git a/src/sage/schemes/hyperelliptic_curves/meson.build b/src/sage/schemes/hyperelliptic_curves/meson.build index e4b017d4192..170d08baaca 100644 --- a/src/sage/schemes/hyperelliptic_curves/meson.build +++ b/src/sage/schemes/hyperelliptic_curves/meson.build @@ -1,5 +1,6 @@ inc_hypellfrob = include_directories('hypellfrob') py.install_sources( + '__init__.py', 'all.py', 'constructor.py', 'hyperelliptic_finite_field.py', diff --git a/src/sage/schemes/meson.build b/src/sage/schemes/meson.build index c74c532b930..4dac80900e9 100644 --- a/src/sage/schemes/meson.build +++ b/src/sage/schemes/meson.build @@ -1,4 +1,9 @@ -py.install_sources('all.py', 'overview.py', subdir: 'sage/schemes') +py.install_sources( + '__init__.py', + 'all.py', + 'overview.py', + subdir: 'sage/schemes', +) install_subdir('affine', install_dir: sage_install_dir / 'schemes') install_subdir('berkovich', install_dir: sage_install_dir / 'schemes') diff --git a/src/sage/schemes/projective/projective_homset.py b/src/sage/schemes/projective/projective_homset.py index 7f86f7ea0f6..da3f5b502cd 100755 --- a/src/sage/schemes/projective/projective_homset.py +++ b/src/sage/schemes/projective/projective_homset.py @@ -640,6 +640,8 @@ def _element_constructor_(self, *v, **kwds): """ if len(v) == 1: v = v[0] + if v == 0: + return self.zero() return self.codomain()._point(self.extended_codomain(), v, **kwds) def _repr_(self): @@ -689,6 +691,23 @@ def base_extend(self, R): 'implemented as modules over rings other than ZZ') return self + def zero(self): + r""" + Return the neutral element in this group of points. + + EXAMPLES:: + + sage: S = EllipticCurve(GF(5), [1,1]).point_homset() + sage: S.zero() + (0 : 1 : 0) + sage: S = EllipticCurve(Zmod(15), [1,1]).point_homset() + sage: S.zero() + (0 : 1 : 0) + """ + return self.codomain()(0) + + _an_element_ = zero + from sage.misc.persist import register_unpickle_override register_unpickle_override('sage.schemes.generic.homset', diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py index c5d6b02c05a..88ab4eadcfc 100755 --- a/src/sage/schemes/projective/projective_point.py +++ b/src/sage/schemes/projective/projective_point.py @@ -533,7 +533,11 @@ def scale_by(self, t): def normalize_coordinates(self): """ - Removes the gcd from the coordinates of this point (including `-1`). + Removes the gcd from the coordinates of this point (including `-1`) + and rescales everything so that the last nonzero entry is as "simple" + as possible. The notion of "simple" here depends on the base ring; + concretely, the last nonzero coordinate will be `1` in a field and + positive over an ordered ring. .. WARNING:: The gcd will depend on the base ring. @@ -552,7 +556,7 @@ def normalize_coordinates(self): sage: P = ProjectiveSpace(Zp(7), 2, 'x') sage: p = P([-5, -15, -2]) sage: p.normalize_coordinates(); p - (5 + O(7^20) : 1 + 2*7 + O(7^20) : 2 + O(7^20)) + (6 + 3*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + 3*7^10 + 3*7^11 + 3*7^12 + 3*7^13 + 3*7^14 + 3*7^15 + 3*7^16 + 3*7^17 + 3*7^18 + 3*7^19 + O(7^20) : 4 + 4*7 + 3*7^2 + 3*7^3 + 3*7^4 + 3*7^5 + 3*7^6 + 3*7^7 + 3*7^8 + 3*7^9 + 3*7^10 + 3*7^11 + 3*7^12 + 3*7^13 + 3*7^14 + 3*7^15 + 3*7^16 + 3*7^17 + 3*7^18 + 3*7^19 + O(7^20) : 1 + O(7^20)) :: @@ -576,8 +580,8 @@ def normalize_coordinates(self): sage: R. = PolynomialRing(QQ) sage: P = ProjectiveSpace(R, 1) sage: Q = P(2*c, 4*c) - sage: Q.normalize_coordinates();Q - (2 : 4) + sage: Q.normalize_coordinates(); Q + (1/2 : 1) A polynomial ring over a ring gives the more intuitive result. :: @@ -614,15 +618,19 @@ def normalize_coordinates(self): else: GCD = R(gcd(self._coords[0], self._coords[1])) index = 2 - neg = self._coords[0] <= 0 and self._coords[1] <= 0 - while not GCD.is_one() and index < len(self._coords): - neg = self._coords[index] <= 0 + while not GCD.is_unit() and index < len(self._coords): GCD = R(gcd(GCD, self._coords[index])) index += 1 - if not GCD.is_one(): + if not GCD.is_unit(): self.scale_by(~GCD) - if neg: - self.scale_by(-ZZ.one()) + index = len(self._coords) - 1 + while not self._coords[index]: + index -= 1 + if self._coords[index].is_unit(): + if not self._coords[index].is_one(): + self.scale_by(~self._coords[index]) + elif self._coords[index] < 0: + self.scale_by(-R.one()) self._normalized = True def dehomogenize(self, n): diff --git a/src/sage/schemes/toric/meson.build b/src/sage/schemes/toric/meson.build index d9dea45fcc8..0e85031ccf5 100644 --- a/src/sage/schemes/toric/meson.build +++ b/src/sage/schemes/toric/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'chow_group.py', 'divisor.py', diff --git a/src/sage/sets/disjoint_set.pyx b/src/sage/sets/disjoint_set.pyx index b991094924b..53e793755ec 100644 --- a/src/sage/sets/disjoint_set.pyx +++ b/src/sage/sets/disjoint_set.pyx @@ -560,6 +560,7 @@ cdef class DisjointSet_of_integers(DisjointSet_class): the new element. The new set is added at the end of ``self``. EXAMPLES:: + sage: d = DisjointSet(5) sage: d.union(1, 2) sage: d.union(0, 1) @@ -568,6 +569,13 @@ cdef class DisjointSet_of_integers(DisjointSet_class): {{0, 1, 2}, {3}, {4}, {5}} sage: d.find(1) 1 + + TESTS:: + + sage: d = DisjointSet(0) + sage: d.make_set() + sage: d + {{0}} """ OP_make_set(self._nodes) diff --git a/src/sage/sets/meson.build b/src/sage/sets/meson.build index 73faddaf9c7..92d4cb4cd17 100644 --- a/src/sage/sets/meson.build +++ b/src/sage/sets/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'all__sagemath_objects.py', 'cartesian_product.py', diff --git a/src/sage/stats/distributions/meson.build b/src/sage/stats/distributions/meson.build index 129d5f74d13..db152755b95 100644 --- a/src/sage/stats/distributions/meson.build +++ b/src/sage/stats/distributions/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'catalog.py', 'dgs.pxd', diff --git a/src/sage/stats/hmm/meson.build b/src/sage/stats/hmm/meson.build index cbf4a30caa5..661c578252e 100644 --- a/src/sage/stats/hmm/meson.build +++ b/src/sage/stats/hmm/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'distributions.pxd', 'hmm.pxd', diff --git a/src/sage/stats/meson.build b/src/sage/stats/meson.build index 414a909270c..52c4b684ac4 100644 --- a/src/sage/stats/meson.build +++ b/src/sage/stats/meson.build @@ -1,4 +1,5 @@ py.install_sources( + '__init__.py', 'all.py', 'basic_stats.py', 'intlist.pxd', diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 4860b4efabd..9fd976fdfc7 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -4737,6 +4737,7 @@ def coerce_binop(method): TypeError: algorithm 1 not supported """ @sage_wraps(method) + @cython.binding(True) def new_method(self, other, *args, **kwargs): if have_same_parent(self, other): return method(self, other, *args, **kwargs) diff --git a/src/sage/structure/mutability.pyx b/src/sage/structure/mutability.pyx index 69d034bd8c3..a8808a29623 100644 --- a/src/sage/structure/mutability.pyx +++ b/src/sage/structure/mutability.pyx @@ -12,6 +12,7 @@ Mutability Cython Implementation # https://www.gnu.org/licenses/ ########################################################################## +cimport cython from sage.misc.decorators import sage_wraps cdef class Mutability: @@ -286,6 +287,7 @@ def require_mutable(f): - Simon King """ @sage_wraps(f) + @cython.binding(True) def new_f(self, *args, **kwds): if getattr(self, '_is_immutable', False): raise ValueError("{} instance is immutable, {} must not be called".format(type(self), repr(f))) @@ -338,6 +340,7 @@ def require_immutable(f): - Simon King """ @sage_wraps(f) + @cython.binding(True) def new_f(self, *args, **kwds): if not getattr(self,'_is_immutable',False): raise ValueError("{} instance is mutable, {} must not be called".format(type(self), repr(f))) diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index 8e8940b5846..1dd2ae070ca 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -2599,17 +2599,19 @@ cdef class Parent(sage.structure.category_object.CategoryObject): sage: # needs sage.schemes sage: E = EllipticCurve([1,0]) sage: coercion_model.get_action(E, ZZ, operator.mul) - Right Integer Multiplication by Integer Ring - on Elliptic Curve defined by y^2 = x^3 + x over Rational Field + Right action by Integer Ring on Elliptic Curve defined by y^2 = x^3 + x over Rational Field sage: coercion_model.get_action(ZZ, E, operator.mul) - Left Integer Multiplication by Integer Ring - on Elliptic Curve defined by y^2 = x^3 + x over Rational Field + Left action by Integer Ring on Elliptic Curve defined by y^2 = x^3 + x over Rational Field sage: coercion_model.get_action(E, int, operator.mul) - Right Integer Multiplication by Set of Python objects of class 'int' - on Elliptic Curve defined by y^2 = x^3 + x over Rational Field + Right action by Integer Ring on Elliptic Curve defined by y^2 = x^3 + x over Rational Field + with precomposition on right by Native morphism: + From: Set of Python objects of class 'int' + To: Integer Ring sage: coercion_model.get_action(int, E, operator.mul) - Left Integer Multiplication by Set of Python objects of class 'int' - on Elliptic Curve defined by y^2 = x^3 + x over Rational Field + Left action by Integer Ring on Elliptic Curve defined by y^2 = x^3 + x over Rational Field + with precomposition on left by Native morphism: + From: Set of Python objects of class 'int' + To: Integer Ring :: @@ -3042,8 +3044,7 @@ cdef class EltPair: sage: K. = Qq(9) # needs sage.rings.padics sage: E = EllipticCurve_from_j(0).base_extend(K) # needs sage.rings.padics sage: E.get_action(ZZ) # needs sage.rings.padics - Right Integer Multiplication - by Integer Ring + Right action by Integer Ring on Elliptic Curve defined by y^2 + (1+O(3^20))*y = x^3 over 3-adic Unramified Extension Field in a defined by x^2 + 2*x + 2 diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 5da1e726a9c..3ce925e0f3a 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -372,6 +372,7 @@ More sanity tests:: # https://www.gnu.org/licenses/ # **************************************************************************** +cimport cython from cysignals.signals cimport sig_on, sig_off from sage.ext.cplusplus cimport ccrepr, ccreadstr @@ -13573,6 +13574,7 @@ def _eval_on_operands(f): Some documentation. """ @sage_wraps(f) + @cython.binding(True) def new_f(ex, *args, **kwds): new_args = list(ex._unpack_operands()) new_args.extend(args) diff --git a/src/sage/symbolic/meson.build b/src/sage/symbolic/meson.build index f46589aa5a0..3d3bbc67afc 100644 --- a/src/sage/symbolic/meson.build +++ b/src/sage/symbolic/meson.build @@ -2,6 +2,7 @@ inc_ginac = include_directories('ginac') inc_pynac = include_directories('.') py.install_sources( + '__init__.py', 'all.py', 'assumptions.py', 'benchmark.py', diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index 65fccee5324..96f3a445e9c 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -187,7 +187,7 @@ cdef class SymbolicRing(sage.rings.abc.SymbolicRing): True """ if isinstance(R, type): - if R in [int, float, long, complex, bool]: + if R in (int, float, complex, bool): return True if is_numpy_type(R): diff --git a/src/sage/version.py b/src/sage/version.py index 37c771ba0f2..1740a25851c 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.6.beta0' -date = '2024-12-08' -banner = 'SageMath version 10.6.beta0, Release Date: 2024-12-08' +version = '10.6.beta1' +date = '2024-12-15' +banner = 'SageMath version 10.6.beta1, Release Date: 2024-12-15' diff --git a/src/tox.ini b/src/tox.ini index a88e9c7f879..68a64b498d5 100644 --- a/src/tox.ini +++ b/src/tox.ini @@ -302,7 +302,6 @@ passenv = RUFF_OUTPUT_FORMAT # 12 F811 [*] Redefinition of unused `CompleteDiscreteValuationRings` from line 49 # 8 PLC0414 [*] Import alias does not rename original package # 7 E743 [ ] Ambiguous function name: `I` -# 7 PLE0101 [ ] Explicit return in `__init__` # 7 PLR0124 [ ] Name compared with itself, consider replacing `a == a` # 5 PLW0127 [ ] Self-assignment of variable `a` # 4 F541 [*] f-string without any placeholders @@ -314,7 +313,7 @@ passenv = RUFF_OUTPUT_FORMAT # 1 F402 [ ] Import `factor` from line 259 shadowed by loop variable # 1 PLC0208 [*] Use a sequence type instead of a `set` when iterating over values # -commands = ruff check --ignore PLR2004,I001,F401,E741,F821,PLR0912,PLR0913,E402,PLR0915,PLW2901,PLR5501,PLR0911,E731,F405,PLR1714,PLR1736,F403,PLR0402,PLW0603,F841,PLW0602,PLW0642,PLR1711,SIM101,PLR1704,PLW3301,PLW1510,E721,PLW0211,PLW0120,F811,PLC2401,PLC0414,E743,PLE0101,PLR0124,PLW0127,F541,PLW1508,PLC3002,E742,PLE0302,PLW0129,F402,PLC0208,PLC0206 {posargs:{toxinidir}/sage/} +commands = ruff check --ignore E402,E721,E731,E741,E742,E743,F401,F402,F403,F405,F541,F811,F821,F841,I001,PLC0206,PLC0208,PLC0414,PLC2401,PLC3002,PLE0302,PLR0124,PLR0402,PLR0911,PLR0912,PLR0913,PLR0915,PLR1704,PLR1711,PLR1714,PLR1736,PLR2004,PLR5501,PLW0120,PLW0127,PLW0129,PLW0211,PLW0602,PLW0603,PLW0642,PLW1508,PLW1510,PLW2901,PLW3301 {posargs:{toxinidir}/sage/} [flake8] rst-roles = diff --git a/tools/update-meson.py b/tools/update-meson.py index 827b7d9b5a3..1a53ac21397 100755 --- a/tools/update-meson.py +++ b/tools/update-meson.py @@ -86,9 +86,6 @@ def update_python_sources(self: Rewriter, visitor: AstPython): to_append: list[StringNode] = [] for file in python_files: file_name = file.name - if file_name == "__init__.py": - # We don't want to add __init__.py files - continue if file_name in src_list: continue token = Token("string", target.filename, 0, 0, 0, None, file_name)