diff --git a/.github/workflows/tox-experimental.yml b/.github/workflows/tox-experimental.yml index 54c8373a362..fc0e8b380c6 100644 --- a/.github/workflows/tox-experimental.yml +++ b/.github/workflows/tox-experimental.yml @@ -41,7 +41,7 @@ jobs: # This list is different from the one in tox.yml: # Trac #31526 switches gcc 4.x-based distributions to using the gcc_spkg configuration factor # Trac #32281 removes gcc 4.x-based distributions whose binutils are unusable - tox_system_factor: [ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie-gcc_spkg, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7-gcc_spkg, centos-8, gentoo-python3.9, gentoo-python3.10, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386-gcc_spkg] + tox_system_factor: [ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, ubuntu-impish, ubunty-jammy, debian-stretch, debian-buster, debian-bullseye, debian-bookworm, debian-sid, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, linuxmint-20.2. linuxmint-20.3, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, fedora-35, fedora-36, centos-7-gcc_spkg, centos-8, gentoo-python3.9, gentoo-python3.10, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386-gcc_spkg] tox_packages_factor: [maximal] targets_pattern: [0-g, h-o, p, q-z] env: diff --git a/.github/workflows/tox-gcc_spkg.yml b/.github/workflows/tox-gcc_spkg.yml deleted file mode 100644 index 1a200a7cf7c..00000000000 --- a/.github/workflows/tox-gcc_spkg.yml +++ /dev/null @@ -1,142 +0,0 @@ -name: Test Linux --without-system-gcc - -## This GitHub Actions workflow runs SAGE_ROOT/tox.ini with select environments, -## whenever a GitHub pull request is opened or synchronized in a repository -## where GitHub Actions are enabled. -## -## It builds and checks some sage spkgs as defined in TARGETS. -## -## A job succeeds if there is no error. -## -## The build is run with "make V=0", so the build logs of individual packages are suppressed. -## -## At the end, all package build logs that contain an error are printed out. -## -## After all jobs have finished (or are canceled) and a short delay, -## tar files of all logs are made available as "build artifacts". - -#on: [push, pull_request] - -on: - pull_request: - types: [opened, synchronize] - push: - tags: - - '*' - workflow_dispatch: - # Allow to run manually - -env: - TARGETS_PRE: all-sage-local - TARGETS: build doc-html - TARGETS_OPTIONAL: ptest - -jobs: - docker: - runs-on: ubuntu-latest - strategy: - fail-fast: false - max-parallel: 5 - matrix: - tox_system_factor: [ubuntu-trusty, ubuntu-focal, fedora-27, fedora-31, debian-buster-i386] - tox_packages_factor: [minimal-gcc_spkg, standard-gcc_spkg] - env: - TOX_ENV: docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - LOGS_ARTIFACT_NAME: logs-commit-${{ github.sha }}-tox-docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - DOCKER_TARGETS: configured with-targets with-targets-optional - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 500 - - name: fetch tags - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - name: free disk space - run: | - df -h - sudo swapoff -a - sudo rm -f /swapfile - sudo apt-get clean - docker rmi $(docker image ls -aq) - echo "Largest packages:" - dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 50 - sudo apt-get --fix-broken --yes remove $(dpkg-query -f '${Package}\n' -W | grep -E '^(ghc-|google-cloud-sdk|google-chrome|firefox|mysql-server|dotnet-sdk|hhvm|mono)') || echo "(error ignored)" - df -h - - name: Install test prerequisites - run: | - sudo DEBIAN_FRONTEND=noninteractive apt-get update - sudo DEBIAN_FRONTEND=noninteractive apt-get install tox - sudo apt-get clean - df -h - - name: Try to login to docker.pkg.github.com - # https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable - run: | - TOKEN="${{ secrets.DOCKER_PKG_GITHUB_TOKEN }}" - if [ -z "$TOKEN" ]; then - TOKEN="${{ secrets.GITHUB_TOKEN }}" - fi - if echo "$TOKEN" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin; then - echo "DOCKER_PUSH_REPOSITORY=docker.pkg.github.com/${{ github.repository }}/" >> $GITHUB_ENV - fi - - run: | - set -o pipefail; EXTRA_DOCKER_BUILD_ARGS="--build-arg USE_MAKEFLAGS=\"-k V=0 SAGE_NUM_THREADS=3\"" tox -e $TOX_ENV -- $TARGETS 2>&1 | sed "/^configure: notice:/s|^|::warning file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;/^configure: warning:/s|^|::warning file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;/^configure: error:/s|^|::error file=artifacts/$LOGS_ARTIFACT_NAME/config.log::|;" - - name: Copy logs from the docker image or build container - run: | - mkdir -p "artifacts/$LOGS_ARTIFACT_NAME" - cp -r .tox/$TOX_ENV/Dockerfile .tox/$TOX_ENV/log "artifacts/$LOGS_ARTIFACT_NAME" - if [ -f .tox/$TOX_ENV/Dockertags ]; then CONTAINERS=$(docker create $(tail -1 .tox/$TOX_ENV/Dockertags) /bin/bash || true); fi - if [ -n "$CONTAINERS" ]; then for CONTAINER in $CONTAINERS; do for ARTIFACT in /sage/logs; do docker cp $CONTAINER:$ARTIFACT artifacts/$LOGS_ARTIFACT_NAME && HAVE_LOG=1; done; if [ -n "$HAVE_LOG" ]; then break; fi; done; fi - if: always() - - uses: actions/upload-artifact@v1 - with: - path: artifacts - name: ${{ env.LOGS_ARTIFACT_NAME }} - if: always() - - name: Print out logs for immediate inspection - # and markup the output with GitHub Actions logging commands - run: | - .github/workflows/scan-logs.sh "artifacts/$LOGS_ARTIFACT_NAME" - if: always() - - name: List docker images - run: | - if [ -f .tox/$TOX_ENV/Dockertags ]; then - cat .tox/$TOX_ENV/Dockertags - fi - if: always() - - local-ubuntu: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - max-parallel: 1 - matrix: - tox_system_factor: [conda-forge-ubuntu] - tox_packages_factor: [minimal-gcc_spkg, standard-gcc_spkg] - env: - TOX_ENV: local-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - LOGS_ARTIFACT_NAME: logs-commit-${{ github.sha }}-tox-local-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} - steps: - - uses: actions/checkout@v2 - - name: Install test prerequisites - run: | - sudo DEBIAN_FRONTEND=noninteractive apt-get update - sudo DEBIAN_FRONTEND=noninteractive apt-get install tox - - name: Build and test with tox - # We use a high parallelization on purpose in order to catch possible parallelization bugs in the build scripts. - # For doctesting, we use a lower parallelization to avoid timeouts. - run: | - MAKE="make -j12" tox -e $TOX_ENV -- SAGE_NUM_THREADS=4 $TARGETS - - name: Prepare logs artifact - run: | - mkdir -p "artifacts/$LOGS_ARTIFACT_NAME"; cp -r .tox/*/log "artifacts/$LOGS_ARTIFACT_NAME" - if: always() - - uses: actions/upload-artifact@v1 - with: - path: artifacts - name: ${{ env.LOGS_ARTIFACT_NAME }} - if: always() - - name: Print out logs for immediate inspection - # and markup the output with GitHub Actions logging commands - run: | - .github/workflows/scan-logs.sh "artifacts/$LOGS_ARTIFACT_NAME" - if: always() diff --git a/.github/workflows/tox-optional.yml b/.github/workflows/tox-optional.yml index bcdf349bee7..715b4492fa3 100644 --- a/.github/workflows/tox-optional.yml +++ b/.github/workflows/tox-optional.yml @@ -41,7 +41,7 @@ jobs: # This list is different from the one in tox.yml: # Trac #31526 switches gcc 4.x-based distributions to using the gcc_spkg configuration factor # Trac #32281 removes gcc 4.x-based distributions whose binutils are unusable - tox_system_factor: [ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie-gcc_spkg, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, centos-7-gcc_spkg, centos-8, gentoo-python3.9, gentoo-python3.10, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386-gcc_spkg] + tox_system_factor: [ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, ubuntu-impish, ubunty-jammy, debian-stretch, debian-buster, debian-bullseye, debian-bookworm, debian-sid, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, linuxmint-20.2. linuxmint-20.3, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, fedora-35, fedora-36, centos-7-gcc_spkg, centos-8, gentoo-python3.9, gentoo-python3.10, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386-gcc_spkg] tox_packages_factor: [maximal] targets_pattern: [0-g, h-o, p, q-z] env: diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 493e8342920..b328a31b721 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -38,7 +38,7 @@ jobs: fail-fast: false max-parallel: 20 matrix: - tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, ubuntu-impish, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, linuxmint-20.2, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, fedora-35, centos-7, centos-8, gentoo-python3.9, gentoo-python3.10, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] + tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, ubuntu-impish, ubunty-jammy, debian-stretch, debian-buster, debian-bullseye, debian-bookworm, debian-sid, linuxmint-17, linuxmint-18, linuxmint-19, linuxmint-19.3, linuxmint-20.1, linuxmint-20.2, linuxmint-20.3, fedora-26, fedora-27, fedora-28, fedora-29, fedora-30, fedora-31, fedora-32, fedora-33, fedora-34, fedora-35, fedora-36, centos-7, centos-8, gentoo-python3.9, gentoo-python3.10, archlinux-latest, opensuse-15, opensuse-15.3, opensuse-tumbleweed, slackware-14.2, conda-forge, ubuntu-bionic-i386, manylinux-2_24-i686, debian-buster-i386, centos-7-i386] tox_packages_factor: [minimal, standard] env: TOX_ENV: docker-${{ matrix.tox_system_factor }}-${{ matrix.tox_packages_factor }} diff --git a/.zenodo.json b/.zenodo.json index e4996fc5334..5ecd065d6a3 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,10 +1,10 @@ { "description": "Mirror of the Sage https://sagemath.org/ source tree", "license": "other-open", - "title": "sagemath/sage: 9.5.beta8", - "version": "9.5.beta8", + "title": "sagemath/sage: 9.5.beta9", + "version": "9.5.beta9", "upload_type": "software", - "publication_date": "2021-12-12", + "publication_date": "2021-12-23", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/9.5.beta8", + "identifier": "https://github.com/sagemath/sage/tree/9.5.beta9", "relation": "isSupplementTo" }, { diff --git a/README.md b/README.md index cf1c448835c..1ad0db4a5be 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ > "Creating a Viable Open Source Alternative to > Magma, Maple, Mathematica, and MATLAB" -> Copyright (C) 2005-2020 The Sage Development Team +> Copyright (C) 2005-2021 The Sage Development Team https://www.sagemath.org @@ -37,10 +37,14 @@ or ask on [ask.sagemath.org](https://ask.sagemath.org). Supported Platforms ------------------- -Sage fully supports all major Linux distributions, recent versions of +Sage attempts to support all major Linux distributions, recent versions of macOS, and Windows (using Cygwin, Windows Subsystem for Linux, or using virtualization). +Detailed information on supported platforms for a specific version of Sage +can be found in the section "Availability and installation help" of the +[release tour](https://wiki.sagemath.org/ReleaseTours) for this version. + We highly appreciate contributions to Sage that fix portability bugs and help port Sage to new platforms; let us know at the [sage-devel mailing list](https://groups.google.com/group/sage-devel). @@ -234,8 +238,10 @@ Guide](https://doc.sagemath.org/html/en/installation). [debian.txt](build/pkgs/_prereq/distros/debian.txt) (also for Ubuntu, Linux Mint, etc.), [fedora.txt](build/pkgs/_prereq/distros/fedora.txt) - (also for Red Hat, CentOS), and - [slackware.txt](build/pkgs/_prereq/distros/slackware.txt). + (also for Red Hat, CentOS), + [opensuse.txt](build/pkgs/_prereq/distros/opensuse.txt) + [slackware.txt](build/pkgs/_prereq/distros/slackware.txt), and + [void.txt](build/pkgs/_prereq/distros/void.txt). 7. Optional: It is recommended that you have both LaTeX and the ImageMagick tools (e.g. the "convert" command) installed since some @@ -500,9 +506,9 @@ do. 1. To make a binary distribution with your currently installed packages, visit [sagemath/binary-pkg](https://github.com/sagemath/binary-pkg). -2. (**Obsolete, probably broken**) To make your own source tarball of Sage, type: +2. To make your own source tarball of Sage, type: - $ sage --sdist + $ make dist The result is placed in the directory `dist/`. diff --git a/VERSION.txt b/VERSION.txt index 2cbf040582f..7d5505fc37c 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.5.beta8, Release Date: 2021-12-12 +SageMath version 9.5.beta9, Release Date: 2021-12-23 diff --git a/build/make/Makefile.in b/build/make/Makefile.in index 12698a7b5c6..f870cc322a4 100644 --- a/build/make/Makefile.in +++ b/build/make/Makefile.in @@ -672,7 +672,16 @@ $(1)-uninstall: $(1)-$(4)-uninstall $(1)-clean: $(1)-uninstall +# Recursive tox invocation (note - we do not set the environment here). +# Setting SAGE_SPKG_WHEELS is for the benefit of sagelib's tox.ini +$(1)-tox-%: FORCE + $(AM_V_at)cd '$$(SAGE_ROOT)/build/pkgs/$(1)/src' && \ + export PATH="$$(SAGE_ORIG_PATH)" && \ + SAGE_SPKG_WHEELS=$$(SAGE_LOCAL)/var/lib/sage/wheels \ + tox -v -v -v -e $$* + .PHONY: $(1) $(1)-uninstall $(1)-build-deps $(1)-no-deps $(1)-clean + endef $(foreach pkgname,$(SCRIPT_PACKAGES),\ diff --git a/build/pkgs/4ti2/distros/cygwin.txt b/build/pkgs/4ti2/distros/cygwin.txt index cd62bb6f0a0..fab3a09f46e 100644 --- a/build/pkgs/4ti2/distros/cygwin.txt +++ b/build/pkgs/4ti2/distros/cygwin.txt @@ -1 +1,2 @@ +lib4ti2_0 lib4ti2-devel diff --git a/build/pkgs/_recommended/distros/debian.txt b/build/pkgs/_recommended/distros/debian.txt index ecb038604a3..c7be25b851a 100644 --- a/build/pkgs/_recommended/distros/debian.txt +++ b/build/pkgs/_recommended/distros/debian.txt @@ -13,5 +13,5 @@ dvipng # to run the Jmol 3D viewer from the console and generate images for 3D plots in the documentation default-jdk # to produce animations -ffmpeg +# ffmpeg -- this is a separate script package libavdevice-dev diff --git a/build/pkgs/_recommended/distros/homebrew.txt b/build/pkgs/_recommended/distros/homebrew.txt index 35999b380cd..975a1991f49 100644 --- a/build/pkgs/_recommended/distros/homebrew.txt +++ b/build/pkgs/_recommended/distros/homebrew.txt @@ -1,8 +1,8 @@ # To convert Jupyter notebooks to pdf: # pandoc -- this is a separate script package # To produce animations: -ffmpeg -imagemagick +# ffmpeg -- this is a separate script package +# imagemagick -- this is a separate script package # Homebrew's texinfo can be used to build the info files for ecl and # maxima but the OS X default version of texinfo cannot. texinfo diff --git a/build/pkgs/_recommended/distros/macports.txt b/build/pkgs/_recommended/distros/macports.txt index 9a4a1a0e1f8..0b2d8b1fe55 100644 --- a/build/pkgs/_recommended/distros/macports.txt +++ b/build/pkgs/_recommended/distros/macports.txt @@ -1,8 +1,8 @@ # To convert Jupyter notebooks to pdf: # pandoc -- this is a separate script package # To produce animations: -ffmpeg -imagemagick +# ffmpeg -- this is a separate script package +# imagemagick -- this is a separate script package # MacPorts's texinfo can be used to build the info files for ecl and # maxima but the OS X default version of texinfo cannot. texinfo diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 446fad340c4..7e027e6fdea 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=07d98dfc41a546e8fbcedc500e0c29927061d2b2 -md5=ccdc4b7a6c1ec9de8a198a5b69bf7cba -cksum=2208364100 +sha1=40df2ac9634227a77a83b6524ba9696b961c3fb6 +md5=696e5df05ca484f2de649d104e6a5149 +cksum=589835683 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index d1714c48f48..80687e5ab67 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -36135459d57177c9480eda0f779c0fb8650727be +b879fe3b0554c844c224d7b139f698caef4c3905 diff --git a/build/pkgs/cysignals/checksums.ini b/build/pkgs/cysignals/checksums.ini index fd7248ee3c6..f374b70697c 100644 --- a/build/pkgs/cysignals/checksums.ini +++ b/build/pkgs/cysignals/checksums.ini @@ -1,5 +1,5 @@ tarball=cysignals-VERSION.tar.gz -sha1=81952b37562c0bbdbe3b458c96ef64f47c92ab45 -md5=667f1ab086e1f5f79ee6ad4826e357b1 -cksum=2113264608 +sha1=7d42fa85f48123a988f43e437b01d13e42aba919 +md5=424a762509abdd80a7b55d302b83aa6e +cksum=1506931565 upstream_url=https://pypi.io/packages/source/c/cysignals/cysignals-VERSION.tar.gz diff --git a/build/pkgs/cysignals/package-version.txt b/build/pkgs/cysignals/package-version.txt index 587c5f0c730..ca7176690dd 100644 --- a/build/pkgs/cysignals/package-version.txt +++ b/build/pkgs/cysignals/package-version.txt @@ -1 +1 @@ -1.10.3 +1.11.2 diff --git a/build/pkgs/debugpy/type b/build/pkgs/debugpy/type index a6a7b9cd726..134d9bc32d5 100644 --- a/build/pkgs/debugpy/type +++ b/build/pkgs/debugpy/type @@ -1 +1 @@ -standard +optional diff --git a/build/pkgs/ffmpeg/SPKG.rst b/build/pkgs/ffmpeg/SPKG.rst new file mode 100644 index 00000000000..a38f6653562 --- /dev/null +++ b/build/pkgs/ffmpeg/SPKG.rst @@ -0,0 +1,25 @@ +ffmpeg: ffmpeg video converter +============================== + +Description +----------- + +ffmpeg is a very fast video and audio converter that can also grab from a live +audio/video source. It can also convert between arbitrary sample rates and +resize video on the fly with a high quality polyphase filter. + +License +------- + +"FFmpeg is licensed under the GNU Lesser General Public License (LGPL) version +2.1 or later. However, FFmpeg incorporates several optional parts and +optimizations that are covered by the GNU General Public License (GPL) version +2 or later. If those parts get used the GPL applies to all of FFmpeg." + +http://ffmpeg.org/legal.html + +Upstream Contact +---------------- + +http://ffmpeg.org/ + diff --git a/build/pkgs/ffmpeg/distros/alpine.txt b/build/pkgs/ffmpeg/distros/alpine.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/alpine.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/distros/arch.txt b/build/pkgs/ffmpeg/distros/arch.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/arch.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/distros/conda.txt b/build/pkgs/ffmpeg/distros/conda.txt new file mode 100644 index 00000000000..e46f52fd301 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/conda.txt @@ -0,0 +1 @@ +imageio-ffmpeg diff --git a/build/pkgs/ffmpeg/distros/debian.txt b/build/pkgs/ffmpeg/distros/debian.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/debian.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/distros/fedora.txt b/build/pkgs/ffmpeg/distros/fedora.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/fedora.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/distros/freebsd.txt b/build/pkgs/ffmpeg/distros/freebsd.txt new file mode 100644 index 00000000000..64d105526b0 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/freebsd.txt @@ -0,0 +1 @@ +multimedia/ffmpeg diff --git a/build/pkgs/ffmpeg/distros/homebrew.txt b/build/pkgs/ffmpeg/distros/homebrew.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/homebrew.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/distros/macports.txt b/build/pkgs/ffmpeg/distros/macports.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/macports.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/distros/nix.txt b/build/pkgs/ffmpeg/distros/nix.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/nix.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/distros/opensuse.txt b/build/pkgs/ffmpeg/distros/opensuse.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/opensuse.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/distros/repology.txt b/build/pkgs/ffmpeg/distros/repology.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/repology.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/distros/void.txt b/build/pkgs/ffmpeg/distros/void.txt new file mode 100644 index 00000000000..20645e64124 --- /dev/null +++ b/build/pkgs/ffmpeg/distros/void.txt @@ -0,0 +1 @@ +ffmpeg diff --git a/build/pkgs/ffmpeg/spkg-configure.m4 b/build/pkgs/ffmpeg/spkg-configure.m4 new file mode 100644 index 00000000000..6a9139bc230 --- /dev/null +++ b/build/pkgs/ffmpeg/spkg-configure.m4 @@ -0,0 +1,5 @@ +SAGE_SPKG_CONFIGURE([ffmpeg], [ + AC_PATH_PROG([FFMPEG], [ffmpeg]) + AS_IF([test -z "$ac_cv_path_FFMPEG"], [sage_spkg_install_ffmpeg=yes]) +]) + diff --git a/build/pkgs/ffmpeg/type b/build/pkgs/ffmpeg/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/ffmpeg/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/imagemagick/SPKG.rst b/build/pkgs/imagemagick/SPKG.rst new file mode 100644 index 00000000000..9bd89270e5b --- /dev/null +++ b/build/pkgs/imagemagick/SPKG.rst @@ -0,0 +1,29 @@ +ImageMagick: A collection of tools and libraries for many image file formats +============================================================================ + +Description +----------- + +A collection of tools and libraries for many image file formats + +License +------- + +Copyright [yyyy] [name of copyright owner] + +Licensed under the ImageMagick License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy +of the License at + + https://imagemagick.org/script/license.php + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. + +Upstream Contact +---------------- + +http://www.imagemagick.org/ diff --git a/build/pkgs/imagemagick/distros/alpine.txt b/build/pkgs/imagemagick/distros/alpine.txt new file mode 100644 index 00000000000..648c25398d2 --- /dev/null +++ b/build/pkgs/imagemagick/distros/alpine.txt @@ -0,0 +1 @@ +imagemagick diff --git a/build/pkgs/imagemagick/distros/arch.txt b/build/pkgs/imagemagick/distros/arch.txt new file mode 100644 index 00000000000..648c25398d2 --- /dev/null +++ b/build/pkgs/imagemagick/distros/arch.txt @@ -0,0 +1 @@ +imagemagick diff --git a/build/pkgs/imagemagick/distros/conda.txt b/build/pkgs/imagemagick/distros/conda.txt new file mode 100644 index 00000000000..648c25398d2 --- /dev/null +++ b/build/pkgs/imagemagick/distros/conda.txt @@ -0,0 +1 @@ +imagemagick diff --git a/build/pkgs/imagemagick/distros/cygwin.txt b/build/pkgs/imagemagick/distros/cygwin.txt new file mode 100644 index 00000000000..7a33d03ff73 --- /dev/null +++ b/build/pkgs/imagemagick/distros/cygwin.txt @@ -0,0 +1 @@ +ImageMagick diff --git a/build/pkgs/imagemagick/distros/debian.txt b/build/pkgs/imagemagick/distros/debian.txt new file mode 100644 index 00000000000..648c25398d2 --- /dev/null +++ b/build/pkgs/imagemagick/distros/debian.txt @@ -0,0 +1 @@ +imagemagick diff --git a/build/pkgs/imagemagick/distros/fedora.txt b/build/pkgs/imagemagick/distros/fedora.txt new file mode 100644 index 00000000000..7a33d03ff73 --- /dev/null +++ b/build/pkgs/imagemagick/distros/fedora.txt @@ -0,0 +1 @@ +ImageMagick diff --git a/build/pkgs/imagemagick/distros/freebsd.txt b/build/pkgs/imagemagick/distros/freebsd.txt new file mode 100644 index 00000000000..6a1a90fb7ed --- /dev/null +++ b/build/pkgs/imagemagick/distros/freebsd.txt @@ -0,0 +1 @@ +graphics/ImageMagick7 diff --git a/build/pkgs/imagemagick/distros/homebrew.txt b/build/pkgs/imagemagick/distros/homebrew.txt new file mode 100644 index 00000000000..648c25398d2 --- /dev/null +++ b/build/pkgs/imagemagick/distros/homebrew.txt @@ -0,0 +1 @@ +imagemagick diff --git a/build/pkgs/imagemagick/distros/macports.txt b/build/pkgs/imagemagick/distros/macports.txt new file mode 100644 index 00000000000..7a33d03ff73 --- /dev/null +++ b/build/pkgs/imagemagick/distros/macports.txt @@ -0,0 +1 @@ +ImageMagick diff --git a/build/pkgs/imagemagick/distros/nix.txt b/build/pkgs/imagemagick/distros/nix.txt new file mode 100644 index 00000000000..648c25398d2 --- /dev/null +++ b/build/pkgs/imagemagick/distros/nix.txt @@ -0,0 +1 @@ +imagemagick diff --git a/build/pkgs/imagemagick/distros/opensuse.txt b/build/pkgs/imagemagick/distros/opensuse.txt new file mode 100644 index 00000000000..7a33d03ff73 --- /dev/null +++ b/build/pkgs/imagemagick/distros/opensuse.txt @@ -0,0 +1 @@ +ImageMagick diff --git a/build/pkgs/imagemagick/distros/repology.txt b/build/pkgs/imagemagick/distros/repology.txt new file mode 100644 index 00000000000..648c25398d2 --- /dev/null +++ b/build/pkgs/imagemagick/distros/repology.txt @@ -0,0 +1 @@ +imagemagick diff --git a/build/pkgs/imagemagick/distros/void.txt b/build/pkgs/imagemagick/distros/void.txt new file mode 100644 index 00000000000..7a33d03ff73 --- /dev/null +++ b/build/pkgs/imagemagick/distros/void.txt @@ -0,0 +1 @@ +ImageMagick diff --git a/build/pkgs/imagemagick/spkg-configure.m4 b/build/pkgs/imagemagick/spkg-configure.m4 new file mode 100644 index 00000000000..c36cdc0e955 --- /dev/null +++ b/build/pkgs/imagemagick/spkg-configure.m4 @@ -0,0 +1,4 @@ +SAGE_SPKG_CONFIGURE([imagemagick], [ + AC_PATH_PROG([CONVERT], [convert]) + AS_IF([test -z "$ac_cv_path_CONVERT"], [sage_spkg_install_imagemagick=yes]) +]) diff --git a/build/pkgs/imagemagick/type b/build/pkgs/imagemagick/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/imagemagick/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/importlib_metadata/checksums.ini b/build/pkgs/importlib_metadata/checksums.ini index 4006b5133d2..d5a9b1a2668 100644 --- a/build/pkgs/importlib_metadata/checksums.ini +++ b/build/pkgs/importlib_metadata/checksums.ini @@ -1,5 +1,5 @@ tarball=importlib_metadata-VERSION.tar.gz -sha1=1572500dd099439cbd86bb70bfe1d206fbc844f6 -md5=5b944bce3fccaf848f0cba7a07f850a1 -cksum=416853070 +sha1=7d15d8e06299a8f24e076600899aceee75ce8b0b +md5=a605ba6ec315bc1324fd6b7210fe7c12 +cksum=448954927 upstream_url=https://pypi.io/packages/source/i/importlib_metadata/importlib_metadata-VERSION.tar.gz diff --git a/build/pkgs/importlib_metadata/package-version.txt b/build/pkgs/importlib_metadata/package-version.txt index 697e993915d..326ec6355f3 100644 --- a/build/pkgs/importlib_metadata/package-version.txt +++ b/build/pkgs/importlib_metadata/package-version.txt @@ -1 +1 @@ -4.8.1 +4.8.2 diff --git a/build/pkgs/ipykernel/checksums.ini b/build/pkgs/ipykernel/checksums.ini index 51eb667e6cf..bf5d60330a7 100644 --- a/build/pkgs/ipykernel/checksums.ini +++ b/build/pkgs/ipykernel/checksums.ini @@ -1,5 +1,5 @@ tarball=ipykernel-VERSION.tar.gz -sha1=19b16c61057bb7f5ad97f7e91bb54cdfec260257 -md5=329c172babb38dff1be130ddf3a609af -cksum=2936157099 -upstream_url=https://pypi.io/packages/source/i/ipykernel/ipykernel-VERSION.tar.gz +sha1=5a5cf7f8c0c02d0c0cc5fe3e0fe7481a86de6552 +md5=f940975eb00de793695c386ad3a8800c +cksum=597841676 +upstream_url=https://files.pythonhosted.org/packages/60/30/cf3867ce0dee0a7230ec5eb85232136c3875688816ad355a7b65f4f4e8ef/ipykernel-6.6.0.tar.gz diff --git a/build/pkgs/ipykernel/dependencies b/build/pkgs/ipykernel/dependencies index eec7126f687..5b3708a31fc 100644 --- a/build/pkgs/ipykernel/dependencies +++ b/build/pkgs/ipykernel/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) ipython_genutils importlib_metadata argcomplete debugpy matplotlib_inline ipython jupyter_client tornado appnope traitlets | $(PYTHON_TOOLCHAIN) +$(PYTHON) ipython_genutils importlib_metadata argcomplete matplotlib_inline ipython jupyter_client tornado appnope traitlets | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/ipykernel/package-version.txt b/build/pkgs/ipykernel/package-version.txt index dc0208aba8e..826f5ce030e 100644 --- a/build/pkgs/ipykernel/package-version.txt +++ b/build/pkgs/ipykernel/package-version.txt @@ -1 +1 @@ -6.3.1 +6.6.0 diff --git a/build/pkgs/ipykernel/patches/debugpy-make-optional.patch b/build/pkgs/ipykernel/patches/debugpy-make-optional.patch new file mode 100644 index 00000000000..308e4dd26f9 --- /dev/null +++ b/build/pkgs/ipykernel/patches/debugpy-make-optional.patch @@ -0,0 +1,21 @@ +--- a/pyproject.toml 2021-12-01 10:23:30.000000000 -0300 ++++ b/pyproject.toml 2021-12-13 18:01:36.239921204 -0300 +@@ -3,7 +3,6 @@ + requires=[ + "setuptools", + "wheel", +- "debugpy", + "ipython>=5", + "jupyter_core>=4.2", + "jupyter_client", +diff -ruN a/setup.py b/setup.py +--- a/setup.py 2021-12-01 10:23:09.000000000 -0300 ++++ b/setup.py 2021-12-13 18:01:40.112873823 -0300 +@@ -63,7 +63,6 @@ + install_requires=[ + 'importlib-metadata<5;python_version<"3.8.0"', + 'argcomplete>=1.12.3;python_version<"3.8.0"', +- 'debugpy>=1.0.0,<2.0', + 'ipython>=7.23.1', + 'traitlets>=5.1.0,<6.0', + 'jupyter_client<8.0', diff --git a/build/pkgs/ipython/checksums.ini b/build/pkgs/ipython/checksums.ini index aa743ea87ee..03da0120c1f 100644 --- a/build/pkgs/ipython/checksums.ini +++ b/build/pkgs/ipython/checksums.ini @@ -1,5 +1,5 @@ tarball=ipython-VERSION.tar.gz -sha1=a64001602601a4a7917a32b496543153f82dd65a -md5=e28a91669dc8b86569a99abed2c67f08 -cksum=4064786540 +sha1=05165e7fdcebde908aa6b65892ef230795a01e6b +md5=159340b8ba158386f938a8b882749fed +cksum=1960065579 upstream_url=https://pypi.io/packages/source/i/ipython/ipython-VERSION.tar.gz diff --git a/build/pkgs/ipython/package-version.txt b/build/pkgs/ipython/package-version.txt index f6b1800f2b5..d88eca009e8 100644 --- a/build/pkgs/ipython/package-version.txt +++ b/build/pkgs/ipython/package-version.txt @@ -1 +1 @@ -7.27.0 +7.29.0 diff --git a/build/pkgs/ipywidgets/checksums.ini b/build/pkgs/ipywidgets/checksums.ini index efb9fe91e67..af4a04db8cf 100644 --- a/build/pkgs/ipywidgets/checksums.ini +++ b/build/pkgs/ipywidgets/checksums.ini @@ -1,5 +1,5 @@ tarball=ipywidgets-VERSION.tar.gz -sha1=8ea8c8e15a4779897dc9f12cc20e29cfe5fb689f -md5=771fd3999f93af336c6e40ae1f5c37a2 -cksum=980834502 +sha1=ad63e99f44fd759c34ab70161e9ac8f4276294f8 +md5=420aabfddee27fc215ad9a9c14c9c529 +cksum=259379988 upstream_url=https://pypi.io/packages/source/i/ipywidgets/ipywidgets-VERSION.tar.gz diff --git a/build/pkgs/ipywidgets/package-version.txt b/build/pkgs/ipywidgets/package-version.txt index b7ade0673a6..bf43f752a04 100644 --- a/build/pkgs/ipywidgets/package-version.txt +++ b/build/pkgs/ipywidgets/package-version.txt @@ -1 +1 @@ -7.6.4.p0 +7.6.5 diff --git a/build/pkgs/jedi/checksums.ini b/build/pkgs/jedi/checksums.ini index f5be931cc5b..2db629c8d82 100644 --- a/build/pkgs/jedi/checksums.ini +++ b/build/pkgs/jedi/checksums.ini @@ -1,5 +1,5 @@ tarball=jedi-VERSION.tar.gz -sha1=f9acd323b88563fafb5bb4dff592794a2713a15c -md5=72707c00e8d6d0b190a5e5664be1cac5 -cksum=620165561 +sha1=e94444bd83b55247fd1f3d27d47cc0b148560134 +md5=d8dba4a98a35530f7f5b461c20aff180 +cksum=4093067035 upstream_url=https://pypi.io/packages/source/j/jedi/jedi-VERSION.tar.gz diff --git a/build/pkgs/jedi/package-version.txt b/build/pkgs/jedi/package-version.txt index 66333910a4b..249afd517d9 100644 --- a/build/pkgs/jedi/package-version.txt +++ b/build/pkgs/jedi/package-version.txt @@ -1 +1 @@ -0.18.0 +0.18.1 diff --git a/build/pkgs/jupyter_client/checksums.ini b/build/pkgs/jupyter_client/checksums.ini index ab486e66883..cbe7b831ca8 100644 --- a/build/pkgs/jupyter_client/checksums.ini +++ b/build/pkgs/jupyter_client/checksums.ini @@ -1,5 +1,5 @@ tarball=jupyter_client-VERSION.tar.gz -sha1=d03249e98f821ac8f43408e71b5390cef336d859 -md5=7c6ef694cde43fd365046869842a1cde -cksum=236454943 +sha1=aad5dd5337e20e53de66e094092333e04d9c4e1d +md5=33fb5eab82f3e89c68f2082b52a8e966 +cksum=481041414 upstream_url=https://pypi.io/packages/source/j/jupyter_client/jupyter_client-VERSION.tar.gz diff --git a/build/pkgs/jupyter_client/package-version.txt b/build/pkgs/jupyter_client/package-version.txt index a8907c025d5..a3fcc7121bb 100644 --- a/build/pkgs/jupyter_client/package-version.txt +++ b/build/pkgs/jupyter_client/package-version.txt @@ -1 +1 @@ -7.0.2 +7.1.0 diff --git a/build/pkgs/jupyter_core/checksums.ini b/build/pkgs/jupyter_core/checksums.ini index 8310cf0abfa..f3a306800fb 100644 --- a/build/pkgs/jupyter_core/checksums.ini +++ b/build/pkgs/jupyter_core/checksums.ini @@ -1,5 +1,5 @@ tarball=jupyter_core-VERSION.tar.gz -sha1=01e57900f8e4475fb90ae533cf7ebd6f4c323867 -md5=adc204a306a7122a61aa742ccc4c252a -cksum=1333962761 +sha1=afa48d85b3611beb42236be3c130767a6524ccfd +md5=77a1e7642abfb834a6046e14b94f5c88 +cksum=1811078933 upstream_url=https://pypi.io/packages/source/j/jupyter_core/jupyter_core-VERSION.tar.gz diff --git a/build/pkgs/jupyter_core/package-version.txt b/build/pkgs/jupyter_core/package-version.txt index 7c66fca5791..5b341fd799e 100644 --- a/build/pkgs/jupyter_core/package-version.txt +++ b/build/pkgs/jupyter_core/package-version.txt @@ -1 +1 @@ -4.7.1 +4.9.1 diff --git a/build/pkgs/nbclient/checksums.ini b/build/pkgs/nbclient/checksums.ini index 914ee9356b2..b1f9790bc67 100644 --- a/build/pkgs/nbclient/checksums.ini +++ b/build/pkgs/nbclient/checksums.ini @@ -1,5 +1,5 @@ tarball=nbclient-VERSION.tar.gz -sha1=9c1059878ae04da987ae00025908ae802cbfc066 -md5=593579773bd8128f845ea01a46ccbedb -cksum=2389564342 +sha1=a5efba590429270c901f4f5a16c8b46a7e6b8e4a +md5=a3cf8c1c121b0718a6ccf384c5683424 +cksum=2650264415 upstream_url=https://pypi.io/packages/source/n/nbclient/nbclient-VERSION.tar.gz diff --git a/build/pkgs/nbclient/package-version.txt b/build/pkgs/nbclient/package-version.txt index 7d8568351b4..416bfb0a221 100644 --- a/build/pkgs/nbclient/package-version.txt +++ b/build/pkgs/nbclient/package-version.txt @@ -1 +1 @@ -0.5.4 +0.5.9 diff --git a/build/pkgs/notebook/checksums.ini b/build/pkgs/notebook/checksums.ini index 1355f59cc1d..29c39e9f07e 100644 --- a/build/pkgs/notebook/checksums.ini +++ b/build/pkgs/notebook/checksums.ini @@ -1,5 +1,5 @@ tarball=notebook-VERSION.tar.gz -sha1=0a6d27e401d04d516c3851862491824007272dc0 -md5=718f7f0267e0111b4ef9ff3238e9f7e8 -cksum=2383141171 +sha1=93736a3a9c5b025f71a25a301b676a40aca3124b +md5=ecb9319cf82a9a83b1c4c8e1cc4efbee +cksum=1697144622 upstream_url=https://pypi.io/packages/source/n/notebook/notebook-VERSION.tar.gz diff --git a/build/pkgs/notebook/package-version.txt b/build/pkgs/notebook/package-version.txt index 133cad286fd..3d05e8cfb4b 100644 --- a/build/pkgs/notebook/package-version.txt +++ b/build/pkgs/notebook/package-version.txt @@ -1 +1 @@ -6.4.3 +6.4.6 diff --git a/build/pkgs/pdf2svg/distros/cygwin.txt b/build/pkgs/pdf2svg/distros/cygwin.txt deleted file mode 100644 index 52c66a99cbf..00000000000 --- a/build/pkgs/pdf2svg/distros/cygwin.txt +++ /dev/null @@ -1 +0,0 @@ -pdf2svg diff --git a/build/pkgs/primecount/distros/conda.txt b/build/pkgs/primecount/distros/conda.txt new file mode 100644 index 00000000000..f67843baa2b --- /dev/null +++ b/build/pkgs/primecount/distros/conda.txt @@ -0,0 +1 @@ +primecount diff --git a/build/pkgs/primecountpy/SPKG.rst b/build/pkgs/primecountpy/SPKG.rst new file mode 100644 index 00000000000..b28e1e0af8b --- /dev/null +++ b/build/pkgs/primecountpy/SPKG.rst @@ -0,0 +1,18 @@ +primecountpy: Cython interface for C++ primecount library +========================================================= + +Description +----------- + +Cython interface for C++ primecount library + +License +------- + +GPLv3 + +Upstream Contact +---------------- + +https://pypi.org/project/primecountpy/ + diff --git a/build/pkgs/primecountpy/checksums.ini b/build/pkgs/primecountpy/checksums.ini new file mode 100644 index 00000000000..f8f503d3ee6 --- /dev/null +++ b/build/pkgs/primecountpy/checksums.ini @@ -0,0 +1,5 @@ +tarball=primecountpy-VERSION.tar.gz +sha1=3526784adad04d67a15f05fb1367d12ec50a59dc +md5=e25806cba603d04cc7bc1852df5d0b92 +cksum=4293153144 +upstream_url=https://pypi.io/packages/source/p/primecountpy/primecountpy-VERSION.tar.gz diff --git a/build/pkgs/primecountpy/dependencies b/build/pkgs/primecountpy/dependencies new file mode 100644 index 00000000000..c141a1d7c2f --- /dev/null +++ b/build/pkgs/primecountpy/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) primecount cysignals | $(PYTHON_TOOLCHAIN) cython + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/primecountpy/install-requires.txt b/build/pkgs/primecountpy/install-requires.txt new file mode 100644 index 00000000000..0f1bdd00a0e --- /dev/null +++ b/build/pkgs/primecountpy/install-requires.txt @@ -0,0 +1 @@ +primecountpy diff --git a/build/pkgs/primecountpy/package-version.txt b/build/pkgs/primecountpy/package-version.txt new file mode 100644 index 00000000000..6e8bf73aa55 --- /dev/null +++ b/build/pkgs/primecountpy/package-version.txt @@ -0,0 +1 @@ +0.1.0 diff --git a/build/pkgs/primecountpy/spkg-install.in b/build/pkgs/primecountpy/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/primecountpy/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/primecountpy/type b/build/pkgs/primecountpy/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/primecountpy/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/primesieve/distros/conda.txt b/build/pkgs/primesieve/distros/conda.txt new file mode 100644 index 00000000000..2425fd390b0 --- /dev/null +++ b/build/pkgs/primesieve/distros/conda.txt @@ -0,0 +1 @@ +primesieve diff --git a/build/pkgs/prompt_toolkit/checksums.ini b/build/pkgs/prompt_toolkit/checksums.ini index 7868d1a6604..0e3fe149968 100644 --- a/build/pkgs/prompt_toolkit/checksums.ini +++ b/build/pkgs/prompt_toolkit/checksums.ini @@ -1,5 +1,5 @@ tarball=prompt_toolkit-VERSION.tar.gz -sha1=20fe2a6126ee2b992d9f77cd20e4b80626b036d7 -md5=ba9e63c3e7e5a4f4ce4e4770a4daba96 -cksum=3647925903 +sha1=5efb27677b89b2c81241645c6658e60771af3b7b +md5=bbaeba1142a64ecf2264eaf4f85201b1 +cksum=1605705668 upstream_url=https://pypi.io/packages/source/p/prompt_toolkit/prompt_toolkit-VERSION.tar.gz diff --git a/build/pkgs/prompt_toolkit/package-version.txt b/build/pkgs/prompt_toolkit/package-version.txt index 3e4a61b7a77..6795d4dc3be 100644 --- a/build/pkgs/prompt_toolkit/package-version.txt +++ b/build/pkgs/prompt_toolkit/package-version.txt @@ -1 +1 @@ -3.0.20 +3.0.22 diff --git a/build/pkgs/python3/checksums.ini b/build/pkgs/python3/checksums.ini index 9bf4797720f..769c1cabe8b 100644 --- a/build/pkgs/python3/checksums.ini +++ b/build/pkgs/python3/checksums.ini @@ -1,5 +1,5 @@ tarball=Python-VERSION.tar.xz -sha1=5208c1d1e0e859f42a9bdbb110efd0855ec4ecf1 -md5=fddb060b483bc01850a3f412eea1d954 -cksum=1991454647 +sha1=6274e5631c520d75bf1f0a046640fd3996fe99f0 +md5=11d12076311563252a995201248d17e5 +cksum=2273440860 upstream_url=https://www.python.org/ftp/python/VERSION/Python-VERSION.tar.xz diff --git a/build/pkgs/python3/package-version.txt b/build/pkgs/python3/package-version.txt index f69abe410a3..b04bfd83847 100644 --- a/build/pkgs/python3/package-version.txt +++ b/build/pkgs/python3/package-version.txt @@ -1 +1 @@ -3.9.7 +3.9.9 diff --git a/build/pkgs/sagelib/dependencies b/build/pkgs/sagelib/dependencies index 53b78ff6f39..1ba845c8f2d 100644 --- a/build/pkgs/sagelib/dependencies +++ b/build/pkgs/sagelib/dependencies @@ -1,4 +1,4 @@ -FORCE $(SCRIPTS) arb boost_cropped $(BLAS) brial cliquer cypari cysignals cython ecl eclib ecm flint libgd gap giac givaro glpk gmpy2 gsl iml jinja2 jupyter_core lcalc lrcalc libbraiding libhomfly libpng linbox m4ri m4rie memory_allocator mpc mpfi mpfr $(MP_LIBRARY) ntl numpy pari pip pkgconfig planarity ppl pplpy primesieve primecount pycygwin $(PYTHON) ratpoints rw sage_conf singular symmetrica zn_poly $(PCFILES) | $(PYTHON_TOOLCHAIN) sage_setup +FORCE $(SCRIPTS) arb boost_cropped $(BLAS) brial cliquer cypari cysignals cython ecl eclib ecm flint libgd gap giac givaro glpk gmpy2 gsl iml jinja2 jupyter_core lcalc lrcalc libbraiding libhomfly libpng linbox m4ri m4rie memory_allocator mpc mpfi mpfr $(MP_LIBRARY) ntl numpy pari pip pkgconfig planarity ppl pplpy primesieve primecount primecountpy pycygwin $(PYTHON) ratpoints rw sage_conf singular symmetrica zn_poly $(PCFILES) | $(PYTHON_TOOLCHAIN) sage_setup ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/sagelib/package-version.txt b/build/pkgs/sagelib/package-version.txt index c373c991e0d..dd1a2438046 100644 --- a/build/pkgs/sagelib/package-version.txt +++ b/build/pkgs/sagelib/package-version.txt @@ -1 +1 @@ -9.5.beta8 +9.5.beta9 diff --git a/build/pkgs/sagelib/spkg-src b/build/pkgs/sagelib/spkg-src index a2aa1d881ca..db4e2682ba1 100755 --- a/build/pkgs/sagelib/spkg-src +++ b/build/pkgs/sagelib/spkg-src @@ -15,7 +15,7 @@ fi # Exit on failure set -e -cd build/pkgs/sagelib +cd "$SAGE_ROOT"/build/pkgs/sagelib cd src python3 -u setup.py --no-user-cfg sdist --dist-dir "$SAGE_DISTFILES" diff --git a/build/pkgs/terminado/checksums.ini b/build/pkgs/terminado/checksums.ini index ba1b3927257..f33efd4b5da 100644 --- a/build/pkgs/terminado/checksums.ini +++ b/build/pkgs/terminado/checksums.ini @@ -1,5 +1,5 @@ tarball=terminado-VERSION.tar.gz -sha1=9a4f75d9f1bad0e7eadf33da774ab4e09d75a47b -md5=8150154423841a90b1d8ca60943ad84c -cksum=2702442904 +sha1=65f40480c1d8077b78dcffb7e0c909eae86998bf +md5=4871263f6aaed18e09603fa6785b4340 +cksum=2070178009 upstream_url=https://pypi.io/packages/source/t/terminado/terminado-VERSION.tar.gz diff --git a/build/pkgs/terminado/package-version.txt b/build/pkgs/terminado/package-version.txt index af88ba82486..34a83616bb5 100644 --- a/build/pkgs/terminado/package-version.txt +++ b/build/pkgs/terminado/package-version.txt @@ -1 +1 @@ -0.11.1 +0.12.1 diff --git a/build/pkgs/widgetsnbextension/checksums.ini b/build/pkgs/widgetsnbextension/checksums.ini index 2f7e9461a0c..81205c5a07e 100644 --- a/build/pkgs/widgetsnbextension/checksums.ini +++ b/build/pkgs/widgetsnbextension/checksums.ini @@ -1,5 +1,5 @@ tarball=widgetsnbextension-VERSION.tar.gz -sha1=f92ee894f3b08cfb6cfac0856d77521bbbf45e92 -md5=3dc5f96919c032a885950e3819bdee7b -cksum=4195536255 +sha1=010f70ab9a6d6e0a4f9607cce0da182ee6c1280a +md5=c126a06559afe658e285bcd483790710 +cksum=160710350 upstream_url=https://pypi.io/packages/source/w/widgetsnbextension/widgetsnbextension-VERSION.tar.gz diff --git a/build/pkgs/widgetsnbextension/package-version.txt b/build/pkgs/widgetsnbextension/package-version.txt index ffe309035c5..87ce492908a 100644 --- a/build/pkgs/widgetsnbextension/package-version.txt +++ b/build/pkgs/widgetsnbextension/package-version.txt @@ -1 +1 @@ -3.5.1.p0 +3.5.2 diff --git a/pkgs/sagemath-standard/setup.py b/pkgs/sagemath-standard/setup.py index 09f2063352b..67442365931 100755 --- a/pkgs/sagemath-standard/setup.py +++ b/pkgs/sagemath-standard/setup.py @@ -80,7 +80,7 @@ t = time.time() from sage_setup.optional_extension import is_package_installed_and_updated distributions = [''] - optional_packages_with_extensions = ['mcqd', 'bliss', 'tdlib', 'primecount', + optional_packages_with_extensions = ['mcqd', 'bliss', 'tdlib', 'coxeter3', 'fes', 'sirocco', 'meataxe'] distributions += ['sagemath-{}'.format(pkg) for pkg in optional_packages_with_extensions diff --git a/src/.relint.yml b/src/.relint.yml index 19e935d93c0..60d77593d80 100644 --- a/src/.relint.yml +++ b/src/.relint.yml @@ -40,3 +40,12 @@ hint: | in mathematics it should be "homogeneous" pattern: 'homogenous' + +# Modularization anti-patterns + +- name: 'namespace_pkg_all_import: import from .all of a namespace package' + hint: | + Sage library code should not import from sage.PAC.KAGE.all when sage.PAC.KAGE is an implicit + Hint: namespace package. Type import_statements("SOME_IDENTIFIER") to find a more specific import. + pattern: 'from\s+sage[.](categories|misc|rings|combinat|graphs|interfaces|libs)[.]all\s+import' + filePattern: '.*[.](py|pyx)$' diff --git a/src/VERSION.txt b/src/VERSION.txt index c373c991e0d..dd1a2438046 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -9.5.beta8 +9.5.beta9 diff --git a/src/bin/sage b/src/bin/sage index fcd263029d0..4c0fa174260 100755 --- a/src/bin/sage +++ b/src/bin/sage @@ -298,6 +298,12 @@ if [ -f "${SELF}-env" ]; then fi fi +if [ -f "${SELF}-src-env-config.in" ]; then + # The sage script is being run out of SAGE_ROOT/src/bin. + # In this case, put this directory in the front of the PATH. + export PATH="$SAGE_SRC/bin:$PATH" +fi + if [ -z "$DOT_SAGE" ]; then export DOT_SAGE="$HOME/.sage" fi diff --git a/src/bin/sage-env b/src/bin/sage-env index 6ec3ba4b6ce..4d3c81152a8 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -295,9 +295,6 @@ fi if [ -n "$SAGE_VENV" ]; then export PATH="$SAGE_VENV/bin:$PATH" fi -if [ -n "$SAGE_SRC" ]; then - export PATH="$SAGE_SRC/bin:$PATH" -fi if [ -n "$SAGE_ROOT" ]; then export PATH="$SAGE_ROOT/build/bin:$PATH" fi diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index 028873c9526..c64bf3609b5 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,5 +1,5 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='9.5.beta8' -SAGE_RELEASE_DATE='2021-12-12' -SAGE_VERSION_BANNER='SageMath version 9.5.beta8, Release Date: 2021-12-12' +SAGE_VERSION='9.5.beta9' +SAGE_RELEASE_DATE='2021-12-23' +SAGE_VERSION_BANNER='SageMath version 9.5.beta9, Release Date: 2021-12-23' diff --git a/src/doc/bootstrap b/src/doc/bootstrap index 3d401e7ce50..8b6d072f360 100755 --- a/src/doc/bootstrap +++ b/src/doc/bootstrap @@ -42,7 +42,7 @@ for SYSTEM in arch debian fedora cygwin homebrew conda; do *:standard) SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" ;; - _recommended:*|pandoc:*) + _recommended:*|pandoc:*|ffmpeg:*|imagemagick:*) RECOMMENDED_SYSTEM_PACKAGES+=" $PKG_SYSTEM_PACKAGES" ;; *) diff --git a/src/doc/en/developer/coding_basics.rst b/src/doc/en/developer/coding_basics.rst index 6d810b85d2f..c24ef0a2f90 100644 --- a/src/doc/en/developer/coding_basics.rst +++ b/src/doc/en/developer/coding_basics.rst @@ -1127,7 +1127,7 @@ framework. Here is a comprehensive list: The following should yield 4. See :trac:`2`. :: - sage: 2+2 # optional: bug + sage: 2+2 # optional - bug 5 sage: 2+2 # known bug 5 diff --git a/src/doc/en/developer/doctesting.rst b/src/doc/en/developer/doctesting.rst index 7b8aa79554a..aad694489d3 100644 --- a/src/doc/en/developer/doctesting.rst +++ b/src/doc/en/developer/doctesting.rst @@ -106,11 +106,11 @@ Sage to test its own modules. Even if we have a system-wide Sage installation, using that version to doctest the modules of a local installation is a recipe for confusion. -If your system Python has the ``tox`` package, you can also run the Sage -doctester as follows:: +You can also run the Sage doctester as follows:: - [jdemeyer@sage sage-6.0]$ cd src - [jdemeyer@sage src]$ tox -- sage/games/sudoku.py + [jdemeyer@sage sage-6.0]$ ./sage -tox -e doctest -- src/sage/games/sudoku.py + +See :ref:`chapter-tools` for more information about tox. Troubleshooting diff --git a/src/doc/en/developer/tools.rst b/src/doc/en/developer/tools.rst index 0a7124177d3..77966e4f203 100644 --- a/src/doc/en/developer/tools.rst +++ b/src/doc/en/developer/tools.rst @@ -8,16 +8,263 @@ Additional development and testing tools ======================================== +Tox +=== + +`Tox `_ is a popular package that is +used by a large number of Python projects as the standard entry point +for testing and linting. + +Sage includes tox as a standard package and uses it for three purposes: + +- For portability testing of the Sage distribution, as we explain in + :ref:`chapter-portability_testing`. This is configured in the file + ``SAGE_ROOT/tox.ini``. + +- For testing modularized distributions of the Sage library. This is configured + in ``tox.ini`` files in subdirectories of ``SAGE_ROOT/pkgs/``, such as + ``SAGE_ROOT/pkgs/sagemath-standard/tox.ini``. Each distribution's configuration + defines tox environments for testing the distribution with different Python + versions and different ways how the dependencies are provided. + We explain this in :ref:`chapter-modularization`. + +- As an entry point for testing and linting of the Sage library, as we describe below. + This is configured in the file ``SAGE_ROOT/src/tox.ini``. + +The tox configuration ``SAGE_ROOT/src/tox.ini`` can be invoked by using the command +``./sage --tox``. (If ``tox`` is available in your system installation, +you can just type ``tox`` instead.) + +This configuration provides an entry point for various testing/linting methods, +known as "tox environments". We can type ``./sage --advanced`` so see what is +available:: + + $ ./sage --advanced + SageMath version 9.2 + ... + Testing files: + ... + --tox [options] -- general entry point for testing + and linting of the Sage library + -e -- run specific test environments + (default: run all except full pycodestyle) + doctest -- run the Sage doctester + (same as "sage -t") + coverage -- give information about doctest coverage of files + (same as "sage --coverage[all]") + startuptime -- display how long each component of Sage takes to start up + (same as "sage --startuptime") + pycodestyle-minimal -- check against Sage's minimal style conventions + relint -- check whether some forbidden patterns appear + (includes all patchbot pattern-exclusion plugins) + codespell -- check for misspelled words in source code + pycodestyle -- check against the Python style conventions of PEP8 + -p auto -- run test environments in parallel + --help -- show tox help + + +Doctest +======= + +The command ``./sage -tox -e doctest`` requires that Sage has been +built already. ``doctest`` is a special tox environment that runs the +Sage doctester in the normal Sage environment. This is equivalent to +using the command ``./sage -t``; see :ref:`chapter-doctesting`. + + +Coverage +======== + +The command ``./sage -tox -e coverage`` requires that Sage has been +built already. ``coverage`` is a special tox environment that is +equivalent to using the command ``./sage --coverageall`` (if no +arguments are provided) or ``./sage --coverage`` (if arguments are +provided). + + +Startuptime +=========== + +The command ``./sage -tox -e startuptime`` requires that Sage has been +built already. ``startuptime`` is a special tox environment that is +equivalent to using the command ``./sage --startuptime``. + + +Pycodestyle +=========== +`Pycodestyle `_ (formerly known as pep8) +checks Python code against the style conventions of `PEP 8 `_. +Tox automatically installs pycodestyle in a separate virtual environment +on the first use. + +Sage defines two configurations for pycodestyle. The command ``./sage -tox -e pycodestyle-minimal`` uses +pycodestyle in a minimal configuration. +As of Sage 9.5, the entire Sage library conforms to this configuration:: + + $ ./sage -tox -e pycodestyle-minimal -- src/sage/ + pycodestyle-minimal installed: pycodestyle==2.8.0 + pycodestyle-minimal run-test-pre: PYTHONHASHSEED='28778046' + pycodestyle-minimal run-test: commands[0] | pycodestyle --select E401,E70,W605,E711,E712,E721 sage + ___________ summary ____________ + pycodestyle-minimal: commands succeeded + congratulations :) + +When preparing a branch for a Sage ticket, developers should verify that ``./sage -tox -e +pycodestyle-minimal`` passes. When the Sage patchbot runs on the ticket, it will perform similar +coding style checks; but running the check locally reduces the turnaround time from hours +to seconds. + +The second configuration is used with the command ``./sage -tox -e pycodestyle`` and runs a +more thorough check:: + + $ ./sage -tox -e pycodestyle -- src/sage/quadratic_forms/quadratic_form.py + pycodestyle installed: pycodestyle==2.8.0 + pycodestyle run-test-pre: PYTHONHASHSEED='2520226550' + pycodestyle run-test: commands[0] | pycodestyle sage/quadratic_forms/quadratic_form.py + sage/quadratic_forms/quadratic_form.py:135:9: E225 missing whitespace around operator + sage/quadratic_forms/quadratic_form.py:163:64: E225 missing whitespace around operator + sage/quadratic_forms/quadratic_form.py:165:52: E225 missing whitespace around operator + sage/quadratic_forms/quadratic_form.py:173:42: E228 missing whitespace around modulo operator + ... + sage/quadratic_forms/quadratic_form.py:1620:9: E266 too many leading '#' for block comment + sage/quadratic_forms/quadratic_form.py:1621:9: E266 too many leading '#' for block comment + 25 E111 indentation is not a multiple of 4 + 2 E117 over-indented + 129 E127 continuation line over-indented for visual indent + 1 E128 continuation line under-indented for visual indent + 4 E201 whitespace after '[' + 4 E202 whitespace before ']' + 2 E222 multiple spaces after operator + 7 E225 missing whitespace around operator + 1 E228 missing whitespace around modulo operator + 25 E231 missing whitespace after ',' + 1 E262 inline comment should start with '# ' + 3 E265 block comment should start with '# ' + 62 E266 too many leading '#' for block comment + 2 E272 multiple spaces before keyword + 2 E301 expected 1 blank line, found 0 + 17 E303 too many blank lines (2) + ERROR: InvocationError for command .../pycodestyle sage/quadratic_forms/quadratic_form.py (exited with code 1) + ___________ summary ____________ + ERROR: pycodestyle: commands failed + +When preparing a branch for a Sage ticket that adds new code, +developers should verify that ``./sage -tox -e pycodestyle`` does not +issue warnings for the added code. This will avoid later cleanup +tickets as the Sage codebase is moving toward full PEP 8 compliance. + +On the other hand, it is usually not advisable to mix coding-style +fixes with productive changes on the same ticket because this would +makes it harder for reviewers to evaluate the changes. + +By passing the options ``--count -qq`` we can reduce the output to +only show the number of style violation warnings. This can be helpful +for planning work on coding-style clean-up tickets that focus on one +or a few related issues:: + + $ ./sage -tox -e pycodestyle -- --count -qq src/sage + pycodestyle installed: pycodestyle==2.8.0 + pycodestyle run-test-pre: PYTHONHASHSEED='3166223974' + pycodestyle run-test: commands[0] | pycodestyle --count -qq sage + 557 E111 indentation is not a multiple of 4 + 1 E112 expected an indented block + 194 E114 indentation is not a multiple of 4 (comment) + ... + 7 E743 ambiguous function definition 'l' + 335 W291 trailing whitespace + 4 W292 no newline at end of file + 229 W293 blank line contains whitespace + 459 W391 blank line at end of file + 97797 + ERROR: InvocationError for command .../pycodestyle --count -qq sage (exited with code 1) + ___________ summary ____________ + ERROR: pycodestyle: commands failed + +*Installation:* (for manual use:) ``pip install -U pycodestyle --user`` + +*Usage:* + +- With tox: See above. + +- Manual: Run ``pycodestyle path/to/the/file.py``. + +- VS Code: Activate by adding the setting ``"python.linting.pycodestyleEnabled": true``, see `official VS Code documentation `__ for details. + +*Configuration:* ``[pycodestyle]`` block in ``SAGE_ROOT/src/tox.ini`` + +*Documentation:* https://pycodestyle.pycqa.org/en/latest/index.html + + +Relint +====== + +`Relint `_ checks all source files for forbidden +text patterns specified by regular expressions. + +Our configuration of relint flags some outdated Python constructions, plain TeX +commands when equivalent LaTeX commands are available, common mistakes in +documentation markup, and modularization anti-patterns. + +*Configuration:* ``SAGE_ROOT/src/.relint.yml`` + +*Documentation:* https://pypi.org/project/relint/ + + +Codespell +========= +`Codespell `_ uses a dictionary to check for +misspelled words in source code. + +Sage defines a configuration for codespell:: + + $ ./sage -tox -e codespell -- src/sage/homology/ + codespell installed: codespell==2.1.0 + codespell run-test-pre: PYTHONHASHSEED='1285169064' + codespell run-test: commands[0] | codespell '--skip=*.png,*.jpg,*.JPG,*.inv,*.dia,*.pdf,*.ico,*#*,*~*,*.bak,*.orig,*.log,*.sobj,*.tar,*.gz,*.pyc,*.o,*.sws,*.so,*.a,.DS_Store' --skip=doc/ca,doc/de,doc/es,doc/hu,doc/ja,doc/ru,doc/fr,doc/it,doc/pt,doc/tr --skip=src/doc/ca,src/doc/de,src/doc/es,src/doc/hu,src/doc/ja,src/doc/ru,src/doc/fr,src/doc/it,src/doc/pt,src/doc/tr '--skip=.git,.tox,worktree*,dist,upstream,logs,local,cythonized,scripts-3,autom4te.cache,tmp,lib.*,*.egg-info' --dictionary=- --dictionary=/Users/mkoeppe/s/sage/sage-rebasing/src/.codespell-dictionary.txt --ignore-words=/Users/mkoeppe/s/sage/sage-rebasing/src/.codespell-ignore.txt sage/homology + sage/homology/hochschild_complex.py:271: mone ==> mono, money, none + sage/homology/hochschild_complex.py:277: mone ==> mono, money, none + sage/homology/hochschild_complex.py:280: mone ==> mono, money, none + sage/homology/chain_complex.py:2185: mor ==> more + sage/homology/chain_complex.py:2204: mor ==> more + sage/homology/chain_complex.py:2210: mor ==> more + sage/homology/chain_complex.py:2211: mor ==> more + sage/homology/chain_complex.py:2214: mor ==> more + sage/homology/chain_complex.py:2215: mor ==> more + ERROR: InvocationError for command .../codespell '--skip=*.png,...' --dictionary=- --dictionary=/Users/mkoeppe/s/sage/sage-rebasing/src/.codespell-dictionary.txt --ignore-words=/Users/mkoeppe/s/sage/sage-rebasing/src/.codespell-ignore.txt sage/homology (exited with code 65) + ___________ summary ____________ + ERROR: codespell: commands failed + +*Configuration:* + +- ``[testenv:codespell]`` block in ``SAGE_ROOT/src/tox.ini`` + +- ``SAGE_ROOT/src/.codespell-dictionary.txt`` and ``SAGE_ROOT/src/.codespell-ignore.txt`` + + Pytest -=============================== +====== `Pytest `_ is a testing framework. -At the moment, Sage is not yet using any tests based on pytest. +It is included in the Sage distribution as an optional package. + +Currently, Sage only makes very limited use of pytest, for testing the +package :mod:`sage.numerical.backends`. + +*Installation:* + +- (for use with the Sage doctester:) ``./sage -i pytest``. + +- (for manual use:) ``pip install -U pytest``, see `documentation `__ for details. -*Installation:* ``pip install -U pytest``, see `documentation `__ for details. *Usage:* -- Manual: Run ``pytest path/to/the/test_file.py`` or ``pytest`` to run all tests (from a virtual environment with Sage installed) -- VS Code: Install the `Python `_ extension and follow the `offical VS Code documentation `__. -*Configuration:* ``conftest.py`` in the source folder + +- Tox, Sage doctester: At the end of ``./sage -t`` (or ``./sage --tox -e doctest``), Pytest is automatically invoked. + +- Manual: Run ``pytest path/to/the/test_file.py`` or ``pytest`` to run all tests (from a virtual environment with the Sage library installed) + +- VS Code: Install the `Python extension `_ and follow the `offical VS Code documentation `__. + +*Configuration:* ``SAGE_ROOT/src/conftest.py`` + *Documentation:* https://docs.pytest.org/en/stable/index.html Pyright @@ -25,23 +272,18 @@ Pyright `Pyright `_ is static type checker. *Installation:* ``npm install -g pyright``, see `documentation `__ for details. + *Usage:* + - Manual: Run ``pyright path/to/the/file.py`` + - VS Code: Install the `Pylance `__ extension. -*Configuration:* ``pyrightconfig.json`` in the root -*Note*: Currently, only the manifolds package is checked. Further packages can be added in the ``pyrightconfig.json`` file. -*Documentation:* https://github.com/microsoft/pyright#documentation -Pycodestyle -=============================== -`Pycodestyle `_ checks against the style conventions of PEP8 Python. +*Configuration:* ``SAGE_ROOT/pyrightconfig.json`` -*Installation:* ``pip install -U pycodestyle --user`` -*Usage:* -- Manual: Run ``pycodestyle path/to/the/file.py`` -- VS Code: Activate by adding the setting ``"python.linting.pycodestyleEnabled": true``, see `official VS Code documentation `__ for details. -*Configuration:* ``[pycodestyle]`` block in ``src/tox.ini`` -*Documentation:* https://pycodestyle.pycqa.org/en/latest/index.html +*Note*: Currently, only the package :mod:`sage.manifolds` is checked. Further packages can be added in the ``pyrightconfig.json`` file. + +*Documentation:* https://github.com/microsoft/pyright#documentation Pyflakes =============================== diff --git a/src/doc/en/installation/source.rst b/src/doc/en/installation/source.rst index 5b7bb0f8937..645e2cd368c 100644 --- a/src/doc/en/installation/source.rst +++ b/src/doc/en/installation/source.rst @@ -380,7 +380,7 @@ include: Ubuntu on Windows Subsystem for Linux (WSL) prerequisite installation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Sage can be installed onto linux running on Windows Subsystem for Linux (WSL). These instructions describe a fresh install of Ubuntu 20.10, but other distibutions or installation methods should work too, though have not been tested. +Sage can be installed onto Linux running on Windows Subsystem for Linux (WSL). These instructions describe a fresh install of Ubuntu 20.10, but other distibutions or installation methods should work too, though have not been tested. - Enable hardware-assisted virtualization in the EFI or BIOS of your system. Refer to your system (or motherboard) maker's documentation for instructions on how to do this. @@ -400,6 +400,12 @@ Sage can be installed onto linux running on Windows Subsystem for Linux (WSL). T - `Upgrade to the Ubuntu 20.10 `_. This step will not be necessary once Ubuntu 20.10 is available in the Microsoft Store. From this point on, follow the instructions in the :ref:`sec-installation-from-sources-linux-recommended-installation` section. +It is strongly recommended to put the Sage source files in the Linux file system, for example, in the ``/home/username/sage`` directory, and not in the Windows file system (e.g. ``/mnt/c/...``). + +You may encounter permission errors of the kind ``"[Errno 13] Permission denied: 'build/bdist.linux-x86_64/wheel/.dist-info'"`` during ``make``. +This usually comes from a permission conflict between the Windows and Linux file system. +To fix it create a temporary build folder in the Linux file system using ``mkdir -p ~/tmp/sage`` and use it for building by ``eval SAGE_BUILD_DIR="~/tmp/sage" make``. +Also see the `related Github issue `_ for other workarounds. When the installation is complete, you may be interested in :ref:`sec-launching-wsl-post-installation`. diff --git a/src/doc/en/reference/misc/index.rst b/src/doc/en/reference/misc/index.rst index 489fc699447..1d15621f8b4 100644 --- a/src/doc/en/reference/misc/index.rst +++ b/src/doc/en/reference/misc/index.rst @@ -141,6 +141,45 @@ Fast Expression Evaluation .. sage/ext/interpreters/wrapper_rdf .. sage/ext/interpreters/wrapper_rr +Features +~~~~~~~~ + +.. toctree:: + :maxdepth: 1 + + sage/features + sage/features/join_feature + sage/features/all + sage/features/sagemath + sage/features/pkg_systems + sage/features/bliss + sage/features/csdp + sage/features/databases + sage/features/dvipng + sage/features/fes + sage/features/ffmpeg + sage/features/four_ti_2 + sage/features/gap + sage/features/graph_generators + sage/features/graphviz + sage/features/imagemagick + sage/features/interfaces + sage/features/internet + sage/features/kenzo + sage/features/latex + sage/features/latte + sage/features/lrs + sage/features/mcqd + sage/features/meataxe + sage/features/mip_backends + sage/features/normaliz + sage/features/pandoc + sage/features/pdf2svg + sage/features/polymake + sage/features/rubiks + sage/features/tdlib + + Code Evaluation --------------- @@ -229,14 +268,7 @@ Distribution sage/misc/package sage/misc/dist - sage/features - sage/features/bliss - sage/features/csdp - sage/features/databases - sage/features/fes - sage/features/gap - sage/features/graph_generators - sage/features/lrs + Credits ~~~~~~~ diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 981d1454c9f..ca804fa5aa9 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -4963,6 +4963,11 @@ REFERENCES: Proceedings of seventh annual ACM symposium on Theory of computing, Pages 245-254, ACM 1975. :doi:`10.1145/800116.803775`. +.. [RT1985] James Roskind and Robert Endre Tarjan. + *A note on finding minimum-cost edge-disjoint spanning trees*. + Mathematics of Operations Research, 10(4):701-708, 1985. + :doi:`10.1287/moor.10.4.701` + .. [RTL76] Donald J. Rose, Robert Endre Tarjan and George S. Lueker. *Algorithmic aspects of vertex elimination on graphs*. SIAM J. Comput., 5(2), 266–283 (1976). diff --git a/src/doc/pt/tutorial/programming.rst b/src/doc/pt/tutorial/programming.rst index 4f99d595a5f..08c0a4713e3 100644 --- a/src/doc/pt/tutorial/programming.rst +++ b/src/doc/pt/tutorial/programming.rst @@ -117,14 +117,11 @@ então faça o seguinte: Recompiling factorial.spyx *************************************************** sage: factorial(50) - 30414093201713378043612608166064768844377641568960512000000000000L + 30414093201713378043612608166064768844377641568960512000000000000 sage: time n = factorial(10000) CPU times: user 0.03 s, sys: 0.00 s, total: 0.03 s Wall time: 0.03 -Aqui o sufixo L indica um "long integer" do Python (veja -:ref:`section-mathannoy`). - Note que o Sage vai recompilar ``factorial.spyx`` se você encerrar e reiniciar o Sage. A biblioteca compilada e compartilhada é armazenada em ``$HOME/.sage/temp/hostname/pid/spyx``. Esses arquivos são diff --git a/src/sage/algebras/free_algebra_quotient.py b/src/sage/algebras/free_algebra_quotient.py index 232a5b30f47..c4e44e80faf 100644 --- a/src/sage/algebras/free_algebra_quotient.py +++ b/src/sage/algebras/free_algebra_quotient.py @@ -59,7 +59,7 @@ # **************************************************************************** from sage.modules.free_module import FreeModule -from sage.algebras.algebra import Algebra +from sage.rings.ring import Algebra from sage.algebras.free_algebra import is_FreeAlgebra from sage.algebras.free_algebra_quotient_element import FreeAlgebraQuotientElement from sage.structure.unique_representation import UniqueRepresentation diff --git a/src/sage/algebras/quatalg/quaternion_algebra.py b/src/sage/algebras/quatalg/quaternion_algebra.py index 10d4a3166e5..4f39581712e 100644 --- a/src/sage/algebras/quatalg/quaternion_algebra.py +++ b/src/sage/algebras/quatalg/quaternion_algebra.py @@ -36,7 +36,7 @@ # **************************************************************************** from sage.arith.all import (hilbert_conductor_inverse, hilbert_conductor, - factor, gcd, kronecker_symbol, valuation) + factor, gcd, kronecker_symbol, valuation) from sage.rings.all import RR, Integer from sage.rings.integer_ring import ZZ from sage.rings.rational import Rational @@ -49,7 +49,6 @@ from sage.rings.number_field.number_field import is_NumberField from sage.rings.power_series_ring import PowerSeriesRing from sage.structure.category_object import normalize_names -from sage.structure.parent_gens import ParentWithGens from sage.structure.parent import Parent from sage.matrix.matrix_space import MatrixSpace from sage.matrix.constructor import diagonal_matrix, matrix @@ -61,11 +60,11 @@ from operator import itemgetter -from .quaternion_algebra_element import \ - QuaternionAlgebraElement_abstract, \ - QuaternionAlgebraElement_generic, \ - QuaternionAlgebraElement_rational_field, \ - QuaternionAlgebraElement_number_field +from .quaternion_algebra_element import ( + QuaternionAlgebraElement_abstract, + QuaternionAlgebraElement_generic, + QuaternionAlgebraElement_rational_field, + QuaternionAlgebraElement_number_field) from . import quaternion_algebra_cython from sage.modular.modsym.p1list import P1List @@ -232,7 +231,7 @@ def create_key(self, arg0, arg1=None, arg2=None, names='i,j,k'): # If arg0 or arg1 are Python data types, coerce them # to the relevant Sage types. This is a bit inelegant. L = [] - for a in [arg0,arg1]: + for a in [arg0, arg1]: if is_RingElement(a): L.append(a) elif isinstance(a, int): @@ -277,12 +276,14 @@ def create_object(self, version, key, **extra_args): K, a, b, names = key return QuaternionAlgebra_ab(K, a, b, names=names) + QuaternionAlgebra = QuaternionAlgebraFactory("QuaternionAlgebra") ######################################################## # Classes ######################################################## + def is_QuaternionAlgebra(A): """ Return ``True`` if ``A`` is of the QuaternionAlgebra data type. @@ -374,7 +375,7 @@ def inner_product_matrix(self): M.set_immutable() return M - def is_commutative(self): + def is_commutative(self) -> bool: """ Return ``False`` always, since all quaternion algebras are noncommutative. @@ -387,7 +388,7 @@ def is_commutative(self): """ return False - def is_division_algebra(self): + def is_division_algebra(self) -> bool: """ Return ``True`` if the quaternion algebra is a division algebra (i.e. every nonzero element in ``self`` is invertible), and ``False`` if the @@ -410,7 +411,7 @@ def is_division_algebra(self): raise NotImplementedError("base field must be rational numbers") return self.discriminant() != 1 - def is_matrix_ring(self): + def is_matrix_ring(self) -> bool: """ Return ``True`` if the quaternion algebra is isomorphic to the 2x2 matrix ring, and ``False`` if ``self`` is a division algebra (i.e. @@ -434,7 +435,7 @@ def is_matrix_ring(self): raise NotImplementedError("base field must be rational numbers") return self.discriminant() == 1 - def is_exact(self): + def is_exact(self) -> bool: """ Return ``True`` if elements of this quaternion algebra are represented exactly, i.e. there is no precision loss when doing arithmetic. A @@ -452,7 +453,7 @@ def is_exact(self): """ return self.base_ring().is_exact() - def is_field(self, proof = True): + def is_field(self, proof=True) -> bool: """ Return ``False`` always, since all quaternion algebras are noncommutative and all fields are commutative. @@ -465,7 +466,7 @@ def is_field(self, proof = True): """ return False - def is_finite(self): + def is_finite(self) -> bool: """ Return ``True`` if the quaternion algebra is finite as a set. @@ -483,7 +484,7 @@ def is_finite(self): """ return self.base_ring().is_finite() - def is_integral_domain(self, proof=True): + def is_integral_domain(self, proof=True) -> bool: """ Return ``False`` always, since all quaternion algebras are noncommutative and integral domains are commutative (in Sage). @@ -496,7 +497,7 @@ def is_integral_domain(self, proof=True): """ return False - def is_noetherian(self): + def is_noetherian(self) -> bool: """ Return ``True`` always, since any quaternion algebra is a noetherian ring (because it is a finitely generated module over a field). @@ -650,13 +651,14 @@ def __init__(self, base_ring, a, b, names='i,j,k'): ... ValueError: 2 is not invertible in Integer Ring """ - ParentWithGens.__init__(self, base_ring, names=names, category=Algebras(base_ring).Division()) + cat = Algebras(base_ring).Division().FiniteDimensional() + Algebra.__init__(self, base_ring, names, category=cat) self._a = a self._b = b - if is_RationalField(base_ring) and a.denominator() == 1 and b.denominator() == 1: + if is_RationalField(base_ring) and a.denominator() == 1 == b.denominator(): self.Element = QuaternionAlgebraElement_rational_field - elif is_NumberField(base_ring) and base_ring.degree() > 2 and base_ring.is_absolute() and \ - a.denominator() == 1 and b.denominator() == 1 and base_ring.defining_polynomial().is_monic(): + elif (is_NumberField(base_ring) and base_ring.degree() > 2 and base_ring.is_absolute() and + a.denominator() == 1 == b.denominator() and base_ring.defining_polynomial().is_monic()): # This QuaternionAlgebraElement_number_field class is not # designed to work with elements of a quadratic field. To # do that, the main thing would be to implement @@ -668,7 +670,7 @@ def __init__(self, base_ring, a, b, names='i,j,k'): else: self.Element = QuaternionAlgebraElement_generic self._populate_coercion_lists_(coerce_list=[base_ring]) - self._gens = [self([0,1,0,0]), self([0,0,1,0]), self([0,0,0,1])] + self._gens = [self([0, 1, 0, 0]), self([0, 0, 1, 0]), self([0, 0, 0, 1])] @cached_method def maximal_order(self, take_shortcuts=True): @@ -759,17 +761,17 @@ def maximal_order(self, take_shortcuts=True): if take_shortcuts and d_A.is_prime() and a in ZZ and b in ZZ: a = ZZ(a) b = ZZ(b) - i,j,k = self.gens() + i, j, k = self.gens() # if necessary, try to swap invariants to match Pizer's paper if (a != -1 and b == -1) or (b == -2) \ or (a != -1 and a != -2 and (-a) % 8 != 1): a, b = b, a i, j = j, i - k = i*j + k = i * j basis = [] - if (a,b) == (-1,-1): + if (a, b) == (-1, -1): basis = [(1+i+j+k)/2, i, j, k] elif a == -1 and (-b).is_prime() and ((-b) % 4 == 3): basis = [(1+j)/2, (i+k)/2, j, k] @@ -779,9 +781,9 @@ def maximal_order(self, take_shortcuts=True): q = -b p = -a - if q % 4 == 3 and kronecker_symbol(p,q) == -1: + if q % 4 == 3 and kronecker_symbol(p, q) == -1: a = 0 - while (a*a*p + 1)%q != 0: + while (a * a * p + 1) % q: a += 1 basis = [(1+j)/2, (i+k)/2, -(j+a*k)/q, k] @@ -796,7 +798,7 @@ def maximal_order(self, take_shortcuts=True): e_new_gens = [] # For each prime at which R is not yet maximal, make it bigger - for (p,p_val) in d_R.factor(): + for (p, p_val) in d_R.factor(): e = R.basis() while self.quaternion_order(e).discriminant().valuation(p) > d_A.valuation(p): # Compute a normalized basis at p @@ -809,8 +811,10 @@ def maximal_order(self, take_shortcuts=True): A = matrix(self.base_ring(), 4, 4, [list(g) for g in e]) e_n = [] - x_rows = A.solve_left(matrix([ V(vec.coefficient_tuple()) for (vec,val) in f ]), check=False).rows() - denoms = [ x.denominator() for x in x_rows ] + x_rows = A.solve_left(matrix([V(vec.coefficient_tuple()) + for (vec, val) in f]), + check=False).rows() + denoms = [x.denominator() for x in x_rows] for i in range(4): vec = f[i][0] val = f[i][1] @@ -854,17 +858,17 @@ def maximal_order(self, take_shortcuts=True): if t.valuation(p) == 0: if b.valuation(p) > 0: x = a - if (x**2 - t*x + a).mod(p**2) == 0: # make sure v_p(...) = 1 + if (x**2 - t*x + a).mod(p**2) == 0: # make sure v_p(...) = 1 x = x + p g = 1/p*(x - e_n[1])*e_n[2] e_n[2] = g e_n[3] = e_n[1]*g else: # t.valuation(p) > 0 - (y,z,w) = maxord_solve_aux_eq(a, b, p) + (y, z, w) = maxord_solve_aux_eq(a, b, p) g = 1/p*(1 + y*e_n[1] + z*e_n[2] + w*e_n[1]*e_n[2]) h = (z*b)*e_n[1] - (y*a)*e_n[2] - e_n[1:4] = [g,h,g*h] + e_n[1:4] = [g, h, g * h] if (1 - a*y**2 - b*z**2 + a*b*w**2).valuation(2) > 2: e_n = basis_for_quaternion_lattice(list(e) + e_n[1:], reverse=True) @@ -1050,8 +1054,7 @@ def ramified_primes(self): sage: QuaternionAlgebra(QQ, -1, -1).ramified_primes() [2] """ - #TODO: more examples - + # TODO: more examples return [f[0] for f in factor(self.discriminant())] def _magma_init_(self, magma): @@ -1220,11 +1223,11 @@ def modp_splitting_data(self, p): i, j, k = self.gens() F = GF(p) - i2 = F(i*i) - j2 = F(j*j) + i2 = F(i * i) + j2 = F(j * j) M = MatrixSpace(F, 2) - I = M([0,i2,1,0]) + I = M([0, i2, 1, 0]) if i2 == 0: raise NotImplementedError("algorithm for computing local splittings not implemented in general (currently require the first invariant to be coprime to p)") i2inv = 1/i2 @@ -1244,9 +1247,9 @@ def modp_splitting_data(self, p): if J*J == j2 and K == -J*I: return I, J, K - J = M([a,b,(j2-a*a)/b, -a]) - K = I*J - assert K == -J*I, "bug in that I,J don't skew commute" + J = M([a, b, (j2 - a * a) / b, -a]) + K = I * J + assert K == -J * I, "bug in that I,J don't skew commute" return I, J, K def modp_splitting_map(self, p): @@ -1272,6 +1275,7 @@ def modp_splitting_map(self, p): """ I, J, K = self.modp_splitting_data(p) F = I.base_ring() + def phi(q): v = [F(a) for a in q.coefficient_tuple()] return v[0] + I*v[1] + J*v[2] + K*v[3] @@ -1299,7 +1303,7 @@ def unpickle_QuaternionAlgebra_v0(*key): return QuaternionAlgebra(*key) -class QuaternionOrder(Algebra): +class QuaternionOrder(Parent): """ An order in a quaternion algebra. @@ -1375,7 +1379,7 @@ def __init__(self, A, basis, check=True): # has rank 4 V = A.base_ring()**4 - if V.span([ V(x.coefficient_tuple()) for x in basis]).dimension() != 4: + if V.span([V(x.coefficient_tuple()) for x in basis]).dimension() != 4: raise ValueError("basis must have rank 4") # The additional checks will work over QQ and over number fields, @@ -1383,15 +1387,15 @@ def __init__(self, A, basis, check=True): # field if A.base_ring() == QQ: # fast code over QQ - M = matrix(QQ, 4, 4, [ x.coefficient_tuple() for x in basis]) - v = M.solve_left(V([1,0,0,0])) + M = matrix(QQ, 4, 4, [x.coefficient_tuple() for x in basis]) + v = M.solve_left(V([1, 0, 0, 0])) if v.denominator() != 1: raise ValueError("lattice must contain 1") # check if multiplicatively closed M1 = basis_for_quaternion_lattice(basis) - M2 = basis_for_quaternion_lattice(list(basis) + [ x*y for x in basis for y in basis]) + M2 = basis_for_quaternion_lattice(list(basis) + [x * y for x in basis for y in basis]) if M1 != M2: raise ValueError("given lattice must be a ring") @@ -1403,21 +1407,24 @@ def __init__(self, A, basis, check=True): pass if O: - M = matrix(A.base_ring(), 4, 4, [ x.coefficient_tuple() for x in basis]) - v = M.solve_left(V([1,0,0,0])) + M = matrix(A.base_ring(), 4, 4, [x.coefficient_tuple() + for x in basis]) + v = M.solve_left(V([1, 0, 0, 0])) if any(a not in O for a in v): raise ValueError("lattice must contain 1") # check if multiplicatively closed - Y = matrix(QQ, 16, 4, [ (x*y).coefficient_tuple() for x in basis for y in basis]) + Y = matrix(QQ, 16, 4, [(x*y).coefficient_tuple() + for x in basis for y in basis]) X = M.solve_left(Y) if any(a not in O for x in X for a in x): raise ValueError("given lattice must be a ring") self.__basis = basis self.__quaternion_algebra = A - Parent.__init__(self, base=ZZ, facade=(A,), category=Algebras(ZZ)) + Parent.__init__(self, base=ZZ, facade=(A,), + category=Algebras(ZZ).Facade().FiniteDimensional()) def gens(self): """ @@ -1811,11 +1818,11 @@ def ternary_quadratic_form(self, include_basis=False): S = twoR + Z # Now we intersect with the trace 0 submodule v = [b.reduced_trace() for b in Q.basis()] - M = matrix(QQ,4,1,v) + M = matrix(QQ, 4, 1, v) tr0 = M.kernel() G = tr0.intersection(S) B = [Q(a) for a in G.basis()] - m = matrix(QQ,[[x.pair(y) for x in B] for y in B]) + m = matrix(QQ, [[x.pair(y) for x in B] for y in B]) from sage.quadratic_forms.quadratic_form import QuadraticForm Q = QuadraticForm(m) if include_basis: @@ -2234,7 +2241,7 @@ def theta_series_vector(self, B): """ B = Integer(B) try: - if len(self.__theta_series_vector)>= B: + if len(self.__theta_series_vector) >= B: return self.__theta_series_vector[:B] except AttributeError: pass @@ -2380,7 +2387,7 @@ def norm(self): r = r.sqrt() # If we know either the left- or the right order, use that one to compute the norm. R = self.__left_order or self.__right_order or self.left_order() - r/= R.discriminant() + r /= R.discriminant() assert r.is_square(), "second is bad!" return r.sqrt() @@ -2423,9 +2430,9 @@ def __mul__(self, right): if not isinstance(right, QuaternionFractionalIdeal_rational): return self._scale(right, left=False) gens = [a*b for a in self.basis() for b in right.basis()] - #if self.__right_order == right.__left_order: - # left_order = self.__left_order - # right_order = right.__right_order + # if self.__right_order == right.__left_order: + # left_order = self.__left_order + # right_order = right.__right_order basis = tuple(basis_for_quaternion_lattice(gens)) A = self.quaternion_algebra() return A.ideal(basis, check=False) @@ -2701,7 +2708,7 @@ def cyclic_right_subideals(self, p, alpha=None): pB, d = pB._clear_denom() ans = [] - Z = matrix(ZZ,2,4) + Z = matrix(ZZ, 2, 4) P1 = P1List(p) if alpha is None: @@ -2710,7 +2717,7 @@ def cyclic_right_subideals(self, p, alpha=None): x = alpha lines = [] for i in range(p+1): - lines.append(P1.normalize(x[0,0], x[0,1])) + lines.append(P1.normalize(x[0, 0], x[0, 1])) x *= alpha for u, v in lines: @@ -2738,7 +2745,8 @@ def cyclic_right_subideals(self, p, alpha=None): # specialized to go elsewhere. ####################################################################### -def basis_for_quaternion_lattice(gens, reverse = False): + +def basis_for_quaternion_lattice(gens, reverse=False): r""" Return a basis for the `\ZZ`-lattice in a quaternion algebra spanned by the given gens. @@ -2804,7 +2812,7 @@ def intersection_of_row_modules_over_ZZ(v): elif len(v) == 2: # real work - the base case a, b = v - s,_ = a.stack(b)._clear_denom() + s, _ = a.stack(b)._clear_denom() s = s.transpose() K = s.right_kernel_matrix(algorithm='pari', basis='computed') n = a.nrows() @@ -2890,7 +2898,6 @@ def normalize_basis_at_p(e, p, B=QuaternionAlgebraElement_abstract.pair): if v < min_v or (v == min_v and (min_m != min_n) and (m == n)): min_m, min_n, min_v = m, n, v - if (min_m == min_n) or p != 2: # In this case we can diagonalize if min_m == min_n: # Diagonal entry has minimal valuation f0 = e[min_m] @@ -2903,7 +2910,7 @@ def normalize_basis_at_p(e, p, B=QuaternionAlgebraElement_abstract.pair): # Orthogonalize remaining vectors with respect to f c = B(f0, f0) for l in range(1, N): - e[l] = e[l] - B(e[l],f0)/c * f0 + e[l] = e[l] - B(e[l], f0) / c * f0 # Recursively normalize remaining vectors f = normalize_basis_at_p(e[1:], p) @@ -2912,29 +2919,29 @@ def normalize_basis_at_p(e, p, B=QuaternionAlgebraElement_abstract.pair): else: # p = 2 and only off-diagonal entries have min. val., gives 2-dim. block # first diagonal entry should have smaller valuation - if B(e[min_m],e[min_m]).valuation(p) > B(e[min_n],e[min_n]).valuation(p): + if B(e[min_m], e[min_m]).valuation(p) > B(e[min_n], e[min_n]).valuation(p): e[min_m], e[min_n] = e[min_n], e[min_m] - f0 = p**min_v / B(e[min_m],e[min_n]) * e[min_m] + f0 = p**min_v / B(e[min_m], e[min_n]) * e[min_m] f1 = e[min_n] # Ensures that (B(f0,f0)/2).valuation(p) <= B(f0,f1).valuation(p) - if B(f0,f1).valuation(p) + 1 < B(f0,f0).valuation(p): - f0 = f0 + f1 + if B(f0, f1).valuation(p) + 1 < B(f0, f0).valuation(p): + f0 += f1 f1 = f0 # Make remaining vectors orthogonal to span of f0, f1 e[min_m] = e[0] e[min_n] = e[1] - B00 = B(f0,f0) - B11 = B(f1,f1) - B01 = B(f0,f1) - d = B00*B11 - B01**2 - tu = [ (B01 * B(f1,e[l]) - B11 * B(f0,e[l]), - B01 * B(f0,e[l]) - B00 * B(f1,e[l])) for l in range(2,N) ] + B00 = B(f0, f0) + B11 = B(f1, f1) + B01 = B(f0, f1) + d = B00 * B11 - B01**2 + tu = [(B01 * B(f1, e[l]) - B11 * B(f0, e[l]), + B01 * B(f0, e[l]) - B00 * B(f1, e[l])) for l in range(2, N)] - e[2:n] = [ e[l] + tu[l-2][0]/d * f0 + tu[l-2][1]/d * f1 for l in range(2,N) ] + e[2:n] = [e[l] + tu[l-2][0]/d * f0 + tu[l-2][1]/d * f1 for l in range(2, N)] # Recursively normalize remaining vectors f = normalize_basis_at_p(e[2:N], p) @@ -2987,12 +2994,11 @@ def maxord_solve_aux_eq(a, b, p): raise RuntimeError("b must have v_p(b) in {0,1}") R = ZZ.quo(ZZ(4)) - lut = { - (R(1), R(1)) : (1,1,1), - (R(1), R(2)) : (1,0,0), - (R(1), R(3)) : (1,0,0), - (R(3), R(1)) : (1,1,1), - (R(3), R(2)) : (1,0,1), - (R(3), R(3)) : (1,1,1), } + lut = {(R(1), R(1)): (1, 1, 1), + (R(1), R(2)): (1, 0, 0), + (R(1), R(3)): (1, 0, 0), + (R(3), R(1)): (1, 1, 1), + (R(3), R(2)): (1, 0, 1), + (R(3), R(3)): (1, 1, 1)} return lut[(R(a), R(b))] diff --git a/src/sage/all.py b/src/sage/all.py index 27cbfe7457b..3b50ef0aded 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -91,6 +91,10 @@ warnings.filterwarnings('ignore', category=DeprecationWarning, module='(.*[.]_vendor[.])?packaging') +# Ignore numpy warnings triggered by pythran +warnings.filterwarnings('ignore', category=DeprecationWarning, + module='pythran') + ################ end setup warnings ############################### diff --git a/src/sage/calculus/calculus.py b/src/sage/calculus/calculus.py index af3b7bd509d..b23549135cf 100644 --- a/src/sage/calculus/calculus.py +++ b/src/sage/calculus/calculus.py @@ -408,9 +408,12 @@ """ import re -from sage.arith.all import algdep -from sage.rings.all import RR, Integer, CC, QQ, RealDoubleElement -from sage.rings.real_mpfr import create_RealNumber +from sage.arith.misc import algdep +from sage.rings.integer import Integer +from sage.rings.rational_field import QQ +from sage.rings.real_double import RealDoubleElement +from sage.rings.real_mpfr import RR, create_RealNumber +from sage.rings.cc import CC from sage.misc.latex import latex from sage.misc.parser import Parser, LookupNameMaker diff --git a/src/sage/calculus/transforms/dft.py b/src/sage/calculus/transforms/dft.py index af6432f7bf7..d1b1dcf0928 100644 --- a/src/sage/calculus/transforms/dft.py +++ b/src/sage/calculus/transforms/dft.py @@ -619,7 +619,7 @@ def fft(self): Indexed sequence: [5.00000000000000, 0.000000000000000, 0.000000000000000, 0.000000000000000, 0.000000000000000] indexed by [0, 1, 2, 3, 4] """ - from sage.rings.all import CC + from sage.rings.cc import CC I = CC.gen() # elements must be coercible into RR @@ -656,7 +656,7 @@ def ifft(self): sage: t.ifft() == s 1 """ - from sage.rings.all import CC + from sage.rings.cc import CC I = CC.gen() # elements must be coercible into RR diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index cd3ff689314..e73b71c0afb 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -1135,7 +1135,7 @@ def normalize_arg(arg): # how to pass in names? names = tuple(_gen_names(elts)) if len(elts) == 1: - from sage.rings.all import CIF, CLF, RLF + from sage.rings.cif import CIF elt = elts[0] try: iv = CIF(elt) @@ -1157,6 +1157,7 @@ def normalize_arg(arg): pass # Force a real embedding when possible, to get the # right ordered ring structure. + from sage.rings.real_lazy import CLF, RLF if (iv.imag().is_zero() or iv.imag().contains_zero() and elt.imag().is_zero()): emb = RLF(elt) diff --git a/src/sage/categories/weyl_groups.py b/src/sage/categories/weyl_groups.py index 2f84ca64bb7..1181c0a93a2 100644 --- a/src/sage/categories/weyl_groups.py +++ b/src/sage/categories/weyl_groups.py @@ -1,5 +1,10 @@ r""" Weyl Groups + +REFERENCES: + +.. [Dye] Dyer. *Bruhat intervals, polyhedral cones and Kazhdan-Lusztig-Stanley polynomials*. Math.Z., 215(2):223-236, 1994. +.. [JahStu] Jahn and Stump. *Bruhat intervals, subword complexes and brick polyhedra for finite Coxeter groups*. Preprint, available at :arxiv:`2103.03715`, 2021. """ #***************************************************************************** # Copyright (C) 2009 Nicolas M. Thiery @@ -150,6 +155,78 @@ def pieri_factors(self, *args, **keywords): if hasattr(ct, "PieriFactors"): return ct.PieriFactors(self, *args, **keywords) raise NotImplementedError("Pieri factors for type {}".format(ct)) + + def bruhat_cone(self, x, y, side='upper', backend='cdd'): + r""" + Return the (upper or lower) Bruhat cone associated to the interval ``[x,y]``. + + To a cover relation `v \prec w` in strong Bruhat order you can assign a positive + root `\beta` given by the unique reflection `s_\beta` such that `s_\beta v = w`. + + The upper Bruhat cone of the interval `[x,y]` is the non-empty, polyhedral cone generated + by the roots corresponding to `x \prec a` for all atoms `a` in the interval. + The lower Bruhat cone of the interval `[x,y]` is the non-empty, polyhedral cone generated + by the roots corresponding to `c \prec y` for all coatoms `c` in the interval. + + INPUT: + + - ``x`` - an element in the group `W` + + - ``y`` - an element in the group `W` + + - ``side`` (default: ``'upper'``) -- must be one of the following: + + * ``'upper'`` - return the upper Bruhat cone of the interval [``x``, ``y``] + * ``'lower'`` - return the lower Bruhat cone of the interval [``x``, ``y``] + + - ``backend`` -- string (default: ``'cdd'``); the backend to use to create the polyhedron + + EXAMPLES:: + + sage: W = WeylGroup(['A',2]) + sage: x = W.from_reduced_word([1]) + sage: y = W.w0 + sage: W.bruhat_cone(x, y) + A 2-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex and 2 rays + + sage: W = WeylGroup(['E',6]) + sage: x = W.one() + sage: y = W.w0 + sage: W.bruhat_cone(x, y, side='lower') + A 6-dimensional polyhedron in QQ^8 defined as the convex hull of 1 vertex and 6 rays + + TESTS:: + + sage: W = WeylGroup(['A',2]) + sage: x = W.one() + sage: y = W.w0 + sage: W.bruhat_cone(x, y, side='nonsense') + Traceback (most recent call last): + ... + ValueError: side must be either 'upper' or 'lower' + + REFERENCES: + + - [Dye]_ + - [JahStu]_ + """ + from sage.modules.free_module_element import vector + if side == 'upper': + roots = [vector((x * r * x.inverse()).reflection_to_root().to_ambient()) + for z, r in x.bruhat_upper_covers_reflections() + if z.bruhat_le(y)] + elif side == 'lower': + roots = [vector((y * r * y.inverse()).reflection_to_root().to_ambient()) + for z, r in y.bruhat_lower_covers_reflections() + if x.bruhat_le(z)] + else: + raise ValueError("side must be either 'upper' or 'lower'") + + from sage.geometry.polyhedron.constructor import Polyhedron + return Polyhedron(vertices=[vector([0] * self.degree())], + rays=roots, + ambient_dim=self.degree(), + backend=backend) @cached_method def quantum_bruhat_graph(self, index_set=()): diff --git a/src/sage/coding/code_constructions.py b/src/sage/coding/code_constructions.py index c5971d48e9e..2b07709d05e 100644 --- a/src/sage/coding/code_constructions.py +++ b/src/sage/coding/code_constructions.py @@ -212,11 +212,12 @@ def _lift2smallest_field(a): if d == k: return a, FF p = FF.characteristic() - F = GF(p**d,"z") - b = pol.roots(F,multiplicities=False)[0] + F = GF((p, d), "z") + b = pol.roots(F, multiplicities=False)[0] return b, F -def permutation_action(g,v): + +def permutation_action(g, v): r""" Returns permutation of rows g\*v. Works on lists, matrices, sequences and vectors (by permuting coordinates). The code requires diff --git a/src/sage/combinat/cluster_algebra_quiver/quiver.py b/src/sage/combinat/cluster_algebra_quiver/quiver.py index 48a9bd9164d..4766fa71d2c 100644 --- a/src/sage/combinat/cluster_algebra_quiver/quiver.py +++ b/src/sage/combinat/cluster_algebra_quiver/quiver.py @@ -40,7 +40,7 @@ from sage.structure.sage_object import SageObject from copy import copy from sage.rings.integer_ring import ZZ -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.infinity import infinity from sage.graphs.all import Graph, DiGraph from sage.graphs.views import EdgesView diff --git a/src/sage/combinat/cyclic_sieving_phenomenon.py b/src/sage/combinat/cyclic_sieving_phenomenon.py index 023d123ab94..e654a8fc94a 100644 --- a/src/sage/combinat/cyclic_sieving_phenomenon.py +++ b/src/sage/combinat/cyclic_sieving_phenomenon.py @@ -24,6 +24,7 @@ # Distributed under the terms of the GNU General Public License (GPL) # https://www.gnu.org/licenses/ # **************************************************************************** +from __future__ import annotations from sage.rings.integer_ring import ZZ from sage.arith.all import lcm from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing @@ -97,7 +98,7 @@ def CyclicSievingPolynomial(L, cyc_act=None, order=None, get_order=False): else: orbit_sizes[length] = 1 - n = lcm(list(orbit_sizes)) + n = lcm(orbit_sizes) if order: if order.mod(n): @@ -110,19 +111,16 @@ def CyclicSievingPolynomial(L, cyc_act=None, order=None, get_order=False): if i == 0: j = sum(orbit_sizes.values()) else: - j = sum(orbit_sizes[l] for l in orbit_sizes - if ZZ(i).mod(n / l) == 0) + j = sum(orb for l, orb in orbit_sizes.items() + if not ZZ(i).mod(n // l)) p += j * q**i p = p(q**(order // n)) - if get_order: - return [p, order] - else: - return p + return [p, order] if get_order else p -def CyclicSievingCheck(L, cyc_act, f, order=None): +def CyclicSievingCheck(L, cyc_act, f, order=None) -> bool: """ Return whether the triple ``(L, cyc_act, f)`` exhibits the cyclic sieving phenomenon. @@ -162,11 +160,10 @@ def CyclicSievingCheck(L, cyc_act, f, order=None): get_order=True) R = p1.parent() q = R.gen() - p2 = R(f).mod(q**n - 1) - return p1 == p2 + return p1 == R(f).mod(q**n - 1) -def orbit_decomposition(L, cyc_act): +def orbit_decomposition(L, cyc_act) -> list[list]: """ Return the orbit decomposition of ``L`` by the action of ``cyc_act``. @@ -196,7 +193,7 @@ def orbit_decomposition(L, cyc_act): """ orbits = [] L_prime = set(L) - while L_prime != set(): + while L_prime: obj = L_prime.pop() orbit = [obj] obj = cyc_act(obj) diff --git a/src/sage/combinat/designs/database.py b/src/sage/combinat/designs/database.py index 02023998fc1..f0ce2091e3f 100644 --- a/src/sage/combinat/designs/database.py +++ b/src/sage/combinat/designs/database.py @@ -1484,7 +1484,7 @@ def OA_17_560(): m = 16 n = p**alpha - G = GF(p**alpha,prefix='x') + G = GF((p, alpha), prefix='x') G_set = sorted(G) # sorted by lexicographic order, G[1] = 1 G_to_int = {v:i for i,v in enumerate(G_set)} # Builds an OA(n+1,n) whose last n-1 columns are diff --git a/src/sage/combinat/dyck_word.py b/src/sage/combinat/dyck_word.py index a98a29f2e26..c8ad21d1c25 100644 --- a/src/sage/combinat/dyck_word.py +++ b/src/sage/combinat/dyck_word.py @@ -78,6 +78,7 @@ # https://www.gnu.org/licenses/ # **************************************************************************** from __future__ import annotations +from typing import Iterator from .combinat import CombinatorialElement, catalan_number from sage.combinat.combinatorial_map import combinatorial_map @@ -141,10 +142,9 @@ def replace_parens(x): """ if x == '(': return open_symbol - elif x == ')': + if x == ')': return close_symbol - else: - raise ValueError + raise ValueError def replace_symbols(x): @@ -185,10 +185,9 @@ def replace_symbols(x): """ if x == open_symbol: return '(' - elif x == close_symbol: + if x == close_symbol: return ')' - else: - raise ValueError + raise ValueError class DyckWord(CombinatorialElement): @@ -605,7 +604,7 @@ def __str__(self) -> str: sage: print(DyckWord([1, 1, 0, 0])) (()) """ - return "".join(map(replace_symbols, [x for x in self])) + return "".join(replace_symbols(x) for x in self) def to_path_string(self, unicode=False) -> str: r""" @@ -1756,7 +1755,27 @@ def tamari_interval(self, other): from sage.combinat.interval_posets import TamariIntervalPosets return TamariIntervalPosets.from_dyck_words(self, other) - def to_area_sequence(self) -> list: + def _area_sequence_iter(self) -> Iterator[int]: + """ + Return an iterator producing the area sequence. + + .. SEEALSO:: :meth:`to_area_sequence` + + EXAMPLES:: + + sage: d = DyckWord([1, 0, 1, 0]) + sage: [a for a in d._area_sequence_iter()] + [0, 0] + """ + a = 0 + for move in self: + if move == open_symbol: + yield a + a += 1 + else: + a -= 1 + + def to_area_sequence(self) -> list[int]: r""" Return the area sequence of the Dyck word ``self``. @@ -1806,15 +1825,7 @@ def to_area_sequence(self) -> list: sage: DyckWord([1,1,0,1,0,0,1,1,0,1,0,1,0,0]).to_area_sequence() [0, 1, 1, 0, 1, 1, 1] """ - seq = [] - a = 0 - for move in self: - if move == open_symbol: - seq.append(a) - a += 1 - else: - a -= 1 - return seq + return list(self._area_sequence_iter()) class DyckWord_complete(DyckWord): @@ -1927,8 +1938,8 @@ def list_parking_functions(self): sage: DyckWord([1,0,1,0,1,0]).list_parking_functions() Standard permutations of 3 """ - alist = self.to_area_sequence() - return Permutations([i - alist[i] + 1 for i in range(len(alist))]) + alist = self._area_sequence_iter() + return Permutations([i - ai + 1 for i, ai in enumerate(alist)]) # TODO: upon implementation of ParkingFunction class # map(ParkingFunction, Permutations([i - alist[i]+1 for i in range(len(alist))])) @@ -1956,9 +1967,9 @@ def reading_permutation(self) -> Permutation: sage: DyckWord([1,0,1,1,0,0,1,0]).reading_permutation() [3, 4, 2, 1] """ - alist = self.to_area_sequence() - if not alist: + if not self: return Permutation([]) # type: ignore + alist = self.to_area_sequence() m = max(alist) p1 = Word([m - alist[-i - 1] for i in range(len(alist))]).standard_permutation() @@ -2075,10 +2086,10 @@ def to_312_avoiding_permutation(self) -> Permutation: True """ n = self.semilength() - area = self.to_area_sequence() + area = self._area_sequence_iter() pi = Permutations(n).one() - for j in range(n): - for i in range(area[j]): + for j, aj in enumerate(area): + for i in range(aj): pi = pi.apply_simple_reflection(j - i) return pi @@ -2793,9 +2804,9 @@ def area_dinv_to_bounce_area_map(self) -> DyckWord: sage: DyckWord([1,0,1,0]).area_dinv_to_bounce_area_map() [1, 1, 0, 0] """ - a = self.to_area_sequence() - if not a: + if not self: return self + a = self.to_area_sequence() a.reverse() image = [] for i in range(max(a), -2, -1): @@ -2844,13 +2855,13 @@ def bounce_area_to_area_dinv_map(self) -> DyckWord: sage: DyckWord([1,0,1,0]).bounce_area_to_area_dinv_map() [1, 1, 0, 0] """ - aseq = self.to_area_sequence() + aseq = self._area_sequence_iter() out: list[int] = [] zeros: list[int] = [] - for i in range(len(aseq)): - p = (zeros + [len(out)])[aseq[i]] + for ai in aseq: + p = (zeros + [len(out)])[ai] out = [1] + out[p:] + [0] + out[:p] - zeros = [0] + [j + len(out) - p for j in zeros[:aseq[i]]] + zeros = [0] + [j + len(out) - p for j in zeros[:ai]] return DyckWord(out) # type:ignore def area(self) -> int: @@ -3093,9 +3104,11 @@ def dinv(self, labeling=None) -> int: """ alist = self.to_area_sequence() cnt = 0 - for j in range(len(alist)): + for j, aj in enumerate(alist): + if labeling is not None: + lj = labeling[j] for i in range(j): - if (alist[i] - alist[j] == 0 and (labeling is None or labeling[i] < labeling[j])) or (alist[i] - alist[j] == 1 and (labeling is None or labeling[i] > labeling[j])): + if (alist[i] == aj and (labeling is None or labeling[i] < lj)) or (alist[i] - aj == 1 and (labeling is None or labeling[i] > lj)): cnt += 1 return cnt @@ -3933,10 +3946,12 @@ def __iter__(self): class height_poset(UniqueRepresentation, Parent): r""" - The poset of complete Dyck words compared componentwise by - ``heights``. + The poset of complete Dyck words compared componentwise by ``heights``. + This is, ``D`` is smaller than or equal to ``D'`` if it is weakly below ``D'``. + + This is implemented by comparison of area sequences. """ def __init__(self): r""" @@ -3974,7 +3989,7 @@ def le(self, dw1, dw2): .. SEEALSO:: - :meth:`heights` + :meth:`~sage.combinat.dyck_word.DyckWord.to_area_sequence` EXAMPLES:: @@ -3994,10 +4009,10 @@ def le(self, dw1, dw2): True, False, False, False, False, True] """ if len(dw1) != len(dw2): - raise ValueError("Length mismatch: %s and %s" % (dw1, dw2)) - sh = dw1.heights() - oh = dw2.heights() - return all(sh[i] <= oh[i] for i in range(len(dw1))) + raise ValueError(f"length mismatch: {dw1} and {dw2}") + ar1 = dw1._area_sequence_iter() + ar2 = dw2._area_sequence_iter() + return all(a1 <= a2 for a1, a2 in zip(ar1, ar2)) class CompleteDyckWords_size(CompleteDyckWords, DyckWords_size): diff --git a/src/sage/combinat/interval_posets.py b/src/sage/combinat/interval_posets.py index 7fd8fe4c933..d698ef689ad 100644 --- a/src/sage/combinat/interval_posets.py +++ b/src/sage/combinat/interval_posets.py @@ -365,7 +365,7 @@ def latex_options(self) -> dict: d["vspace"] = self.parent().options["latex_vspace"] return d - def _find_node_positions(self, hspace=1, vspace=1) -> dict: + def _find_node_positions(self, hspace=1, vspace=1) -> dict[int, list]: """ Compute a nice embedding. @@ -677,7 +677,7 @@ def increasing_cover_relations(self) -> list[tuple[int, int]]: break return relations - def increasing_roots(self) -> list: + def increasing_roots(self) -> list[int]: r""" Return the root vertices of the initial forest of ``self``. @@ -714,7 +714,7 @@ def increasing_roots(self) -> list: root = i return roots - def increasing_children(self, v) -> list: + def increasing_children(self, v) -> list[int]: r""" Return the children of ``v`` in the initial forest of ``self``. @@ -819,7 +819,7 @@ def decreasing_cover_relations(self) -> list[tuple[int, int]]: break return relations - def decreasing_roots(self) -> list: + def decreasing_roots(self) -> list[int]: r""" Return the root vertices of the final forest of ``self``. @@ -850,7 +850,7 @@ def decreasing_roots(self) -> list: root = i return roots - def decreasing_children(self, v) -> list: + def decreasing_children(self, v) -> list[int]: r""" Return the children of ``v`` in the final forest of ``self``. @@ -1388,7 +1388,7 @@ def _unicode_art_(self): [[3, 4], [3, 2], [4, 5], [2, 5], [1, 5]] """ n = self.size() - M = [[u'o' if i == j else u' ' for i in range(n)] for j in range(n)] + M = [['o' if i == j else ' ' for i in range(n)] for j in range(n)] def superpose(x, y, b): # put symbol b at position x, y @@ -1398,46 +1398,46 @@ def superpose(x, y, b): a = M[i][j] if a == ' ': M[i][j] = b - elif a == u'╮': + elif a == '╮': if b == a: pass - elif b == u'─': - M[i][j] = u'┬' - elif b == u'│': - M[i][j] = u'┤' - elif a == u'╰': + elif b == '─': + M[i][j] = '┬' + elif b == '│': + M[i][j] = '┤' + elif a == '╰': if b == a: pass - elif b == u'─': - M[i][j] = u'┴' - elif b == u'│': - M[i][j] = u'├' - elif a == u'─': + elif b == '─': + M[i][j] = '┴' + elif b == '│': + M[i][j] = '├' + elif a == '─': if b == a: pass - elif b == u'╮': - M[i][j] = u'┬' - elif b == u'╰': - M[i][j] = u'┴' - elif a == u'│': + elif b == '╮': + M[i][j] = '┬' + elif b == '╰': + M[i][j] = '┴' + elif a == '│': if b == a: pass - elif b == u'╮': - M[i][j] = u'┤' - elif b == u'╰': - M[i][j] = u'├' + elif b == '╮': + M[i][j] = '┤' + elif b == '╰': + M[i][j] = '├' for i, j in self.poset().hasse_diagram().edges(labels=False): if i > j: - superpose(i, j, u'╰') + superpose(i, j, '╰') for k in range(j + 1, i): - superpose(k, j, u'│') - superpose(i, k, u'─') + superpose(k, j, '│') + superpose(i, k, '─') else: - superpose(i, j, u'╮') + superpose(i, j, '╮') for k in range(i + 1, j): - superpose(i, k, u'─') - superpose(k, j, u'│') + superpose(i, k, '─') + superpose(k, j, '│') from sage.typeset.unicode_art import UnicodeArt return UnicodeArt([''.join(ligne) for ligne in M]) @@ -1487,7 +1487,7 @@ def _richcmp_(self, other, op) -> bool: return richcmp((self.size(), self.cubical_coordinates()), (other.size(), other.cubical_coordinates()), op) - def __iter__(self) -> Iterator: + def __iter__(self) -> Iterator[int]: r""" Iterate through the vertices of ``self``. @@ -1689,7 +1689,7 @@ def contains_dyck_word(self, dyck_word) -> bool: """ return self.contains_binary_tree(dyck_word.to_binary_tree_tamari()) - def intersection(self, other): + def intersection(self, other: TIP) -> TIP: r""" Return the interval-poset formed by combining the relations from both ``self`` and ``other``. It corresponds to the intersection @@ -2062,7 +2062,7 @@ def add(perm: list, i): add(perm, i) return Permutation(perm) # type:ignore - def linear_extensions(self) -> Iterator: + def linear_extensions(self) -> Iterator[Permutation]: r""" Return an iterator on the permutations which are linear extensions of ``self``. @@ -2084,7 +2084,7 @@ def linear_extensions(self) -> Iterator: for ext in self._poset.linear_extensions(): yield Permutation(ext) # type:ignore - def lower_contained_intervals(self) -> Iterator: + def lower_contained_intervals(self) -> Iterator[TIP]: r""" If ``self`` represents the interval `[t_1, t_2]` of the Tamari lattice, return an iterator on all intervals `[t_1,t]` with @@ -2231,7 +2231,7 @@ def dyck_words(self) -> Iterator: for ip in self.lower_contained_intervals(): yield ip.upper_dyck_word() - def maximal_chain_tamari_intervals(self) -> Iterator: + def maximal_chain_tamari_intervals(self) -> Iterator[TIP]: r""" Return an iterator on the upper contained intervals of one longest chain of the Tamari interval represented by ``self``. @@ -2378,7 +2378,7 @@ def tamari_inversions(self) -> list[tuple[int, int]]: """ return list(self.tamari_inversions_iter()) - def tamari_inversions_iter(self) -> Iterator: + def tamari_inversions_iter(self) -> Iterator[tuple[int, int]]: r""" Iterate over the Tamari inversions of ``self``, in lexicographic order. @@ -2577,7 +2577,7 @@ def decomposition_to_triple(self) -> None | tuple[TIP, TIP, int]: right = self.subposet(root + 1, n + 1) return left, right, r - def grafting_tree(self): + def grafting_tree(self) -> LabelledBinaryTree: """ Return the grafting tree of the interval-poset. @@ -2973,7 +2973,7 @@ def check_poset(poset) -> bool: return True @staticmethod - def final_forest(element): + def final_forest(element) -> TIP: r""" Return the final forest of a binary tree, an interval-poset or a Dyck word. @@ -3080,7 +3080,7 @@ def get_relations(bt, start=1): return TamariIntervalPoset(P, check=False) # type:ignore @staticmethod - def initial_forest(element): + def initial_forest(element) -> TIP: r""" Return the initial forest of a binary tree, an interval-poset or a Dyck word. @@ -3718,7 +3718,7 @@ def cardinality(self) -> Integer: return (2 * binomial(4 * n + 1, n - 1)) // (n * (n + 1)) # return Integer(2 * factorial(4*n+1)/(factorial(n+1)*factorial(3*n+2))) - def __iter__(self) -> Iterator: + def __iter__(self) -> Iterator[TIP]: r""" Recursive generation: we iterate through all interval-posets of size ``size - 1`` and add all possible relations to the last diff --git a/src/sage/combinat/parallelogram_polyomino.py b/src/sage/combinat/parallelogram_polyomino.py index 9dbdf47baf1..ab23a04ac0f 100644 --- a/src/sage/combinat/parallelogram_polyomino.py +++ b/src/sage/combinat/parallelogram_polyomino.py @@ -16,6 +16,7 @@ # the License, or (at your option) any later version. # https://www.gnu.org/licenses/ # ***************************************************************************** +from __future__ import annotations from sage.structure.list_clone import ClonableList from sage.structure.unique_representation import UniqueRepresentation @@ -153,7 +154,7 @@ def __init__(self, name='', **options): for key in self._available_options: self._options[key] = self._available_options[key]["default"] - def __repr__(self): + def __repr__(self) -> str: r""" Return a string representation of ``self``. @@ -382,7 +383,7 @@ def __iter__(self): """ return self._available_options.__iter__() - def keys(self): + def keys(self) -> list: r""" Return the list of the options in ``self``. @@ -544,10 +545,10 @@ def _dispatch(self, obj, dispatch_to, option, *get_values, **set_values): ....: ) sage: opt = ParallelogramPolyominoesOptions['tikz_options'] sage: opt - {'color_bounce_0': u'red', - 'color_bounce_1': u'blue', - 'color_line': u'black', - 'color_point': u'black', + {'color_bounce_0': 'red', + 'color_bounce_1': 'blue', + 'color_line': 'black', + 'color_point': 'black', 'line_size': 1, 'mirror': None, 'point_size': 3.5, @@ -576,13 +577,13 @@ class _drawing_tool: sage: opt = ParallelogramPolyominoesOptions['tikz_options'] sage: dt = _drawing_tool(opt) sage: dt.draw_line([1, 1], [-1, -1]) - u'\n \\draw[color=black, line width=1] (1.000000, 1.000000) -- + '\n \\draw[color=black, line width=1] (1.000000, 1.000000) -- (-1.000000, -1.000000);' sage: fct = lambda vec: [2*vec[0], vec[1]] sage: dt = _drawing_tool(opt, fct) sage: dt.draw_line([1, 1], [-1, -1]) - u'\n \\draw[color=black, line width=1] (2.000000, 1.000000) -- + '\n \\draw[color=black, line width=1] (2.000000, 1.000000) -- (-2.000000, -1.000000);' sage: import copy @@ -590,7 +591,7 @@ class _drawing_tool: sage: opt['mirror'] = [0,1] sage: dt = _drawing_tool(opt) sage: dt.draw_line([1, 1], [-1, -1]) - u'\n \\draw[color=black, line width=1] (-1.000000, 1.000000) -- + '\n \\draw[color=black, line width=1] (-1.000000, 1.000000) -- (1.000000, -1.000000);' """ @@ -614,7 +615,7 @@ def __init__(self, options, XY=lambda v: v): sage: opt = ParallelogramPolyominoesOptions['tikz_options'] sage: dt = _drawing_tool(opt) sage: dt.draw_line([1, 1], [-1, -1]) - u'\n \\draw[color=black, line width=1] (1.000000, 1.000000) -- + '\n \\draw[color=black, line width=1] (1.000000, 1.000000) -- (-1.000000, -1.000000);' """ self._XY = lambda v: XY([float(v[0]), float(v[1])]) @@ -681,7 +682,7 @@ def translate(pos, v): The translated position. """ - return [pos[0]+v[0], pos[1]+v[1]] + return [pos[0] + v[0], pos[1] + v[1]] def rotate(pos, angle): r""" @@ -767,9 +768,8 @@ def draw_line(self, v1, v2, color=None, size=None): sage: opt = ParallelogramPolyominoesOptions['tikz_options'] sage: dt = _drawing_tool(opt) sage: dt.draw_line([1, 1], [-1, -1]) - u'\n \\draw[color=black, line width=1] (1.000000, 1.000000) -- + '\n \\draw[color=black, line width=1] (1.000000, 1.000000) -- (-1.000000, -1.000000);' - """ if color is None: color = self._color_line @@ -809,15 +809,14 @@ def draw_polyline(self, list_of_vertices, color=None, size=None): sage: opt = ParallelogramPolyominoesOptions['tikz_options'] sage: dt = _drawing_tool(opt) sage: dt.draw_polyline([[1, 1], [-1, -1], [0,0]]) - u'\n \\draw[color=black, line width=1] (1.000000, 1.000000) -- + '\n \\draw[color=black, line width=1] (1.000000, 1.000000) -- (-1.000000, -1.000000);\n \\draw[color=black, line width=1] (-1.000000, -1.000000) -- (0.000000, 0.000000);' """ res = "" for i in range(len(list_of_vertices)-1): res += self.draw_line( - list_of_vertices[i], list_of_vertices[i+1], color, size - ) + list_of_vertices[i], list_of_vertices[i+1], color, size) return res def draw_point(self, p1, color=None, size=None): @@ -849,8 +848,7 @@ def draw_point(self, p1, color=None, size=None): sage: opt = ParallelogramPolyominoesOptions['tikz_options'] sage: dt = _drawing_tool(opt) sage: dt.draw_point([1, 1]) - u'\n \\filldraw[color=black] (1.000000, 1.000000) circle (3.5pt);' - + '\n \\filldraw[color=black] (1.000000, 1.000000) circle (3.5pt);' """ if color is None: color = self._color_point @@ -988,22 +986,22 @@ def _unicode_art_(self): data = list(zip(self.lower_widths(), self.upper_widths())) - txt = [u'┌' + u'┬' * (data[0][1] - 1) + u'┐'] + txt = ['┌' + '┬' * (data[0][1] - 1) + '┐'] for i in range(1, len(data)): x1, y1 = data[i-1] x2, y2 = data[i] - line = [u' ' * x1] + line = [' ' * x1] if x1 == x2: - line += [u'├'] + line += ['├'] else: - line += [u'└' + u'┴' * (x2 - x1 - 1) + u'┼'] - line += [u'┼' * (y1 - x2 - 1)] + line += ['└' + '┴' * (x2 - x1 - 1) + '┼'] + line += ['┼' * (y1 - x2 - 1)] if y1 == y2: - line += [u'┤'] + line += ['┤'] else: - line += [u'┼' + u'┬' * (y2 - y1 - 1) + u'┐'] + line += ['┼' + '┬' * (y2 - y1 - 1) + '┐'] txt += [''.join(line)] - txt += [u' ' * data[-1][0] + u'└' + u'┴' * (data[-1][1] - data[-1][0] - 1) + u'┘'] + txt += [' ' * data[-1][0] + '└' + '┴' * (data[-1][1] - data[-1][0] - 1) + '┘'] return UnicodeArt(txt, baseline=0) @@ -1176,7 +1174,7 @@ def __init__(self, parent, value, check=True): self.check() self._options = None - def reflect(self): + def reflect(self) -> ParallelogramPolyomino: r""" Return the parallelogram polyomino obtained by switching rows and columns. @@ -1208,9 +1206,10 @@ def reflect(self): if self.size() == 1: return self a, b = self - return ParallelogramPolyomino([[1-v for v in b], [1-v for v in a]]) + return ParallelogramPolyomino([[1 - v for v in b], + [1 - v for v in a]]) - def rotate(self): + def rotate(self) -> ParallelogramPolyomino: r""" Return the parallelogram polyomino obtained by rotation of 180 degrees. @@ -1229,7 +1228,6 @@ def rotate(self): a, b = self return ParallelogramPolyomino([b[::-1], a[::-1]]) - def _to_dyck_delest_viennot(self): r""" Convert to a Dyck word using the Delest-Viennot bijection. @@ -1253,7 +1251,7 @@ def _to_dyck_delest_viennot(self): """ from sage.combinat.dyck_word import DyckWord dyck = [] - dick_size = self.size()-1 + dick_size = self.size() - 1 if not dick_size: return DyckWord([]) upper_path = self.upper_path() @@ -1274,8 +1272,6 @@ def _to_dyck_delest_viennot_peaks_valleys(self): peak heights are the column heights and whose valley heights are the overlaps between adjacent columns. - - EXAMPLES: This is the example in Figure 8 of [DeVi1984]_:: @@ -1289,7 +1285,6 @@ def _to_dyck_delest_viennot_peaks_valleys(self): sage: pp = ParallelogramPolyomino([[1], [1]]) sage: pp._to_dyck_delest_viennot_peaks_valleys() [] - """ from sage.combinat.dyck_word import DyckWord a = self.heights() @@ -1297,8 +1292,8 @@ def _to_dyck_delest_viennot_peaks_valleys(self): b = [0] + [a[i]-u[i+1]+u[i]-1 for i in range(len(a)-1)] + [0] dyck = [] for i in range(len(a)): - dyck.extend([1]*(a[i]-b[i])) - dyck.extend([0]*(a[i]-b[i+1])) + dyck.extend([1] * (a[i] - b[i])) + dyck.extend([0] * (a[i] - b[i + 1])) return DyckWord(dyck) @combinatorial_map(name="To Dyck word") @@ -1364,14 +1359,13 @@ def _from_dyck_word_delest_viennot(dyck): sage: gamma_inv = ParallelogramPolyomino._from_dyck_word_delest_viennot sage: all(all(D == gamma(gamma_inv(D)) for D in DyckWords(n)) for n in range(7)) True - """ l = [1] + list(dyck) + [0] word_up = [] word_down = [] for i in range(0, len(l), 2): word_up.append(l[i]) - word_down.append(1 - l[i+1]) + word_down.append(1 - l[i + 1]) return ParallelogramPolyomino([word_down, word_up]) @staticmethod @@ -1526,7 +1520,7 @@ def _to_binary_tree_Aval_Boussicault(self, position=[0, 0]): @combinatorial_map(name="To binary tree") def to_binary_tree(self, bijection=None): r""" - Convert to a binary tree + Convert to a binary tree. INPUT: @@ -1562,7 +1556,7 @@ def _to_ordered_tree_via_dyck(self): Convert the parallelogram polyominoe (PP) by using first the Delest-Viennot bijection between PP and Dyck paths, and then by using the classical bijection between Dyck paths and - ordered trees + ordered trees. This last bijection is described in [DerZak1980]_ (see page 12 and Figure 3.1 of page 13). @@ -1697,12 +1691,11 @@ def make_tree(b_tree, d): if b_tree == BinaryTree(): return OrderedTree([]) res = [] - res.append(make_tree(b_tree[1-d], 1-d)) + res.append(make_tree(b_tree[1 - d], 1 - d)) res += make_tree(b_tree[d], d) return OrderedTree(res) return make_tree( - self.to_binary_tree(bijection='Aval-Boussicault'), 1 - ) + self.to_binary_tree(bijection='Aval-Boussicault'), 1) @combinatorial_map(name="To ordered tree") def to_ordered_tree(self, bijection=None): @@ -1770,17 +1763,17 @@ def get_options(self): sage: pp = ParallelogramPolyomino([[0, 1], [1, 0]]) sage: pp.get_options() Current options for ParallelogramPolyominoes_size - - display: u'list' + - display: 'list' - drawing_components: {'bounce_0': False, 'bounce_1': False, 'bounce_values': False, 'diagram': True, 'tree': False} - - latex: u'drawing' - - tikz_options: {'color_bounce_0': u'red', - 'color_bounce_1': u'blue', - 'color_line': u'black', - 'color_point': u'black', + - latex: 'drawing' + - tikz_options: {'color_bounce_0': 'red', + 'color_bounce_1': 'blue', + 'color_line': 'black', + 'color_point': 'black', 'line_size': 1, 'mirror': None, 'point_size': 3.5, @@ -1831,7 +1824,7 @@ def set_options(self, *get_value, **set_value): self._options = deepcopy(self.get_options()) self._options(*get_value, **set_value) - def upper_path(self): + def upper_path(self) -> list: r""" Get the upper path of the parallelogram polyomino. @@ -1845,7 +1838,7 @@ def upper_path(self): """ return list(ClonableList.__getitem__(self, 1)) - def lower_path(self): + def lower_path(self) -> list: r""" Get the lower path of the parallelogram polyomino. @@ -1979,7 +1972,7 @@ def lower_widths(self): """ return ParallelogramPolyomino._prefix_lengths(self.lower_path(), 1) - def widths(self): + def widths(self) -> list: r""" Return a list of the widths of the parallelogram polyomino. @@ -2012,7 +2005,7 @@ def widths(self): widths.append(uw[i] - lw[i]) return widths - def degree_convexity(self): + def degree_convexity(self) -> int: r""" Return the degree convexity of a parallelogram polyomino. @@ -2047,7 +2040,7 @@ def degree_convexity(self): l1 = len(self.bounce_path(direction=1)) return min(l0, l1) - 1 - def is_flat(self): + def is_flat(self) -> bool: r""" Return whether the two bounce paths join together in the rightmost cell of the bottom row of P. @@ -2075,7 +2068,7 @@ def is_flat(self): l1 = len(self.bounce_path(direction=1)) return l0 == l1 - def is_k_directed(self, k): + def is_k_directed(self, k) -> bool: r""" Return whether the Polyomino Parallelogram is k-directed. @@ -2125,7 +2118,7 @@ def is_k_directed(self, k): """ return self.degree_convexity() <= k - def heights(self): + def heights(self) -> list: r""" Return a list of heights of the parallelogram polyomino. @@ -2358,7 +2351,7 @@ def __getitem__(self, column): return self.polyomino.get_array()[self.row][column] return 0 - def is_inside(self): + def is_inside(self) -> bool: r""" Return ``True`` if the row is inside the parallelogram polyomino, return ``False`` otherwise. @@ -2385,11 +2378,10 @@ def is_inside(self): ....: for i in [-1,0,3,5,6] ....: ] [False, True, True, True, False] - """ return 0 <= self.row and self.row < self.polyomino.height() - def is_outside(self): + def is_outside(self) -> bool: r""" Return ``True`` if the row is outside the parallelogram polyomino, return ``False`` otherwise. @@ -2419,7 +2411,7 @@ def is_outside(self): """ return not self.is_inside() - def __repr__(self): + def __repr__(self) -> str: r""" Return a string representation of ``self``. @@ -2654,7 +2646,7 @@ def area(self): """ return sum(h for h in self.heights()) - def _repr_(self): + def _repr_(self) -> str: r""" Return a string representation of the parallelogram polyomino. @@ -2673,7 +2665,7 @@ def _repr_(self): """ return self.get_options()._dispatch(self, '_repr_', 'display') - def _repr_list(self): + def _repr_list(self) -> str: r""" Return a string representation with list style. @@ -2689,7 +2681,7 @@ def _repr_list(self): """ return ClonableList._repr_(self) - def _repr_drawing(self): + def _repr_drawing(self) -> str: r""" Return a string representing a drawing of the parallelogram polyomino. @@ -2717,10 +2709,10 @@ def get_tikz_options(self): sage: pp = ParallelogramPolyomino([[0, 1], [1, 0]]) sage: pp.get_tikz_options() - {'color_bounce_0': u'red', - 'color_bounce_1': u'blue', - 'color_line': u'black', - 'color_point': u'black', + {'color_bounce_0': 'red', + 'color_bounce_1': 'blue', + 'color_line': 'black', + 'color_point': 'black', 'line_size': 1, 'mirror': None, 'point_size': 3.5, @@ -3132,7 +3124,6 @@ def get_node_position_from_box(self, box_position, direction, nb_crossed_nodes=[ [3, 1] sage: l [0] - """ pos = list(box_position) if self[pos[0]][pos[1]] == 0: @@ -3144,7 +3135,7 @@ def get_node_position_from_box(self, box_position, direction, nb_crossed_nodes=[ pos[direction] += 1 return pos - def box_is_node(self, pos): + def box_is_node(self, pos) -> bool: r""" Return True if the box contains a node in the context of the Aval-Boussicault bijection between parallelogram polyomino and binary @@ -3181,13 +3172,13 @@ def box_is_node(self, pos): """ if self[pos[0]][pos[1]] == 0: return False - if self[pos[0]-1][pos[1]] == 0: + if self[pos[0] - 1][pos[1]] == 0: return True - if self[pos[0]][pos[1]-1] == 0: + if self[pos[0]][pos[1] - 1] == 0: return True return False - def box_is_root(self, box): + def box_is_root(self, box) -> bool: r""" Return ``True`` if the box contains the root of the tree : it is the top-left box of the parallelogram polyomino. @@ -3285,7 +3276,7 @@ def _get_number_of_nodes_in_the_bounding_path(self, box, direction): nb_sons = [0] box = self.get_node_position_from_box(box, direction, nb_sons) direction = 1 - direction - path.append(nb_sons[0]-1) + path.append(nb_sons[0] - 1) path.reverse() return path @@ -3667,7 +3658,7 @@ def to_tikz(self): res += self._to_tikz_tree() return res - def geometry(self): + def geometry(self) -> list: r""" Return a pair [h, w] containing the height and the width of the parallelogram polyomino. @@ -3783,7 +3774,7 @@ def _plot_bounce(self, directions=[0,1]): a,b = u,v return G - def _plot_bounce_values(self,bounce=0): + def _plot_bounce_values(self, bounce=0): r""" Return a plot containing the value of bounce along the specified bounce path. @@ -3875,7 +3866,6 @@ def plot(self): ....: ) sage: pp.plot() Graphics object consisting of 7 graphics primitives - """ G = Graphics() @@ -3898,7 +3888,7 @@ def plot(self): G.axes(False) return G - def size(self): + def size(self) -> int: r""" Return the size of the parallelogram polyomino. @@ -3968,7 +3958,7 @@ def _latex_list(self): sage: pp = ParallelogramPolyomino([[0,1],[1,0]]) sage: pp._latex_list() - u'\\[[[0, 1], [1, 0]]\\]' + '\\[[[0, 1], [1, 0]]\\]' """ return "\\[%s\\]" % self._repr_list() @@ -4060,7 +4050,7 @@ def _default_policy(self): """ return TopMostParentPolicy(self, (), ParallelogramPolyomino) - def _repr_(self): + def _repr_(self) -> str: r""" Return the string representation of the parallelogram polyominoes factory. @@ -4072,6 +4062,7 @@ def _repr_(self): """ return "Factory for parallelogram polyominoes" + ParallelogramPolyominoes = ParallelogramPolyominoesFactory() ParallelogramPolyominoes.__doc__ = \ ParallelogramPolyominoesFactory.__call__.__doc__ @@ -4109,7 +4100,7 @@ def __init__(self, size, policy): self, (size, ), policy, category=FiniteEnumeratedSets() ) - def _repr_(self): + def _repr_(self) -> str: r""" Return the string representation of the set of parallelogram polyominoes @@ -4192,7 +4183,7 @@ def __iter__(self): True """ from sage.combinat.dyck_word import DyckWords - for dyck in DyckWords(self.size()-1): + for dyck in DyckWords(self.size() - 1): yield ParallelogramPolyomino.from_dyck_word(dyck) def get_options(self): @@ -4205,7 +4196,7 @@ def get_options(self): sage: pps = ParallelogramPolyominoes(5) sage: pps.get_options() Current options for ParallelogramPolyominoes_size - - display: u'list' + - display: 'list' ... """ return self.options @@ -4293,7 +4284,7 @@ def __init__(self, policy): facade=True, keepkey=False, category=self.category() ) - def _repr_(self): + def _repr_(self) -> str: r""" Return a string representation of the set of parallelogram polyominoes. @@ -4331,7 +4322,7 @@ def get_options(self): sage: options = PPS.get_options() sage: options Current options for ParallelogramPolyominoes_size - - display: u'list' + - display: 'list' ... """ return self.options diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index b80813efc1a..92003f515b3 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -6232,9 +6232,9 @@ def canonical_label(self, algorithm=None): sage: Poset().canonical_label() # Test the empty poset Finite poset containing 0 elements - sage: D2 = posets.DiamondPoset(4).canonical_label(algorithm='bliss') # optional: bliss - sage: B2 = posets.BooleanLattice(2).canonical_label(algorithm='bliss') # optional: bliss - sage: D2 == B2 # optional: bliss + sage: D2 = posets.DiamondPoset(4).canonical_label(algorithm='bliss') # optional - bliss + sage: B2 = posets.BooleanLattice(2).canonical_label(algorithm='bliss') # optional - bliss + sage: D2 == B2 # optional - bliss True """ canonical_label = self._hasse_diagram.canonical_label(certificate=True, diff --git a/src/sage/combinat/species/series.py b/src/sage/combinat/species/series.py index 64862dd9d74..a664cf0102c 100644 --- a/src/sage/combinat/species/series.py +++ b/src/sage/combinat/species/series.py @@ -40,7 +40,7 @@ from sage.misc.repr import repr_lincomb from sage.misc.cachefunc import cached_method -from sage.algebras.algebra import Algebra +from sage.rings.ring import Algebra from sage.structure.parent import Parent from sage.categories.all import Rings from sage.structure.element import Element, parent, AlgebraElement diff --git a/src/sage/combinat/subword_complex.py b/src/sage/combinat/subword_complex.py index f59113ef04f..6a3d1ecd58a 100644 --- a/src/sage/combinat/subword_complex.py +++ b/src/sage/combinat/subword_complex.py @@ -1693,7 +1693,7 @@ def minkowski_summand(self, i): if G.coxeter_matrix().is_crystallographic(): min_sum = [[QQ(v) for v in F.extended_weight_configuration()[i]] for F in self] else: - from sage.rings.all import CC + from sage.rings.cc import CC from warnings import warn warn("the polytope is build with rational vertices", RuntimeWarning) min_sum = [[QQ(CC(v)) for v in F.extended_weight_configuration()[i]] for F in self] @@ -1744,7 +1744,7 @@ def brick_polytope(self, coefficients=None): if G.coxeter_matrix().is_crystallographic(): BV = [[QQ(v) for v in V] for V in BV] else: - from sage.rings.all import CC + from sage.rings.cc import CC from warnings import warn warn("the polytope is build with rational vertices", RuntimeWarning) BV = [[QQ(CC(v).real()) for v in V] for V in BV] diff --git a/src/sage/combinat/words/morphic.py b/src/sage/combinat/words/morphic.py index b6085868ee9..3fbbe48f26b 100644 --- a/src/sage/combinat/words/morphic.py +++ b/src/sage/combinat/words/morphic.py @@ -329,7 +329,7 @@ def __iter__(self): sage: w = m.fixed_point('a') Traceback (most recent call last): ... - TypeError: self (=a->ac, b->aac) is not an endomorphism + TypeError: self (=a->ac, b->aac) is not self-composable We check that :trac:`8595` is fixed:: diff --git a/src/sage/combinat/words/morphism.py b/src/sage/combinat/words/morphism.py index 3a25a49b64c..685fb8fef1f 100644 --- a/src/sage/combinat/words/morphism.py +++ b/src/sage/combinat/words/morphism.py @@ -364,6 +364,18 @@ def __init__(self, data, domain=None, codomain=None): sage: WordMorphism(',,,a->ab,,,b->ba,,') WordMorphism: a->ab, b->ba + + sage: WordMorphism({(1,2):'ab', 'a': ['c', (1,2), 'a']}) + WordMorphism: (1, 2)->ab, a->c,(1, 2),a + + sage: WordMorphism({'a':'a'}, domain=FiniteWords('ab')) + Traceback (most recent call last): + ... + ValueError: invalid input; the keys of the dictionary must coincide with the domain alphabet + sage: WordMorphism({'a':'a', 'b':'b'}, domain=FiniteWords('a')) + Traceback (most recent call last): + ... + ValueError: invalid input; the keys of the dictionary must coincide with the domain alphabet """ if isinstance(data, WordMorphism): self._domain = data._domain @@ -399,8 +411,14 @@ def __init__(self, data, domain=None, codomain=None): domain = domain.finite_words() elif not isinstance(domain, FiniteWords): raise TypeError("the codomain must be a set of finite words") + A = domain.alphabet() + if len(self._morph) != A.cardinality() or not all(a in A for a in self._morph): + raise ValueError('invalid input; the keys of the dictionary must coincide with the domain alphabet') else: - dom_alph.sort() + try: + dom_alph.sort() + except TypeError: + dom_alph.sort(key=str) domain = FiniteWords(dom_alph) self._domain = domain @@ -442,7 +460,7 @@ def _build_dict(self, s): def _build_codomain(self, data): r""" - Returns a Words domain containing all the letter in the keys of + Return a Words domain containing all the letter in the keys of data (which must be a dictionary). TESTS: @@ -474,7 +492,11 @@ def _build_codomain(self, data): except Exception: it = [val] codom_alphabet.update(it) - return FiniteWords(sorted(codom_alphabet)) + try: + codom_alphabet = sorted(codom_alphabet) + except TypeError: + codom_alphabet = sorted(codom_alphabet, key=str) + return FiniteWords(codom_alphabet) @cached_method def __hash__(self): @@ -488,7 +510,7 @@ def __hash__(self): def __eq__(self, other): r""" - Returns ``True`` if ``self`` is equal to ``other``. + Return ``True`` if ``self`` is equal to ``other``. EXAMPLES:: @@ -521,7 +543,7 @@ def __eq__(self, other): def __ne__(self, other): r""" - Returns whether ``self`` is not equal to ``other``. + Return whether ``self`` is not equal to ``other``. EXAMPLES:: @@ -546,7 +568,7 @@ def __ne__(self, other): def __repr__(self): r""" - Returns the string representation of the morphism. + Return the string representation of the morphism. EXAMPLES:: @@ -565,7 +587,7 @@ def __repr__(self): def __str__(self): r""" - Returns the morphism in str. + Return the morphism in str. EXAMPLES:: @@ -603,7 +625,7 @@ def __str__(self): def __call__(self, w, order=1, datatype=None): r""" - Returns the image of ``w`` under self to the given order. + Return the image of ``w`` under self to the given order. INPUT: @@ -920,7 +942,7 @@ def _latex_(self): def __mul__(self, other): r""" - Returns the morphism ``self``\*``other``. + Return the morphism ``self``\*``other``. EXAMPLES:: @@ -976,7 +998,7 @@ def __mul__(self, other): def __pow__(self, exp): r""" - Returns the power of ``self`` with exponent = ``exp``. + Return the power of ``self`` with exponent = ``exp``. INPUT: @@ -1033,7 +1055,7 @@ def __pow__(self, exp): def extend_by(self, other): r""" - Returns ``self`` extended by ``other``. + Return ``self`` extended by ``other``. Let `\varphi_1:A^*\rightarrow B^*` and `\varphi_2:C^*\rightarrow D^*` be two morphisms. A morphism `\mu:(A\cup C)^*\rightarrow (B\cup D)^*` @@ -1083,7 +1105,7 @@ def extend_by(self, other): def restrict_domain(self, alphabet): r""" - Returns a restriction of ``self`` to the given alphabet. + Return a restriction of ``self`` to the given alphabet. INPUT: @@ -1116,7 +1138,7 @@ def restrict_domain(self, alphabet): def _matrix_(self, R=None): r""" - Returns the incidence matrix of the morphism over the specified ring. + Return the incidence matrix of the morphism over the specified ring. EXAMPLES:: @@ -1146,7 +1168,7 @@ def _matrix_(self, R=None): def incidence_matrix(self): r""" - Returns the incidence matrix of the morphism. The order of the rows + Return the incidence matrix of the morphism. The order of the rows and column are given by the order defined on the alphabet of the domain and the codomain. @@ -1179,7 +1201,7 @@ def incidence_matrix(self): def domain(self): r""" - Returns domain of ``self``. + Return domain of ``self``. EXAMPLES:: @@ -1194,7 +1216,7 @@ def domain(self): def codomain(self): r""" - Returns the codomain of ``self``. + Return the codomain of ``self``. EXAMPLES:: @@ -1207,7 +1229,8 @@ def codomain(self): def is_endomorphism(self): r""" - Returns ``True`` if the codomain is a subset of the domain. + Return whether ``self`` is an endomorphism, that is if the + domain coincide with the codomain. EXAMPLES:: @@ -1231,6 +1254,28 @@ def is_endomorphism(self): """ return self.codomain() == self.domain() + def is_self_composable(self): + r""" + Return whether the codomain of ``self`` is contained in the domain. + + EXAMPLES:: + + sage: f = WordMorphism('a->a,b->a') + sage: f.is_endomorphism() + False + sage: f.is_self_composable() + True + """ + Adom = self.domain().alphabet() + Acodom = self.codomain().alphabet() + if Adom == Acodom: + return True + if Adom.cardinality() < Acodom.cardinality(): + return False + if Adom.cardinality() == Infinity: + raise NotImplementedError + return all(a in Adom for a in Acodom) + def image(self, letter): r""" Return the image of a letter. @@ -1275,7 +1320,7 @@ def image(self, letter): def images(self): r""" - Returns the list of all the images of the letters of the alphabet + Return the list of all the images of the letters of the alphabet under ``self``. EXAMPLES:: @@ -1289,7 +1334,7 @@ def images(self): def reversal(self): r""" - Returns the reversal of ``self``. + Return the reversal of ``self``. EXAMPLES:: @@ -1302,7 +1347,7 @@ def reversal(self): def is_empty(self): r""" - Returns ``True`` if the cardinality of the domain is zero and + Return ``True`` if the cardinality of the domain is zero and ``False`` otherwise. EXAMPLES:: @@ -1316,7 +1361,7 @@ def is_empty(self): def is_erasing(self): r""" - Returns ``True`` if ``self`` is an erasing morphism, i.e. the image of a + Return ``True`` if ``self`` is an erasing morphism, i.e. the image of a letter is the empty word. EXAMPLES:: @@ -1337,7 +1382,7 @@ def is_erasing(self): def is_identity(self): r""" - Returns ``True`` if ``self`` is the identity morphism. + Return ``True`` if ``self`` is the identity morphism. EXAMPLES:: @@ -1379,7 +1424,7 @@ def is_identity(self): def partition_of_domain_alphabet(self): r""" - Returns a partition of the domain alphabet. + Return a partition of the domain alphabet. Let `\varphi:\Sigma^*\rightarrow\Sigma^*` be an involution. There exists a triple of sets `(A, B, C)` such that @@ -1441,7 +1486,7 @@ def partition_of_domain_alphabet(self): def is_involution(self): r""" - Returns ``True`` if ``self`` is an involution, i.e. its square + Return ``True`` if ``self`` is an involution, i.e. its square is the identity. INPUT: @@ -1473,7 +1518,7 @@ def is_involution(self): def pisot_eigenvector_right(self): r""" - Returns the right eigenvector of the incidence matrix associated + Return the right eigenvector of the incidence matrix associated to the largest eigenvalue (in absolute value). Unicity of the result is guaranteed when the multiplicity of the @@ -1503,7 +1548,7 @@ def pisot_eigenvector_right(self): def pisot_eigenvector_left(self): r""" - Returns the left eigenvector of the incidence matrix associated + Return the left eigenvector of the incidence matrix associated to the largest eigenvalue (in absolute value). Unicity of the result is guaranteed when the multiplicity of the @@ -1533,7 +1578,7 @@ def pisot_eigenvector_left(self): def _check_primitive(self): r""" - Returns ``True`` if all the letters of the domain appear in all the + Return ``True`` if all the letters of the domain appear in all the images of letters of the domain. INPUT: @@ -1564,7 +1609,7 @@ def _check_primitive(self): def is_primitive(self): r""" - Returns ``True`` if ``self`` is primitive. + Return ``True`` if ``self`` is primitive. A morphism `\varphi` is *primitive* if there exists an positive integer `k` such that for all `\alpha\in\Sigma`, @@ -1634,7 +1679,7 @@ def is_primitive(self): def is_prolongable(self, letter): r""" - Returns ``True`` if ``self`` is prolongable on ``letter``. + Return ``True`` if ``self`` is prolongable on ``letter``. A morphism `\varphi` is prolongable on a letter `a` if `a` is a prefix of `\varphi(a)`. @@ -1693,7 +1738,7 @@ def is_prolongable(self, letter): def is_uniform(self, k=None): r""" - Returns True if self is a `k`-uniform morphism. + Return True if self is a `k`-uniform morphism. Let `k` be a positive integer. A morphism `\phi` is called `k`-uniform if for every letter `\alpha`, we have `|\phi(\alpha)| = k`. In other @@ -1728,14 +1773,15 @@ def is_uniform(self, k=None): def fixed_point(self, letter): r""" - Returns the fixed point of ``self`` beginning by the given ``letter``. + Return the fixed point of ``self`` beginning by the given ``letter``. A fixed point of morphism `\varphi` is a word `w` such that `\varphi(w) = w`. INPUT: - - ``self`` - an endomorphism, must be prolongable on ``letter`` + - ``self`` - an endomorphism (or more generally a self-composable + morphism), must be prolongable on ``letter`` - ``letter`` - in the domain of ``self``, the first letter of the fixed point. @@ -1790,6 +1836,14 @@ def fixed_point(self, letter): sage: (m^2).fixed_point(letter='a') word: abbabaabbaababbabaababbaabbabaabbaababba... + 6. With a self-composable but not endomorphism + + sage: m = WordMorphism('a->cbc,b->bc,c->b') + sage: m.is_endomorphism() + False + sage: m.fixed_point('b') + word: bcbbcbcbbcbbcbcbbcbcbbcbbcbcbbcbbcbcbbcb... + TESTS:: sage: WordMorphism('a->ab,b->,c->ba', codomain=W).fixed_point(letter='b') @@ -1807,10 +1861,10 @@ def fixed_point(self, letter): sage: WordMorphism('a->aa,b->aac').fixed_point(letter='a') Traceback (most recent call last): ... - TypeError: self (=a->aa, b->aac) is not an endomorphism + TypeError: self (=a->aa, b->aac) is not self-composable """ - if not self.is_endomorphism(): - raise TypeError("self (=%s) is not an endomorphism"%self) + if not self.is_self_composable(): + raise TypeError("self (=%s) is not self-composable"%self) if not self.is_prolongable(letter=letter): raise TypeError("self must be prolongable on %s"%letter) @@ -1832,7 +1886,7 @@ def fixed_point(self, letter): def fixed_points(self): r""" - Returns the list of all fixed points of ``self``. + Return the list of all fixed points of ``self``. EXAMPLES:: @@ -1959,7 +2013,8 @@ def periodic_points(self): sage: WordMorphism('a->a,b->bb').periodic_points() [[word: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...]] """ - assert self.is_endomorphism(), "f should be an endomorphism" + if not self.is_endomorphism(): + raise ValueError("f should be an endomorphism") if self.is_erasing(): raise NotImplementedError("f should be non erasing") @@ -2136,7 +2191,7 @@ def language(self, n, u=None): def conjugate(self, pos): r""" - Returns the morphism where the image of the letter by ``self`` + Return the morphism where the image of the letter by ``self`` is conjugated of parameter ``pos``. INPUT: @@ -2162,7 +2217,7 @@ def conjugate(self, pos): def has_left_conjugate(self): r""" - Returns ``True`` if all the non empty images of ``self`` begins with + Return ``True`` if all the non empty images of ``self`` begins with the same letter. EXAMPLES:: @@ -2197,7 +2252,7 @@ def has_left_conjugate(self): def has_right_conjugate(self): r""" - Returns ``True`` if all the non empty images of ``self`` ends with the + Return ``True`` if all the non empty images of ``self`` ends with the same letter. EXAMPLES:: @@ -2220,7 +2275,7 @@ def has_right_conjugate(self): def list_of_conjugates(self): r""" - Returns the list of all the conjugate morphisms of ``self``. + Return the list of all the conjugate morphisms of ``self``. DEFINITION: @@ -2307,7 +2362,7 @@ def list_of_conjugates(self): def is_in_classP(self, f=None): r""" - Returns ``True`` if ``self`` is in class `P` (or `f`-`P`). + Return ``True`` if ``self`` is in class `P` (or `f`-`P`). DEFINITION : Let `A` be an alphabet. We say that a primitive substitution `S` is in the *class P* if there @@ -2385,7 +2440,7 @@ def is_in_classP(self, f=None): def has_conjugate_in_classP(self, f=None): r""" - Returns ``True`` if ``self`` has a conjugate in class `f`-`P`. + Return ``True`` if ``self`` has a conjugate in class `f`-`P`. DEFINITION : Let `A` be an alphabet. We say that a primitive substitution `S` is in the *class P* if there @@ -2476,7 +2531,7 @@ def dual_map(self, k=1): @cached_method def rauzy_fractal_projection(self, eig=None, prec=53): r""" - Returns a dictionary giving the projection of the canonical basis. + Return a dictionary giving the projection of the canonical basis. See the method :meth:`rauzy_fractal_plot` for more details about the projection. @@ -2589,7 +2644,7 @@ def rauzy_fractal_projection(self, eig=None, prec=53): def rauzy_fractal_points(self, n=None, exchange=False, eig=None, translate=None, prec=53): r""" - Returns a dictionary of list of points associated with the pieces + Return a dictionary of list of points associated with the pieces of the Rauzy fractal of ``self``. INPUT: @@ -2698,7 +2753,7 @@ def rauzy_fractal_points(self, n=None, exchange=False, eig=None, translate=None, def rauzy_fractal_plot(self, n=None, exchange=False, eig=None, translate=None, prec=53, \ colormap='hsv', opacity=None, plot_origin=None, plot_basis=False, point_size=None): r""" - Returns a plot of the Rauzy fractal associated with a substitution. + Return a plot of the Rauzy fractal associated with a substitution. The substitution does not have to be irreducible. The usual definition of a Rauzy fractal requires that @@ -3089,11 +3144,15 @@ def growing_letters(self): no_loops.difference_update(cycle) new_morph = {x: [z for z in new_morph[x] if z in no_loops] for x in no_loops} - # Remove letters ending in a cycle # NOTE: here we should actually be using the domain made of the # remaining letters in new_morph. However, building the corresponding # alphabet and finite words cost much more time than using the same - # domain. + # domain. Instead we just erase the corresponding letters. + for a in self._domain.alphabet(): + if a not in new_morph: + new_morph[a] = self._codomain() + + # Remove letters ending in a cycle new_morph = WordMorphism(new_morph, domain=self.domain(), codomain=self.codomain()) return new_morph.immortal_letters() @@ -3104,7 +3163,7 @@ def immortal_letters(self): A letter `a` is *immortal* for the morphism `s` if the length of the iterates of `| s^n(a) |` is larger than zero as `n` goes to infinity. - Requires this morphism to be an endomorphism. + Requires this morphism to be self-composable. EXAMPLES:: @@ -3118,9 +3177,12 @@ def immortal_letters(self): ['a', 'b'] sage: WordMorphism('a->', domain=Words('a'), codomain=Words('a')).immortal_letters() [] + + sage: WordMorphism('a->').immortal_letters() + [] """ - if not self.is_endomorphism(): - raise TypeError(f'self ({self}) is not an endomorphism') + if not self.is_self_composable(): + raise TypeError(f'self ({self}) is not an self-composable') forward = {} backward = {letter: set() for letter in self._morph} @@ -3146,9 +3208,149 @@ def immortal_letters(self): return sorted(forward, key=self.domain().alphabet().rank) + def letter_growth_types(self): + r""" + Return the mortal, polynomial and exponential growing letters. + + The growth of `| s^n(a) |` as `n` goes to `\infty` is always of the + form `\alpha^n n^\beta` (where `\alpha` is a Perron number and + `\beta` an integer). + + Without doing any linear algebra three cases can be differentiated: + mortal (ultimately empty or `\alpha=0`); polynomial (`\alpha=1`); + exponential (`\alpha > 1`). This is what is done in this method. + + It requires this morphism to be an endomorphism. + + OUTPUT: + + The output is a 3-tuple of lists (mortal, polynomial, exponential) + where: + + - ``mortal``: list of mortal letters + - ``polynomial``: a list of lists where ``polynomial[i]`` is the + list of letters with growth `n^i`. + - ``exponential``: list of at least exponentionally growing letters + + EXAMPLES:: + + sage: s = WordMorphism('a->abc,b->bc,c->c') + sage: mortal, poly, expo = s.letter_growth_types() + sage: mortal + [] + sage: poly + [['c'], ['b'], ['a']] + sage: expo + [] + + When three mortal letters (c, d, and e), and two letters (a, b) are + not growing:: + + sage: s = WordMorphism('a->bc,b->cac,c->de,d->,e->') + sage: s^20 + WordMorphism: a->cacde, b->debcde, c->, d->, e-> + sage: mortal, poly, expo = s.letter_growth_types() + sage: mortal + ['c', 'd', 'e'] + sage: poly + [['a', 'b']] + sage: expo + [] + + :: + + sage: s = WordMorphism('a->abcd,b->bc,c->c,d->a') + sage: mortal, poly, expo = s.letter_growth_types() + sage: mortal + [] + sage: poly + [['c'], ['b']] + sage: expo + ['a', 'd'] + + TESTS:: + + sage: s = WordMorphism('a->a') + sage: s.letter_growth_types() + ([], [['a']], []) + + :: + + sage: s = WordMorphism('a->b,b->a') + sage: s.letter_growth_types() + ([], [['a', 'b']], []) + + :: + + sage: s = WordMorphism('a->abcd,b->cd,c->dd,d->') + sage: s.letter_growth_types() + (['b', 'c', 'd'], [['a']], []) + + :: + + sage: s = WordMorphism('a->', domain=Words('a'), codomain=Words('a')) + sage: s.letter_growth_types() + (['a'], [], []) + """ + immortal = set(self.immortal_letters()) + mortal = [a for a in self.domain().alphabet() + if a not in immortal] + + # Starting with degree d=0, search for letters with polynomial + # growth of degree d. + polynomial = [] + m = {a : [b for b in self.image(a) if b in immortal] for a in immortal} + while True: + # Construct the permutation of letters containing all letters whose + # iterated images under morphism m is always of length 1. + not_growing = {a : image_a[0] for (a,image_a) in m.items() if len(image_a) == 1} + preimages = {} + roots = [] + for k, v in not_growing.items(): + if v not in not_growing: + roots.append(v) + if v not in preimages: + preimages[v] = [] + preimages[v].append(k) + + while roots: + v = roots.pop() + for k in preimages.get(v): + del not_growing[k] + if k in preimages: + roots.append(k) + + # The letters inside not_growing are the ones with polynomial + # growth d. If there is none, then the remaining letters in m + # have exponential growth. + if not not_growing: + break + polynomial.append(list(not_growing)) + + # clean the morphism m for the next iteration by removing the + # letters with polynomial growth degree d + m = {a : [b for b in L if b not in not_growing] for a, L in m.items() + if a not in not_growing} + + exponential = list(m) + + # sort the letters as in the input alphabet if possible + A = self.domain().alphabet() + try: + rank = A.rank + except AttributeError: + pass + else: + mortal.sort(key=rank) + for letters in polynomial: + letters.sort(key=rank) + exponential.sort(key=rank) + + return mortal, polynomial, exponential + def abelian_rotation_subspace(self): r""" - Returns the subspace on which the incidence matrix of ``self`` acts by + Return the subspace on which the incidence matrix of ``self`` acts by roots of unity. EXAMPLES:: diff --git a/src/sage/cpython/getattr.pyx b/src/sage/cpython/getattr.pyx index 25c9a1a0aaf..04203a483fc 100644 --- a/src/sage/cpython/getattr.pyx +++ b/src/sage/cpython/getattr.pyx @@ -310,8 +310,6 @@ cpdef getattr_from_other_class(self, cls, name): sage: "__weakref__" in dir(A) True - sage: "__weakref__" in dir(1) # py2 - False sage: 1.__weakref__ Traceback (most recent call last): ... diff --git a/src/sage/crypto/boolean_function.pyx b/src/sage/crypto/boolean_function.pyx index 647bdf760ca..4abe16bbf1f 100644 --- a/src/sage/crypto/boolean_function.pyx +++ b/src/sage/crypto/boolean_function.pyx @@ -313,14 +313,14 @@ cdef class BooleanFunction(SageObject): raise ValueError("the length of the truth table must be a power of 2") # then, initialize our bitset - bitset_init(self._truth_table, L) + bitset_init(self._truth_table, L) for 0<= i < L: bitset_set_to(self._truth_table, i, x[i])#int(x[i])&1) elif isinstance(x, BooleanPolynomial): # initialisation from a Boolean polynomial self._nvariables = ZZ(x.parent().ngens()) - bitset_init(self._truth_table, (1< (1< (1< (1< (1<x)._truth_table) else: raise TypeError("unable to init the Boolean function") @@ -504,7 +504,7 @@ cdef class BooleanFunction(SageObject): """ cdef bitset_t anf cdef mp_bitcnt_t i, inf, sup, j - bitset_init(anf, (1< (1< capacity) def __dealloc__(self): """ @@ -1796,11 +1796,11 @@ cdef class Bitset(FrozenBitset): sage: a.remove(2) Traceback (most recent call last): ... - KeyError: 2L + KeyError: 2 sage: a.remove(4) Traceback (most recent call last): ... - KeyError: 4L + KeyError: 4 sage: a 100 sage: a = Bitset('000001' * 15); sorted(list(a)) @@ -2286,7 +2286,7 @@ def test_bitset_remove(py_a, long n): sage: test_bitset_remove('01', 0) Traceback (most recent call last): ... - KeyError: 0L + KeyError: 0 sage: test_bitset_remove('01', 1) a 01 a.size 2 diff --git a/src/sage/data_structures/bitset_base.pxd b/src/sage/data_structures/bitset_base.pxd index 44baaedaec9..e14d1976826 100644 --- a/src/sage/data_structures/bitset_base.pxd +++ b/src/sage/data_structures/bitset_base.pxd @@ -32,8 +32,9 @@ AUTHORS: # The python wrapper and all the doctests are in bitset.pyx/bitset.pxd. from libc.string cimport strlen -from cysignals.memory cimport check_calloc, check_reallocarray, sig_malloc, sig_free +from cysignals.memory cimport check_calloc, check_allocarray, check_reallocarray, sig_malloc, sig_free from memory_allocator cimport MemoryAllocator +from memory_allocator.memory_allocator cimport align from cython.operator import preincrement as preinc from sage.cpython.string cimport char_to_str, str_to_bytes, bytes_to_str @@ -160,7 +161,7 @@ cdef inline mp_limb_t limb_lower_bits_up(mp_bitcnt_t n): ############################################################################# # Bitset Initalization ############################################################################# -cdef inline bint bitset_init(bitset_t bits, mp_bitcnt_t size) except -1: +cdef inline bint bitset_init(fused_bitset_t bits, mp_bitcnt_t size) except -1: """ Allocate an empty bitset of size ``size``. @@ -169,27 +170,19 @@ cdef inline bint bitset_init(bitset_t bits, mp_bitcnt_t size) except -1: if size <= 0: raise ValueError("bitset capacity must be greater than 0") - bits.size = size - bits.limbs = (size - 1) / (8 * LIMB_SIZE) + 1 - bits.bits = check_calloc(bits.limbs, LIMB_SIZE) - -cdef inline bint bitset_init_with_allocator(fused_bitset_t bits, mp_bitcnt_t size, MemoryAllocator mem) except -1: - """ - Allocate an empty bitset of size ``size``. - - Size must be at least 1. - - Note that ``sparse_bitset_t`` is assumed to be allocated over-aligned. - """ - if size <= 0: - raise ValueError("bitset capacity must be greater than 0") + cdef mp_bitcnt_t extra bits.size = size - bits.limbs = ((size - 1) / (8*ALIGNMENT) + 1) * (ALIGNMENT/LIMB_SIZE) - bits.bits = mem.aligned_calloc(ALIGNMENT, bits.limbs, LIMB_SIZE) - if fused_bitset_t is sparse_bitset_t: + if fused_bitset_t is bitset_t: + bits.limbs = (size - 1) / (8 * LIMB_SIZE) + 1 + bits.bits = check_calloc(bits.limbs, LIMB_SIZE) + else: + bits.limbs = ((size - 1) / (8*ALIGNMENT) + 1) * (ALIGNMENT/LIMB_SIZE) + extra = (ALIGNMENT + LIMB_SIZE - 2) // LIMB_SIZE + bits.mem = check_calloc(bits.limbs + extra, LIMB_SIZE) + bits.bits = align(bits.mem, ALIGNMENT) bits.non_zero_chunks_are_initialized = False - bits.non_zero_chunks = mem.allocarray((bits.limbs*LIMB_SIZE)/ALIGNMENT, sizeof(mp_bitcnt_t)) + bits.non_zero_chunks = check_allocarray((bits.limbs*LIMB_SIZE) / ALIGNMENT, sizeof(mp_bitcnt_t)) cdef inline bint bitset_check_alignment(fused_bitset_t bits): """ @@ -222,11 +215,15 @@ cdef inline int bitset_realloc(bitset_t bits, mp_bitcnt_t size) except -1: # Zero removed bits bitset_fix(bits) -cdef inline void bitset_free(bitset_t bits): +cdef inline void bitset_free(fused_bitset_t bits): """ Deallocate the memory in bits. """ - sig_free(bits.bits) + if fused_bitset_t is bitset_t: + sig_free(bits.bits) + else: + sig_free(bits.mem) + sig_free(bits.non_zero_chunks) cdef inline void bitset_clear(fused_bitset_t bits): """ diff --git a/src/sage/data_structures/sparse_bitset.pxd b/src/sage/data_structures/sparse_bitset.pxd index 26fca207eca..ce1a0f0a468 100644 --- a/src/sage/data_structures/sparse_bitset.pxd +++ b/src/sage/data_structures/sparse_bitset.pxd @@ -43,6 +43,9 @@ cdef struct sparse_bitset_s: # NOTE: ``sparse_bitset_t`` is assumed to be allocated over-aligned. mp_limb_t* bits + # Pointer to the memory of ``bits``. + void* mem + # Storing the non zero positions can safe time, when performing # multiple comparisons. # E.g. one can set them while computing the intersection diff --git a/src/sage/databases/findstat.py b/src/sage/databases/findstat.py index ada9bb53ee0..ffc1e5a4bfd 100644 --- a/src/sage/databases/findstat.py +++ b/src/sage/databases/findstat.py @@ -46,7 +46,7 @@ For example:: sage: PM = PerfectMatchings - sage: r = findstat([(m, m.number_of_nestings()) for n in range(6) for m in PM(2*n)]); r # optional -- internet + sage: r = findstat([(m, m.number_of_nestings()) for n in range(6) for m in PM(2*n)], depth=1); r # optional -- internet 0: St000042oMp00116 (quality [100, 100]) 1: St000041 (quality [20, 100]) ... @@ -60,7 +60,8 @@ The composition `f_n \circ ... \circ f_2 \circ f_1` applied to the objects sent to FindStat agrees with all ``(object, value)`` - pairs of `s` in the database. + pairs of `s` in the database. The optional parameter ``depth=1`` + limits the output to `n=1`. Suppose that the quality of the match is `(q_a, q_d)`. Then `q_a` is the percentage of ``(object, value)`` pairs that are in @@ -99,7 +100,8 @@ statistics. Instead of submitting a list of ``(object, value)`` pairs, we pass a list of pairs ``(objects, values)``:: - sage: r = findstat([(PM(2*n), [m.number_of_nestings() for m in PM(2*n)]) for n in range(5)], depth=0); r # optional -- internet + sage: data = [(PM(2*n), [m.number_of_nestings() for m in PM(2*n)]) for n in range(5)] + sage: findstat(data, depth=0) # optional -- internet 0: St000041 (quality [99, 100]) 1: St000042 (quality [99, 100]) @@ -116,9 +118,7 @@ advertise yet another way to pass values to FindStat:: sage: r = findstat(Permutations, lambda pi: pi.saliances()[0], depth=2); r # optional -- internet - 0: St000740oMp00066 with offset 1 (quality [100, 100]) - ... - 7: St000051oMp00061oMp00069 (quality [87, 86]) + 0: St000740oMp00087 with offset 1 (quality [100, 100]) ... Note that some of the matches are up to a global offset. For @@ -127,7 +127,7 @@ sage: r[0].info() # optional -- internet after adding 1 to every value and applying - Mp00066: inverse: Permutations -> Permutations + Mp00087: inverse first fundamental transformation: Permutations -> Permutations to the objects (see `.compound_map()` for details) your input matches @@ -138,20 +138,11 @@ Let us pick another particular result:: - sage: s = next(s for s in r if s.statistic().id() == 51); s # optional -- internet - St000051oMp00061oMp00069 (quality [87, 86]) - + sage: s = findstat("St000051oMp00061oMp00069") # optional -- internet sage: s.info() # optional -- internet - after applying Mp00069: complement: Permutations -> Permutations Mp00061: to increasing tree: Permutations -> Binary trees - to the objects (see `.compound_map()` for details) - - your input matches St000051: The size of the left subtree of a binary tree. - - among the values you sent, 87 percent are actually in the database, - among the distinct values you sent, 86 percent are actually in the database To obtain the value of the statistic sent to FindStat on a given object, apply the maps in the list in the given order to this object, @@ -777,9 +768,9 @@ def _distribution_from_data(data, domain, max_values, generating_functions=False TESTS:: sage: from sage.databases.findstat import _distribution_from_data, FindStatCollection + sage: n = 3; l = lambda i: [pi for pi in Permutations(n) if pi(1) == i] + sage: data = [([pi for pi in l(i)], [pi(1) for pi in l(i)]) for i in range(1,n+1)] sage: cc = FindStatCollection(1) # optional -- internet - sage: n = 3; l = lambda i: [pi for pi in Permutations(n) if pi(1) == i] # optional -- internet - sage: data = [([pi for pi in l(i)], [pi(1) for pi in l(i)]) for i in range(1,n+1)] # optional -- internet sage: _distribution_from_data(data, cc, 10) # optional -- internet [([[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]], [1, 1, 2, 2, 3, 3])] @@ -973,12 +964,12 @@ def findstat(query=None, values=None, distribution=None, domain=None, The database can be searched by providing a list of pairs:: sage: l = [m for n in range(1, 4) for m in PerfectMatchings(2*n)] - sage: q = findstat([(m, m.number_of_nestings()) for m in l], depth=0); q # optional -- internet + sage: findstat([(m, m.number_of_nestings()) for m in l], depth=0) # optional -- internet 0: St000041 (quality [100, 100]) or a dictionary:: - sage: p = findstat({m: m.number_of_nestings() for m in l}, depth=0); p # optional -- internet + sage: findstat({m: m.number_of_nestings() for m in l}, depth=0) # optional -- internet 0: St000041 (quality [100, 100]) Note however, that the results of these two queries need not @@ -1050,7 +1041,7 @@ def findstat(query=None, values=None, distribution=None, domain=None, Check that ``None`` values are omitted:: - sage: findstat("graphs", lambda g: g.diameter() if g.is_connected() else None, max_values=100) # optional -- internet + sage: findstat("graphs", lambda g: g.diameter() if g.is_connected() else None, max_values=100, depth=0) # optional -- internet 0: St000259 (quality [100, 100]) """ try: @@ -1228,13 +1219,13 @@ def findmap(*args, **kwargs): The database can be searched by providing a list of pairs:: sage: l = [pi for n in range(5) for pi in Permutations(n)] - sage: q = findmap([(pi, pi.complement().increasing_tree_shape()) for pi in l], depth=2); q # optional -- internet - 0: Mp00061oMp00069 (quality [100]) + sage: findmap([(pi, pi.complement().increasing_tree_shape()) for pi in l], depth=2) # optional -- internet + 0: Mp00061oMp00069 (quality [...]) or a dictionary:: - sage: p = findmap({pi: pi.complement().increasing_tree_shape() for pi in l}, depth=2); p # optional -- internet - 0: Mp00061oMp00069 (quality [100]) + sage: findmap({pi: pi.complement().increasing_tree_shape() for pi in l}, depth=2) # optional -- internet + 0: Mp00061oMp00069 (quality [...]) Note however, that the results of these two queries need not compare equal, because we compare queries by the data @@ -1437,7 +1428,7 @@ def __init__(self, id, data=None, function=None): TESTS:: - sage: from sage.databases.findstat import FindStatFunction, FindStatCollection # optional -- internet + sage: from sage.databases.findstat import FindStatFunction, FindStatCollection sage: FindStatFunction("St000000", # optional -- internet ....: data={"Bibliography": {}, ....: "Code": "", @@ -1473,7 +1464,7 @@ def _data(self): TESTS:: - sage: from sage.databases.findstat import FindStatFunction, FindStatCollection # optional -- internet + sage: from sage.databases.findstat import FindStatFunction, FindStatCollection sage: FindStatFunction("St000000", # optional -- internet ....: data={"Bibliography": {}, ....: "Code": "", @@ -1502,7 +1493,8 @@ def __call__(self, elt): EXAMPLES:: - sage: q = findstat("graphs", lambda g: g.diameter() if g.is_connected() else None, max_values=100) # optional -- internet + sage: s = lambda g: g.diameter() if g.is_connected() else None + sage: q = findstat("graphs", s, max_values=100) # optional -- internet sage: q(graphs.PetersenGraph().copy(immutable=True)) # optional -- internet 2 """ @@ -2139,7 +2131,8 @@ def __call__(self, elt): EXAMPLES:: - sage: q = findstat("graphs", lambda g: g.diameter() if g.is_connected() else None, max_values=100) # optional -- internet + sage: s = lambda g: g.diameter() if g.is_connected() else None + sage: q = findstat("graphs", s, max_values=100) # optional -- internet sage: q(graphs.PetersenGraph().copy(immutable=True)) # optional -- internet 2 """ @@ -2377,6 +2370,17 @@ def __hash__(self): """ return self.id() + def info(self): + """ + Print a detailed description of the statistic. + + EXAMPLES:: + + sage: findstat("St000042").info() # optional -- internet + St000042: The number of crossings of a perfect matching. + """ + print(" %s" % self) + _all_statistics = {} class FindStatStatistics(UniqueRepresentation, Parent): @@ -2534,13 +2538,12 @@ def __init__(self, data=None, values_of=None, distribution_of=None, EXAMPLES:: sage: from sage.databases.findstat import FindStatStatisticQuery - sage: data = [[[m],[m.number_of_nestings()]] for n in range(5) for m in PerfectMatchings(2*n)] # optional -- internet + sage: data = [[[m], [m.number_of_nestings()]] for n in range(5) for m in PerfectMatchings(2*n)] sage: FindStatStatisticQuery(domain=12, data=data, depth=1) # optional -- internet 0: St000041 (quality [99, 100]) - 1: St000042oMp00116 (quality [99, 100]) - 2: St000233oMp00092 (quality [99, 100]) - 3: St000496oMp00092 (quality [99, 100]) - 4: St001513oMp00058 (quality [15, 57]) + 1: St000041oMp00113 (quality [99, 100]) + 2: St000042oMp00116 (quality [99, 100]) + ... """ self._first_terms = data if data is not None and known_terms is None: @@ -2674,7 +2677,8 @@ def __repr__(self): EXAMPLES:: sage: PM = PerfectMatchings - sage: r = findstat([(m, m.number_of_nestings()) for n in range(6) for m in PM(2*n)], depth=1); r # optional -- internet + sage: data = [(m, m.number_of_nestings()) for n in range(6) for m in PM(2*n)] + sage: findstat(data, depth=1) # optional -- internet 0: St000042oMp00116 (quality [100, 100]) 1: St000041 (quality [20, 100]) ... @@ -2690,7 +2694,8 @@ def __getitem__(self, i): EXAMPLES:: sage: PM = PerfectMatchings - sage: r = findstat([(m, m.number_of_nestings()) for n in range(6) for m in PM(2*n)], depth=1) # optional -- internet + sage: data = [(m, m.number_of_nestings()) for n in range(6) for m in PM(2*n)] + sage: r = findstat(data, depth=1) # optional -- internet sage: r[1] # optional -- internet St000041 (quality [20, 100]) """ @@ -2702,6 +2707,8 @@ def __init__(self, id, domain=None, check=True): """ Initialize a compound statistic. + A compound statistic is a sequence of maps followed by a statistic. + INPUT: - ``id`` -- a padded identifier @@ -2852,6 +2859,21 @@ def browse(self): webbrowser.open(FINDSTAT_URL_STATISTICS + self.id_str()) + def info(self): + """ + Print a detailed description of the compound statistic. + + EXAMPLES:: + + sage: findstat("St000042oMp00116").info() # optional -- internet + Mp00116: Kasraoui-Zeng: Perfect matchings -> Perfect matchings + St000042: The number of crossings of a perfect matching. + """ + if len(self.compound_map()): + self.compound_map().info() + self.statistic().info() + + class FindStatMatchingStatistic(FindStatCompoundStatistic): def __init__(self, matching_statistic, offset, quality, domain=None): """ @@ -2966,14 +2988,11 @@ def info(self): print("and applying") else: print("after applying") - for mp in self.compound_map(): - print(" %s: %s -> %s" % (mp, - mp.domain().name("plural"), - mp.codomain().name("plural"))) + self.compound_map().info() print("to the objects (see `.compound_map()` for details)") print() print("your input matches") - print(" %s" % self.statistic()) + self.statistic().info() print() print("among the values you sent, %s percent are actually in the database," % self.quality()[0]) print("among the distinct values you sent, %s percent are actually in the database" % self.quality()[1]) @@ -3238,6 +3257,19 @@ def set_name(self, value): self._modified = True self._data_cache["Name"] = value + def info(self): + """ + Print a detailed description of the map. + + EXAMPLES:: + + sage: findmap("Mp00116").info() # optional -- internet + Mp00116: Kasraoui-Zeng: Perfect matchings -> Perfect matchings + """ + print(" %s: %s -> %s" % (self, + self.domain().name("plural"), + self.codomain().name("plural"))) + _all_maps = {} class FindStatMaps(UniqueRepresentation, Parent): @@ -3427,7 +3459,8 @@ def __init__(self, data=None, values_of=None, distribution_of=None, EXAMPLES:: sage: from sage.databases.findstat import FindStatMapQuery - sage: FindStatMapQuery(domain=1, codomain=10, data=[[[pi],[pi.complement().increasing_tree_shape()]] for pi in Permutations(4)]) # optional -- internet + sage: data = [[[pi], [pi.complement().increasing_tree_shape()]] for pi in Permutations(4)] + sage: FindStatMapQuery(domain=1, codomain=10, data=data) # optional -- internet 0: Mp00061oMp00069 (quality [100]) """ self._first_terms = data @@ -3508,7 +3541,8 @@ def __repr__(self): EXAMPLES:: sage: from sage.databases.findstat import FindStatMapQuery - sage: FindStatMapQuery(domain=1, codomain=10, data=[[[pi],[pi.complement().increasing_tree_shape()]] for pi in Permutations(4)]) # optional -- internet + sage: data = [[[pi],[pi.complement().increasing_tree_shape()]] for pi in Permutations(4)] + sage: FindStatMapQuery(domain=1, codomain=10, data=data) # optional -- internet 0: Mp00061oMp00069 (quality [100]) """ if self._result: @@ -3522,7 +3556,8 @@ def __getitem__(self, i): EXAMPLES:: sage: from sage.databases.findstat import FindStatMapQuery - sage: r = FindStatMapQuery(domain=1, codomain=10, data=[[[pi],[pi.complement().increasing_tree_shape()]] for pi in Permutations(4)]) # optional -- internet + sage: data = [[[pi],[pi.complement().increasing_tree_shape()]] for pi in Permutations(4)] + sage: r = FindStatMapQuery(domain=1, codomain=10, data=data) # optional -- internet sage: r[0] # optional -- internet Mp00061oMp00069 (quality [100]) """ @@ -3700,6 +3735,19 @@ def browse(self): """ webbrowser.open(FINDSTAT_URL_MAPS + self.id_str()) + def info(self): + """ + Print a detailed explanation of the compound map. + + EXAMPLES:: + + sage: findmap("Mp00099oMp00127").info() # optional -- internet + Mp00127: left-to-right-maxima to Dyck path: Permutations -> Dyck paths + Mp00099: bounce path: Dyck paths -> Dyck paths + """ + for mp in self: + mp.info() + class FindStatMatchingMap(FindStatCompoundMap): def __init__(self, matching_map, quality, domain=None, codomain=None): @@ -3750,6 +3798,25 @@ def quality(self): """ return self._quality[:] + def info(self): + """ + Print a detailed explanation of the match. + + EXAMPLES:: + + sage: from sage.databases.findstat import FindStatMatchingMap + sage: FindStatMatchingMap("Mp00099oMp00127", [83]).info() # optional -- internet + your input matches + Mp00127: left-to-right-maxima to Dyck path: Permutations -> Dyck paths + Mp00099: bounce path: Dyck paths -> Dyck paths + + among the values you sent, 83 percent are actually in the database + """ + print("your input matches") + super().info() + print() + print("among the values you sent, %s percent are actually in the database" % self.quality()[0]) + ###################################################################### # collections diff --git a/src/sage/databases/sql_db.py b/src/sage/databases/sql_db.py index 5a14e45be94..14e229aaaff 100644 --- a/src/sage/databases/sql_db.py +++ b/src/sage/databases/sql_db.py @@ -245,7 +245,7 @@ def construct_skeleton(database): sage: G = SQLDatabase(GraphDatabase().__dblocation__, False) sage: from sage.databases.sql_db import construct_skeleton sage: sorted(construct_skeleton(G)) - [u'aut_grp', u'degrees', u'graph_data', u'misc', u'spectrum'] + ['aut_grp', 'degrees', 'graph_data', 'misc', 'spectrum'] """ skeleton = {} cur = database.__connection__.cursor() @@ -253,7 +253,7 @@ def construct_skeleton(database): from sage.env import GRAPHS_DATA_DIR for table in exe.fetchall(): skeleton[table[0]] = {} - exe1 = cur.execute("PRAGMA table_info(%s)"%table[0]) + exe1 = cur.execute("PRAGMA table_info(%s)" % table[0]) for col in exe1.fetchall(): if not col[2]: typ = u'NOTYPE' diff --git a/src/sage/docs/instancedoc.pyx b/src/sage/docs/instancedoc.pyx index 13fc059b06c..57f5e737b24 100644 --- a/src/sage/docs/instancedoc.pyx +++ b/src/sage/docs/instancedoc.pyx @@ -312,11 +312,7 @@ def instancedoc(cls): TypeError: expected type, got 7 sage: class OldStyle: pass - sage: instancedoc(OldStyle) # py2 - Traceback (most recent call last): - ... - TypeError: expected type, got - sage: instancedoc(OldStyle) # py3 + sage: instancedoc(OldStyle) Traceback (most recent call last): ... TypeError: instancedoc requires to have an '_instancedoc_' attribute diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 251e4f34a43..88bdb6e49b8 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -38,7 +38,7 @@ from .forker import DocTestDispatcher from .reporting import DocTestReporter from .util import Timer, count_noun, dict_difference -from .external import external_software, available_software +from .external import available_software from .parsing import parse_optional_tags nodoctest_regex = re.compile(r'\s*(#+|%+|r"+|"+|\.\.)\s*nodoctest') @@ -409,11 +409,6 @@ def __init__(self, options, args): options.optional.update(system.name for system in package_systems()) - logger = sys.stderr if options.verbose else None - from sage.features.sagemath import sage_features - options.optional.update(feature.name - for feature in sage_features(logger=logger)) - # Check that all tags are valid for o in options.optional: if not optionaltag_regex.search(o): @@ -928,7 +923,7 @@ def run_doctests(self): ---------------------------------------------------------------------- Total time for all tests: ... seconds cpu time: ... seconds - cumulative wall time: ... seconds + cumulative wall time: ... seconds... """ nfiles = 0 nother = 0 @@ -1004,6 +999,7 @@ def cleanup(self, final=True): Total time for all tests: ... seconds cpu time: ... seconds cumulative wall time: ... seconds + Features detected... 0 sage: DC.cleanup() """ @@ -1194,6 +1190,7 @@ def run(self): Total time for all tests: ... seconds cpu time: ... seconds cumulative wall time: ... seconds + Features detected... 0 We check that :trac:`25378` is fixed (testing external packages @@ -1207,7 +1204,7 @@ def run(self): sage: DC.run() Running doctests with ID ... Using --optional=external,sage - External software to be detected: ... + Features to be detected: ... Doctesting 1 file. sage -t ....py [0 tests, ... s] @@ -1217,7 +1214,7 @@ def run(self): Total time for all tests: ... seconds cpu time: ... seconds cumulative wall time: ... seconds - External software detected for doctesting:... + Features detected... 0 """ @@ -1247,18 +1244,16 @@ def run(self): pass self.log("Using --optional=" + self._optional_tags_string()) - if self.options.optional is True or 'external' in self.options.optional: - self.log("External software to be detected: " + ','.join(external_software)) - + available_software._allow_external = self.options.optional is True or 'external' in self.options.optional + self.log("Features to be detected: " + ','.join(available_software.detectable())) self.add_files() self.expand_files_into_sources() self.filter_sources() self.sort_sources() self.run_doctests() - if self.options.optional is True or 'external' in self.options.optional: - self.log("External software detected for doctesting: " - + ','.join(available_software.seen())) + self.log("Features detected for doctesting: " + + ','.join(available_software.seen())) self.cleanup() return self.reporter.error_status @@ -1286,6 +1281,7 @@ def run_doctests(module, options=None): Total time for all tests: ... seconds cpu time: ... seconds cumulative wall time: ... seconds + Features detected... """ import sys sys.stdout.flush() diff --git a/src/sage/doctest/external.py b/src/sage/doctest/external.py index cdd09eb464b..84dae19ea59 100644 --- a/src/sage/doctest/external.py +++ b/src/sage/doctest/external.py @@ -333,6 +333,26 @@ def has_4ti2(): from sage.features.four_ti_2 import FourTi2 return FourTi2().is_present() +def external_features(): + r""" + Generate the features that are only to be tested if ``--optional=external`` is used. + + EXAMPLES:: + + sage: from sage.doctest.external import external_features + sage: next(external_features()) + Feature('internet') + """ + from sage.features.internet import Internet + yield Internet() + import sage.features.latex + yield from sage.features.latex.all_features() + import sage.features.interfaces + yield from sage.features.interfaces.all_features() + from sage.features.mip_backends import CPLEX, Gurobi + yield CPLEX() + yield Gurobi() + def external_software(): """ Return the alphabetical list of external software supported by this module. @@ -343,28 +363,10 @@ def external_software(): sage: sorted(external_software) == external_software True """ - supported = list() - for func in globals(): - if func.startswith(prefix): - supported.append(func[len(prefix):]) - return sorted(supported) + return sorted(f.name for f in external_features()) external_software = external_software() -def _lookup(software): - """ - Test if the software is available on the system. - - EXAMPLES:: - - sage: sage.doctest.external._lookup('internet') # random, optional - internet - True - """ - if software in external_software: - func = globals().get(prefix + software) - return func() - else: - return False class AvailableSoftware(object): """ @@ -375,15 +377,11 @@ class AvailableSoftware(object): sage: from sage.doctest.external import external_software, available_software sage: external_software - ['4ti2', - 'cplex', - 'dvipng', - 'ffmpeg', - 'graphviz', + ['cplex', 'gurobi', - 'imagemagick', 'internet', 'latex', + 'latex_package_tkz_graph', 'lualatex', 'macaulay2', 'magma', @@ -391,10 +389,7 @@ class AvailableSoftware(object): 'mathematica', 'matlab', 'octave', - 'pandoc', - 'pdf2svg', 'pdflatex', - 'rubiks', 'scilab', 'xelatex'] sage: 'internet' in available_software # random, optional - internet @@ -413,10 +408,17 @@ def __init__(self): sage: S.seen() # random [] """ + self._allow_external = True # For multiprocessing of doctests, the data self._seen should be # shared among subprocesses. Thus we use Array class from the # multiprocessing module. - self._seen = Array('i', len(external_software)) # initialized to zeroes + from sage.features.all import all_features + self._external_features = set(external_features()) + features = set(self._external_features) + features.update(all_features()) + self._features = sorted(features, key=lambda feature: feature.name) + self._indices = {feature.name: idx for idx, feature in enumerate(self._features)} + self._seen = Array('i', len(self._features)) # initialized to zeroes def __contains__(self, item): """ @@ -429,11 +431,13 @@ def __contains__(self, item): True """ try: - idx = external_software.index(item) - except Exception: + idx = self._indices[item] + except KeyError: return False if not self._seen[idx]: - if _lookup(item): + if not self._allow_external and self._features[idx] in self._external_features: + self._seen[idx] = -1 # not available + elif self._features[idx].is_present(): self._seen[idx] = 1 # available else: self._seen[idx] = -1 # not available @@ -459,6 +463,14 @@ def issuperset(self, other): return False return True + def detectable(self): + """ + Return the list of names of those features for which testing their presence is allowed. + """ + return [feature.name + for feature in self._features + if self._allow_external or feature not in self._external_features] + def seen(self): """ Return the list of detected external software. @@ -469,6 +481,9 @@ def seen(self): sage: available_software.seen() # random ['internet', 'latex', 'magma'] """ - return [external_software[i] for i in range(len(external_software)) if self._seen[i] > 0] + return [feature.name + for feature, seen in zip(self._features, self._seen) + if seen > 0] + available_software = AvailableSoftware() diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 6690c627e75..6f49a480219 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1835,6 +1835,14 @@ def sel_exit(): # report(), parallel testing can easily fail # with a "Too many open files" error. w.save_result_output() + # In python3 multiprocessing.Process also + # opens a pipe internally, which has to be + # closed here, as well. + # But afterwards, exitcode and pid are + # no longer available. + w.copied_exitcode = w.exitcode + w.copied_pid = w.pid + w.close() finished.append(w) workers = new_workers @@ -1854,10 +1862,10 @@ def sel_exit(): self.controller.reporter.report( w.source, w.killed, - w.exitcode, + w.copied_exitcode, w.result, w.output, - pid=w.pid) + pid=w.copied_pid) pending_tests -= 1 @@ -2056,6 +2064,7 @@ def __init__(self, source, options, funclist=[]): Total time for all tests: ... seconds cpu time: ... seconds cumulative wall time: ... seconds + Features detected... """ multiprocessing.Process.__init__(self) @@ -2101,6 +2110,7 @@ def run(self): Total time for all tests: ... seconds cpu time: ... seconds cumulative wall time: ... seconds + Features detected... """ os.setpgid(os.getpid(), os.getpid()) diff --git a/src/sage/doctest/parsing.py b/src/sage/doctest/parsing.py index 4378c45c713..649fde044af 100644 --- a/src/sage/doctest/parsing.py +++ b/src/sage/doctest/parsing.py @@ -742,8 +742,7 @@ def parse(self, string, *args): if self.optional_tags is not True: extra = optional_tags - self.optional_tags # set difference if extra: - if not('external' in self.optional_tags - and available_software.issuperset(extra)): + if not available_software.issuperset(extra): continue elif self.optional_only: self.optionals['sage'] += 1 diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index f51e347670a..67439257047 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -115,9 +115,9 @@ def __init__(self, controller): self.stats = {} self.error_status = 0 - def have_optional_tag(self, tag): + def were_doctests_with_optional_tag_run(self, tag): r""" - Return whether doctests marked with this tag are run. + Return whether doctests marked with this tag were run. INPUT: @@ -135,17 +135,25 @@ def have_optional_tag(self, tag): :: - sage: DTR.have_optional_tag('sage') + sage: DTR.were_doctests_with_optional_tag_run('sage') True - sage: DTR.have_optional_tag('nice_unavailable_package') + sage: DTR.were_doctests_with_optional_tag_run('nice_unavailable_package') False + When latex is available, doctests marked with optional tag + ``latex`` are run by default since :trac:`32174`:: + + sage: filename = os.path.join(SAGE_SRC,'sage','misc','latex.py') + sage: DC = DocTestController(DocTestDefaults(),[filename]) + sage: DTR = DocTestReporter(DC) + sage: DTR.were_doctests_with_optional_tag_run('latex') # optional - latex + True + """ if self.controller.options.optional is True or tag in self.controller.options.optional: return True - if 'external' in self.controller.options.optional: - if tag in available_software.seen(): - return True + if tag in available_software.seen(): + return True return False def report_head(self, source): @@ -495,7 +503,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): if self.controller.options.show_skipped: log(" %s for not implemented functionality not run"%(count_noun(nskipped, "test"))) else: - if not self.have_optional_tag(tag): + if not self.were_doctests_with_optional_tag_run(tag): if tag == "bug": if self.controller.options.show_skipped: log(" %s not run due to known bugs"%(count_noun(nskipped, "test"))) diff --git a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py index 94a8c414415..4d6c1021abe 100644 --- a/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py +++ b/src/sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py @@ -25,7 +25,7 @@ from sage.functions.hyperbolic import cosh from sage.matrix.constructor import matrix from sage.matrix.matrix_space import MatrixSpace -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.complex_mpfr import ComplexField from sage.rings.finite_rings.integer_mod_ring import Zmod from sage.rings.integer_ring import ZZ diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx b/src/sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx index e3769c89f54..0228711e7f3 100644 --- a/src/sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx +++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx @@ -115,13 +115,13 @@ cpdef _fast_possible_periods(self, return_points=False): points_periods.append([P_proj, period]) l = P_proj.multiplier(self, period, False) lorders = set() - for poly,_ in l.charpoly().factor(): + for poly, _ in l.charpoly().factor(): if poly.degree() == 1: eig = -poly.constant_coefficient() if not eig: - continue # exclude 0 + continue # exclude 0 else: - eig = GF(p**poly.degree(), 't', modulus=poly).gen() + eig = GF((p, poly.degree()), 't', modulus=poly).gen() if eig: lorders.add(eig.multiplicative_order()) S = subsets(lorders) diff --git a/src/sage/dynamics/complex_dynamics/mandel_julia.py b/src/sage/dynamics/complex_dynamics/mandel_julia.py index 19185da190e..ad49e07203c 100644 --- a/src/sage/dynamics/complex_dynamics/mandel_julia.py +++ b/src/sage/dynamics/complex_dynamics/mandel_julia.py @@ -45,7 +45,9 @@ from sage.repl.image import Image from sage.functions.log import logb from sage.functions.other import floor -from sage.rings.all import QQ, CC, CDF +from sage.rings.rational_field import QQ +from sage.rings.cc import CC +from sage.rings.complex_double import CDF from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.schemes.projective.projective_space import ProjectiveSpace from sage.misc.prandom import randint diff --git a/src/sage/dynamics/complex_dynamics/mandel_julia_helper.pyx b/src/sage/dynamics/complex_dynamics/mandel_julia_helper.pyx index 7a6ee5aa51a..ed6bf330ecb 100644 --- a/src/sage/dynamics/complex_dynamics/mandel_julia_helper.pyx +++ b/src/sage/dynamics/complex_dynamics/mandel_julia_helper.pyx @@ -29,7 +29,7 @@ from sage.functions.log import exp, log from sage.symbolic.constants import pi from sage.symbolic.relation import solve from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.real_double import RDF from sage.rings.complex_double import CDF from sage.ext.fast_callable import fast_callable diff --git a/src/sage/env.py b/src/sage/env.py index 70ed1e3b6bc..c4953cfa654 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -267,20 +267,12 @@ def _get_shared_lib_path(*libnames: str) -> Optional[str]: EXAMPLES:: - sage: import sys - sage: from fnmatch import fnmatch sage: from sage.env import _get_shared_lib_path - sage: lib_filename = _get_shared_lib_path("Singular", "singular-Singular") - sage: if sys.platform == 'cygwin': - ....: pattern = "*/cygSingular-*.dll" - ....: elif sys.platform == 'darwin': - ....: pattern = "*/libSingular-*.dylib" - ....: else: - ....: pattern = "*/lib*Singular-*.so" - sage: fnmatch(str(lib_filename), pattern) + sage: "gap" in _get_shared_lib_path("gap") True sage: _get_shared_lib_path("an_absurd_lib") is None True + """ for libname in libnames: diff --git a/src/sage/ext/fast_callable.pyx b/src/sage/ext/fast_callable.pyx index d49f1fcfdb4..ad08d337a54 100644 --- a/src/sage/ext/fast_callable.pyx +++ b/src/sage/ext/fast_callable.pyx @@ -372,9 +372,7 @@ def fast_callable(x, domain=None, vars=None, sage: K. = QQ[] sage: p = x*y^2 + 1/3*y^2 - x*z - y*z sage: fp = fast_callable(p, domain=RDF) - sage: fp.op_list() # py2 - [('load_const', 0.0), ('load_const', -1.0), ('load_arg', 0), ('ipow', 1), ('load_arg', 2), ('ipow', 1), 'mul', 'mul', 'add', ('load_const', 1.0), ('load_arg', 0), ('ipow', 1), ('load_arg', 1), ('ipow', 2), 'mul', 'mul', 'add', ('load_const', 0.3333333333333333), ('load_arg', 1), ('ipow', 2), 'mul', 'add', ('load_const', -1.0), ('load_arg', 1), ('ipow', 1), ('load_arg', 2), ('ipow', 1), 'mul', 'mul', 'add', 'return'] - sage: fp.op_list() # py3 + sage: fp.op_list() [('load_const', 0.0), ('load_const', 1.0), ('load_arg', 0), ('ipow', 1), ('load_arg', 1), ('ipow', 2), 'mul', 'mul', 'add', ('load_const', 0.3333333333333333), ('load_arg', 1), ('ipow', 2), 'mul', 'add', ('load_const', -1.0), ('load_arg', 0), ('ipow', 1), ('load_arg', 2), ('ipow', 1), 'mul', 'mul', 'add', ('load_const', -1.0), ('load_arg', 1), ('ipow', 1), ('load_arg', 2), ('ipow', 1), 'mul', 'mul', 'add', 'return'] sage: fp(e, pi, sqrt(2)) # abs tol 3e-14 21.831120464939584 diff --git a/src/sage/features/__init__.py b/src/sage/features/__init__.py index 71ab3c02633..81a6bb990a8 100644 --- a/src/sage/features/__init__.py +++ b/src/sage/features/__init__.py @@ -3,10 +3,11 @@ Testing for features of the environment at runtime A computation can require a certain package to be installed in the runtime -environment. Abstractly such a package describes a :class`Feature` which can +environment. Abstractly such a package describes a :class:`Feature` which can be tested for at runtime. It can be of various kinds, most prominently an -:class:`Executable` in the PATH or an additional package for some installed -system such as a :class:`GapPackage`. +:class:`Executable` in the ``PATH``, a :class:`PythonModule`, or an additional +package for some installed +system such as a :class:`~sage.features.gap.GapPackage`. AUTHORS: @@ -27,7 +28,7 @@ Here we test whether the grape GAP package is available:: sage: from sage.features.gap import GapPackage - sage: GapPackage("grape", spkg="gap_packages").is_present() # optional: gap_packages + sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_packages FeatureTestResult('gap_package_grape', True) Note that a :class:`FeatureTestResult` acts like a bool in most contexts:: @@ -59,7 +60,7 @@ class TrivialClasscallMetaClass(type): """ - A trivial version of :class:`ClasscallMetaclass` without Cython dependencies. + A trivial version of :class:`sage.misc.classcall_metaclass.ClasscallMetaclass` without Cython dependencies. """ def __call__(cls, *args, **kwds): r""" @@ -146,7 +147,7 @@ def is_present(self): EXAMPLES:: sage: from sage.features.gap import GapPackage - sage: GapPackage("grape", spkg="gap_packages").is_present() # optional: gap_packages + sage: GapPackage("grape", spkg="gap_packages").is_present() # optional - gap_packages FeatureTestResult('gap_package_grape', True) sage: GapPackage("NOT_A_PACKAGE", spkg="gap_packages").is_present() FeatureTestResult('gap_package_NOT_A_PACKAGE', False) @@ -251,6 +252,7 @@ def find_resolution(): return lazy_string(find_resolution) + class FeatureNotPresentError(RuntimeError): r""" A missing feature error. @@ -373,7 +375,8 @@ def __repr__(self): def package_systems(): """ - Return a list of ``PackageSystem`` objects representing the available package systems. + Return a list of :class:~sage.features.pkg_systems.PackageSystem` objects + representing the available package systems. The list is ordered by decreasing preference. @@ -387,6 +390,7 @@ def package_systems(): from subprocess import run, CalledProcessError, PIPE global _cache_package_systems if _cache_package_systems is None: + from .pkg_systems import PackageSystem, SagePackageSystem, PipPackageSystem _cache_package_systems = [] # Try to use scripts from SAGE_ROOT (or an installation of sage_bootstrap) # to obtain system package advice. @@ -402,178 +406,10 @@ def package_systems(): return _cache_package_systems -class PackageSystem(Feature): - r""" - A feature describing a system package manager. - - EXAMPLES:: - - sage: from sage.features import PackageSystem - sage: PackageSystem('conda') - Feature('conda') - """ - def _is_present(self): - r""" - Test whether ``self`` appears in the list of available package systems. - - EXAMPLES:: - - sage: from sage.features import PackageSystem - sage: debian = PackageSystem('debian') - sage: debian.is_present() # indirect doctest, random - True - """ - return self in package_systems() - - def spkg_installation_hint(self, spkgs, *, prompt=" !", feature=None): - r""" - Return a string that explains how to install ``feature``. - - EXAMPLES:: - - sage: from sage.features import PackageSystem - sage: homebrew = PackageSystem('homebrew') - sage: homebrew.spkg_installation_hint('openblas') # optional - SAGE_ROOT - 'To install openblas using the homebrew package manager, you can try to run:\n!brew install openblas' - """ - if isinstance(spkgs, (tuple, list)): - spkgs = ' '.join(spkgs) - if feature is None: - feature = spkgs - return self._spkg_installation_hint(spkgs, prompt, feature) - - def _spkg_installation_hint(self, spkgs, prompt, feature): - r""" - Return a string that explains how to install ``feature``. - - Override this method in derived classes. - - EXAMPLES:: - - sage: from sage.features import PackageSystem - sage: fedora = PackageSystem('fedora') - sage: fedora.spkg_installation_hint('openblas') # optional - SAGE_ROOT - 'To install openblas using the fedora package manager, you can try to run:\n!sudo yum install openblas-devel' - """ - from subprocess import run, CalledProcessError, PIPE - lines = [] - system = self.name - try: - proc = run(f'sage-get-system-packages {system} {spkgs}', - shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=True) - system_packages = proc.stdout.strip() - print_sys = f'sage-print-system-package-command {system} --verbose --sudo --prompt="{prompt}"' - command = f'{print_sys} update && {print_sys} install {system_packages}' - proc = run(command, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=True) - command = proc.stdout.strip() - if command: - lines.append(f'To install {feature} using the {system} package manager, you can try to run:') - lines.append(command) - return '\n'.join(lines) - except CalledProcessError: - pass - return f'No equivalent system packages for {system} are known to Sage.' - -class SagePackageSystem(PackageSystem): - r""" - The feature describing the Sage package manager. - - EXAMPLES:: - - sage: from sage.features import SagePackageSystem - sage: SagePackageSystem() - Feature('sage_spkg') - """ - @staticmethod - def __classcall__(cls): - r""" - Normalize initargs. - - TESTS:: - - sage: from sage.features import SagePackageSystem - sage: SagePackageSystem() is SagePackageSystem() # indirect doctest - True - """ - return PackageSystem.__classcall__(cls, "sage_spkg") - - def _is_present(self): - r""" - Test whether ``sage-spkg`` is available. - - EXAMPLES:: - - sage: from sage.features import SagePackageSystem - sage: bool(SagePackageSystem().is_present()) # indirect doctest, optional - sage_spkg - True - """ - from subprocess import run, DEVNULL, CalledProcessError - try: - # "sage -p" is a fast way of checking whether sage-spkg is available. - run('sage -p', shell=True, stdout=DEVNULL, stderr=DEVNULL, check=True) - return True - except CalledProcessError: - return False - - def _spkg_installation_hint(self, spkgs, prompt, feature): - r""" - Return a string that explains how to install ``feature``. - - EXAMPLES:: - - sage: from sage.features import SagePackageSystem - sage: print(SagePackageSystem().spkg_installation_hint(['foo', 'bar'], prompt="### ", feature='foobarability')) # indirect doctest - To install foobarability using the Sage package manager, you can try to run: - ### sage -i foo bar - """ - lines = [] - lines.append(f'To install {feature} using the Sage package manager, you can try to run:') - lines.append(f'{prompt}sage -i {spkgs}') - return '\n'.join(lines) - -class PipPackageSystem(PackageSystem): - r""" - The feature describing the Pip package manager. - - EXAMPLES:: - - sage: from sage.features import PipPackageSystem - sage: PipPackageSystem() - Feature('pip') - """ - @staticmethod - def __classcall__(cls): - r""" - Normalize initargs. - - TESTS:: - - sage: from sage.features import PipPackageSystem - sage: PipPackageSystem() is PipPackageSystem() # indirect doctest - True - """ - return PackageSystem.__classcall__(cls, "pip") - - def _is_present(self): - r""" - Test whether ``pip`` is available. - - EXAMPLES:: - - sage: from sage.features import PipPackageSystem - sage: bool(PipPackageSystem().is_present()) # indirect doctest - True - """ - from subprocess import run, DEVNULL, CalledProcessError - try: - run('sage -pip --version', shell=True, stdout=DEVNULL, stderr=DEVNULL, check=True) - return True - except CalledProcessError: - return False class Executable(Feature): r""" - A feature describing an executable in the PATH. + A feature describing an executable in the ``PATH``. .. NOTE:: diff --git a/src/sage/features/all.py b/src/sage/features/all.py new file mode 100644 index 00000000000..2ec0c267b83 --- /dev/null +++ b/src/sage/features/all.py @@ -0,0 +1,27 @@ +r""" +Enumeration of all defined features +""" + +def all_features(): + r""" + Return an iterable of all features. + + EXAMPLES:: + + sage: from sage.features.all import all_features + sage: sorted(all_features(), key=lambda f: f.name) # random + [...Feature('sage.combinat')...] + """ + import pkgutil + import importlib + import sage.features + # Following https://packaging.python.org/guides/creating-and-discovering-plugins/#using-namespace-packages + for finder, name, ispkg in pkgutil.iter_modules(sage.features.__path__, sage.features.__name__ + "."): + module = importlib.import_module(name) + try: + af = module.all_features + except AttributeError: + pass + else: + if af != all_features: + yield from af() diff --git a/src/sage/features/bliss.py b/src/sage/features/bliss.py index d870ade5973..e8efd3e8f97 100644 --- a/src/sage/features/bliss.py +++ b/src/sage/features/bliss.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Checks for bliss +Features for testing the presence of ``bliss`` """ from . import CythonFeature, PythonModule from .join_feature import JoinFeature @@ -24,13 +24,13 @@ class BlissLibrary(CythonFeature): r""" - A :class:`Feature` which describes whether the Bliss library is + A :class:`~sage.features.Feature` which describes whether the Bliss library is present and functional. EXAMPLES:: sage: from sage.features.bliss import BlissLibrary - sage: BlissLibrary().require() # optional: bliss + sage: BlissLibrary().require() # optional - libbliss """ def __init__(self): @@ -39,22 +39,22 @@ def __init__(self): sage: from sage.features.bliss import BlissLibrary sage: BlissLibrary() - Feature('Bliss') + Feature('libbliss') """ - CythonFeature.__init__(self, "Bliss", test_code=TEST_CODE, + CythonFeature.__init__(self, "libbliss", test_code=TEST_CODE, spkg="bliss", url="http://www.tcs.hut.fi/Software/bliss/") class Bliss(JoinFeature): r""" - A :class:`Feature` which describes whether the :mod:`sage.graphs.bliss` + A :class:`~sage.features.Feature` which describes whether the :mod:`sage.graphs.bliss` module is available in this installation of Sage. EXAMPLES:: sage: from sage.features.bliss import Bliss - sage: Bliss().require() # optional: bliss + sage: Bliss().require() # optional - bliss """ def __init__(self): r""" @@ -69,3 +69,7 @@ def __init__(self): JoinFeature.__init__(self, "bliss", [PythonModule("sage.graphs.bliss", spkg="bliss", url="http://www.tcs.hut.fi/Software/bliss/")]) + + +def all_features(): + return [Bliss()] diff --git a/src/sage/features/csdp.py b/src/sage/features/csdp.py index 1d739f684eb..d08c0089cf9 100644 --- a/src/sage/features/csdp.py +++ b/src/sage/features/csdp.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Testing for CSDP at runtime +Feature for testing the presence of ``csdp`` """ import os @@ -13,13 +13,13 @@ class CSDP(Executable): r""" - A :class:`sage.features.Feature` which checks for the ``theta`` binary + A :class:`~sage.features.Feature` which checks for the ``theta`` binary of CSDP. EXAMPLES:: sage: from sage.features.csdp import CSDP - sage: CSDP().is_present() # optional: csdp + sage: CSDP().is_present() # optional - csdp FeatureTestResult('csdp', True) """ def __init__(self): @@ -40,7 +40,7 @@ def is_functional(self): EXAMPLES:: sage: from sage.features.csdp import CSDP - sage: CSDP().is_functional() # optional: csdp + sage: CSDP().is_functional() # optional - csdp FeatureTestResult('csdp', True) """ from sage.misc.temporary_file import tmp_filename @@ -64,3 +64,7 @@ def is_functional(self): .format(command=" ".join(command))) return FeatureTestResult(self, True) + + +def all_features(): + return [CSDP()] diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index 7fff8075914..669b958ab58 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Testing for databases at runtime +Features for testing the presence of various databases """ @@ -12,7 +12,7 @@ class DatabaseConwayPolynomials(StaticFile): r""" - A :class:`Feature` which describes the presence of Frank Luebeck's + A :class:`~sage.features.Feature` which describes the presence of Frank Luebeck's database of Conway polynomials. EXAMPLES:: @@ -46,7 +46,7 @@ def __init__(self): class DatabaseCremona(StaticFile): r""" - A :class:`Feature` which describes the presence of John Cremona's + A :class:`~sage.features.Feature` which describes the presence of John Cremona's database of elliptic curves. INPUT: @@ -59,7 +59,7 @@ class DatabaseCremona(StaticFile): sage: from sage.features.databases import DatabaseCremona sage: DatabaseCremona('cremona_mini').is_present() FeatureTestResult('database_cremona_mini_ellcurve', True) - sage: DatabaseCremona().is_present() # optional: database_cremona_ellcurve + sage: DatabaseCremona().is_present() # optional - database_cremona_ellcurve FeatureTestResult('database_cremona_ellcurve', True) """ def __init__(self, name="cremona", spkg="database_cremona_ellcurve"): @@ -80,12 +80,12 @@ def __init__(self, name="cremona", spkg="database_cremona_ellcurve"): class DatabaseJones(StaticFile): r""" - A :class:`Feature` which describes the presence of John Jones's tables of number fields. + A :class:`~sage.features.Feature` which describes the presence of John Jones's tables of number fields. EXAMPLES:: sage: from sage.features.databases import DatabaseJones - sage: bool(DatabaseJones().is_present()) # optional: database_jones_numfield + sage: bool(DatabaseJones().is_present()) # optional - database_jones_numfield True """ def __init__(self): @@ -104,7 +104,7 @@ def __init__(self): class DatabaseKnotInfo(PythonModule): r""" - A :class:`Feature` which describes the presence of the databases at the + A :class:`~sage.features.Feature` which describes the presence of the databases at the web-pages `KnotInfo `__ and `LinkInfo `__. @@ -113,7 +113,7 @@ class DatabaseKnotInfo(PythonModule): EXAMPLES:: sage: from sage.features.databases import DatabaseKnotInfo - sage: DatabaseKnotInfo().is_present() # optional: database_knotinfo + sage: DatabaseKnotInfo().is_present() # optional - database_knotinfo FeatureTestResult('database_knotinfo', True) """ def __init__(self): @@ -125,3 +125,10 @@ def __init__(self): True """ PythonModule.__init__(self, 'database_knotinfo', spkg='database_knotinfo') + + +def all_features(): + return [DatabaseConwayPolynomials(), + DatabaseCremona(), DatabaseCremona('cremona_mini'), + DatabaseJones(), + DatabaseKnotInfo()] diff --git a/src/sage/features/dvipng.py b/src/sage/features/dvipng.py index 27c5bddc059..44ef6c7074a 100644 --- a/src/sage/features/dvipng.py +++ b/src/sage/features/dvipng.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for dvipng +Feature for testing the presence of ``dvipng`` """ # **************************************************************************** # Copyright (C) 2021 Sebastien Labbe @@ -16,12 +16,12 @@ class dvipng(Executable): r""" - A :class:`sage.features.Feature` describing the presence of ``dvipng`` + A :class:`~sage.features.Feature` describing the presence of ``dvipng`` EXAMPLES:: sage: from sage.features.dvipng import dvipng - sage: dvipng().is_present() # optional: dvipng + sage: dvipng().is_present() # optional - dvipng FeatureTestResult('dvipng', True) """ def __init__(self): @@ -34,3 +34,7 @@ def __init__(self): """ Executable.__init__(self, "dvipng", executable="dvipng", url="https://savannah.nongnu.org/projects/dvipng/") + + +def all_features(): + return [dvipng()] diff --git a/src/sage/features/fes.py b/src/sage/features/fes.py index 69fee67be9b..50a2eba97c3 100644 --- a/src/sage/features/fes.py +++ b/src/sage/features/fes.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Checks for FES +Features for testing the presence of ``fes`` """ from . import CythonFeature, PythonModule @@ -52,13 +52,13 @@ class InternalState: class LibFESLibrary(CythonFeature): r""" - A :class:`Feature` which describes whether the FES library + A :class:`~sage.features.Feature` which describes whether the FES library is present and functional. EXAMPLES:: sage: from sage.features.fes import LibFESLibrary - sage: LibFESLibrary().require() # optional: fes + sage: LibFESLibrary().require() # optional - fes """ def __init__(self): r""" @@ -74,13 +74,13 @@ def __init__(self): class LibFES(JoinFeature): r""" - A :class:`Feature` which describes whether the :mod:`sage.libs.fes` + A :class:`~sage.features.Feature` which describes whether the :mod:`sage.libs.fes` module has been enabled for this build of Sage and is functional. EXAMPLES:: sage: from sage.features.fes import LibFES - sage: LibFES().require() # optional: fes + sage: LibFES().require() # optional - fes """ def __init__(self): r""" diff --git a/src/sage/features/ffmpeg.py b/src/sage/features/ffmpeg.py index 68245d24c27..ae84a1d4af7 100644 --- a/src/sage/features/ffmpeg.py +++ b/src/sage/features/ffmpeg.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for FFmpeg +Feature for testing the presence of ``ffmpeg`` """ # **************************************************************************** # Copyright (C) 2018 Sebastien Labbe @@ -17,12 +17,12 @@ class FFmpeg(Executable): r""" - A :class:`sage.features.Feature` describing the presence of ``FFmpeg`` + A :class:`~sage.features.Feature` describing the presence of ``ffmpeg`` EXAMPLES:: sage: from sage.features.ffmpeg import FFmpeg - sage: FFmpeg().is_present() # optional: ffmpeg + sage: FFmpeg().is_present() # optional - ffmpeg FeatureTestResult('ffmpeg', True) """ def __init__(self): @@ -34,4 +34,9 @@ def __init__(self): True """ Executable.__init__(self, "ffmpeg", executable="ffmpeg", + spkg="ffmpeg", url="https://www.ffmpeg.org/") + + +def all_features(): + return [FFmpeg()] diff --git a/src/sage/features/four_ti_2.py b/src/sage/features/four_ti_2.py index 83ac7b6d2ab..e898f601599 100644 --- a/src/sage/features/four_ti_2.py +++ b/src/sage/features/four_ti_2.py @@ -1,10 +1,14 @@ +r""" +Features for testing the presence of ``4ti2`` +""" + from . import Executable from .join_feature import JoinFeature class FourTi2Executable(Executable): r""" - Feature for the 4ti2 executables. + A :class:`~sage.features.Feature` for the 4ti2 executables. """ def __init__(self, name): r""" @@ -23,7 +27,7 @@ def __init__(self, name): class FourTi2(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of the ``4ti2`` executables. + A :class:`~sage.features.Feature` describing the presence of the ``4ti2`` executables. EXAMPLES:: @@ -44,3 +48,7 @@ def __init__(self): # same list is tested in build/pkgs/4ti2/spkg-configure.m4 for x in ('hilbert', 'markov', 'graver', 'zsolve', 'qsolve', 'rays', 'ppi', 'circuits', 'groebner')]) + + +def all_features(): + return [FourTi2()] diff --git a/src/sage/features/gap.py b/src/sage/features/gap.py index 2e4263de22e..20add30c114 100644 --- a/src/sage/features/gap.py +++ b/src/sage/features/gap.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for GAP features +Features for testing the presence of GAP packages """ from . import Feature, FeatureTestResult @@ -8,7 +8,7 @@ class GapPackage(Feature): r""" - A feature describing the presence of a GAP package. + A :class:`~sage.features.Feature` describing the presence of a GAP package. EXAMPLES:: diff --git a/src/sage/features/graph_generators.py b/src/sage/features/graph_generators.py index a47dca0e5d5..0ecd63bad76 100644 --- a/src/sage/features/graph_generators.py +++ b/src/sage/features/graph_generators.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check various graph generator programs +Features for testing the presence of various graph generator programs """ import os @@ -11,13 +11,12 @@ class Plantri(Executable): r""" - A :class:`sage.features.graph_generators.Feature` which checks for - the ``plantri`` binary. + A :class:`~sage.features.Feature` which checks for the ``plantri`` binary. EXAMPLES:: sage: from sage.features.graph_generators import Plantri - sage: Plantri().is_present() # optional: plantri + sage: Plantri().is_present() # optional - plantri FeatureTestResult('plantri', True) """ def __init__(self): @@ -39,7 +38,7 @@ def is_functional(self): EXAMPLES:: sage: from sage.features.graph_generators import Plantri - sage: Plantri().is_functional() # optional: plantri + sage: Plantri().is_functional() # optional - plantri FeatureTestResult('plantri', True) """ command = ["plantri", "4"] @@ -59,13 +58,12 @@ def is_functional(self): class Buckygen(Executable): r""" - A :class:`sage.features.graph_generators.Feature` which checks for the ``buckygen`` - binary. + A :class:`~sage.features.Feature` which checks for the ``buckygen`` binary. EXAMPLES:: sage: from sage.features.graph_generators import Buckygen - sage: Buckygen().is_present() # optional: buckygen + sage: Buckygen().is_present() # optional - buckygen FeatureTestResult('buckygen', True) """ def __init__(self): @@ -87,7 +85,7 @@ def is_functional(self): EXAMPLES:: sage: from sage.features.graph_generators import Buckygen - sage: Buckygen().is_functional() # optional: buckygen + sage: Buckygen().is_functional() # optional - buckygen FeatureTestResult('buckygen', True) """ command = ["buckygen", "-d", "22d"] @@ -107,13 +105,13 @@ def is_functional(self): class Benzene(Executable): r""" - A :class:`sage.features.graph_generators.Feature` which checks for the ``benzene`` + A :class:`~sage.features.Feature` which checks for the ``benzene`` binary. EXAMPLES:: sage: from sage.features.graph_generators import Benzene - sage: Benzene().is_present() # optional: benzene + sage: Benzene().is_present() # optional - benzene FeatureTestResult('benzene', True) """ def __init__(self): @@ -135,7 +133,7 @@ def is_functional(self): EXAMPLES:: sage: from sage.features.graph_generators import Benzene - sage: Benzene().is_functional() # optional: benzene + sage: Benzene().is_functional() # optional - benzene FeatureTestResult('benzene', True) """ devnull = open(os.devnull, 'wb') @@ -152,3 +150,9 @@ def is_functional(self): reason="Call `{command}` did not produce output that started with `{expected}`.".format(command=" ".join(command), expected=expected)) return FeatureTestResult(self, True) + + +def all_features(): + return [Plantri(), + Buckygen(), + Benzene()] diff --git a/src/sage/features/graphviz.py b/src/sage/features/graphviz.py index 8df3c8002bc..0a0c8ba88e9 100644 --- a/src/sage/features/graphviz.py +++ b/src/sage/features/graphviz.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for graphviz +Features for testing the presence of ``graphviz`` """ # **************************************************************************** # Copyright (C) 2018 Sebastien Labbe @@ -17,13 +17,12 @@ class dot(Executable): r""" - A :class:`sage.features.Executable` describing the presence of - ``dot`` + A :class:`~sage.features.Feature` describing the presence of ``dot`` EXAMPLES:: sage: from sage.features.graphviz import dot - sage: dot().is_present() # optional: graphviz + sage: dot().is_present() # optional - graphviz FeatureTestResult('dot', True) """ def __init__(self): @@ -41,13 +40,12 @@ def __init__(self): class neato(Executable): r""" - A :class:`sage.features.Executable` describing the presence of - ``neato`` + A :class:`~sage.features.Feature` describing the presence of ``neato`` EXAMPLES:: sage: from sage.features.graphviz import neato - sage: neato().is_present() # optional: graphviz + sage: neato().is_present() # optional - graphviz FeatureTestResult('neato', True) """ def __init__(self): @@ -65,13 +63,12 @@ def __init__(self): class twopi(Executable): r""" - A :class:`sage.features.Executable` describing the presence of - ``twopi`` + A :class:`~sage.features.Feature` describing the presence of ``twopi`` EXAMPLES:: sage: from sage.features.graphviz import twopi - sage: twopi().is_present() # optional: graphviz + sage: twopi().is_present() # optional - graphviz FeatureTestResult('twopi', True) """ def __init__(self): @@ -89,13 +86,14 @@ def __init__(self): class Graphviz(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of - ``dot``, ``neato`` and ``twopi``. + A :class:`~sage.features.Feature` describing the presence of + the ``dot``, ``neato``, and ``twopi`` executables from the + ``graphviz`` package. EXAMPLES:: sage: from sage.features.graphviz import Graphviz - sage: Graphviz().is_present() # optional: graphviz + sage: Graphviz().is_present() # optional - graphviz FeatureTestResult('graphviz', True) """ def __init__(self): @@ -110,3 +108,7 @@ def __init__(self): [dot(), neato(), twopi()], spkg="graphviz", url="https://www.graphviz.org/") + + +def all_features(): + return [Graphviz()] diff --git a/src/sage/features/imagemagick.py b/src/sage/features/imagemagick.py index 7ea27ea40de..a35dcb17de2 100644 --- a/src/sage/features/imagemagick.py +++ b/src/sage/features/imagemagick.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for imagemagick +Feature for testing the presence of ``imagemagick`` Currently we only check for the presence of ``convert``. When needed other commands like ``magick``, ``magick-script``, ``convert``, ``mogrify``, @@ -23,7 +23,7 @@ class ImageMagick(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of + A :class:`~sage.features.Feature` describing the presence of ``ImageMagick`` Currently, only the availability of ``convert`` is checked. @@ -31,7 +31,7 @@ class ImageMagick(JoinFeature): EXAMPLES:: sage: from sage.features.imagemagick import ImageMagick - sage: ImageMagick().is_present() # optional: imagemagick + sage: ImageMagick().is_present() # optional - imagemagick FeatureTestResult('imagemagick', True) """ def __init__(self): @@ -44,5 +44,9 @@ def __init__(self): """ JoinFeature.__init__(self, "imagemagick", [Executable("convert", executable="convert")], - spkg="_recommended", + spkg="imagemagick", url="https://www.imagemagick.org/") + + +def all_features(): + return [ImageMagick()] diff --git a/src/sage/features/interfaces.py b/src/sage/features/interfaces.py index 9991cfc690c..8daa59f58e1 100644 --- a/src/sage/features/interfaces.py +++ b/src/sage/features/interfaces.py @@ -1,5 +1,5 @@ r""" -Check for working interpreter interfaces +Features for testing whether interpreter interfaces are functional """ import importlib @@ -8,7 +8,7 @@ class InterfaceFeature(Feature): r""" - A :class:`Feature` describing whether an :class:`~sage.interfaces.interface.Interface` is present and functional. + A :class:`~sage.features.Feature` describing whether an :class:`~sage.interfaces.interface.Interface` is present and functional. TESTS:: @@ -83,7 +83,7 @@ def _is_present(self): class Magma(InterfaceFeature): r""" - A :class:`sage.features.Feature` describing whether :class:`sage.interfaces.magma.Magma` + A :class:`~sage.features.Feature` describing whether :class:`sage.interfaces.magma.Magma` is present and functional. EXAMPLES:: @@ -100,7 +100,7 @@ def __classcall__(cls): class Matlab(InterfaceFeature): r""" - A :class:`sage.features.Feature` describing whether :class:`sage.interfaces.matlab.Matlab` + A :class:`~sage.features.Feature` describing whether :class:`sage.interfaces.matlab.Matlab` is present and functional. EXAMPLES:: @@ -117,7 +117,7 @@ def __classcall__(cls): class Mathematica(InterfaceFeature): r""" - A :class:`sage.features.Feature` describing whether :class:`sage.interfaces.mathematica.Mathematica` + A :class:`~sage.features.Feature` describing whether :class:`sage.interfaces.mathematica.Mathematica` is present and functional. EXAMPLES:: @@ -134,7 +134,7 @@ def __classcall__(cls): class Maple(InterfaceFeature): r""" - A :class:`sage.features.Feature` describing whether :class:`sage.interfaces.maple.Maple` + A :class:`~sage.features.Feature` describing whether :class:`sage.interfaces.maple.Maple` is present and functional. EXAMPLES:: @@ -151,7 +151,7 @@ def __classcall__(cls): class Macaulay2(InterfaceFeature): r""" - A :class:`sage.features.Feature` describing whether :class:`sage.interfaces.macaulay2.Macaulay2` + A :class:`~sage.features.Feature` describing whether :class:`sage.interfaces.macaulay2.Macaulay2` is present and functional. EXAMPLES:: @@ -168,7 +168,7 @@ def __classcall__(cls): class Octave(InterfaceFeature): r""" - A :class:`sage.features.Feature` describing whether :class:`sage.interfaces.octave.Octave` + A :class:`~sage.features.Feature` describing whether :class:`sage.interfaces.octave.Octave` is present and functional. EXAMPLES:: @@ -185,7 +185,7 @@ def __classcall__(cls): class Scilab(InterfaceFeature): r""" - A :class:`sage.features.Feature` describing whether :class:`sage.interfaces.scilab.Scilab` + A :class:`~sage.features.Feature` describing whether :class:`sage.interfaces.scilab.Scilab` is present and functional. EXAMPLES:: @@ -204,7 +204,7 @@ def all_features(): r""" Return features corresponding to interpreter interfaces. - EXAMPLES:: + EXAMPLES:: sage: from sage.features.interfaces import all_features sage: list(all_features()) diff --git a/src/sage/features/internet.py b/src/sage/features/internet.py index 77fa6f0046b..5c6a077bcbc 100644 --- a/src/sage/features/internet.py +++ b/src/sage/features/internet.py @@ -1,9 +1,13 @@ +r""" +Feature for testing if the Internet is available +""" + from . import Feature, FeatureTestResult class Internet(Feature): r""" - A feature describing if Internet is available. + A :class:`~sage.features.Feature` describing if Internet is available. Failure of connecting to the site "https://www.sagemath.org" within a second is regarded as internet being not available. @@ -44,3 +48,7 @@ def _is_present(self): return FeatureTestResult(self, True) except urllib.error.URLError: return FeatureTestResult(self, False) + + +def all_features(): + return [Internet()] diff --git a/src/sage/features/join_feature.py b/src/sage/features/join_feature.py index a4f9aaca47f..8252db4f5bf 100644 --- a/src/sage/features/join_feature.py +++ b/src/sage/features/join_feature.py @@ -7,7 +7,7 @@ class JoinFeature(Feature): r""" - Join of several :class:`sage.features.Feature` instances. + Join of several :class:`~sage.features.Feature` instances. EXAMPLES:: diff --git a/src/sage/features/kenzo.py b/src/sage/features/kenzo.py index 7012dc230ec..df2e658e975 100644 --- a/src/sage/features/kenzo.py +++ b/src/sage/features/kenzo.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- r""" -Check for Kenzo +Feature for testing the presence of ``kenzo`` """ -from sage.libs.ecl import ecl_eval from . import Feature, FeatureTestResult class Kenzo(Feature): r""" - A :class:`sage.features.Feature` describing the presence of ``Kenzo``. + A :class:`~sage.features.Feature` describing the presence of ``Kenzo``. EXAMPLES:: @@ -37,6 +36,7 @@ def _is_present(self): sage: Kenzo()._is_present() # optional - kenzo FeatureTestResult('kenzo', True) """ + from sage.libs.ecl import ecl_eval # Redirection of ECL and Maxima stdout to /dev/null # This is also done in the Maxima library, but we # also do it here for redundancy. @@ -56,3 +56,6 @@ def _is_present(self): return FeatureTestResult(self, False, reason="Unable to make ECL require kenzo") return FeatureTestResult(self, True) + +def all_features(): + return [Kenzo()] diff --git a/src/sage/features/latex.py b/src/sage/features/latex.py index a0f0d6f4096..eba827a4978 100644 --- a/src/sage/features/latex.py +++ b/src/sage/features/latex.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for pdflatex and equivalent programs +Features for testing the presence of ``latex`` and equivalent programs """ # **************************************************************************** # Copyright (C) 2021 Sebastien Labbe @@ -12,16 +12,16 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from . import Executable, FeatureTestResult +from . import StaticFile, Executable, FeatureTestResult, FeatureNotPresentError class latex(Executable): r""" - A :class:`sage.features.Feature` describing the presence of ``latex`` + A :class:`~sage.features.Feature` describing the presence of ``latex`` EXAMPLES:: sage: from sage.features.latex import latex - sage: latex().is_present() # optional: latex + sage: latex().is_present() # optional - latex FeatureTestResult('latex', True) """ def __init__(self): @@ -37,12 +37,12 @@ def __init__(self): def is_functional(self): r""" - Return whether `latex` in the path is functional. + Return whether ``latex`` in the path is functional. - EXAMPLES: + EXAMPLES:: sage: from sage.features.latex import latex - sage: latex().is_functional() # optional: latex + sage: latex().is_functional() # optional - latex FeatureTestResult('latex', True) """ lines = [] @@ -76,12 +76,12 @@ def is_functional(self): class pdflatex(Executable): r""" - A :class:`sage.features.Feature` describing the presence of ``pdflatex`` + A :class:`~sage.features.Feature` describing the presence of ``pdflatex`` EXAMPLES:: sage: from sage.features.latex import pdflatex - sage: pdflatex().is_present() # optional: pdflatex + sage: pdflatex().is_present() # optional - pdflatex FeatureTestResult('pdflatex', True) """ def __init__(self): @@ -97,12 +97,12 @@ def __init__(self): class xelatex(Executable): r""" - A :class:`sage.features.Feature` describing the presence of ``xelatex`` + A :class:`~sage.features.Feature` describing the presence of ``xelatex`` EXAMPLES:: sage: from sage.features.latex import xelatex - sage: xelatex().is_present() # optional: xelatex + sage: xelatex().is_present() # optional - xelatex FeatureTestResult('xelatex', True) """ def __init__(self): @@ -118,12 +118,12 @@ def __init__(self): class lualatex(Executable): r""" - A :class:`sage.features.Feature` describing the presence of ``lualatex`` + A :class:`~sage.features.Feature` describing the presence of ``lualatex`` EXAMPLES:: sage: from sage.features.latex import lualatex - sage: lualatex().is_present() # optional: lualatex + sage: lualatex().is_present() # optional - lualatex FeatureTestResult('lualatex', True) """ def __init__(self): @@ -136,3 +136,74 @@ def __init__(self): """ Executable.__init__(self, "lualatex", executable="lualatex", url="https://www.latex-project.org/") + + +class TeXFile(StaticFile): + r""" + A :class:`sage.features.Feature` describing the presence of a TeX file + + EXAMPLES:: + + sage: from sage.features.latex import TeXFile + sage: TeXFile('x', 'x.tex').is_present() # optional: pdflatex + FeatureTestResult('x', True) + sage: TeXFile('nonexisting', 'xxxxxx-nonexisting-file.tex').is_present() # optional - pdflatex + FeatureTestResult('nonexisting', False) + """ + def __init__(self, name, filename, **kwds): + StaticFile.__init__(self, name, filename, search_path=[], **kwds) + + def absolute_path(self): + r""" + The absolute path of the file. + + EXAMPLES:: + + sage: from sage.features.latex import TeXFile + sage: feature = TeXFile('latex_class_article', 'article.cls') + sage: feature.absolute_path() # optional - pdflatex + '.../latex/base/article.cls' + """ + from subprocess import run, CalledProcessError, PIPE + try: + proc = run(['kpsewhich', self.filename], + stdout=PIPE, stderr=PIPE, + universal_newlines=True, check=True) + return proc.stdout.strip() + except CalledProcessError: + raise FeatureNotPresentError(self, + reason="{filename!r} not found by kpsewhich".format(filename=self.filename)) + + +class LaTeXPackage(TeXFile): + r""" + A :class:`sage.features.Feature` describing the presence of a LaTeX package + (``.sty`` file). + + EXAMPLES:: + + sage: from sage.features.latex import LaTeXPackage + sage: LaTeXPackage('graphics').is_present() # optional - pdflatex + FeatureTestResult('latex_package_graphics', True) + """ + @staticmethod + def __classcall__(cls, package_name, **kwds): + """ + TESTS:: + + sage: from sage.features.latex import LaTeXPackage + sage: LaTeXPackage('graphics') is LaTeXPackage('graphics') + True + """ + return TeXFile.__classcall__(cls, + f'latex_package_{package_name}'.replace('-', '_'), + f'{package_name}.sty', + **kwds) + + +def all_features(): + return [latex(), + pdflatex(), + xelatex(), + lualatex(), + LaTeXPackage("tkz-graph")] diff --git a/src/sage/features/latte.py b/src/sage/features/latte.py index 7b4d9dda756..6202152501f 100644 --- a/src/sage/features/latte.py +++ b/src/sage/features/latte.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for LattE +Features for testing the presence of ``latte_int`` """ from . import Executable from .join_feature import JoinFeature @@ -45,7 +45,7 @@ def __init__(self): class Latte(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of the ``LattE`` + A :class:`~sage.features.Feature` describing the presence of the ``LattE`` binaries which comes as a part of ``latte_int``. EXAMPLES:: @@ -65,3 +65,7 @@ def __init__(self): JoinFeature.__init__(self, "latte_int", (Latte_count(), Latte_integrate()), description="LattE") + + +def all_features(): + return [Latte()] diff --git a/src/sage/features/lrs.py b/src/sage/features/lrs.py index ab1871a6314..4defebd069f 100644 --- a/src/sage/features/lrs.py +++ b/src/sage/features/lrs.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for lrs +Feature for testing the presence of ``lrslib`` """ import os @@ -12,13 +12,13 @@ class Lrs(Executable): r""" - A :class:`sage.features.Feature` describing the presence of the ``lrs`` + A :class:`~sage.features.Feature` describing the presence of the ``lrs`` binary which comes as a part of ``lrslib``. EXAMPLES:: sage: from sage.features.lrs import Lrs - sage: Lrs().is_present() # optional: lrslib + sage: Lrs().is_present() # optional - lrslib FeatureTestResult('lrslib', True) """ def __init__(self): @@ -39,7 +39,7 @@ def is_functional(self): EXAMPLES:: sage: from sage.features.lrs import Lrs - sage: Lrs().is_functional() # optional: lrslib + sage: Lrs().is_functional() # optional - lrslib FeatureTestResult('lrslib', True) """ from sage.misc.temporary_file import tmp_filename @@ -63,3 +63,7 @@ def is_functional(self): expected=" or ".join(expected_list))) return FeatureTestResult(self, True) + + +def all_features(): + return [Lrs()] diff --git a/src/sage/features/mcqd.py b/src/sage/features/mcqd.py index 5fe2f26e881..131b175aabc 100644 --- a/src/sage/features/mcqd.py +++ b/src/sage/features/mcqd.py @@ -1,15 +1,19 @@ +r""" +Features for testing the presence of ``mcqd`` +""" + from . import PythonModule from .join_feature import JoinFeature class Mcqd(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of :mod:`~sage.graphs.mcqd` + A :class:`~sage.features.Feature` describing the presence of :mod:`~sage.graphs.mcqd` EXAMPLES:: sage: from sage.features.mcqd import Mcqd - sage: Mcqd().is_present() # optional: mcqd + sage: Mcqd().is_present() # optional - mcqd FeatureTestResult('mcqd', True) """ @@ -25,3 +29,7 @@ def __init__(self): # Will be changed to spkg='sagemath_mcqd' later JoinFeature.__init__(self, 'mcqd', [PythonModule('sage.graphs.mcqd', spkg='mcqd')]) + + +def all_features(): + return [Mcqd()] diff --git a/src/sage/features/meataxe.py b/src/sage/features/meataxe.py index eb32016ac3e..d3f7fce200e 100644 --- a/src/sage/features/meataxe.py +++ b/src/sage/features/meataxe.py @@ -1,10 +1,14 @@ +r""" +Feature for testing the presence of ``meataxe`` +""" + from . import PythonModule from .join_feature import JoinFeature class Meataxe(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of ``MeatAxe``. + A :class:`~sage.features.Feature` describing the presence of ``meataxe``. EXAMPLES:: @@ -24,3 +28,7 @@ def __init__(self): # Will be changed to spkg='sagemath_meataxe' later JoinFeature.__init__(self, 'meataxe', [PythonModule('sage.matrix.matrix_gfpn_dense', spkg='meataxe')]) + + +def all_features(): + return [Meataxe()] diff --git a/src/sage/features/mip_backends.py b/src/sage/features/mip_backends.py index bec5d958247..70f6ffb8d74 100644 --- a/src/sage/features/mip_backends.py +++ b/src/sage/features/mip_backends.py @@ -1,10 +1,14 @@ +r""" +Features for testing the presence of :class:`MixedIntegerLinearProgram` backends +""" + from . import Feature, FeatureTestResult from .join_feature import JoinFeature class MIPBackend(Feature): r""" - A feature describing whether a :class:`MixedIntegerLinearProgram` backend is available. + A :class:`~sage.features.Feature` describing whether a :class:`MixedIntegerLinearProgram` backend is available. """ def _is_present(self): r""" @@ -26,7 +30,7 @@ def _is_present(self): class CPLEX(MIPBackend): r""" - A feature describing whether a :class:`MixedIntegerLinearProgram` backend ``CPLEX`` is available. + A :class:`~sage.features.Feature` describing whether the :class:`MixedIntegerLinearProgram` backend ``CPLEX`` is available. """ def __init__(self): r""" @@ -42,7 +46,7 @@ def __init__(self): class Gurobi(MIPBackend): r""" - A feature describing whether a :class:`MixedIntegerLinearProgram` backend ``Gurobi`` is available. + A :class:`~sage.features.Feature` describing whether the :class:`MixedIntegerLinearProgram` backend ``Gurobi`` is available. """ def __init__(self): r""" @@ -58,7 +62,7 @@ def __init__(self): class COIN(JoinFeature): r""" - A feature describing whether a :class:`MixedIntegerLinearProgram` backend ``COIN`` is available. + A :class:`~sage.features.Feature` describing whether the :class:`MixedIntegerLinearProgram` backend ``COIN`` is available. """ def __init__(self): r""" @@ -71,3 +75,9 @@ def __init__(self): JoinFeature.__init__(self, 'sage_numerical_backends_coin', [MIPBackend('coin')], spkg='sage_numerical_backends_coin') + + +def all_features(): + return [CPLEX(), + Gurobi(), + COIN()] diff --git a/src/sage/features/normaliz.py b/src/sage/features/normaliz.py index ceb67875b01..5c3ab4255f3 100644 --- a/src/sage/features/normaliz.py +++ b/src/sage/features/normaliz.py @@ -1,5 +1,5 @@ r""" -Check for pynormaliz +Feature for testing the presence of ``pynormaliz`` """ from . import PythonModule from .join_feature import JoinFeature @@ -7,7 +7,7 @@ class PyNormaliz(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of the + A :class:`~sage.features.Feature` describing the presence of the Python package ``PyNormaliz``. EXAMPLES:: @@ -26,3 +26,7 @@ def __init__(self): """ JoinFeature.__init__(self, 'pynormaliz', [PythonModule('PyNormaliz', spkg="pynormaliz")]) + + +def all_features(): + return [PyNormaliz()] diff --git a/src/sage/features/pandoc.py b/src/sage/features/pandoc.py index 566032a643b..0a2bbcdacdb 100644 --- a/src/sage/features/pandoc.py +++ b/src/sage/features/pandoc.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for pandoc +Feature for testing the presence of ``pandoc`` """ # **************************************************************************** # Copyright (C) 2018 Thierry Monteil @@ -17,12 +17,12 @@ class Pandoc(Executable): r""" - A :class:`sage.features.Feature` describing the presence of ``pandoc`` + A :class:`~sage.features.Feature` describing the presence of ``pandoc`` EXAMPLES:: sage: from sage.features.pandoc import Pandoc - sage: Pandoc().is_present() # optional: pandoc + sage: Pandoc().is_present() # optional - pandoc FeatureTestResult('pandoc', True) """ def __init__(self): @@ -35,3 +35,7 @@ def __init__(self): """ Executable.__init__(self, "pandoc", executable="pandoc", url="https://pandoc.org/") + + +def all_features(): + return [Pandoc()] diff --git a/src/sage/features/pdf2svg.py b/src/sage/features/pdf2svg.py index f0b634f5ff4..9de3fb3cfd6 100644 --- a/src/sage/features/pdf2svg.py +++ b/src/sage/features/pdf2svg.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for pdf2svg +Feature for testing the presence of ``pdf2svg`` """ # **************************************************************************** # Copyright (C) 2021 Sebastien Labbe @@ -16,12 +16,12 @@ class pdf2svg(Executable): r""" - A :class:`sage.features.Feature` describing the presence of ``pdf2svg`` + A :class:`~sage.features.Feature` describing the presence of ``pdf2svg`` EXAMPLES:: sage: from sage.features.pdf2svg import pdf2svg - sage: pdf2svg().is_present() # optional: pdf2svg + sage: pdf2svg().is_present() # optional - pdf2svg FeatureTestResult('pdf2svg', True) """ def __init__(self): @@ -35,3 +35,7 @@ def __init__(self): Executable.__init__(self, "pdf2svg", executable="pdf2svg", spkg='pdf2svg', url="http://www.cityinthesky.co.uk/opensource/pdf2svg/") + + +def all_features(): + return [pdf2svg()] diff --git a/src/sage/features/pkg_systems.py b/src/sage/features/pkg_systems.py new file mode 100644 index 00000000000..db33c21ad1a --- /dev/null +++ b/src/sage/features/pkg_systems.py @@ -0,0 +1,177 @@ +r""" +Features for testing the presence of package systems +""" +from . import Feature + + +class PackageSystem(Feature): + r""" + A :class:`Feature` describing a system package manager. + + EXAMPLES:: + + sage: from sage.features.pkg_systems import PackageSystem + sage: PackageSystem('conda') + Feature('conda') + """ + def _is_present(self): + r""" + Test whether ``self`` appears in the list of available package systems. + + EXAMPLES:: + + sage: from sage.features.pkg_systems import PackageSystem + sage: debian = PackageSystem('debian') + sage: debian.is_present() # indirect doctest, random + True + """ + from . import package_systems + return self in package_systems() + + def spkg_installation_hint(self, spkgs, *, prompt=" !", feature=None): + r""" + Return a string that explains how to install ``feature``. + + EXAMPLES:: + + sage: from sage.features.pkg_systems import PackageSystem + sage: homebrew = PackageSystem('homebrew') + sage: homebrew.spkg_installation_hint('openblas') # optional - SAGE_ROOT + 'To install openblas using the homebrew package manager, you can try to run:\n!brew install openblas' + """ + if isinstance(spkgs, (tuple, list)): + spkgs = ' '.join(spkgs) + if feature is None: + feature = spkgs + return self._spkg_installation_hint(spkgs, prompt, feature) + + def _spkg_installation_hint(self, spkgs, prompt, feature): + r""" + Return a string that explains how to install ``feature``. + + Override this method in derived classes. + + EXAMPLES:: + + sage: from sage.features.pkg_systems import PackageSystem + sage: fedora = PackageSystem('fedora') + sage: fedora.spkg_installation_hint('openblas') # optional - SAGE_ROOT + 'To install openblas using the fedora package manager, you can try to run:\n!sudo yum install openblas-devel' + """ + from subprocess import run, CalledProcessError, PIPE + lines = [] + system = self.name + try: + proc = run(f'sage-get-system-packages {system} {spkgs}', + shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=True) + system_packages = proc.stdout.strip() + print_sys = f'sage-print-system-package-command {system} --verbose --sudo --prompt="{prompt}"' + command = f'{print_sys} update && {print_sys} install {system_packages}' + proc = run(command, shell=True, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=True) + command = proc.stdout.strip() + if command: + lines.append(f'To install {feature} using the {system} package manager, you can try to run:') + lines.append(command) + return '\n'.join(lines) + except CalledProcessError: + pass + return f'No equivalent system packages for {system} are known to Sage.' + + +class SagePackageSystem(PackageSystem): + r""" + A :class:`Feature` describing the package manager of the SageMath distribution. + + EXAMPLES:: + + sage: from sage.features.pkg_systems import SagePackageSystem + sage: SagePackageSystem() + Feature('sage_spkg') + """ + @staticmethod + def __classcall__(cls): + r""" + Normalize initargs. + + TESTS:: + + sage: from sage.features.pkg_systems import SagePackageSystem + sage: SagePackageSystem() is SagePackageSystem() # indirect doctest + True + """ + return PackageSystem.__classcall__(cls, "sage_spkg") + + def _is_present(self): + r""" + Test whether ``sage-spkg`` is available. + + EXAMPLES:: + + sage: from sage.features.pkg_systems import SagePackageSystem + sage: bool(SagePackageSystem().is_present()) # indirect doctest, optional - sage_spkg + True + """ + from subprocess import run, DEVNULL, CalledProcessError + try: + # "sage -p" is a fast way of checking whether sage-spkg is available. + run('sage -p', shell=True, stdout=DEVNULL, stderr=DEVNULL, check=True) + return True + except CalledProcessError: + return False + + def _spkg_installation_hint(self, spkgs, prompt, feature): + r""" + Return a string that explains how to install ``feature``. + + EXAMPLES:: + + sage: from sage.features.pkg_systems import SagePackageSystem + sage: print(SagePackageSystem().spkg_installation_hint(['foo', 'bar'], prompt="### ", feature='foobarability')) # indirect doctest + To install foobarability using the Sage package manager, you can try to run: + ### sage -i foo bar + """ + lines = [] + lines.append(f'To install {feature} using the Sage package manager, you can try to run:') + lines.append(f'{prompt}sage -i {spkgs}') + return '\n'.join(lines) + + +class PipPackageSystem(PackageSystem): + r""" + A :class:`Feature` describing the Pip package manager. + + EXAMPLES:: + + sage: from sage.features.pkg_systems import PipPackageSystem + sage: PipPackageSystem() + Feature('pip') + """ + @staticmethod + def __classcall__(cls): + r""" + Normalize initargs. + + TESTS:: + + sage: from sage.features.pkg_systems import PipPackageSystem + sage: PipPackageSystem() is PipPackageSystem() # indirect doctest + True + """ + return PackageSystem.__classcall__(cls, "pip") + + def _is_present(self): + r""" + Test whether ``pip`` is available. + + EXAMPLES:: + + sage: from sage.features.pkg_systems import PipPackageSystem + sage: bool(PipPackageSystem().is_present()) # indirect doctest + True + """ + from subprocess import run, DEVNULL, CalledProcessError + try: + run('sage -pip --version', shell=True, stdout=DEVNULL, stderr=DEVNULL, check=True) + return True + except CalledProcessError: + return False diff --git a/src/sage/features/polymake.py b/src/sage/features/polymake.py index 1e6d7b81b99..579951c047f 100644 --- a/src/sage/features/polymake.py +++ b/src/sage/features/polymake.py @@ -1,16 +1,19 @@ +r""" +Feature for testing the presence of the Python interface to polymake +""" from . import PythonModule from .join_feature import JoinFeature class JuPyMake(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of the ``JuPyMake`` + A :class:`~sage.features.Feature` describing the presence of the :mod:`JuPyMake` module, a Python interface to the polymake library. EXAMPLES:: sage: from sage.features.polymake import JuPyMake - sage: JuPyMake().is_present() # optional: jupymake + sage: JuPyMake().is_present() # optional - jupymake FeatureTestResult('jupymake', True) """ def __init__(self): @@ -23,3 +26,7 @@ def __init__(self): """ JoinFeature.__init__(self, "jupymake", [PythonModule("JuPyMake", spkg="jupymake")]) + + +def all_features(): + return [JuPyMake()] diff --git a/src/sage/features/rubiks.py b/src/sage/features/rubiks.py index 9074fe58c81..be84cbfba22 100644 --- a/src/sage/features/rubiks.py +++ b/src/sage/features/rubiks.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- r""" -Check for rubiks +Features for testing the presence of ``rubiks`` """ # **************************************************************************** # This program is free software: you can redistribute it and/or modify @@ -15,13 +15,12 @@ class cu2(Executable): r""" - A :class:`sage.features.Executable` describing the presence of - ``cu2`` + A :class:`~sage.features.Feature` describing the presence of ``cu2`` EXAMPLES:: sage: from sage.features.rubiks import cu2 - sage: cu2().is_present() # optional: rubiks + sage: cu2().is_present() # optional - rubiks FeatureTestResult('cu2', True) """ def __init__(self): @@ -38,13 +37,12 @@ def __init__(self): class size222(Executable): r""" - A :class:`sage.features.Executable` describing the presence of - ``size222`` + A :class:`~sage.features.Feature` describing the presence of ``size222`` EXAMPLES:: sage: from sage.features.rubiks import size222 - sage: size222().is_present() # optional: rubiks + sage: size222().is_present() # optional - rubiks FeatureTestResult('size222', True) """ def __init__(self): @@ -61,13 +59,12 @@ def __init__(self): class optimal(Executable): r""" - A :class:`sage.features.Executable` describing the presence of - ``optimal`` + A :class:`~sage.features.Feature` describing the presence of ``optimal`` EXAMPLES:: sage: from sage.features.rubiks import optimal - sage: optimal().is_present() # optional: rubiks + sage: optimal().is_present() # optional - rubiks FeatureTestResult('optimal', True) """ def __init__(self): @@ -84,13 +81,12 @@ def __init__(self): class mcube(Executable): r""" - A :class:`sage.features.Executable` describing the presence of - ``mcube`` + A :class:`~sage.features.Feature` describing the presence of ``mcube`` EXAMPLES:: sage: from sage.features.rubiks import mcube - sage: mcube().is_present() # optional: rubiks + sage: mcube().is_present() # optional - rubiks FeatureTestResult('mcube', True) """ def __init__(self): @@ -107,13 +103,12 @@ def __init__(self): class dikcube(Executable): r""" - A :class:`sage.features.Executable` describing the presence of - ``dikcube`` + A :class:`~sage.features.Feature` describing the presence of ``dikcube`` EXAMPLES:: sage: from sage.features.rubiks import dikcube - sage: dikcube().is_present() # optional: rubiks + sage: dikcube().is_present() # optional - rubiks FeatureTestResult('dikcube', True) """ def __init__(self): @@ -130,13 +125,12 @@ def __init__(self): class cubex(Executable): r""" - A :class:`sage.features.Executable` describing the presence of - ``cubex`` + A :class:`~sage.features.Feature` describing the presence of ``cubex`` EXAMPLES:: sage: from sage.features.rubiks import cubex - sage: cubex().is_present() # optional: rubiks + sage: cubex().is_present() # optional - rubiks FeatureTestResult('cubex', True) """ def __init__(self): @@ -153,14 +147,14 @@ def __init__(self): class Rubiks(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of + A :class:`~sage.features.Feature` describing the presence of ``cu2``, ``cubex``, ``dikcube``, ``mcube``, ``optimal``, and ``size222``. EXAMPLES:: sage: from sage.features.rubiks import Rubiks - sage: Rubiks().is_present() # optional: rubiks + sage: Rubiks().is_present() # optional - rubiks FeatureTestResult('rubiks', True) """ def __init__(self): @@ -174,3 +168,7 @@ def __init__(self): JoinFeature.__init__(self, "rubiks", [cu2(), size222(), optimal(), mcube(), dikcube(), cubex()], spkg="rubiks") + + +def all_features(): + return [Rubiks()] diff --git a/src/sage/features/sagemath.py b/src/sage/features/sagemath.py index 2d7d6e9ab79..221d596d7be 100644 --- a/src/sage/features/sagemath.py +++ b/src/sage/features/sagemath.py @@ -1,5 +1,5 @@ r""" -Check for SageMath Python modules +Features for testing the presence of Python modules in the Sage library """ from . import PythonModule from .join_feature import JoinFeature @@ -7,7 +7,7 @@ class sage__combinat(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of ``sage.combinat``. + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.combinat`. EXAMPLES:: @@ -32,7 +32,7 @@ def __init__(self): class sage__geometry__polyhedron(PythonModule): r""" - A :class:`sage.features.Feature` describing the presence of ``sage.geometry.polyhedron``. + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.geometry.polyhedron`. EXAMPLES:: @@ -54,7 +54,7 @@ def __init__(self): class sage__graphs(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of ``sage.graphs``. + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.graphs`. EXAMPLES:: @@ -76,7 +76,7 @@ def __init__(self): class sage__plot(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of ``sage.plot``. + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.plot`. EXAMPLES:: @@ -98,7 +98,7 @@ def __init__(self): class sage__rings__number_field(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of ``sage.rings.number_field``. + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.number_field`. EXAMPLES:: @@ -120,7 +120,7 @@ def __init__(self): class sage__rings__real_double(PythonModule): r""" - A :class:`sage.features.Feature` describing the presence of ``sage.rings.real_double``. + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.rings.real_double`. EXAMPLES:: @@ -141,7 +141,7 @@ def __init__(self): class sage__symbolic(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of ``sage.symbolic``. + A :class:`~sage.features.Feature` describing the presence of :mod:`sage.symbolic`. EXAMPLES:: @@ -162,12 +162,12 @@ def __init__(self): spkg="sagemath_symbolics") -def sage_features(logger=None): - """ +def all_features(): + r""" Return features corresponding to parts of the Sage library. - These tags are named after Python packages/modules (e.g., :mod:`~sage.symbolic`), - not distribution packages (``sagemath-symbolics``). + These features are named after Python packages/modules (e.g., :mod:`sage.symbolic`), + not distribution packages (**sagemath-symbolics**). This design is motivated by a separation of concerns: The author of a module that depends on some functionality provided by a Python module usually already knows the @@ -175,26 +175,19 @@ def sage_features(logger=None): know about the distribution package that provides the Python module. Instead, we associate distribution packages to Python modules in - :mod:`sage.features.sagemath` via the ``spkg`` parameter of :class:`Feature`. + :mod:`sage.features.sagemath` via the ``spkg`` parameter of + :class:`~sage.features.Feature`. EXAMPLES:: - sage: from sage.features.sagemath import sage_features - sage: list(sage_features()) # random - [Feature('sage.graphs'), - Feature('sage.plot'), - Feature('sage.rings.number_field'), - Feature('sage.rings.real_double')] + sage: from sage.features.sagemath import all_features + sage: list(all_features()) + [Feature('sage.combinat'), ...] """ - for feature in [sage__combinat(), - sage__geometry__polyhedron(), - sage__graphs(), - sage__plot(), - sage__rings__number_field(), - sage__rings__real_double(), - sage__symbolic()]: - result = feature.is_present() - if logger: - logger.write(f'{result}, reason: {result.reason}\n') - if result: - yield feature + return [sage__combinat(), + sage__geometry__polyhedron(), + sage__graphs(), + sage__plot(), + sage__rings__number_field(), + sage__rings__real_double(), + sage__symbolic()] diff --git a/src/sage/features/tdlib.py b/src/sage/features/tdlib.py index cae1f5abc78..ff38f6e23a5 100644 --- a/src/sage/features/tdlib.py +++ b/src/sage/features/tdlib.py @@ -1,10 +1,14 @@ +r""" +Features for testing the presence of ``tdlib`` +""" + from . import PythonModule from .join_feature import JoinFeature class Tdlib(JoinFeature): r""" - A :class:`sage.features.Feature` describing the presence of the ``TDLib``. + A :class:`~sage.features.Feature` describing the presence of the ``tdlib``. """ def __init__(self): r""" @@ -18,3 +22,7 @@ def __init__(self): # Will be changed to spkg='sagemath_tdlib' later JoinFeature.__init__(self, 'tdlib', [PythonModule('sage.graphs.graph_decompositions.tdlib', spkg='tdlib')]) + + +def all_features(): + return [Tdlib()] diff --git a/src/sage/functions/airy.py b/src/sage/functions/airy.py index 8d7e6f82601..cf32fb8f1c8 100644 --- a/src/sage/functions/airy.py +++ b/src/sage/functions/airy.py @@ -232,7 +232,8 @@ def _evalf_(self, x, **kwargs): if algorithm == 'scipy': if hasattr(parent, 'prec') and parent.prec() > 53: raise NotImplementedError("%s not implemented for precision > 53" % self.name()) - from sage.rings.all import RR, CC + from sage.rings.real_mpfr import RR + from sage.rings.cc import CC from sage.functions.other import real, imag from scipy.special import airy as airy if x in RR: @@ -331,7 +332,8 @@ def _evalf_(self, x, **kwargs): if algorithm == 'scipy': if hasattr(parent, 'prec') and parent.prec() > 53: raise NotImplementedError("%s not implemented for precision > 53" % self.name()) - from sage.rings.all import RR, CC + from sage.rings.real_mpfr import RR + from sage.rings.cc import CC from sage.functions.other import real, imag from scipy.special import airy as airy if x in RR: @@ -669,7 +671,8 @@ def _evalf_(self, x, **kwargs): if algorithm == 'scipy': if hasattr(parent, 'prec') and parent.prec() > 53: raise NotImplementedError("%s not implemented for precision > 53" % self.name()) - from sage.rings.all import RR, CC + from sage.rings.real_mpfr import RR + from sage.rings.cc import CC from sage.functions.other import real, imag from scipy.special import airy as airy if x in RR: @@ -768,7 +771,8 @@ def _evalf_(self, x, **kwargs): if algorithm == 'scipy': if hasattr(parent, 'prec') and parent.prec() > 53: raise NotImplementedError("%s not implemented for precision > 53" % self.name()) - from sage.rings.all import RR, CC + from sage.rings.real_mpfr import RR + from sage.rings.cc import CC from sage.functions.other import real, imag from scipy.special import airy as airy if x in RR: diff --git a/src/sage/functions/gamma.py b/src/sage/functions/gamma.py index 3a4deef625d..dc1eefcc89d 100644 --- a/src/sage/functions/gamma.py +++ b/src/sage/functions/gamma.py @@ -796,6 +796,7 @@ def __init__(self): GinacFunction.__init__(self, "psi", nargs=1, latex_name=r'\psi', conversions=dict(mathematica='PolyGamma', maxima='psi[0]', + maple='Psi', sympy='digamma', fricas='digamma')) @@ -847,10 +848,16 @@ def __init__(self): polygamma(2, x) sage: psi(2, x)._fricas_() # optional - fricas polygamma(2,x) + + Fixed conversion:: + + sage: psi(2,x)._maple_init_() + 'Psi(2,x)' """ GinacFunction.__init__(self, "psi", nargs=2, latex_name=r'\psi', conversions=dict(mathematica='PolyGamma', sympy='polygamma', + maple='Psi', giac='Psi', fricas='polygamma')) @@ -1041,6 +1048,7 @@ def __init__(self): latex_name=r"\operatorname{B}", conversions=dict(maxima='beta', mathematica='Beta', + maple='Beta', sympy='beta', fricas='Beta', giac='Beta')) @@ -1049,7 +1057,7 @@ def _method_arguments(self, x, y): r""" TESTS:: - sage: RBF(beta(sin(3),sqrt(RBF(2).add_error(1e-8)/3))) + sage: RBF(beta(sin(3),sqrt(RBF(2).add_error(1e-8)/3))) # abs tol 6e-7 [7.407662 +/- 6.17e-7] """ return [x, y] diff --git a/src/sage/functions/orthogonal_polys.py b/src/sage/functions/orthogonal_polys.py index fdeb5144122..69cb712e777 100644 --- a/src/sage/functions/orthogonal_polys.py +++ b/src/sage/functions/orthogonal_polys.py @@ -302,7 +302,10 @@ import warnings from sage.misc.latex import latex -from sage.rings.all import ZZ, QQ, RR, CC +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.real_mpfr import RR +from sage.rings.cc import CC from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing import sage.rings.abc @@ -495,8 +498,7 @@ def _eval_(self, n, x): sage: chebyshev_T(5,Qp(3)(2)) 2 + 3^2 + 3^3 + 3^4 + 3^5 + O(3^20) sage: chebyshev_T(100001/2, 2) - doctest:...: RuntimeWarning: mpmath failed, keeping expression unevaluated - chebyshev_T(100001/2, 2) + ...chebyshev_T(100001/2, 2) sage: chebyshev_U._eval_(1.5, Mod(8,9)) is None True """ @@ -1911,7 +1913,7 @@ class Func_hermite(GinacFunction): ... RuntimeError: hermite_eval: The index n must be a nonnegative integer - sage: _ = var('m x') + sage: m,x = SR.var('m,x') sage: hermite(m, x).diff(m) Traceback (most recent call last): ... @@ -1962,7 +1964,7 @@ def __init__(self): EXAMPLES:: - sage: _ = var('n a b x') + sage: n,a,b,x = SR.var('n,a,b,x') sage: loads(dumps(jacobi_P)) jacobi_P sage: jacobi_P(n, a, b, x, hold=True)._sympy_() @@ -1976,7 +1978,7 @@ def _eval_(self, n, a, b, x): """ EXAMPLES:: - sage: _ = var('n a b x') + sage: n,a,b,x = SR.var('n,a,b,x') sage: jacobi_P(1,n,n,n) (n + 1)*n sage: jacobi_P(2,n,n,n) @@ -2080,17 +2082,20 @@ class Func_ultraspherical(GinacFunction): sage: t = PolynomialRing(RationalField(),"t").gen() sage: gegenbauer(3,2,t) 32*t^3 - 12*t - sage: _ = var('x') - sage: for N in range(100): - ....: n = ZZ.random_element(5, 5001) - ....: a = QQ.random_element().abs() + 5 - ....: assert ((n+1)*ultraspherical(n+1,a,x) - 2*x*(n+a)*ultraspherical(n,a,x) + (n+2*a-1)*ultraspherical(n-1,a,x)).expand().is_zero() + sage: x = SR.var('x') + sage: n = ZZ.random_element(5, 5001) + sage: a = QQ.random_element().abs() + 5 + sage: s = ( (n+1)*ultraspherical(n+1,a,x) + ....: - 2*x*(n+a)*ultraspherical(n,a,x) + ....: + (n+2*a-1)*ultraspherical(n-1,a,x) ) + sage: s.expand().is_zero() + True sage: ultraspherical(5,9/10,3.1416) 6949.55439044240 sage: ultraspherical(5,9/10,RealField(100)(pi)) 6949.4695419382702451843080687 - sage: _ = var('a n') + sage: a,n = SR.var('a,n') sage: gegenbauer(2,a,x) 2*(a + 1)*a*x^2 - a sage: gegenbauer(3,a,x) diff --git a/src/sage/functions/prime_pi.pyx b/src/sage/functions/prime_pi.pyx index 7d70e1edcb1..dc73f9593aa 100644 --- a/src/sage/functions/prime_pi.pyx +++ b/src/sage/functions/prime_pi.pyx @@ -9,6 +9,10 @@ AUTHORS: - \R. Andrew Ohana (2011): complete rewrite, ~5x speedup +- Dima Pasechnik (2021): removed buggy cython code, replaced it with + calls to primecount/primecountpy spkg + + EXAMPLES:: sage: z = sage.functions.prime_pi.PrimePi() @@ -28,36 +32,10 @@ EXAMPLES:: # https://www.gnu.org/licenses/ # **************************************************************************** -from cypari2.paridecl cimport * -from cysignals.signals cimport * -from cysignals.memory cimport sig_malloc, sig_realloc, sig_free - -from libc.stdint cimport int_fast8_t, uint_fast16_t, uint8_t, uint32_t, uint64_t from sage.rings.integer cimport Integer -from sage.libs.pari.all import pari from sage.symbolic.function cimport BuiltinFunction -from sage.libs.gmp.mpz cimport * - - -cdef uint64_t arg_to_uint64(x, str s1, str s2) except -1: - if not isinstance(x, Integer): - from .other import floor - x = Integer(floor(x)) - if mpz_sgn((x).value) <= 0: - return 0ull - if mpz_sizeinbase((x).value, 2) > 63: - raise NotImplementedError("computation of " + s1 + " for x >= " - + "2^63 is not implemented") - if mpz_sizeinbase((x).value, 2) > 43: - import warnings - warnings.warn("computation of %s for large x can take minutes, "%s1 - + "hours, or days depending on the size of %s"%s2) - if mpz_sizeinbase((x).value, 2) > 50: - warnings.warn("computation of %s for x >= 2^50 has not "%s1 - + "been as thoroughly tested as for smaller values") - cdef uint64_t ret = mpz_get_ui((x).value) & 0xfffffffful - ret += (mpz_get_ui(((x>>32)).value)) << 32ull - return ret +from primecountpy.primecount import prime_pi as _prime_pi +from primecountpy.primecount import phi as _phi cdef class PrimePi(BuiltinFunction): def __init__(self): @@ -92,24 +70,6 @@ cdef class PrimePi(BuiltinFunction): sage: prime_pi(500509) 41581 - These examples test a variety of odd inputs:: - - sage: prime_pi(3.5) - 2 - sage: prime_pi(sqrt(2357)) - 15 - sage: prime_pi(mod(30957, 9750979)) - Traceback (most recent call last): - ... - TypeError: cannot coerce arguments: positive characteristic not allowed in symbolic computations - - We test non-trivial ``prime_bound`` values:: - - sage: prime_pi(100000, 10000) - 9592 - sage: prime_pi(500509, 50051) - 41581 - The following test is to verify that :trac:`4670` has been essentially resolved:: @@ -121,55 +81,11 @@ cdef class PrimePi(BuiltinFunction): sage: P = plot(prime_pi, 50, 100) - .. NOTE:: - - This uses a recursive implementation, using the optimizations - described in [Oha2011]_. - - AUTHOR: - - - \R. Andrew Ohana (2011) """ super(PrimePi, self).__init__('prime_pi', latex_name=r"\pi", conversions={'mathematica':'PrimePi', 'pari':'primepi', 'sympy':'primepi'}) - cdef uint32_t *__primes - cdef uint32_t __numPrimes, __maxSieve, __primeBound - cdef int_fast8_t *__tabS - cdef uint_fast16_t *__smallPi - cdef byteptr __pariPrimePtr - - def __dealloc__(self): - if self.__smallPi != NULL: - sig_free(self.__smallPi) - sig_free(self.__tabS) - - cdef void _init_tables(self): - pari.init_primes(0xffffu) - self.__pariPrimePtr = diffptr - self.__smallPi = sig_malloc( - 0x10000u * sizeof(uint_fast16_t)) - cdef uint32_t p=0u, i=0u, k=0u - while i < 0xfff1u: # 0xfff1 is the last prime up to 0xffff - NEXT_PRIME_VIADIFF(p, self.__pariPrimePtr) - while i < p: - self.__smallPi[i] = k - i += 1u - k += 1u - while i <= 0xffffu: - self.__smallPi[i] = k - i += 1u - - self.__tabS = sig_malloc(2310*sizeof(int_fast8_t)) - for i in range(2310u): - self.__tabS[i] = ((i+1u)/2u - (i+3u)/6u - (i+5u)/10u + (i+15u)/30u - - (i+7u)/14u + (i+21u)/42u + (i+35u)/70u - (i+105u)/210u - - (i+11u)/22u + (i+33u)/66u + (i+55u)/110u + (i+77u)/154u - - (i+165u)/330u - (i+231u)/462u - (i+385u)/770u - + (i+1155u)/2310u - ((i/77u)<<4u)) - - def __call__(self, *args, coerce=True, hold=False): r""" EXAMPLES:: @@ -190,8 +106,6 @@ cdef class PrimePi(BuiltinFunction): if len(args) > 2: raise TypeError("Symbolic function %s takes 1 or 2"%self._name + " arguments (%s given)"%len(args)) - else: - self.__primeBound = 0u if len(args) < 2 else args[1] return super(PrimePi, self).__call__(args[0], coerce=coerce, hold=hold) def _eval_(self, x): @@ -208,12 +122,6 @@ cdef class PrimePi(BuiltinFunction): 9592 sage: prime_pi._eval_(500509) 41581 - sage: prime_pi._eval_(3.5) - 2 - sage: prime_pi._eval_(sqrt(2357)) - 15 - sage: prime_pi._eval_(str(-2^100)) - 0 sage: prime_pi._eval_(mod(30957, 9750979)) 3337 @@ -232,209 +140,32 @@ cdef class PrimePi(BuiltinFunction): 80316571436 156661034233 - This implementation uses unsigned 64-bit ints and does not support + This implementation uses 64-bit ints and does not support :math:`x \geq 2^63`:: sage: prime_pi(2^63) Traceback (most recent call last): ... - NotImplementedError: computation of prime_pi for x >= 2^63 is not implemented + OverflowError: ...to convert... + + TESTS: + + Check that :trac:`24960` is fixed:: + + sage: prime_pi(642763101936913) + 19439675999019 + sage: prime_pi(10.5) + 4 """ - cdef uint64_t z + from sage.functions.other import floor try: - z = arg_to_uint64(x, 'prime_pi', 'x') - except NotImplementedError: - raise + z = Integer(x) except TypeError: - return None - if self.__smallPi == NULL: - self._init_tables() - z = self._pi(z, self.__primeBound) - self._clean_cache() - return Integer(z) - - cdef uint64_t _pi(self, uint64_t x, uint64_t b) except -1: - r""" - Returns pi(x) under the assumption that 0 <= x < 2^64 - """ - if x <= 0xffffull: return self.__smallPi[x] - if b*b < x: - b = Integer(x).sqrtrem()[0] - elif b > x: - b = x - self._init_primes(b) - if not sig_on_no_except(): - self._clean_cache() - cython_check_exception() - b = self.__numPrimes - b += self._phi(x, b)-1ull - sig_off() - return b - - cdef uint32_t _cached_count(self, uint32_t p): - r""" - For p < 65536, returns the value stored in ``self.__smallPi[p]``. For - p <= ``self.__maxSieve``, uses a binary search on ``self.__primes`` to - compute pi(p). - """ - # inspired by Yann Laigle-Chapuy's suggestion - if p <= 0xffffu: return self.__smallPi[p] - cdef uint32_t size = (self.__numPrimes)>>1u - # Use the expected density of primes for expected inputs to make an - # educated guess - if p>>3u < size: - size = p>>3u - # deal with edge case separately - elif p >= self.__primes[self.__numPrimes-1u]: - return self.__numPrimes - cdef uint32_t pos = size - cdef uint32_t prime - while size: - prime = self.__primes[pos] - size >>= 1u - if prime < p: pos += size - elif prime > p: pos -= size - else: return pos+1u - if self.__primes[pos] <= p: - while self.__primes[pos] <= p: pos += 1u - return pos - while self.__primes[pos] > p: pos -= 1u - return pos+1u - - cdef void _clean_cache(self): - if self.__numPrimes: - sig_free(self.__primes) - self.__numPrimes = 0u - self.__maxSieve = 0u - - cdef uint64_t _init_primes(self, uint32_t b) except -1: - """ - Populates ``self.__primes`` with all primes < b - """ - cdef uint32_t *prime - cdef uint32_t newNumPrimes, i - pari.init_primes(b+1u) - self.__pariPrimePtr = diffptr - newNumPrimes = self._pi(b, 0ull) - if self.__numPrimes: - prime = sig_realloc(self.__primes, - newNumPrimes * sizeof(uint32_t)) - else: - prime = sig_malloc(newNumPrimes*sizeof(uint32_t)) - if not sig_on_no_except(): - self.__numPrimes = newNumPrimes - self._clean_cache() - cython_check_exception() - if prime == NULL: - raise RuntimeError("not enough memory, maybe try with a smaller " - + "prime_bound?") - self.__primes = prime - prime += self.__numPrimes - for i in range(self.__numPrimes, newNumPrimes): - prime[0] = 0u if prime == self.__primes else prime[-1] - NEXT_PRIME_VIADIFF(prime[0], self.__pariPrimePtr) - prime += 1 - self.__numPrimes = newNumPrimes - self.__maxSieve = b - sig_off() - - cdef uint64_t _phi(self, uint64_t x, uint64_t i): - r""" - Legendre's formula: returns the number of primes :math:`\leq` ``x`` - that are not divisible by the first ``i`` primes - """ - if not i: return x - # explicitly compute for small i - cdef uint64_t s = (x+1ull)>>1ull - if i == 1ull: return s - s -= (x+3ull)/6ull - if i == 2ull: return s - s -= (x+5ull)/10ull - (x+15ull)/30ull - if i == 3ull: return s - s -= ((x+7ull)/14ull - (x+21ull)/42ull - (x+35ull)/70ull + - (x+105ull)/210ull) - if i == 4ull: return s - s -= ((x+11ull)/22ull - (x+33ull)/66ull - (x+55ull)/110ull - - (x+77ull)/154ull + (x+165ull)/330ull + (x+231ull)/462ull + - (x+385ull)/770ull - (x+1155ull)/2310ull) - if i == 5ull: return s - cdef uint64_t y=x/13ull, j=5ull - cdef uint32_t *prime=self.__primes+5 - # switch to 32-bit as quickly as possible - while y > 0xffffffffull: - s -= self._phi(y, j) - j += 1ull - if j == i: return s - prime += 1 - y = x/(prime[0]) - # get y <= maxSieve so we can use a binary search with our table of - # primes - while y > (self.__maxSieve): - s -= self._phi32(y, j) - j += 1ull - if j == i: return s - prime += 1 - y = x/(prime[0]) - cdef uint64_t prime2 = prime[-1] - # get p^2 > y so that we can use the identity phi(x,a)=pi(x)-a+1 - while prime2*prime2 <= y: - s -= self._phi32(y, j) - j += 1ull - if j == i: return s - prime2 = prime[0] - prime += 1 - y = x/(prime[0]) - s += j - # use the identity phi(x,a) = pi(x)-a+1 and compute pi using a binary - # search - while prime2 < y: - s -= self._cached_count(y)-j - j += 1ull - if j == i: return s-i - prime2 = prime[0] - prime += 1 - y = x/(prime[0]) - return s-i - - cdef uint32_t _phi32(self, uint32_t x, uint32_t i): - """ - Same as _phi except specialized for 32-bit ints - """ - # table method for explicit computation was suggested by Yann - # Laigle-Chapuy - if i == 5u: return ((x/77u)<<4u) + self.__tabS[x%2310u] - cdef uint32_t s = ((x/77u)<<4u) + self.__tabS[x%2310u] - cdef uint32_t y = x/13u, j = 5u - cdef uint32_t *prime = self.__primes+5 - while y > self.__maxSieve: - s -= self._phi32(y, j) - j += 1u - if j == i: return s - prime += 1 - y = x/prime[0] - cdef uint32_t prime2 = prime[-1] - while prime2*prime2 <= y: - s -= self._phi32(y, j) - j += 1u - if j == i: return s - prime2 = prime[0] - prime += 1 - y = x/prime[0] - s += j - while 0xffffu < y: - s -= self._cached_count(y)-j - j += 1u - if j == i: return s-i - prime += 1 - y = x/prime[0] - while prime2 < y: - s -= self.__smallPi[y]-j - j += 1u - if j == i: return s-i - prime2 = prime[0] - prime += 1 - y = x/prime[0] - return s-i + try: + z = Integer(floor(x)) + except TypeError: + return None + return _prime_pi(z) def plot(self, xmin=0, xmax=100, vertical_lines=True, **kwds): """ @@ -451,7 +182,7 @@ cdef class PrimePi(BuiltinFunction): sage: plot(prime_pi, 1, 100) Graphics object consisting of 1 graphics primitive - sage: prime_pi.plot(-2, sqrt(2501), thickness=2, vertical_lines=False) + sage: prime_pi.plot(1, 51, thickness=2, vertical_lines=False) Graphics object consisting of 16 graphics primitives """ from sage.plot.step import plot_step_function @@ -497,27 +228,15 @@ cpdef Integer legendre_phi(x, a): 14688 sage: legendre_phi(91753, 5973) 2893 - sage: legendre_phi(7.5, 2) - 3 - sage: legendre_phi(str(-2^100), 92372) - 0 sage: legendre_phi(4215701455, 6450023226) 1 - .. NOTE:: - - This uses a recursive implementation, using the optimizations - described in [Oha2011]_. - - AUTHOR: - - - \R. Andrew Ohana (2011) """ if not isinstance(a, Integer): a = Integer(a) if a < Integer(0): raise ValueError("a (=%s) must be non-negative"%a) - cdef uint64_t y = arg_to_uint64(x, 'legendre_phi', 'x and a') + y = Integer(x) # legendre_phi(x, a) = 0 when x <= 0 if not y: return Integer(0) @@ -525,13 +244,6 @@ cpdef Integer legendre_phi(x, a): # legendre_phi(x, 0) = x if a == Integer(0): return Integer(y) - # Use knowledge about the density of primes to quickly compute for many - # cases where a is unusually large - if a > Integer(y>>1ull): return Integer(1) - if y > 1916ull: - chk = Integer(y)*Integer(13271040)//Integer(86822723) - if a > chk: return Integer(1) - # If a > prime_pi(2^32), we compute phi(x,a) = max(pi(x)-a+1,1) if a > Integer(203280221): ret = prime_pi(x)-a+Integer(1) @@ -539,17 +251,6 @@ cpdef Integer legendre_phi(x, a): return ret # Deal with the general case - if (prime_pi).__smallPi == NULL: - (prime_pi)._init_tables() - cdef uint32_t z = pari.prime(a) - if z >= y: return Integer(1) - (prime_pi)._init_primes(z) - if not sig_on_no_except(): - (prime_pi)._clean_cache() - cython_check_exception() - y = (prime_pi)._phi(y, mpz_get_ui((a).value)) - sig_off() - (prime_pi)._clean_cache() - return Integer(y) + return Integer(_phi(y, a)) partial_sieve_function = legendre_phi diff --git a/src/sage/functions/transcendental.py b/src/sage/functions/transcendental.py index e37f4c89422..fbbb1995c56 100644 --- a/src/sage/functions/transcendental.py +++ b/src/sage/functions/transcendental.py @@ -20,8 +20,11 @@ import sys import sage.rings.complex_mpfr as complex_field -from sage.rings.all import (ComplexField, ZZ, RR, RDF) -from sage.rings.complex_mpfr import is_ComplexNumber +from sage.rings.integer_ring import ZZ +from sage.rings.real_mpfr import RR +from sage.rings.real_double import RDF +from sage.rings.complex_mpfr import ComplexField, is_ComplexNumber +from sage.rings.cc import CC from sage.rings.real_mpfr import (RealField, is_RealNumber) from sage.symbolic.function import GinacFunction, BuiltinFunction @@ -32,7 +35,6 @@ from .gamma import psi from .other import factorial -CC = complex_field.ComplexField() I = CC.gen(0) @@ -140,8 +142,15 @@ def __init__(self): +infinity sage: zeta(SR(1.0)) Infinity + + Fixed conversion:: + + sage: zeta(3)._maple_init_() + 'Zeta(3)' """ - GinacFunction.__init__(self, 'zeta', conversions={'giac':'Zeta'}) + GinacFunction.__init__(self, 'zeta', conversions={'giac': 'Zeta', + 'maple': 'Zeta', + 'mathematica': 'Zeta'}) zeta = Function_zeta() @@ -213,6 +222,7 @@ def __init__(self): """ BuiltinFunction.__init__(self, 'hurwitz_zeta', nargs=2, conversions=dict(mathematica='HurwitzZeta', + maple='Zeta', sympy='zeta'), latex_name=r'\zeta') diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 13cd5c29996..dac52adcc65 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -86,7 +86,7 @@ from sage.symbolic.constants import I from sage.misc.lazy_attribute import lazy_attribute from sage.rings.infinity import infinity -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.real_mpfr import RR from sage.plot.arc import arc from sage.plot.line import line @@ -887,14 +887,14 @@ def perpendicular_bisector(self): sage: PD = HyperbolicPlane().PD() sage: g = PD.get_geodesic(-0.3+0.4*I,+0.7-0.1*I) - sage: h = g.perpendicular_bisector() - sage: P = g.plot(color='blue')+h.plot(color='orange');P # optional - sage.plot + sage: h = g.perpendicular_bisector().complete() + sage: P = g.plot(color='blue')+h.plot(color='orange');P Graphics object consisting of 4 graphics primitives .. PLOT:: g = HyperbolicPlane().PD().get_geodesic(-0.3+0.4*I,+0.7-0.1*I) - h = g.perpendicular_bisector() + h = g.perpendicular_bisector().complete() sphinx_plot(g.plot(color='blue')+h.plot(color='orange')) Complete geodesics cannot be bisected:: @@ -908,10 +908,14 @@ def perpendicular_bisector(self): TESTS:: sage: g = HyperbolicPlane().PD().random_geodesic() - sage: h = g.perpendicular_bisector() + sage: h = g.perpendicular_bisector().complete() sage: bool(h.intersection(g)[0].coordinates() - g.midpoint().coordinates() < 10**-9) True + sage: g = HyperbolicPlane().UHP().random_geodesic() + sage: h = g.perpendicular_bisector().complete() + sage: bool(h.intersection(g)[0].coordinates() - g.midpoint().coordinates() < 10**-9) + True """ P = self._cached_geodesic.perpendicular_bisector() @@ -1036,9 +1040,9 @@ def length(self): return self._model._dist_points(self._start.coordinates(), self._end.coordinates()) -#*********************************************************************** +# *********************************************************************** # UHP geodesics -#*********************************************************************** +# *********************************************************************** class HyperbolicGeodesicUHP(HyperbolicGeodesic): @@ -1148,7 +1152,7 @@ def plot(self, boundary=True, **options): Graphics object consisting of 2 graphics primitives Plotting a line with ``boundary=False``. :: - + sage: g = HyperbolicPlane().UHP().get_geodesic(0, I) sage: g.plot(boundary=False) # optional - sage.plot Graphics object consisting of 1 graphics primitive @@ -1328,39 +1332,212 @@ def intersection(self, other): sage: g.intersection(h) [Point in UHP 2/3*sqrt(-2) + 13/3] + .. PLOT:: + + UHP = HyperbolicPlane().UHP() + g = UHP.get_geodesic(3, 5) + h = UHP.get_geodesic(4, 7) + P = g.intersection(h) + pict = g.plot(color="red")+h.plot(color="red") + sphinx_plot(pict) + If the given geodesics do not intersect, the function returns an empty list:: sage: g = UHP.get_geodesic(4, 5) - sage: h = UHP.get_geodesic(5, 7) + sage: h = UHP.get_geodesic(6, 7) sage: g.intersection(h) [] + .. PLOT:: + + UHP = HyperbolicPlane().UHP() + g = UHP.get_geodesic(4.0, 5.0) + h = UHP.get_geodesic(6.0, 7.0) + sphinx_plot(g.plot() + h.plot()) + + If the given geodesics are asymptotically parallel, the function returns the common boundary point:: + + sage: g = UHP.get_geodesic(4, 5) + sage: h = UHP.get_geodesic(5, 7) + sage: g.intersection(h) + [Boundary point in UHP 5.00000000000000] + + .. PLOT:: + + UHP = HyperbolicPlane().UHP() + g = UHP.get_geodesic(4.0, 5.0) + h = UHP.get_geodesic(6.0, 7.0) + sphinx_plot(g.plot() + h.plot()) + If the given geodesics are identical, return that geodesic:: sage: g = UHP.get_geodesic(4 + I, 18*I) sage: h = UHP.get_geodesic(4 + I, 18*I) sage: g.intersection(h) - [Boundary point in UHP -1/8*sqrt(114985) - 307/8, - Boundary point in UHP 1/8*sqrt(114985) - 307/8] + Geodesic in UHP from I + 4 to 18*I + + TESTS: + + sage: UHP = HyperbolicPlane().UHP() + sage: g1 = UHP.get_geodesic(2*QQbar.gen(), 5) + sage: g2 = UHP.get_geodesic(-1/2, Infinity) + sage: g1.intersection(g2) + [] + + sage: UHP = HyperbolicPlane().UHP() #case Ia + sage: UHP = HyperbolicPlane().UHP() + sage: g1=UHP.get_geodesic(-1,I) + sage: g2=UHP.get_geodesic(0,2) + sage: g1.intersection(g2) + [] + + sage: UHP = HyperbolicPlane().UHP() #case Ib + sage: g1=UHP.get_geodesic(-1,I) + sage: g2=UHP.get_geodesic(1/2,1/2+2*I) + sage: g1.intersection(g2) + [] + + sage: UHP = HyperbolicPlane().UHP() #case IIa + sage: g1=UHP.get_geodesic(-1,+1) + sage: g2=UHP.get_geodesic(-1,2) + sage: g1.intersection(g2) + [Boundary point in UHP -1.00000000000000] + + sage: UHP = HyperbolicPlane().UHP() #case IIb + sage: g1=UHP.get_geodesic(-1,+Infinity) + sage: g2=UHP.get_geodesic(+1,+Infinity) + sage: g1.intersection(g2) + [Boundary point in UHP +infinity] + + sage: UHP = HyperbolicPlane().UHP() #case IIc + sage: g1=UHP.get_geodesic(-1,-1+I) + sage: g2=UHP.get_geodesic(+1,+1+I) + sage: g1.intersection(g2) + [] + + sage: UHP = HyperbolicPlane().UHP() #case IId + sage: g1=UHP.get_geodesic(-1,+1) + sage: g2=UHP.get_geodesic(-1,-1+2*I) + sage: g1.intersection(g2) + [Boundary point in UHP -1.00000000000000] + + sage: UHP = HyperbolicPlane().UHP() #case IIIa + sage: g1=UHP.get_geodesic(-1,I) + sage: g2=UHP.get_geodesic(+1,(+cos(pi/3)+I*sin(pi/3))) + sage: g1.intersection(g2) + [] + + sage: UHP = HyperbolicPlane().UHP() #case IIIb + sage: g1=UHP.get_geodesic(I,2*I) + sage: g2=UHP.get_geodesic(3*I,4*I) + sage: g1.intersection(g2) + [] + + sage: UHP = HyperbolicPlane().UHP() #case IVa + sage: g1=UHP.get_geodesic(3*I,Infinity) + sage: g2=UHP.get_geodesic(2*I,4*I) + sage: g1.intersection(g2) + Geodesic in UHP from 3.00000000000000*I to 4.00000000000000*I + + sage: UHP = HyperbolicPlane().UHP() #case IVb + sage: g1=UHP.get_geodesic(I,3*I) + sage: g2=UHP.get_geodesic(2*I,4*I) + sage: g1.intersection(g2) + Geodesic in UHP from 2.00000000000000*I to 3.00000000000000*I + + sage: UHP = HyperbolicPlane().UHP() #case IVc + sage: g1=UHP.get_geodesic(2*I,infinity) + sage: g2=UHP.get_geodesic(3*I,infinity) + sage: g1.intersection(g2) + Geodesic in UHP from 3.00000000000000*I to +infinity """ - start_1, end_1 = sorted(self.ideal_endpoints(), key=str) - start_2, end_2 = sorted(other.ideal_endpoints(), key=str) - if start_1 == start_2 and end_1 == end_2: # Unoriented geods are same - return [start_1, end_1] - if start_1 == start_2: - return start_1 - elif end_1 == end_2: - return end_1 - A = self.reflection_involution() - B = other.reflection_involution() - C = A * B - if C.classification() in ['hyperbolic', 'parabolic']: - return [] - return C.fixed_point_set() + UHP = self.model() + # Both geodesic need to be UHP geodesics for this to work + if(other.model() != UHP): + other = other.to_model(UHP) + # Get endpoints and ideal endpoints + i_start_1, i_end_1 = sorted(self.ideal_endpoints(), key=str) + i_start_2, i_end_2 = sorted(other.ideal_endpoints(), key=str) + start_1, end_1 = [CC(x.coordinates()) for x in self.endpoints()] + start_2, end_2 = [CC(x.coordinates()) for x in other.endpoints()] + # sort the geodesic endpoints according to start_1.real() < end_1.real() and if start_1.real() == end_1.real() + # then start_1.imag() < end_1.imag() + if start_1.real() > end_1.real(): # enforce + start_1, end_1 = end_1, start_1 + elif start_1.real() == end_1.real(): + if start_1.imag() > end_1.imag(): + start_1, end_1 = end_1, start_1 + # sort the geodesic endpoints according to start_2.real() < end_2.real() and if start_2.real() == end_2.real() + # then start_2.imag() < end_2.imag() + if start_2.real() > end_2.real(): + start_2, end_2 = end_2, start_2 + elif start_2.real() == end_2.real(): + if start_2.imag() > end_2.imag(): + start_2, end_2 = end_2, start_2 + if i_start_1 == i_start_2 and i_end_1 == i_end_2: + # Unoriented segments lie on the same complete geodesic + if start_1 == start_2 and end_1 == end_2: + return self + if start_1.real() == end_1.real() or end_1.real().is_infinity(): + # Both geodesics are vertical + if start_2.imag() < start_1.imag(): + # make sure always start_1.imag() <= start_2.imag() + start_1, start_2 = start_2, start_1 + end_1, end_2 = end_2, end_1 + if end_1 == start_2: + return [UHP.get_point(end_1)] + elif end_1.real().is_infinity() and end_2.real().is_infinity(): + return UHP.get_geodesic(start_2, end_2) + elif end_1.imag() < start_2.imag(): + return [] + else: + return UHP.get_geodesic(start_2, end_1) + else: + # Neither geodesic is vertical + # make sure always start_1.real() <= start_2.real() + if start_2.real() < start_1.real(): + start_1, start_2 = start_2, start_1 + end_1, end_2 = end_2, end_1 + if end_1 == start_2: + return [UHP.get_point(end_1)] + elif end_1.real() < start_2.real(): + return [] + else: + return UHP.get_geodesic(start_2, end_1) + else: + # Both segments do not have the same complete geodesic + # make sure always start_1.real() <= start_2.real() + if start_2.real() < start_1.real(): + start_1, start_2 = start_2, start_1 + end_1, end_2 = end_2, end_1 + if self.is_asymptotically_parallel(other): + # asymptotic parallel + if start_1 == start_2: + return [UHP.get_point(start_1)] + elif end_1 == start_2 or end_1 == end_2: + return [UHP.get_point(end_1)] + else: + return [] + else: + A = self.reflection_involution() + B = other.reflection_involution() + C = A * B + if C.classification() in ['hyperbolic', 'parabolic']: + return [] + else: + # the fixed point needs not to lie in both segments of geodesic + if end_1 == start_2: + return [UHP().get_point(end_1)] + else: + P = CC(C.fixed_point_set()[0].coordinates()) + if start_1.real() <= P.real() <= end_1.real() and start_2.real() <= P.real() <= end_2.real(): + return C.fixed_point_set() + else: + return [] def perpendicular_bisector(self): # UHP r""" @@ -1369,25 +1546,25 @@ def perpendicular_bisector(self): # UHP EXAMPLES:: - sage: UHP = HyperbolicPlane().UHP() - sage: g = UHP.random_geodesic() - sage: h = g.perpendicular_bisector() - sage: c = lambda x: x.coordinates() - sage: bool(c(g.intersection(h)[0]) - c(g.midpoint()) < 10**-9) - True + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.random_geodesic() + sage: h = g.perpendicular_bisector().complete() + sage: c = lambda x: x.coordinates() + sage: bool(c(g.intersection(h)[0]) - c(g.midpoint()) < 10**-9) + True :: sage: UHP = HyperbolicPlane().UHP() sage: g = UHP.get_geodesic(1+I,2+0.5*I) - sage: h = g.perpendicular_bisector() - sage: show(g.plot(color='blue')+h.plot(color='orange')) # optional - sage.plot + sage: h = g.perpendicular_bisector().complete() + sage: show(g.plot(color='blue')+h.plot(color='orange')) .. PLOT:: UHP = HyperbolicPlane().UHP() g = UHP.get_geodesic(1+I,2+0.5*I) - h = g.perpendicular_bisector() + h = g.perpendicular_bisector().complete() sphinx_plot(g.plot(color='blue')+h.plot(color='orange')) Infinite geodesics cannot be bisected:: @@ -1694,7 +1871,7 @@ def angle(self, other): # UHP arccos(7/8) sage: h.angle(g) arccos(7/8) - + Angle between circle and line. Note that ``1/2*sqrt(2)`` equals ``1/4*pi``. :: @@ -1963,9 +2140,9 @@ def _crossratio_matrix(p0, p1, p2): # UHP return matrix([[p1 - p2, (p1 - p2)*(-p0)], [p1 - p0, (p1 - p0)*(-p2)]]) -#*********************************************************************** +# *********************************************************************** # Other geodesics -#*********************************************************************** +# *********************************************************************** class HyperbolicGeodesicPD(HyperbolicGeodesic): @@ -2036,7 +2213,7 @@ def plot(self, boundary=True, **options): Graphics object consisting of 2 graphics primitives sage: h = PD.get_geodesic(exp(2*I*pi/11), exp(1*I*pi/11)) sage: H = h.plot(thickness=6, color="orange"); H # optional - sage.plot - Graphics object consisting of 2 graphics primitives # optional - sage.plot + Graphics object consisting of 2 graphics primitives sage: show(G+H) # optional - sage.plot .. PLOT:: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 7adb33ed6b9..e8da817f755 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -84,7 +84,7 @@ from sage.functions.other import imag, real from sage.misc.functional import sqrt from sage.functions.all import arccosh -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.real_double import RDF from sage.rings.real_mpfr import RR from sage.rings.infinity import infinity diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index 2fba9b50df9..6100acd25e2 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -68,7 +68,7 @@ from sage.matrix.constructor import matrix from sage.modules.free_module_element import vector from sage.rings.infinity import infinity -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.real_mpfr import RR from sage.functions.other import real, imag diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index c7ad1ffaea9..1af48504986 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -42,12 +42,10 @@ from sage.modules.free_module_element import vector from sage.modules.vector_space_morphism import linear_transformation from sage.matrix.constructor import matrix -from sage.misc.lazy_import import lazy_import -lazy_import('sage.groups.matrix_gps.finitely_generated', 'MatrixGroup') from sage.geometry.convex_set import AffineHullProjectionData from .constructor import Polyhedron -from .base2 import Polyhedron_base2 +from .base4 import Polyhedron_base4 ######################################################################### # Notes if you want to implement your own backend: @@ -92,7 +90,7 @@ def is_Polyhedron(X): ######################################################################### -class Polyhedron_base(Polyhedron_base2): +class Polyhedron_base(Polyhedron_base4): """ Base class for Polyhedron objects @@ -165,138 +163,6 @@ class Polyhedron_base(Polyhedron_base2): sage: TestSuite(P).run() """ - def _init_empty_polyhedron(self): - """ - Initializes an empty polyhedron. - - TESTS:: - - sage: Polyhedron().vertex_adjacency_matrix() # indirect doctest - [] - sage: Polyhedron().facet_adjacency_matrix() - [0] - """ - Polyhedron_base2._init_empty_polyhedron(self) - - V_matrix = matrix(ZZ, 0, 0, 0) - V_matrix.set_immutable() - self.vertex_adjacency_matrix.set_cache(V_matrix) - - H_matrix = matrix(ZZ, 1, 1, 0) - H_matrix.set_immutable() - self.facet_adjacency_matrix.set_cache(H_matrix) - - def _sage_input_(self, sib, coerced): - """ - Return Sage command to reconstruct ``self``. - - See :mod:`sage.misc.sage_input` for details. - - .. TODO:: - - Add the option ``preparse`` to the method. - - EXAMPLES:: - - sage: P = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='ppl') - sage: sage_input(P) - Polyhedron(backend='ppl', base_ring=QQ, rays=[(QQ(1), QQ(1))], vertices=[(QQ(0), QQ(1)), (QQ(1), QQ(0))]) - sage: P = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='normaliz') # optional - pynormaliz - sage: sage_input(P) # optional - pynormaliz - Polyhedron(backend='normaliz', base_ring=QQ, rays=[(QQ(1), QQ(1))], vertices=[(QQ(0), QQ(1)), (QQ(1), QQ(0))]) - sage: P = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='polymake') # optional - polymake - sage: sage_input(P) # optional - polymake - Polyhedron(backend='polymake', base_ring=QQ, rays=[(QQ(1), QQ(1))], vertices=[(QQ(1), QQ(0)), (QQ(0), QQ(1))]) - """ - kwds = dict() - kwds['base_ring'] = sib(self.base_ring()) - kwds['backend'] = sib(self.backend()) - if self.n_vertices() > 0: - kwds['vertices'] = [sib(tuple(v)) for v in self.vertices()] - if self.n_rays() > 0: - kwds['rays'] = [sib(tuple(r)) for r in self.rays()] - if self.n_lines() > 0: - kwds['lines'] = [sib(tuple(l)) for l in self.lines()] - return sib.name('Polyhedron')(**kwds) - - def _facet_adjacency_matrix(self): - """ - Compute the facet adjacency matrix in case it has not been - computed during initialization. - - EXAMPLES:: - - sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)]) - sage: p._facet_adjacency_matrix() - [0 1 1] - [1 0 1] - [1 1 0] - - Checks that :trac:`22455` is fixed:: - - sage: s = polytopes.simplex(2) - sage: s._facet_adjacency_matrix() - [0 1 1] - [1 0 1] - [1 1 0] - - """ - # TODO: This implementation computes the whole face lattice, - # which is much more information than necessary. - M = matrix(ZZ, self.n_facets(), self.n_facets(), 0) - codim = self.ambient_dim()-self.dim() - - def set_adjacent(h1, h2): - if h1 is h2: - return - i = h1.index() - codim - j = h2.index() - codim - M[i, j] = 1 - M[j, i] = 1 - - for face in self.faces(self.dim()-2): - Hrep = face.ambient_Hrepresentation() - assert(len(Hrep) == codim+2) - set_adjacent(Hrep[-2], Hrep[-1]) - M.set_immutable() - return M - - def _delete(self): - """ - Delete this polyhedron. - - This speeds up creation of new polyhedra by reusing - objects. After recycling a polyhedron object, it is not in a - consistent state any more and neither the polyhedron nor its - H/V-representation objects may be used any more. - - .. SEEALSO:: - - :meth:`~sage.geometry.polyhedron.parent.Polyhedra_base.recycle` - - EXAMPLES:: - - sage: p = Polyhedron([(0,0),(1,0),(0,1)]) - sage: p._delete() - - sage: vertices = [(0,0,0,0),(1,0,0,0),(0,1,0,0),(1,1,0,0),(0,0,1,0),(0,0,0,1)] - sage: def loop_polyhedra(): - ....: for i in range(100): - ....: p = Polyhedron(vertices) - - sage: timeit('loop_polyhedra()') # not tested - random - 5 loops, best of 3: 79.5 ms per loop - - sage: def loop_polyhedra_with_recycling(): - ....: for i in range(100): - ....: p = Polyhedron(vertices) - ....: p._delete() - - sage: timeit('loop_polyhedra_with_recycling()') # not tested - random - 5 loops, best of 3: 57.3 ms per loop - """ - self.parent().recycle(self) - def _test_basic_properties(self, tester=None, **options): """ Run some basic tests to see, that some general assertion on polyhedra hold. @@ -322,82 +188,6 @@ def _test_basic_properties(self, tester=None, **options): if self.n_inequalities() < 40: tester.assertEqual(self, Polyhedron(ieqs=self.inequalities(), eqns=self.equations(), ambient_dim=self.ambient_dim())) - @cached_method - def vertex_facet_graph(self, labels=True): - r""" - Return the vertex-facet graph. - - This function constructs a directed bipartite graph. - The nodes of the graph correspond to the vertices of the polyhedron - and the facets of the polyhedron. There is an directed edge - from a vertex to a face if and only if the vertex is incident to the face. - - INPUT: - - - ``labels`` -- boolean (default: ``True``); decide how the nodes - of the graph are labelled. Either with the original vertices/facets - of the Polyhedron or with integers. - - OUTPUT: - - - a bipartite DiGraph. If ``labels`` is ``True``, then the nodes - of the graph will actually be the vertices and facets of ``self``, - otherwise they will be integers. - - .. SEEALSO:: - - :meth:`combinatorial_automorphism_group`, - :meth:`is_combinatorially_isomorphic`. - - EXAMPLES:: - - sage: P = polytopes.cube() - sage: G = P.vertex_facet_graph(); G - Digraph on 14 vertices - sage: G.vertices(key = lambda v: str(v)) - [A vertex at (-1, -1, -1), - A vertex at (-1, -1, 1), - A vertex at (-1, 1, -1), - A vertex at (-1, 1, 1), - A vertex at (1, -1, -1), - A vertex at (1, -1, 1), - A vertex at (1, 1, -1), - A vertex at (1, 1, 1), - An inequality (-1, 0, 0) x + 1 >= 0, - An inequality (0, -1, 0) x + 1 >= 0, - An inequality (0, 0, -1) x + 1 >= 0, - An inequality (0, 0, 1) x + 1 >= 0, - An inequality (0, 1, 0) x + 1 >= 0, - An inequality (1, 0, 0) x + 1 >= 0] - sage: G.automorphism_group().is_isomorphic(P.hasse_diagram().automorphism_group()) - True - sage: O = polytopes.octahedron(); O - A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 6 vertices - sage: O.vertex_facet_graph() - Digraph on 14 vertices - sage: H = O.vertex_facet_graph() - sage: G.is_isomorphic(H) - False - sage: G2 = copy(G) - sage: G2.reverse_edges(G2.edges()) - sage: G2.is_isomorphic(H) - True - - TESTS: - - Check that :trac:`28828` is fixed:: - - sage: G._immutable - True - - Check that :trac:`29188` is fixed:: - - sage: P = polytopes.cube() - sage: P.vertex_facet_graph().is_isomorphic(P.vertex_facet_graph(False)) - True - """ - return self.combinatorial_polyhedron().vertex_facet_graph(names=labels) - def plot(self, point=None, line=None, polygon=None, # None means unspecified by the user wireframe='blue', fill='green', @@ -1079,223 +869,6 @@ def to_linear_program(self, solver=None, return_variable=False, base_ring=None): else: return p - @cached_method - def vertices_matrix(self, base_ring=None): - """ - Return the coordinates of the vertices as the columns of a matrix. - - INPUT: - - - ``base_ring`` -- A ring or ``None`` (default). The base ring - of the returned matrix. If not specified, the base ring of - the polyhedron is used. - - OUTPUT: - - A matrix over ``base_ring`` whose columns are the coordinates - of the vertices. A ``TypeError`` is raised if the coordinates - cannot be converted to ``base_ring``. - - .. WARNING:: - - If the polyhedron has lines, return the coordinates of the vertices - of the ``Vrepresentation``. However, the represented polyhedron - has no 0-dimensional faces (i.e. vertices):: - - sage: P = Polyhedron(rays=[[1,0,0]],lines=[[0,1,0]]) - sage: P.vertices_matrix() - [0] - [0] - [0] - sage: P.faces(0) - () - - EXAMPLES:: - - sage: triangle = Polyhedron(vertices=[[1,0],[0,1],[1,1]]) - sage: triangle.vertices_matrix() - [0 1 1] - [1 0 1] - sage: (triangle/2).vertices_matrix() - [ 0 1/2 1/2] - [1/2 0 1/2] - sage: (triangle/2).vertices_matrix(ZZ) - Traceback (most recent call last): - ... - TypeError: no conversion of this rational to integer - - TESTS: - - Check that :trac:`28828` is fixed:: - - sage: P.vertices_matrix().is_immutable() - True - """ - if base_ring is None: - base_ring = self.base_ring() - m = matrix(base_ring, self.ambient_dim(), self.n_vertices()) - for i, v in enumerate(self.vertices()): - for j in range(self.ambient_dim()): - m[j, i] = v[j] - m.set_immutable() - return m - - def bounded_edges(self): - """ - Return the bounded edges (excluding rays and lines). - - OUTPUT: - - A generator for pairs of vertices, one pair per edge. - - EXAMPLES:: - - sage: p = Polyhedron(vertices=[[1,0],[0,1]], rays=[[1,0],[0,1]]) - sage: [ e for e in p.bounded_edges() ] - [(A vertex at (0, 1), A vertex at (1, 0))] - sage: for e in p.bounded_edges(): print(e) - (A vertex at (0, 1), A vertex at (1, 0)) - """ - obj = self.Vrepresentation() - for i in range(len(obj)): - if not obj[i].is_vertex(): - continue - for j in range(i+1, len(obj)): - if not obj[j].is_vertex(): - continue - if self.vertex_adjacency_matrix()[i, j] == 0: - continue - yield (obj[i], obj[j]) - - @cached_method - def vertex_adjacency_matrix(self): - """ - Return the binary matrix of vertex adjacencies. - - EXAMPLES:: - - sage: polytopes.simplex(4).vertex_adjacency_matrix() - [0 1 1 1 1] - [1 0 1 1 1] - [1 1 0 1 1] - [1 1 1 0 1] - [1 1 1 1 0] - - The rows and columns of the vertex adjacency matrix correspond - to the :meth:`Vrepresentation` objects: vertices, rays, and - lines. The `(i,j)` matrix entry equals `1` if the `i`-th and - `j`-th V-representation object are adjacent. - - Two vertices are adjacent if they are the endpoints of an - edge, that is, a one-dimensional face. For unbounded polyhedra - this clearly needs to be generalized and we define two - V-representation objects (see - :mod:`sage.geometry.polyhedron.constructor`) to be adjacent if - they together generate a one-face. There are three possible - combinations: - - * Two vertices can bound a finite-length edge. - - * A vertex and a ray can generate a half-infinite edge - starting at the vertex and with the direction given by the - ray. - - * A vertex and a line can generate an infinite edge. The - position of the vertex on the line is arbitrary in this - case, only its transverse position matters. The direction of - the edge is given by the line generator. - - For example, take the half-plane:: - - sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) - sage: half_plane.Hrepresentation() - (An inequality (1, 0) x + 0 >= 0,) - - Its (non-unique) V-representation consists of a vertex, a ray, - and a line. The only edge is spanned by the vertex and the - line generator, so they are adjacent:: - - sage: half_plane.Vrepresentation() - (A line in the direction (0, 1), A ray in the direction (1, 0), A vertex at (0, 0)) - sage: half_plane.vertex_adjacency_matrix() - [0 0 1] - [0 0 0] - [1 0 0] - - In one dimension higher, that is for a half-space in 3 - dimensions, there is no one-dimensional face. Hence nothing is - adjacent:: - - sage: Polyhedron(ieqs=[(0,1,0,0)]).vertex_adjacency_matrix() - [0 0 0 0] - [0 0 0 0] - [0 0 0 0] - [0 0 0 0] - - EXAMPLES: - - In a bounded polygon, every vertex has precisely two adjacent ones:: - - sage: P = Polyhedron(vertices=[(0, 1), (1, 0), (3, 0), (4, 1)]) - sage: for v in P.Vrep_generator(): - ....: print("{} {}".format(P.adjacency_matrix().row(v.index()), v)) - (0, 1, 0, 1) A vertex at (0, 1) - (1, 0, 1, 0) A vertex at (1, 0) - (0, 1, 0, 1) A vertex at (3, 0) - (1, 0, 1, 0) A vertex at (4, 1) - - If the V-representation of the polygon contains vertices and - one ray, then each V-representation object is adjacent to two - V-representation objects:: - - sage: P = Polyhedron(vertices=[(0, 1), (1, 0), (3, 0), (4, 1)], - ....: rays=[(0,1)]) - sage: for v in P.Vrep_generator(): - ....: print("{} {}".format(P.adjacency_matrix().row(v.index()), v)) - (0, 1, 0, 0, 1) A ray in the direction (0, 1) - (1, 0, 1, 0, 0) A vertex at (0, 1) - (0, 1, 0, 1, 0) A vertex at (1, 0) - (0, 0, 1, 0, 1) A vertex at (3, 0) - (1, 0, 0, 1, 0) A vertex at (4, 1) - - If the V-representation of the polygon contains vertices and - two distinct rays, then each vertex is adjacent to two - V-representation objects (which can now be vertices or - rays). The two rays are not adjacent to each other:: - - sage: P = Polyhedron(vertices=[(0, 1), (1, 0), (3, 0), (4, 1)], - ....: rays=[(0,1), (1,1)]) - sage: for v in P.Vrep_generator(): - ....: print("{} {}".format(P.adjacency_matrix().row(v.index()), v)) - (0, 1, 0, 0, 0) A ray in the direction (0, 1) - (1, 0, 1, 0, 0) A vertex at (0, 1) - (0, 1, 0, 0, 1) A vertex at (1, 0) - (0, 0, 0, 0, 1) A ray in the direction (1, 1) - (0, 0, 1, 1, 0) A vertex at (3, 0) - - The vertex adjacency matrix has base ring integers. This way one can express various - counting questions:: - - sage: P = polytopes.cube() - sage: Q = P.stack(P.faces(2)[0]) - sage: M = Q.vertex_adjacency_matrix() - sage: sum(M) - (4, 4, 3, 3, 4, 4, 4, 3, 3) - sage: G = Q.vertex_graph() - sage: G.degree() - [4, 4, 3, 3, 4, 4, 4, 3, 3] - - TESTS: - - Check that :trac:`28828` is fixed:: - - sage: P.adjacency_matrix().is_immutable() - True - """ - return self.combinatorial_polyhedron().vertex_adjacency_matrix() - - adjacency_matrix = vertex_adjacency_matrix - def boundary_complex(self): """ Return the simplicial complex given by the boundary faces of ``self``, @@ -1351,280 +924,6 @@ def boundary_complex(self): else: raise NotImplementedError("this function is only implemented for simplicial polytopes") - @cached_method - def facet_adjacency_matrix(self): - """ - Return the adjacency matrix for the facets and hyperplanes. - - EXAMPLES:: - - sage: s4 = polytopes.simplex(4, project=True) - sage: s4.facet_adjacency_matrix() - [0 1 1 1 1] - [1 0 1 1 1] - [1 1 0 1 1] - [1 1 1 0 1] - [1 1 1 1 0] - - The facet adjacency matrix has base ring integers. This way one can express various - counting questions:: - - sage: P = polytopes.cube() - sage: Q = P.stack(P.faces(2)[0]) - sage: M = Q.facet_adjacency_matrix() - sage: sum(M) - (4, 4, 4, 4, 3, 3, 3, 3, 4) - - TESTS: - - Check that :trac:`28828` is fixed:: - - sage: s4.facet_adjacency_matrix().is_immutable() - True - """ - return self._facet_adjacency_matrix() - - @cached_method - def incidence_matrix(self): - """ - Return the incidence matrix. - - .. NOTE:: - - The columns correspond to inequalities/equations in the - order :meth:`Hrepresentation`, the rows correspond to - vertices/rays/lines in the order - :meth:`Vrepresentation`. - - .. SEEALSO:: - - :meth:`slack_matrix`. - - EXAMPLES:: - - sage: p = polytopes.cuboctahedron() - sage: p.incidence_matrix() - [0 0 1 1 0 1 0 0 0 0 1 0 0 0] - [0 0 0 1 0 0 1 0 1 0 1 0 0 0] - [0 0 1 1 1 0 0 1 0 0 0 0 0 0] - [1 0 0 1 1 0 1 0 0 0 0 0 0 0] - [0 0 0 0 0 1 0 0 1 1 1 0 0 0] - [0 0 1 0 0 1 0 1 0 0 0 1 0 0] - [1 0 0 0 0 0 1 0 1 0 0 0 1 0] - [1 0 0 0 1 0 0 1 0 0 0 0 0 1] - [0 1 0 0 0 1 0 0 0 1 0 1 0 0] - [0 1 0 0 0 0 0 0 1 1 0 0 1 0] - [0 1 0 0 0 0 0 1 0 0 0 1 0 1] - [1 1 0 0 0 0 0 0 0 0 0 0 1 1] - sage: v = p.Vrepresentation(0) - sage: v - A vertex at (-1, -1, 0) - sage: h = p.Hrepresentation(2) - sage: h - An inequality (1, 1, -1) x + 2 >= 0 - sage: h.eval(v) # evaluation (1, 1, -1) * (-1/2, -1/2, 0) + 1 - 0 - sage: h*v # same as h.eval(v) - 0 - sage: p.incidence_matrix() [0,2] # this entry is (v,h) - 1 - sage: h.contains(v) - True - sage: p.incidence_matrix() [2,0] # note: not symmetric - 0 - - The incidence matrix depends on the ambient dimension:: - - sage: simplex = polytopes.simplex(); simplex - A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 4 vertices - sage: simplex.incidence_matrix() - [1 1 1 1 0] - [1 1 1 0 1] - [1 1 0 1 1] - [1 0 1 1 1] - sage: simplex = simplex.affine_hull_projection(); simplex - A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices - sage: simplex.incidence_matrix() - [1 1 1 0] - [1 1 0 1] - [1 0 1 1] - [0 1 1 1] - - An incidence matrix does not determine a unique - polyhedron:: - - sage: P = Polyhedron(vertices=[[0,1],[1,1],[1,0]]) - sage: P.incidence_matrix() - [1 1 0] - [1 0 1] - [0 1 1] - - sage: Q = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]]) - sage: Q.incidence_matrix() - [1 1 0] - [1 0 1] - [0 1 1] - - - An example of two polyhedra with isomorphic face lattices - but different incidence matrices:: - - sage: Q.incidence_matrix() - [1 1 0] - [1 0 1] - [0 1 1] - - sage: R = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,3/2], [3/2,1]]) - sage: R.incidence_matrix() - [1 1 0] - [1 0 1] - [0 1 0] - [0 0 1] - - The incidence matrix has base ring integers. This way one can express various - counting questions:: - - sage: P = polytopes.twenty_four_cell() - sage: M = P.incidence_matrix() - sage: sum(sum(x) for x in M) == P.flag_f_vector(0,3) - True - - TESTS: - - Check that :trac:`28828` is fixed:: - - sage: R.incidence_matrix().is_immutable() - True - - Test that this method works for inexact base ring - (``cdd`` sets the cache already):: - - sage: P = polytopes.dodecahedron(exact=False) - sage: M = P.incidence_matrix.cache - sage: P.incidence_matrix.clear_cache() - sage: M == P.incidence_matrix() - True - """ - if self.base_ring() in (ZZ, QQ): - # Much faster for integers or rationals. - incidence_matrix = self.slack_matrix().zero_pattern_matrix(ZZ) - incidence_matrix.set_immutable() - return incidence_matrix - - incidence_matrix = matrix(ZZ, self.n_Vrepresentation(), - self.n_Hrepresentation(), 0) - - Vvectors_vertices = tuple((v.vector(), v.index()) - for v in self.Vrep_generator() - if v.is_vertex()) - Vvectors_rays_lines = tuple((v.vector(), v.index()) - for v in self.Vrep_generator() - if not v.is_vertex()) - - # Determine ``is_zero`` to save lots of time. - if self.base_ring().is_exact(): - def is_zero(x): - return not x - else: - is_zero = self._is_zero - - for H in self.Hrep_generator(): - Hconst = H.b() - Hvec = H.A() - Hindex = H.index() - for Vvec, Vindex in Vvectors_vertices: - if is_zero(Hvec*Vvec + Hconst): - incidence_matrix[Vindex, Hindex] = 1 - - # A ray or line is considered incident with a hyperplane, - # if it is orthogonal to the normal vector of the hyperplane. - for Vvec, Vindex in Vvectors_rays_lines: - if is_zero(Hvec*Vvec): - incidence_matrix[Vindex, Hindex] = 1 - - incidence_matrix.set_immutable() - return incidence_matrix - - @cached_method - def slack_matrix(self): - r""" - Return the slack matrix. - - The entries correspond to the evaluation of the Hrepresentation - elements on the Vrepresentation elements. - - .. NOTE:: - - The columns correspond to inequalities/equations in the - order :meth:`Hrepresentation`, the rows correspond to - vertices/rays/lines in the order - :meth:`Vrepresentation`. - - .. SEEALSO:: - - :meth:`incidence_matrix`. - - EXAMPLES:: - - sage: P = polytopes.cube() - sage: P.slack_matrix() - [0 2 2 2 0 0] - [0 0 2 2 0 2] - [0 0 0 2 2 2] - [0 2 0 2 2 0] - [2 2 0 0 2 0] - [2 2 2 0 0 0] - [2 0 2 0 0 2] - [2 0 0 0 2 2] - - sage: P = polytopes.cube(intervals='zero_one') - sage: P.slack_matrix() - [0 1 1 1 0 0] - [0 0 1 1 0 1] - [0 0 0 1 1 1] - [0 1 0 1 1 0] - [1 1 0 0 1 0] - [1 1 1 0 0 0] - [1 0 1 0 0 1] - [1 0 0 0 1 1] - - sage: P = polytopes.dodecahedron().faces(2)[0].as_polyhedron() - sage: P.slack_matrix() - [1/2*sqrt5 - 1/2 0 0 1 1/2*sqrt5 - 1/2 0] - [ 0 0 1/2*sqrt5 - 1/2 1/2*sqrt5 - 1/2 1 0] - [ 0 1/2*sqrt5 - 1/2 1 0 1/2*sqrt5 - 1/2 0] - [ 1 1/2*sqrt5 - 1/2 0 1/2*sqrt5 - 1/2 0 0] - [1/2*sqrt5 - 1/2 1 1/2*sqrt5 - 1/2 0 0 0] - - sage: P = Polyhedron(rays=[[1, 0], [0, 1]]) - sage: P.slack_matrix() - [0 0] - [0 1] - [1 0] - - TESTS:: - - sage: Polyhedron().slack_matrix() - [] - sage: Polyhedron(base_ring=QuadraticField(2)).slack_matrix().base_ring() - Number Field in a with defining polynomial x^2 - 2 with a = 1.41... - """ - if not self.n_Vrepresentation() or not self.n_Hrepresentation(): - slack_matrix = matrix(self.base_ring(), self.n_Vrepresentation(), - self.n_Hrepresentation(), 0) - else: - Vrep_matrix = matrix(self.base_ring(), self.Vrepresentation()) - Hrep_matrix = matrix(self.base_ring(), self.Hrepresentation()) - - # Getting homogeneous coordinates of the Vrepresentation. - hom_helper = matrix(self.base_ring(), [1 if v.is_vertex() else 0 for v in self.Vrepresentation()]) - hom_Vrep = hom_helper.stack(Vrep_matrix.transpose()) - - slack_matrix = (Hrep_matrix * hom_Vrep).transpose() - - slack_matrix.set_immutable() - return slack_matrix - @cached_method def center(self): """ @@ -1748,51 +1047,6 @@ def centroid(self, engine='auto', **kwds): pass return centroid - def a_maximal_chain(self): - r""" - Return a maximal chain of the face lattice in increasing order. - - EXAMPLES:: - - sage: P = polytopes.cube() - sage: P.a_maximal_chain() - [A -1-dimensional face of a Polyhedron in ZZ^3, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, - A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 8 vertices] - sage: P = polytopes.cube() - sage: chain = P.a_maximal_chain(); chain - [A -1-dimensional face of a Polyhedron in ZZ^3, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, - A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 8 vertices] - sage: [face.ambient_V_indices() for face in chain] - [(), (5,), (0, 5), (0, 3, 4, 5), (0, 1, 2, 3, 4, 5, 6, 7)] - - TESTS:: - - Check output for the empty polyhedron:: - - sage: P = Polyhedron() - sage: P.a_maximal_chain() - [A -1-dimensional face of a Polyhedron in ZZ^0] - """ - comb_chain = self.combinatorial_polyhedron().a_maximal_chain() - - from sage.geometry.polyhedron.face import combinatorial_face_to_polyhedral_face - empty_face = self.faces(-1)[0] - universe = self.faces(self.dim())[0] - - if self.dim() == -1: - return [empty_face] - - return [empty_face] + \ - [combinatorial_face_to_polyhedral_face(self, face) - for face in comb_chain] + \ - [universe] - @cached_method def radius_square(self): """ @@ -2009,512 +1263,47 @@ def is_inscribed(self, certificate=False): else: return is_inscribed - @cached_method - def combinatorial_polyhedron(self): - r""" - Return the combinatorial type of ``self``. - - See :class:`sage.geometry.polyhedron.combinatorial_polyhedron.base.CombinatorialPolyhedron`. - - EXAMPLES:: + def hyperplane_arrangement(self): + """ + Return the hyperplane arrangement defined by the equations and + inequalities. - sage: polytopes.cube().combinatorial_polyhedron() - A 3-dimensional combinatorial polyhedron with 6 facets + OUTPUT: - sage: polytopes.cyclic_polytope(4,10).combinatorial_polyhedron() - A 4-dimensional combinatorial polyhedron with 35 facets + A :class:`hyperplane arrangement + ` + consisting of the hyperplanes defined by the + :meth:`Hrepresentation`. + If the polytope is full-dimensional, this is the hyperplane + arrangement spanned by the facets of the polyhedron. - sage: Polyhedron(rays=[[0,1], [1,0]]).combinatorial_polyhedron() - A 2-dimensional combinatorial polyhedron with 2 facets - """ - from sage.geometry.polyhedron.combinatorial_polyhedron.base import CombinatorialPolyhedron - return CombinatorialPolyhedron(self) + EXAMPLES:: - def _test_combinatorial_polyhedron(self, tester=None, **options): + sage: p = polytopes.hypercube(2) + sage: p.hyperplane_arrangement() + Arrangement <-t0 + 1 | -t1 + 1 | t1 + 1 | t0 + 1> """ - Run test suite of combinatorial polyhedron. - - TESTS:: + names = tuple('t' + str(i) for i in range(self.ambient_dim())) + from sage.geometry.hyperplane_arrangement.arrangement import HyperplaneArrangements + field = self.base_ring().fraction_field() + H = HyperplaneArrangements(field, names) + return H(self) - sage: polytopes.cross_polytope(3)._test_combinatorial_polyhedron() + @cached_method + def gale_transform(self): """ - from sage.misc.sage_unittest import TestSuite + Return the Gale transform of a polytope as described in the + reference below. - tester = self._tester(tester=tester, **options) - tester.info("\n Running the test suite of self.combinatorial_polyhedron()") - TestSuite(self.combinatorial_polyhedron()).run(verbose=tester._verbose, - prefix=tester._prefix+" ") - tester.info(tester._prefix+" ", newline = False) + OUTPUT: - def simplicity(self): - r""" - Return the largest integer `k` such that the polytope is `k`-simple. + A list of vectors, the Gale transform. The dimension is the + dimension of the affine dependencies of the vertices of the + polytope. - A polytope `P` is `k`-simple, if every `(d-1-k)`-face - is contained in exactly `k+1` facets of `P` for `1 \leq k \leq d-1`. - Equivalently it is `k`-simple if the polar/dual polytope is `k`-simplicial. - If `self` is a simplex, it returns its dimension. + EXAMPLES: - EXAMPLES:: - - sage: polytopes.hypersimplex(4,2).simplicity() - 1 - sage: polytopes.hypersimplex(5,2).simplicity() - 2 - sage: polytopes.hypersimplex(6,2).simplicity() - 3 - sage: polytopes.simplex(3).simplicity() - 3 - sage: polytopes.simplex(1).simplicity() - 1 - - The method is not implemented for unbounded polyhedra:: - - sage: p = Polyhedron(vertices=[(0,0)],rays=[(1,0),(0,1)]) - sage: p.simplicity() - Traceback (most recent call last): - ... - NotImplementedError: this function is implemented for polytopes only - """ - if not(self.is_compact()): - raise NotImplementedError("this function is implemented for polytopes only") - return self.combinatorial_polyhedron().simplicity() - - def is_simple(self): - """ - Test for simplicity of a polytope. - - See :wikipedia:`Simple_polytope` - - EXAMPLES:: - - sage: p = Polyhedron([[0,0,0],[1,0,0],[0,1,0],[0,0,1]]) - sage: p.is_simple() - True - sage: p = Polyhedron([[0,0,0],[4,4,0],[4,0,0],[0,4,0],[2,2,2]]) - sage: p.is_simple() - False - """ - if not self.is_compact(): - return False - return self.combinatorial_polyhedron().is_simple() - - def simpliciality(self): - r""" - Return the largest integer `k` such that the polytope is `k`-simplicial. - - A polytope is `k`-simplicial, if every `k`-face is a simplex. - If `self` is a simplex, returns its dimension. - - EXAMPLES:: - - sage: polytopes.cyclic_polytope(10,4).simpliciality() - 3 - sage: polytopes.hypersimplex(5,2).simpliciality() - 2 - sage: polytopes.cross_polytope(4).simpliciality() - 3 - sage: polytopes.simplex(3).simpliciality() - 3 - sage: polytopes.simplex(1).simpliciality() - 1 - - The method is not implemented for unbounded polyhedra:: - - sage: p = Polyhedron(vertices=[(0,0)],rays=[(1,0),(0,1)]) - sage: p.simpliciality() - Traceback (most recent call last): - ... - NotImplementedError: this function is implemented for polytopes only - """ - if not(self.is_compact()): - raise NotImplementedError("this function is implemented for polytopes only") - return self.combinatorial_polyhedron().simpliciality() - - def is_simplicial(self): - """ - Tests if the polytope is simplicial - - A polytope is simplicial if every facet is a simplex. - - See :wikipedia:`Simplicial_polytope` - - EXAMPLES:: - - sage: p = polytopes.hypercube(3) - sage: p.is_simplicial() - False - sage: q = polytopes.simplex(5, project=True) - sage: q.is_simplicial() - True - sage: p = Polyhedron([[0,0,0],[1,0,0],[0,1,0],[0,0,1]]) - sage: p.is_simplicial() - True - sage: q = Polyhedron([[1,1,1],[-1,1,1],[1,-1,1],[-1,-1,1],[1,1,-1]]) - sage: q.is_simplicial() - False - sage: P = polytopes.simplex(); P - A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 4 vertices - sage: P.is_simplicial() - True - - The method is not implemented for unbounded polyhedra:: - - sage: p = Polyhedron(vertices=[(0,0)],rays=[(1,0),(0,1)]) - sage: p.is_simplicial() - Traceback (most recent call last): - ... - NotImplementedError: this function is implemented for polytopes only - """ - if not(self.is_compact()): - raise NotImplementedError("this function is implemented for polytopes only") - return self.combinatorial_polyhedron().is_simplicial() - - def is_pyramid(self, certificate=False): - """ - Test whether the polytope is a pyramid over one of its facets. - - INPUT: - - - ``certificate`` -- boolean (default: ``False``); specifies whether - to return a vertex of the polytope which is the apex of a pyramid, - if found - - OUTPUT: - - If ``certificate`` is ``True``, returns a tuple containing: - - 1. Boolean. - 2. The apex of the pyramid or ``None``. - - If ``certificate`` is ``False`` returns a boolean. - - EXAMPLES:: - - sage: P = polytopes.simplex(3) - sage: P.is_pyramid() - True - sage: P.is_pyramid(certificate=True) - (True, A vertex at (1, 0, 0, 0)) - sage: egyptian_pyramid = polytopes.regular_polygon(4).pyramid() # optional - sage.rings.number_field - sage: egyptian_pyramid.is_pyramid() # optional - sage.rings.number_field - True - sage: Q = polytopes.octahedron() - sage: Q.is_pyramid() - False - - For the `0`-dimensional polyhedron, the output is ``True``, - but it cannot be constructed as a pyramid over the empty polyhedron:: - - sage: P = Polyhedron([[0]]) - sage: P.is_pyramid() - True - sage: Polyhedron().pyramid() - Traceback (most recent call last): - ... - ZeroDivisionError: rational division by zero - """ - if not self.is_compact(): - raise ValueError("polyhedron has to be compact") - - return self.combinatorial_polyhedron().is_pyramid(certificate) - - def is_bipyramid(self, certificate=False): - r""" - Test whether the polytope is combinatorially equivalent to a - bipyramid over some polytope. - - INPUT: - - - ``certificate`` -- boolean (default: ``False``); specifies whether - to return two vertices of the polytope which are the apices of a - bipyramid, if found - - OUTPUT: - - If ``certificate`` is ``True``, returns a tuple containing: - - 1. Boolean. - 2. ``None`` or a tuple containing: - a. The first apex. - b. The second apex. - - If ``certificate`` is ``False`` returns a boolean. - - EXAMPLES:: - - sage: P = polytopes.octahedron() - sage: P.is_bipyramid() - True - sage: P.is_bipyramid(certificate=True) - (True, [A vertex at (-1, 0, 0), A vertex at (1, 0, 0)]) - sage: Q = polytopes.cyclic_polytope(3,7) - sage: Q.is_bipyramid() - False - sage: R = Q.bipyramid() - sage: R.is_bipyramid(certificate=True) - (True, [A vertex at (-1, 3, 13, 63), A vertex at (1, 3, 13, 63)]) - - TESTS:: - - sage: P = polytopes.permutahedron(4).bipyramid() - sage: P.is_bipyramid() - True - - sage: P = polytopes.cube() - sage: P.is_bipyramid() - False - - sage: P = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]]) - sage: P.is_bipyramid() - Traceback (most recent call last): - ... - ValueError: polyhedron has to be compact - - ALGORITHM: - - Assume all faces of a polyhedron to be given as lists of vertices. - - A polytope is a bipyramid with apexes `v`, `w` if and only if for each - proper face `v \in F` there exists a face `G` with - `G \setminus \{w\} = F \setminus \{v\}` - and vice versa (for each proper face - `w \in F` there exists ...). - - To check this property it suffices to check for all facets of the polyhedron. - """ - if not self.is_compact(): - raise ValueError("polyhedron has to be compact") - - from sage.misc.functional import is_odd - n_verts = self.n_vertices() - n_facets = self.n_facets() - if is_odd(n_facets): - if certificate: - return (False, None) - return False - - IM = self.incidence_matrix() - if self.n_equations(): - # Remove equations from the incidence matrix, - # such that this is the vertex-facet incidences matrix. - I1 = IM.transpose() - I2 = I1[[i for i in range(self.n_Hrepresentation()) - if not self.Hrepresentation()[i].is_equation()]] - IM = I2.transpose() - - facets_incidences = [set(column.nonzero_positions()) for column in IM.columns()] - verts_incidences = dict() - for i in range(n_verts): - v_i = set(IM.row(i).nonzero_positions()) - if len(v_i) == n_facets/2: - verts_incidences[i] = v_i - - # Find two vertices ``vert1`` and ``vert2`` such that one of them - # lies on exactly half of the facets, and the other one lies on - # exactly the other half. - from itertools import combinations - for index1, index2 in combinations(verts_incidences, 2): - vert1_incidences = verts_incidences[index1] - vert2_incidences = verts_incidences[index2] - vert1and2 = vert1_incidences.union(vert2_incidences) - if len(vert1and2) == n_facets: - # We have found two candidates for apexes. - # Remove from each facet ``index1`` resp. ``index2``. - test_facets = set(frozenset(facet_inc.difference({index1, index2})) - for facet_inc in facets_incidences) - if len(test_facets) == n_facets/2: - # For each `F` containing `index1` there is - # `G` containing `index2` such that - # `F \setminus \{index1\} = G \setminus \{index2\} - # and vice versa. - if certificate: - V = self.vertices() - return (True, [V[index1], V[index2]]) - return True - - if certificate: - return (False, None) - return False - - def is_prism(self, certificate=False): - """ - Test whether the polytope is combinatorially equivalent to a prism of - some polytope. - - INPUT: - - - ``certificate`` -- boolean (default: ``False``); specifies whether - to return two facets of the polytope which are the bases of a prism, - if found - - OUTPUT: - - If ``certificate`` is ``True``, returns a tuple containing: - - 1. Boolean. - 2. ``None`` or a tuple containing: - a. List of the vertices of the first base facet. - b. List of the vertices of the second base facet. - - If ``certificate`` is ``False`` returns a boolean. - - EXAMPLES:: - - sage: P = polytopes.cube() - sage: P.is_prism() - True - sage: P.is_prism(certificate=True) - (True, - [[A vertex at (1, -1, -1), - A vertex at (1, 1, -1), - A vertex at (1, 1, 1), - A vertex at (1, -1, 1)], - [A vertex at (-1, -1, 1), - A vertex at (-1, -1, -1), - A vertex at (-1, 1, -1), - A vertex at (-1, 1, 1)]]) - sage: Q = polytopes.cyclic_polytope(3,8) - sage: Q.is_prism() - False - sage: R = Q.prism() - sage: R.is_prism(certificate=True) - (True, - [[A vertex at (1, 6, 36, 216), - A vertex at (1, 0, 0, 0), - A vertex at (1, 7, 49, 343), - A vertex at (1, 5, 25, 125), - A vertex at (1, 1, 1, 1), - A vertex at (1, 2, 4, 8), - A vertex at (1, 4, 16, 64), - A vertex at (1, 3, 9, 27)], - [A vertex at (0, 3, 9, 27), - A vertex at (0, 6, 36, 216), - A vertex at (0, 0, 0, 0), - A vertex at (0, 7, 49, 343), - A vertex at (0, 5, 25, 125), - A vertex at (0, 1, 1, 1), - A vertex at (0, 2, 4, 8), - A vertex at (0, 4, 16, 64)]]) - - TESTS:: - - sage: P = polytopes.cross_polytope(5) - sage: P.is_prism() - False - - sage: P = polytopes.permutahedron(4).prism() - sage: P.is_prism() - True - - sage: P = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]]) - sage: P.is_prism() - Traceback (most recent call last): - ... - NotImplementedError: polyhedron has to be compact - - ALGORITHM: - - See :meth:`Polyhedron_base.is_bipyramid`. - """ - if not self.is_compact(): - raise NotImplementedError("polyhedron has to be compact") - - from sage.misc.functional import is_odd - n_verts = self.n_vertices() - n_facets = self.n_facets() - if is_odd(n_verts): - if certificate: - return (False, None) - return False - - IM = self.incidence_matrix() - if self.n_equations(): - # Remove equations from the incidence matrix, - # such that this is the vertex-facet incidences matrix. - I1 = IM.transpose() - I2 = I1[[i for i in range(self.n_Hrepresentation()) - if not self.Hrepresentation()[i].is_equation()]] - IM = I2.transpose() - - verts_incidences = [set(row.nonzero_positions()) for row in IM.rows()] - facets_incidences = dict() - for j in range(n_facets): - F_j = set(IM.column(j).nonzero_positions()) - if len(F_j) == n_verts/2: - facets_incidences[j] = F_j - - # Find two vertices ``facet1`` and ``facet2`` such that one of them - # contains exactly half of the vertices, and the other one contains - # exactly the other half. - from itertools import combinations - for index1, index2 in combinations(facets_incidences, 2): - facet1_incidences = facets_incidences[index1] - facet2_incidences = facets_incidences[index2] - facet1and2 = facet1_incidences.union(facet2_incidences) - if len(facet1and2) == n_verts: - # We have found two candidates for base faces. - # Remove from each vertex ``index1`` resp. ``index2``. - test_verts = set(frozenset(vert_inc.difference({index1, index2})) - for vert_inc in verts_incidences) - if len(test_verts) == n_verts/2: - # For each vertex containing `index1` there is - # another one contained in `index2` - # and vice versa. - # Other than `index1` and `index2` both are contained in - # exactly the same facets. - if certificate: - V = self.vertices() - facet1_vertices = [V[i] for i in facet1_incidences] - facet2_vertices = [V[i] for i in facet2_incidences] - return (True, [facet1_vertices, facet2_vertices]) - return True - - if certificate: - return (False, None) - return False - - def hyperplane_arrangement(self): - """ - Return the hyperplane arrangement defined by the equations and - inequalities. - - OUTPUT: - - A :class:`hyperplane arrangement - ` - consisting of the hyperplanes defined by the - :meth:`Hrepresentation`. - If the polytope is full-dimensional, this is the hyperplane - arrangement spanned by the facets of the polyhedron. - - EXAMPLES:: - - sage: p = polytopes.hypercube(2) - sage: p.hyperplane_arrangement() - Arrangement <-t0 + 1 | -t1 + 1 | t1 + 1 | t0 + 1> - """ - names = tuple('t' + str(i) for i in range(self.ambient_dim())) - from sage.geometry.hyperplane_arrangement.arrangement import HyperplaneArrangements - field = self.base_ring().fraction_field() - H = HyperplaneArrangements(field, names) - return H(self) - - @cached_method - def gale_transform(self): - """ - Return the Gale transform of a polytope as described in the - reference below. - - OUTPUT: - - A list of vectors, the Gale transform. The dimension is the - dimension of the affine dependencies of the vertices of the - polytope. - - EXAMPLES: - - This is from the reference, for a triangular prism:: + This is from the reference, for a triangular prism:: sage: p = Polyhedron(vertices = [[0,0],[0,1],[1,0]]) sage: p2 = p.prism() @@ -4652,34 +3441,6 @@ def lawrence_polytope(self): parent = self.parent().change_ring(self.base_ring(), ambient_dim=self.ambient_dim() + n) return parent.element_class(parent, [lambda_V, [], []], None) - def is_lawrence_polytope(self): - """ - Return ``True`` if ``self`` is a Lawrence polytope. - - A polytope is called a Lawrence polytope if it has a centrally - symmetric (normalized) Gale diagram. - - EXAMPLES:: - - sage: P = polytopes.hypersimplex(5,2) - sage: L = P.lawrence_polytope() - sage: L.is_lattice_polytope() - True - sage: egyptian_pyramid = polytopes.regular_polygon(4).pyramid() - sage: egyptian_pyramid.is_lawrence_polytope() - True - sage: polytopes.octahedron().is_lawrence_polytope() - False - - REFERENCES: - - For more information, see [BaSt1990]_. - """ - if not self.is_compact(): - raise NotImplementedError("self must be a polytope") - - return self.combinatorial_polyhedron().is_lawrence_polytope() - def _test_lawrence(self, tester=None, **options): """ Run tests on the methods related to lawrence extensions. @@ -4893,1185 +3654,9 @@ def barycentric_subdivision(self, subdivision_frac=None): new_ieqs = polar.inequalities_list() + new_ineq new_eqns = polar.equations_list() - polar = parent.element_class(parent, None, [new_ieqs, new_eqns]) - - return (polar.polar(in_affine_span=True)) + barycenter - - def face_lattice(self): - """ - Return the face-lattice poset. - - OUTPUT: - - A :class:`~sage.combinat.posets.posets.FinitePoset`. Elements - are given as - :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. - - In the case of a full-dimensional polytope, the faces are - pairs (vertices, inequalities) of the spanning vertices and - corresponding saturated inequalities. In general, a face is - defined by a pair (V-rep. objects, H-rep. objects). The - V-representation objects span the face, and the corresponding - H-representation objects are those inequalities and equations - that are saturated on the face. - - The bottom-most element of the face lattice is the "empty - face". It contains no V-representation object. All - H-representation objects are incident. - - The top-most element is the "full face". It is spanned by all - V-representation objects. The incident H-representation - objects are all equations and no inequalities. - - In the case of a full-dimensional polytope, the "empty face" - and the "full face" are the empty set (no vertices, all - inequalities) and the full polytope (all vertices, no - inequalities), respectively. - - ALGORITHM: - - See :mod:`sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator`. - - .. NOTE:: - - The face lattice is not cached, as long as this creates a memory leak, see :trac:`28982`. - - EXAMPLES:: - - sage: square = polytopes.hypercube(2) - sage: fl = square.face_lattice();fl - Finite lattice containing 10 elements - sage: list(f.ambient_V_indices() for f in fl) - [(), (0,), (1,), (0, 1), (2,), (1, 2), (3,), (0, 3), (2, 3), (0, 1, 2, 3)] - sage: poset_element = fl[5] - sage: a_face = poset_element - sage: a_face - A 1-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 2 vertices - sage: a_face.ambient_V_indices() - (1, 2) - sage: set(a_face.ambient_Vrepresentation()) == \ - ....: set([square.Vrepresentation(1), square.Vrepresentation(2)]) - True - sage: a_face.ambient_Vrepresentation() - (A vertex at (1, 1), A vertex at (-1, 1)) - sage: a_face.ambient_Hrepresentation() - (An inequality (0, -1) x + 1 >= 0,) - - A more complicated example:: - - sage: c5_10 = Polyhedron(vertices = [[i,i^2,i^3,i^4,i^5] for i in range(1,11)]) - sage: c5_10_fl = c5_10.face_lattice() - sage: [len(x) for x in c5_10_fl.level_sets()] - [1, 10, 45, 100, 105, 42, 1] - - Note that if the polyhedron contains lines then there is a - dimension gap between the empty face and the first non-empty - face in the face lattice:: - - sage: line = Polyhedron(vertices=[(0,)], lines=[(1,)]) - sage: [ fl.dim() for fl in line.face_lattice() ] - [-1, 1] - - TESTS:: - - sage: c5_20 = Polyhedron(vertices = [[i,i^2,i^3,i^4,i^5] - ....: for i in range(1,21)]) - sage: c5_20_fl = c5_20.face_lattice() # long time - sage: [len(x) for x in c5_20_fl.level_sets()] # long time - [1, 20, 190, 580, 680, 272, 1] - sage: polytopes.hypercube(2).face_lattice().plot() # optional - sage.plot - Graphics object consisting of 27 graphics primitives - sage: level_sets = polytopes.cross_polytope(2).face_lattice().level_sets() - sage: level_sets[0][0].ambient_V_indices(), level_sets[-1][0].ambient_V_indices() - ((), (0, 1, 2, 3)) - - Various degenerate polyhedra:: - - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[[0,0,0],[1,0,0],[0,1,0]]).face_lattice().level_sets()] - [[()], [(0,), (1,), (2,)], [(0, 1), (0, 2), (1, 2)], [(0, 1, 2)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(1,0,0),(0,1,0)], rays=[(0,0,1)]).face_lattice().level_sets()] - [[()], [(1,), (2,)], [(0, 1), (0, 2), (1, 2)], [(0, 1, 2)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(rays=[(1,0,0),(0,1,0)], vertices=[(0,0,1)]).face_lattice().level_sets()] - [[()], [(0,)], [(0, 1), (0, 2)], [(0, 1, 2)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(rays=[(1,0),(0,1)], vertices=[(0,0)]).face_lattice().level_sets()] - [[()], [(0,)], [(0, 1), (0, 2)], [(0, 1, 2)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(1,),(0,)]).face_lattice().level_sets()] - [[()], [(0,), (1,)], [(0, 1)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(1,0,0),(0,1,0)], lines=[(0,0,1)]).face_lattice().level_sets()] - [[()], [(0, 1), (0, 2)], [(0, 1, 2)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0,0)], vertices=[(0,0,1)]).face_lattice().level_sets()] - [[()], [(0, 1)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0),(0,1)], vertices=[(0,0)]).face_lattice().level_sets()] - [[()], [(0, 1, 2)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0)], rays=[(0,1)], vertices=[(0,0)]).face_lattice().level_sets()] - [[()], [(0, 1)], [(0, 1, 2)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(0,)], lines=[(1,)]).face_lattice().level_sets()] - [[()], [(0, 1)]] - sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0)], vertices=[(0,0)]).face_lattice().level_sets()] - [[()], [(0, 1)]] - - """ - from sage.combinat.posets.lattices import FiniteLatticePoset - return FiniteLatticePoset(self.hasse_diagram()) - - @cached_method - def hasse_diagram(self): - r""" - Return the Hasse diagram of the face lattice of ``self``. - - This is the Hasse diagram of the poset of the faces of ``self``. - - OUTPUT: a directed graph - - EXAMPLES:: - - sage: P = polytopes.regular_polygon(4).pyramid() # optional - sage.rings.number_field - sage: D = P.hasse_diagram(); D # optional - sage.rings.number_field - Digraph on 20 vertices - sage: D.degree_polynomial() # optional - sage.rings.number_field - x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3 - - Faces of an mutable polyhedron are not hashable. Hence those are not suitable as - vertices of the hasse diagram. Use the combinatorial polyhedron instead:: - - sage: P = polytopes.regular_polygon(4).pyramid() # optional - sage.rings.number_field - sage: parent = P.parent() # optional - sage.rings.number_field - sage: parent = parent.change_ring(QQ, backend='ppl') # optional - sage.rings.number_field - sage: Q = parent._element_constructor_(P, mutable=True) # optional - sage.rings.number_field - sage: Q.hasse_diagram() # optional - sage.rings.number_field - Traceback (most recent call last): - ... - TypeError: mutable polyhedra are unhashable - sage: C = Q.combinatorial_polyhedron() # optional - sage.rings.number_field - sage: D = C.hasse_diagram() # optional - sage.rings.number_field - sage: set(D.vertices()) == set(range(20)) # optional - sage.rings.number_field - True - sage: def index_to_combinatorial_face(n): - ....: return C.face_by_face_lattice_index(n) - sage: D.relabel(index_to_combinatorial_face, inplace=True) # optional - sage.rings.number_field - sage: D.vertices() # optional - sage.rings.number_field - [A -1-dimensional face of a 3-dimensional combinatorial polyhedron, - A 0-dimensional face of a 3-dimensional combinatorial polyhedron, - A 0-dimensional face of a 3-dimensional combinatorial polyhedron, - A 0-dimensional face of a 3-dimensional combinatorial polyhedron, - A 0-dimensional face of a 3-dimensional combinatorial polyhedron, - A 0-dimensional face of a 3-dimensional combinatorial polyhedron, - A 1-dimensional face of a 3-dimensional combinatorial polyhedron, - A 1-dimensional face of a 3-dimensional combinatorial polyhedron, - A 1-dimensional face of a 3-dimensional combinatorial polyhedron, - A 1-dimensional face of a 3-dimensional combinatorial polyhedron, - A 1-dimensional face of a 3-dimensional combinatorial polyhedron, - A 1-dimensional face of a 3-dimensional combinatorial polyhedron, - A 1-dimensional face of a 3-dimensional combinatorial polyhedron, - A 1-dimensional face of a 3-dimensional combinatorial polyhedron, - A 2-dimensional face of a 3-dimensional combinatorial polyhedron, - A 2-dimensional face of a 3-dimensional combinatorial polyhedron, - A 2-dimensional face of a 3-dimensional combinatorial polyhedron, - A 2-dimensional face of a 3-dimensional combinatorial polyhedron, - A 2-dimensional face of a 3-dimensional combinatorial polyhedron, - A 3-dimensional face of a 3-dimensional combinatorial polyhedron] - sage: D.degree_polynomial() # optional - sage.rings.number_field - x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3 - """ - - from sage.geometry.polyhedron.face import combinatorial_face_to_polyhedral_face - C = self.combinatorial_polyhedron() - D = C.hasse_diagram() - - def index_to_polyhedron_face(n): - return combinatorial_face_to_polyhedral_face( - self, C.face_by_face_lattice_index(n)) - - return D.relabel(index_to_polyhedron_face, inplace=False, immutable=True) - - def face_generator(self, face_dimension=None, dual=None): - r""" - Return an iterator over the faces of given dimension. - - If dimension is not specified return an iterator over all faces. - - INPUT: - - - ``face_dimension`` -- integer (default ``None``), - yield only faces of this dimension if specified - - ``dual`` -- boolean (default ``None``); - if ``True``, generate the faces using the vertices; - if ``False``, generate the faces using the facets; - if ``None``, pick automatically - - OUTPUT: - - A :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator_geom`. - This class iterates over faces as - :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. See - :mod:`~sage.geometry.polyhedron.face` for details. The order - is random but fixed. - - EXAMPLES:: - - sage: P = polytopes.cube() - sage: it = P.face_generator() - sage: it - Iterator over the faces of a 3-dimensional polyhedron in ZZ^3 - sage: list(it) - [A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 8 vertices, - A -1-dimensional face of a Polyhedron in ZZ^3, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices] - - sage: P = polytopes.hypercube(4) - sage: list(P.face_generator(2))[:4] - [A 2-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices, - A 2-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices, - A 2-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices, - A 2-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices] - - If a polytope has more facets than vertices, the dual mode is chosen:: - - sage: P = polytopes.cross_polytope(3) - sage: list(P.face_generator()) - [A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 6 vertices, - A -1-dimensional face of a Polyhedron in ZZ^3, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices] - - The face iterator can also be slightly modified. - In non-dual mode we can skip subfaces of the current (proper) face:: - - sage: P = polytopes.cube() - sage: it = P.face_generator(dual=False) - sage: _ = next(it), next(it) - sage: face = next(it) - sage: face.ambient_H_indices() - (5,) - sage: it.ignore_subfaces() - sage: face = next(it) - sage: face.ambient_H_indices() - (4,) - sage: it.ignore_subfaces() - sage: [face.ambient_H_indices() for face in it] - [(3,), - (2,), - (1,), - (0,), - (2, 3), - (1, 3), - (1, 2, 3), - (1, 2), - (0, 2), - (0, 1, 2), - (0, 1)] - - In dual mode we can skip supfaces of the current (proper) face:: - - sage: P = polytopes.cube() - sage: it = P.face_generator(dual=True) - sage: _ = next(it), next(it) - sage: face = next(it) - sage: face.ambient_V_indices() - (7,) - sage: it.ignore_supfaces() - sage: next(it) - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex - sage: face = next(it) - sage: face.ambient_V_indices() - (5,) - sage: it.ignore_supfaces() - sage: [face.ambient_V_indices() for face in it] - [(4,), - (3,), - (2,), - (1,), - (0,), - (1, 6), - (3, 4), - (2, 3), - (0, 3), - (0, 1, 2, 3), - (1, 2), - (0, 1)] - - In non-dual mode, we cannot skip supfaces:: - - sage: it = P.face_generator(dual=False) - sage: _ = next(it), next(it) - sage: next(it) - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices - sage: it.ignore_supfaces() - Traceback (most recent call last): - ... - ValueError: only possible when in dual mode - - In dual mode, we cannot skip subfaces:: - - sage: it = P.face_generator(dual=True) - sage: _ = next(it), next(it) - sage: next(it) - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex - sage: it.ignore_subfaces() - Traceback (most recent call last): - ... - ValueError: only possible when not in dual mode - - We can only skip sub-/supfaces of proper faces:: - - sage: it = P.face_generator(dual=False) - sage: next(it) - A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 8 vertices - sage: it.ignore_subfaces() - Traceback (most recent call last): - ... - ValueError: iterator not set to a face yet - - .. SEEALSO:: - - :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator_geom`. - - ALGORITHM: - - See :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator`. - - TESTS:: - - sage: P = polytopes.simplex() - sage: list(P.face_generator(-2)) - [] - sage: list(P.face_generator(-1)) - [A -1-dimensional face of a Polyhedron in ZZ^4] - sage: list(P.face_generator(3)) - [A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices] - - sage: list(Polyhedron().face_generator()) - [A -1-dimensional face of a Polyhedron in ZZ^0] - - Check that :trac:`29155` is fixed:: - - sage: P = polytopes.permutahedron(3) - sage: [f] = P.face_generator(2) - sage: f.ambient_Hrepresentation() - (An equation (1, 1, 1) x - 6 == 0,) - """ - from sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator import FaceIterator_geom - return FaceIterator_geom(self, output_dimension=face_dimension, dual=dual) - - def faces(self, face_dimension): - """ - Return the faces of given dimension - - INPUT: - - - ``face_dimension`` -- integer. The dimension of the faces - whose representation will be returned. - - OUTPUT: - - A tuple of - :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. See - :mod:`~sage.geometry.polyhedron.face` for details. The order - is random but fixed. - - .. SEEALSO:: - - :meth:`face_generator`, - :meth:`facet`. - - EXAMPLES: - - Here we find the vertex and face indices of the eight three-dimensional - facets of the four-dimensional hypercube:: - - sage: p = polytopes.hypercube(4) - sage: list(f.ambient_V_indices() for f in p.faces(3)) - [(0, 5, 6, 7, 8, 9, 14, 15), - (1, 4, 5, 6, 10, 13, 14, 15), - (1, 2, 6, 7, 8, 10, 11, 15), - (8, 9, 10, 11, 12, 13, 14, 15), - (0, 3, 4, 5, 9, 12, 13, 14), - (0, 2, 3, 7, 8, 9, 11, 12), - (1, 2, 3, 4, 10, 11, 12, 13), - (0, 1, 2, 3, 4, 5, 6, 7)] - - sage: face = p.faces(3)[3] - sage: face.ambient_Hrepresentation() - (An inequality (1, 0, 0, 0) x + 1 >= 0,) - sage: face.vertices() - (A vertex at (-1, -1, 1, -1), - A vertex at (-1, -1, 1, 1), - A vertex at (-1, 1, -1, -1), - A vertex at (-1, 1, 1, -1), - A vertex at (-1, 1, 1, 1), - A vertex at (-1, 1, -1, 1), - A vertex at (-1, -1, -1, 1), - A vertex at (-1, -1, -1, -1)) - - You can use the - :meth:`~sage.geometry.polyhedron.representation.PolyhedronRepresentation.index` - method to enumerate vertices and inequalities:: - - sage: def get_idx(rep): return rep.index() - sage: [get_idx(_) for _ in face.ambient_Hrepresentation()] - [4] - sage: [get_idx(_) for _ in face.ambient_Vrepresentation()] - [8, 9, 10, 11, 12, 13, 14, 15] - - sage: [ ([get_idx(_) for _ in face.ambient_Vrepresentation()], - ....: [get_idx(_) for _ in face.ambient_Hrepresentation()]) - ....: for face in p.faces(3) ] - [([0, 5, 6, 7, 8, 9, 14, 15], [7]), - ([1, 4, 5, 6, 10, 13, 14, 15], [6]), - ([1, 2, 6, 7, 8, 10, 11, 15], [5]), - ([8, 9, 10, 11, 12, 13, 14, 15], [4]), - ([0, 3, 4, 5, 9, 12, 13, 14], [3]), - ([0, 2, 3, 7, 8, 9, 11, 12], [2]), - ([1, 2, 3, 4, 10, 11, 12, 13], [1]), - ([0, 1, 2, 3, 4, 5, 6, 7], [0])] - - TESTS:: - - sage: pr = Polyhedron(rays = [[1,0,0],[-1,0,0],[0,1,0]], vertices = [[-1,-1,-1]], lines=[(0,0,1)]) - sage: pr.faces(4) - () - sage: pr.faces(3)[0].ambient_V_indices() - (0, 1, 2, 3) - sage: pr.facets()[0].ambient_V_indices() - (0, 1, 2) - sage: pr.faces(1) - () - sage: pr.faces(0) - () - sage: pr.faces(-1) - (A -1-dimensional face of a Polyhedron in QQ^3,) - """ - return tuple(self.face_generator(face_dimension)) - - def facets(self): - r""" - Return the facets of the polyhedron. - - Facets are the maximal nontrivial faces of polyhedra. - The empty face and the polyhedron itself are trivial. - - A facet of a `d`-dimensional polyhedron is a face of dimension - `d-1`. For `d \neq 0` the converse is true as well. - - OUTPUT: - - A tuple of - :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. See - :mod:`~sage.geometry.polyhedron.face` for details. The order - is random but fixed. - - .. SEEALSO:: :meth:`facets` - - EXAMPLES: - - Here we find the eight three-dimensional facets of the - four-dimensional hypercube:: - - sage: p = polytopes.hypercube(4) - sage: p.facets() - (A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices) - - This is the same result as explicitly finding the - three-dimensional faces:: - - sage: dim = p.dimension() - sage: p.faces(dim-1) - (A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, - A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices) - - The ``0``-dimensional polyhedron does not have facets:: - - sage: P = Polyhedron([[0]]) - sage: P.facets() - () - """ - if self.dimension() == 0: - return () - return self.faces(self.dimension()-1) - - def join_of_Vrep(self, *Vrepresentatives): - r""" - Return the smallest face that contains ``Vrepresentatives``. - - INPUT: - - - ``Vrepresentatives`` -- vertices/rays/lines of ``self`` or indices of such - - OUTPUT: a :class:`~sage.geometry.polyhedron.face.PolyhedronFace` - - .. NOTE:: - - In the case of unbounded polyhedra, the join of rays etc. may not be well-defined. - - EXAMPLES:: - - sage: P = polytopes.permutahedron(5) - sage: P.join_of_Vrep(1) - A 0-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 1 vertex - sage: P.join_of_Vrep() - A -1-dimensional face of a Polyhedron in ZZ^5 - sage: P.join_of_Vrep(0,12,13).ambient_V_indices() - (0, 12, 13, 68) - - The input is flexible:: - - sage: P.join_of_Vrep(2, P.vertices()[3], P.Vrepresentation(4)) - A 2-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 6 vertices - - :: - - sage: P = polytopes.cube() - sage: a, b = P.faces(0)[:2] - sage: P.join_of_Vrep(a, b) - A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices - - In the case of an unbounded polyhedron, the join may not be well-defined:: - - sage: P = Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,1]]) - sage: P.join_of_Vrep(0) - A 0-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex - sage: P.join_of_Vrep(0,1) - A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 2 vertices - sage: P.join_of_Vrep(0,2) - A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 ray - sage: P.join_of_Vrep(1,2) - A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 ray - sage: P.join_of_Vrep(2) - Traceback (most recent call last): - ... - ValueError: the join is not well-defined - - The ``Vrepresentatives`` must be of ``self``:: - - sage: P = polytopes.cube(backend='ppl') - sage: Q = polytopes.cube(backend='field') - sage: v = P.vertices()[0] - sage: P.join_of_Vrep(v) - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex - sage: Q.join_of_Vrep(v) - Traceback (most recent call last): - ... - ValueError: not a Vrepresentative of ``self`` - sage: f = P.faces(0)[0] - sage: P.join_of_Vrep(v) - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex - sage: Q.join_of_Vrep(v) - Traceback (most recent call last): - ... - ValueError: not a Vrepresentative of ``self`` - - TESTS: - - ``least_common_superface_of_Vrep`` is an alias:: - - sage: P.least_common_superface_of_Vrep(v) - A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex - sage: P.least_common_superface_of_Vrep == P.join_of_Vrep - True - - Error message for invalid input:: - - sage: P.join_of_Vrep('foo') - Traceback (most recent call last): - ... - ValueError: foo is not a Vrepresentative - """ - from sage.geometry.polyhedron.representation import Vrepresentation - from sage.geometry.polyhedron.face import PolyhedronFace - - new_indices = [0]*len(Vrepresentatives) - for i, v in enumerate(Vrepresentatives): - if isinstance(v, PolyhedronFace) and v.dim() == 0: - if v.polyhedron() is not self: - raise ValueError("not a Vrepresentative of ``self``") - new_indices[i] = v.ambient_V_indices()[0] - elif v in ZZ: - new_indices[i] = v - elif isinstance(v, Vrepresentation): - if v.polyhedron() is not self: - raise ValueError("not a Vrepresentative of ``self``") - new_indices[i] = v.index() - else: - raise ValueError("{} is not a Vrepresentative".format(v)) - - return self.face_generator().join_of_Vrep(*new_indices) - - least_common_superface_of_Vrep = join_of_Vrep - - def meet_of_Hrep(self, *Hrepresentatives): - r""" - Return the largest face that is contained in ``Hrepresentatives``. - - INPUT: - - - ``Hrepresentatives`` -- facets or indices of Hrepresentatives; - the indices are assumed to be the indices of the Hrepresentation - - OUTPUT: a :class:`~sage.geometry.polyhedron.face.PolyhedronFace` - - EXAMPLES:: - - sage: P = polytopes.permutahedron(5) - sage: P.meet_of_Hrep() - A 4-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 120 vertices - sage: P.meet_of_Hrep(1) - A 3-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 24 vertices - sage: P.meet_of_Hrep(4) - A 3-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 12 vertices - sage: P.meet_of_Hrep(1,3,7) - A 1-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 2 vertices - sage: P.meet_of_Hrep(1,3,7).ambient_H_indices() - (0, 1, 3, 7) - - The indices are the indices of the Hrepresentation. - ``0`` corresponds to an equation and is ignored:: - - sage: P.meet_of_Hrep(0) - A 4-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 120 vertices - - The input is flexible:: - - sage: P.meet_of_Hrep(P.facets()[-1], P.inequalities()[2], 7) - A 1-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 2 vertices - - The ``Hrepresentatives`` must belong to ``self``:: - - sage: P = polytopes.cube(backend='ppl') - sage: Q = polytopes.cube(backend='field') - sage: f = P.facets()[0] - sage: P.meet_of_Hrep(f) - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices - sage: Q.meet_of_Hrep(f) - Traceback (most recent call last): - ... - ValueError: not a facet of ``self`` - sage: f = P.inequalities()[0] - sage: P.meet_of_Hrep(f) - A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices - sage: Q.meet_of_Hrep(f) - Traceback (most recent call last): - ... - ValueError: not a facet of ``self`` - - TESTS: - - Equations are not considered by the combinatorial polyhedron. - We check that the index corresponds to the Hrepresentation index:: - - sage: P = polytopes.permutahedron(3, backend='field') - sage: P.Hrepresentation() - (An inequality (0, 0, 1) x - 1 >= 0, - An inequality (0, 1, 0) x - 1 >= 0, - An inequality (0, 1, 1) x - 3 >= 0, - An inequality (1, 0, 0) x - 1 >= 0, - An inequality (1, 0, 1) x - 3 >= 0, - An inequality (1, 1, 0) x - 3 >= 0, - An equation (1, 1, 1) x - 6 == 0) - sage: P.meet_of_Hrep(0).ambient_Hrepresentation() - (An inequality (0, 0, 1) x - 1 >= 0, An equation (1, 1, 1) x - 6 == 0) - - sage: P = polytopes.permutahedron(3, backend='ppl') - sage: P.Hrepresentation() - (An equation (1, 1, 1) x - 6 == 0, - An inequality (1, 1, 0) x - 3 >= 0, - An inequality (-1, -1, 0) x + 5 >= 0, - An inequality (0, 1, 0) x - 1 >= 0, - An inequality (-1, 0, 0) x + 3 >= 0, - An inequality (1, 0, 0) x - 1 >= 0, - An inequality (0, -1, 0) x + 3 >= 0) - sage: P.meet_of_Hrep(1).ambient_Hrepresentation() - (An equation (1, 1, 1) x - 6 == 0, An inequality (1, 1, 0) x - 3 >= 0) - - ``greatest_common_subface_of_Hrep`` is an alias:: - - sage: P.greatest_common_subface_of_Hrep(1).ambient_Hrepresentation() - (An equation (1, 1, 1) x - 6 == 0, An inequality (1, 1, 0) x - 3 >= 0) - sage: P.greatest_common_subface_of_Hrep == P.meet_of_Hrep - True - - Error message for invalid input:: - - sage: P.meet_of_Hrep('foo') - Traceback (most recent call last): - ... - ValueError: foo is not a Hrepresentative - """ - from sage.geometry.polyhedron.representation import Inequality, Equation - from sage.geometry.polyhedron.face import PolyhedronFace - - # Equations are ignored by combinatorial polyhedron for indexing. - offset = 0 - if self.n_equations() and self.Hrepresentation(0).is_equation(): - offset = self.n_equations() - - new_indices = [] - for i, facet in enumerate(Hrepresentatives): - if isinstance(facet, PolyhedronFace) and facet.dim() + 1 == self.dim(): - if facet.polyhedron() is not self: - raise ValueError("not a facet of ``self``") - H_indices = facet.ambient_H_indices() - facet = H_indices[0] if H_indices[0] >= offset else H_indices[-1] - - if facet in ZZ and facet >= offset: - # Note that ``CombinatorialPolyhedron`` ignores indices of equations - # and has equations last. - new_indices.append(facet - offset) - elif isinstance(facet, Inequality): - if facet.polyhedron() is not self: - raise ValueError("not a facet of ``self``") - new_indices.append(facet.index() - offset) - elif isinstance(facet, Equation): - # Ignore equations. - continue - elif facet in ZZ and 0 <= facet < offset: - # Ignore equations. - continue - else: - raise ValueError("{} is not a Hrepresentative".format(facet)) - - return self.face_generator().meet_of_Hrep(*new_indices) - - greatest_common_subface_of_Hrep = meet_of_Hrep - - def _test_combinatorial_face_as_combinatorial_polyhedron(self, tester=None, **options): - """ - Run tests on obtaining the combinatorial face as combinatorial polyhedron. - - TESTS:: - - sage: polytopes.cross_polytope(3)._test_combinatorial_face_as_combinatorial_polyhedron() - """ - if not self.is_compact(): - return - if self.dim() > 7 or self.n_vertices() > 100 or self.n_facets() > 100: - # Avoid very long tests. - return - if self.dim() < 1: - # Avoid trivial cases. - return - - from sage.misc.prandom import random - - if tester is None: - tester = self._tester(**options) - - it = self.face_generator() - _ = next(it), next(it) # get rid of non_proper faces - C1 = self.combinatorial_polyhedron() - it1 = C1.face_iter() - C2 = C1.dual() - it2 = C2.face_iter(dual=not it1.dual) - - for f in it: - f1 = next(it1) - f2 = next(it2) - if random() < 0.95: - # Only test a random 5 percent of the faces. - continue - - P = f.as_polyhedron() - D1 = f1.as_combinatorial_polyhedron() - D2 = f2.as_combinatorial_polyhedron(quotient=True).dual() - D1._test_bitsets(tester, **options) - D2._test_bitsets(tester, **options) - try: - import sage.graphs.graph - except ImportError: - pass - else: - tester.assertTrue(P.combinatorial_polyhedron().vertex_facet_graph().is_isomorphic(D1.vertex_facet_graph())) - tester.assertTrue(P.combinatorial_polyhedron().vertex_facet_graph().is_isomorphic(D2.vertex_facet_graph())) - - @cached_method(do_pickle=True) - def f_vector(self, num_threads=None, parallelization_depth=None): - r""" - Return the f-vector. - - INPUT: - - - ``num_threads`` -- integer (optional); specify the number of threads; - otherwise determined by :func:`~sage.parallel.ncpus.ncpus` - - - ``parallelization_depth`` -- integer (optional); specify - how deep in the lattice the parallelization is done - - OUTPUT: - - Return a vector whose `i`-th entry is the number of - `i-2`-dimensional faces of the polytope. - - .. NOTE:: - - The ``vertices`` as given by :meth:`Polyhedron_base.vertices` - do not need to correspond to `0`-dimensional faces. If a polyhedron - contains `k` lines they correspond to `k`-dimensional faces. - See example below - - EXAMPLES:: - - sage: p = Polyhedron(vertices=[[1, 2, 3], [1, 3, 2], - ....: [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1], [0, 0, 0]]) - sage: p.f_vector() - (1, 7, 12, 7, 1) - - sage: polytopes.cyclic_polytope(4,10).f_vector() - (1, 10, 45, 70, 35, 1) - - sage: polytopes.hypercube(5).f_vector() - (1, 32, 80, 80, 40, 10, 1) - - Polyhedra with lines do not have `0`-faces:: - - sage: Polyhedron(ieqs=[[1,-1,0,0],[1,1,0,0]]).f_vector() - (1, 0, 0, 2, 1) - - However, the method :meth:`Polyhedron_base.vertices` returns - two points that belong to the ``Vrepresentation``:: - - sage: P = Polyhedron(ieqs=[[1,-1,0],[1,1,0]]) - sage: P.vertices() - (A vertex at (1, 0), A vertex at (-1, 0)) - sage: P.f_vector() - (1, 0, 2, 1) - - TESTS: - - Check that :trac:`28828` is fixed:: - - sage: P.f_vector().is_immutable() - True - - The cache of the f-vector is being pickled:: - - sage: P = polytopes.cube() - sage: P.f_vector() - (1, 8, 12, 6, 1) - sage: Q = loads(dumps(P)) - sage: Q.f_vector.is_in_cache() - True - """ - return self.combinatorial_polyhedron().f_vector(num_threads, parallelization_depth) - - def flag_f_vector(self, *args): - r""" - Return the flag f-vector. - - For each `-1 < i_0 < \dots < i_n < d` the flag f-vector - counts the number of flags `F_0 \subset \dots \subset F_n` - with `F_j` of dimension `i_j` for each `0 \leq j \leq n`, - where `d` is the dimension of the polyhedron. - - INPUT: - - - ``args`` -- integers (optional); specify an entry of the - flag-f-vector; must be an increasing sequence of integers - - OUTPUT: - - - a dictionary, if no arguments were given - - - an Integer, if arguments were given - - EXAMPLES: - - Obtain the entire flag-f-vector:: - - sage: P = polytopes.twenty_four_cell() - sage: P.flag_f_vector() - {(-1,): 1, - (0,): 24, - (0, 1): 192, - (0, 1, 2): 576, - (0, 1, 2, 3): 1152, - (0, 1, 3): 576, - (0, 2): 288, - (0, 2, 3): 576, - (0, 3): 144, - (1,): 96, - (1, 2): 288, - (1, 2, 3): 576, - (1, 3): 288, - (2,): 96, - (2, 3): 192, - (3,): 24, - (4,): 1} - - Specify an entry:: - - sage: P.flag_f_vector(0,3) - 144 - sage: P.flag_f_vector(2) - 96 - - Leading ``-1`` and trailing entry of dimension are allowed:: - - sage: P.flag_f_vector(-1,0,3) - 144 - sage: P.flag_f_vector(-1,0,3,4) - 144 - - One can get the number of trivial faces:: - - sage: P.flag_f_vector(-1) - 1 - sage: P.flag_f_vector(4) - 1 - - Polyhedra with lines, have ``0`` entries accordingly:: - - sage: P = (Polyhedron(lines=[[1]]) * polytopes.cross_polytope(3)) - sage: P.flag_f_vector() - {(-1,): 1, - (0, 1): 0, - (0, 1, 2): 0, - (0, 1, 3): 0, - (0, 2): 0, - (0, 2, 3): 0, - (0, 3): 0, - (0,): 0, - (1, 2): 24, - (1, 2, 3): 48, - (1, 3): 24, - (1,): 6, - (2, 3): 24, - (2,): 12, - (3,): 8, - 4: 1} - - If the arguments are not stricly increasing or out of range, a key error is raised:: - - sage: P.flag_f_vector(-1,0,3,6) - Traceback (most recent call last): - ... - KeyError: (0, 3, 6) - sage: P.flag_f_vector(-1,3,0) - Traceback (most recent call last): - ... - KeyError: (3, 0) - """ - flag = self._flag_f_vector() - if len(args) == 0: - return flag - elif len(args) == 1: - return flag[(args[0],)] - else: - dim = self.dimension() - if args[0] == -1: - args = args[1:] - if args[-1] == dim: - args = args[:-1] - return flag[tuple(args)] - - @cached_method(do_pickle=True) - def _flag_f_vector(self): - r""" - Return the flag-f-vector. - - See :meth:`flag_f_vector`. - - TESTS:: - - sage: polytopes.hypercube(4)._flag_f_vector() - {(-1,): 1, - (0,): 16, - (0, 1): 64, - (0, 1, 2): 192, - (0, 1, 2, 3): 384, - (0, 1, 3): 192, - (0, 2): 96, - (0, 2, 3): 192, - (0, 3): 64, - (1,): 32, - (1, 2): 96, - (1, 2, 3): 192, - (1, 3): 96, - (2,): 24, - (2, 3): 48, - (3,): 8, - (4,): 1} - """ - return self.combinatorial_polyhedron()._flag_f_vector() - - def vertex_graph(self): - """ - Return a graph in which the vertices correspond to vertices - of the polyhedron, and edges to edges. - - ..NOTE:: - - The graph of a polyhedron with lines has no vertices, - as the polyhedron has no vertices (`0`-faces). - - The method :meth:`Polyhedron_base:vertices` returns - the defining points in this case. - - EXAMPLES:: - - sage: g3 = polytopes.hypercube(3).vertex_graph(); g3 - Graph on 8 vertices - sage: g3.automorphism_group().cardinality() - 48 - sage: s4 = polytopes.simplex(4).vertex_graph(); s4 - Graph on 5 vertices - sage: s4.is_eulerian() - True - - The graph of an unbounded polyhedron - is the graph of the bounded complex:: - - sage: open_triangle = Polyhedron(vertices=[[1,0], [0,1]], - ....: rays =[[1,1]]) - sage: open_triangle.vertex_graph() - Graph on 2 vertices - - The graph of a polyhedron with lines has no vertices:: - - sage: line = Polyhedron(lines=[[0,1]]) - sage: line.vertex_graph() - Graph on 0 vertices - - TESTS: - - Check for a line segment (:trac:`30545`):: - - sage: polytopes.simplex(1).graph().edges() - [(A vertex at (0, 1), A vertex at (1, 0), None)] - """ - return self.combinatorial_polyhedron().vertex_graph() - - graph = vertex_graph - - def vertex_digraph(self, f, increasing=True): - r""" - Return the directed graph of the polyhedron according to a linear form. - - The underlying undirected graph is the graph of vertices and edges. - - INPUT: - - - ``f`` -- a linear form. The linear form can be provided as: - - - a vector space morphism with one-dimensional codomain, (see - :meth:`sage.modules.vector_space_morphism.linear_transformation` - and - :class:`sage.modules.vector_space_morphism.VectorSpaceMorphism`) - - a vector ; in this case the linear form is obtained by duality - using the dot product: ``f(v) = v.dot_product(f)``. - - - ``increasing`` -- boolean (default ``True``) whether to orient - edges in the increasing or decreasing direction. - - By default, an edge is oriented from `v` to `w` if - `f(v) \leq f(w)`. - - If `f(v)=f(w)`, then two opposite edges are created. - - EXAMPLES:: - - sage: penta = Polyhedron([[0,0],[1,0],[0,1],[1,2],[3,2]]) - sage: G = penta.vertex_digraph(vector([1,1])); G - Digraph on 5 vertices - sage: G.sinks() - [A vertex at (3, 2)] - - sage: A = matrix(ZZ, [[1], [-1]]) - sage: f = linear_transformation(A) - sage: G = penta.vertex_digraph(f) ; G - Digraph on 5 vertices - sage: G.is_directed_acyclic() - False - - .. SEEALSO:: + polar = parent.element_class(parent, None, [new_ieqs, new_eqns]) - :meth:`vertex_graph` - """ - from sage.modules.vector_space_morphism import VectorSpaceMorphism - if isinstance(f, VectorSpaceMorphism): - if f.codomain().dimension() == 1: - orientation_check = lambda v: f(v) >= 0 - else: - raise TypeError('the linear map f must have ' - 'one-dimensional codomain') - else: - try: - if f.is_vector(): - orientation_check = lambda v: v.dot_product(f) >= 0 - else: - raise TypeError('f must be a linear map or a vector') - except AttributeError: - raise TypeError('f must be a linear map or a vector') - if not increasing: - f = -f - from sage.graphs.digraph import DiGraph - dg = DiGraph() - for j in range(self.n_vertices()): - vj = self.Vrepresentation(j) - for vi in vj.neighbors(): - if orientation_check(vj.vector() - vi.vector()): - dg.add_edge(vi, vj) - return dg + return (polar.polar(in_affine_span=True)) + barycenter def polar(self, in_affine_span=False): """ @@ -6196,41 +3781,6 @@ def move_vertex_to_subspace(vertex): [new_ieqs, t_eqns], Vrep_minimal=True, Hrep_minimal=True, pref_rep=pref_rep) - def is_self_dual(self): - r""" - Return whether the polytope is self-dual. - - A polytope is self-dual if its face lattice is isomorphic to the face - lattice of its dual polytope. - - EXAMPLES:: - - sage: polytopes.simplex().is_self_dual() - True - sage: polytopes.twenty_four_cell().is_self_dual() - True - sage: polytopes.cube().is_self_dual() - False - sage: polytopes.hypersimplex(5,2).is_self_dual() - False - sage: P = Polyhedron(vertices=[[1/2, 1/3]], rays=[[1, 1]]).is_self_dual() - Traceback (most recent call last): - ... - ValueError: polyhedron has to be compact - - """ - if not self.is_compact(): - raise ValueError("polyhedron has to be compact") - - n = self.n_vertices() - m = self.n_facets() - if n != m: - return False - - G1 = self.vertex_facet_graph() - G2 = G1.reverse() - return G1.is_isomorphic(G2) - def pyramid(self): """ Return a polyhedron that is a pyramid over the original. @@ -7437,118 +4987,6 @@ def _integrate_latte_(self, polynomial, **kwds): polynomial, cdd=True, **kwds) - def is_simplex(self): - r""" - Return whether the polyhedron is a simplex. - - A simplex is a bounded polyhedron with `d+1` vertices, where - `d` is the dimension. - - EXAMPLES:: - - sage: Polyhedron([(0,0,0), (1,0,0), (0,1,0)]).is_simplex() - True - sage: polytopes.simplex(3).is_simplex() - True - sage: polytopes.hypercube(3).is_simplex() - False - """ - return self.is_compact() and (self.dim()+1 == self.n_vertices()) - - def neighborliness(self): - r""" - Return the largest ``k``, such that the polyhedron is ``k``-neighborly. - - A polyhedron is `k`-neighborly if every set of `n` vertices forms a face - for `n` up to `k`. - - In case of the `d`-dimensional simplex, it returns `d + 1`. - - .. SEEALSO:: - - :meth:`is_neighborly` - - EXAMPLES:: - - sage: cube = polytopes.cube() - sage: cube.neighborliness() - 1 - sage: P = Polyhedron(); P - The empty polyhedron in ZZ^0 - sage: P.neighborliness() - 0 - sage: P = Polyhedron([[0]]); P - A 0-dimensional polyhedron in ZZ^1 defined as the convex hull of 1 vertex - sage: P.neighborliness() - 1 - sage: S = polytopes.simplex(5); S - A 5-dimensional polyhedron in ZZ^6 defined as the convex hull of 6 vertices - sage: S.neighborliness() - 6 - sage: C = polytopes.cyclic_polytope(7,10); C - A 7-dimensional polyhedron in QQ^7 defined as the convex hull of 10 vertices - sage: C.neighborliness() - 3 - sage: C = polytopes.cyclic_polytope(6,11); C - A 6-dimensional polyhedron in QQ^6 defined as the convex hull of 11 vertices - sage: C.neighborliness() - 3 - sage: [polytopes.cyclic_polytope(5,n).neighborliness() for n in range(6,10)] - [6, 2, 2, 2] - - """ - return self.combinatorial_polyhedron().neighborliness() - - def is_neighborly(self, k=None): - r""" - Return whether the polyhedron is neighborly. - - If the input ``k`` is provided, then return whether the polyhedron is ``k``-neighborly - - A polyhedron is neighborly if every set of `n` vertices forms a face - for `n` up to floor of half the dimension of the polyhedron. - It is `k`-neighborly if this is true for `n` up to `k`. - - INPUT: - - - ``k`` -- the dimension up to which to check if every set of ``k`` - vertices forms a face. If no ``k`` is provided, check up to floor - of half the dimension of the polyhedron. - - OUTPUT: - - - ``True`` if every set of up to ``k`` vertices forms a face, - - ``False`` otherwise - - .. SEEALSO:: - - :meth:`neighborliness` - - EXAMPLES:: - - sage: cube = polytopes.hypercube(3) - sage: cube.is_neighborly() - True - sage: cube = polytopes.hypercube(4) - sage: cube.is_neighborly() - False - - Cyclic polytopes are neighborly:: - - sage: all(polytopes.cyclic_polytope(i, i + 1 + j).is_neighborly() for i in range(5) for j in range(3)) - True - - The neighborliness of a polyhedron equals floor of dimension half - (or larger in case of a simplex) if and only if the polyhedron - is neighborly:: - - sage: testpolys = [polytopes.cube(), polytopes.cyclic_polytope(6, 9), polytopes.simplex(6)] - sage: [(P.neighborliness()>=floor(P.dim()/2)) == P.is_neighborly() for P in testpolys] - [True, True, True] - - """ - return self.combinatorial_polyhedron().is_neighborly() - @cached_method def bounding_box(self, integral=False, integral_hull=False): r""" @@ -7617,615 +5055,6 @@ def bounding_box(self, integral=False, integral_hull=False): box_min.append(min_coord) return (tuple(box_min), tuple(box_max)) - @cached_method - def combinatorial_automorphism_group(self, vertex_graph_only=False): - """ - Computes the combinatorial automorphism group. - - If ``vertex_graph_only`` is ``True``, the automorphism group - of the vertex-edge graph of the polyhedron is returned. Otherwise - the automorphism group of the vertex-facet graph, which is - isomorphic to the automorphism group of the face lattice is returned. - - INPUT: - - - ``vertex_graph_only`` -- boolean (default: ``False``); whether - to return the automorphism group of the vertex edges graph or - of the lattice - - OUTPUT: - - A - :class:`PermutationGroup` - that is isomorphic to the combinatorial automorphism group is - returned. - - - if ``vertex_graph_only`` is ``True``: - The automorphism group of the vertex-edge graph of the polyhedron - - - if ``vertex_graph_only`` is ``False`` (default): - The automorphism group of the vertex-facet graph of the polyhedron, - see :meth:`vertex_facet_graph`. This group is isomorphic to the - automorphism group of the face lattice of the polyhedron. - - NOTE: - - Depending on ``vertex_graph_only``, this method returns groups - that are not necessarily isomorphic, see the examples below. - - .. SEEALSO:: - - :meth:`is_combinatorially_isomorphic`, - :meth:`graph`, - :meth:`vertex_facet_graph`. - - EXAMPLES:: - - sage: quadrangle = Polyhedron(vertices=[(0,0),(1,0),(0,1),(2,3)]) - sage: quadrangle.combinatorial_automorphism_group().is_isomorphic(groups.permutation.Dihedral(4)) - True - sage: quadrangle.restricted_automorphism_group() - Permutation Group with generators [()] - - Permutations of the vertex graph only exchange vertices with vertices:: - - sage: P = Polyhedron(vertices=[(1,0), (1,1)], rays=[(1,0)]) - sage: P.combinatorial_automorphism_group(vertex_graph_only=True) - Permutation Group with generators [(A vertex at (1,0),A vertex at (1,1))] - - This shows an example of two polytopes whose vertex-edge graphs are isomorphic, - but their face_lattices are not isomorphic:: - - sage: Q=Polyhedron([[-123984206864/2768850730773, -101701330976/922950243591, -64154618668/2768850730773, -2748446474675/2768850730773], - ....: [-11083969050/98314591817, -4717557075/98314591817, -32618537490/98314591817, -91960210208/98314591817], - ....: [-9690950/554883199, -73651220/554883199, 1823050/554883199, -549885101/554883199], [-5174928/72012097, 5436288/72012097, -37977984/72012097, 60721345/72012097], - ....: [-19184/902877, 26136/300959, -21472/902877, 899005/902877], [53511524/1167061933, 88410344/1167061933, 621795064/1167061933, 982203941/1167061933], - ....: [4674489456/83665171433, -4026061312/83665171433, 28596876672/83665171433, -78383796375/83665171433], [857794884940/98972360190089, -10910202223200/98972360190089, 2974263671400/98972360190089, -98320463346111/98972360190089]]) - sage: C = polytopes.cyclic_polytope(4,8) - sage: C.is_combinatorially_isomorphic(Q) - False - sage: C.combinatorial_automorphism_group(vertex_graph_only=True).is_isomorphic(Q.combinatorial_automorphism_group(vertex_graph_only=True)) - True - sage: C.combinatorial_automorphism_group(vertex_graph_only=False).is_isomorphic(Q.combinatorial_automorphism_group(vertex_graph_only=False)) - False - - The automorphism group of the face lattice is isomorphic to the combinatorial automorphism group:: - - sage: CG = C.hasse_diagram().automorphism_group() - sage: C.combinatorial_automorphism_group().is_isomorphic(CG) - True - sage: QG = Q.hasse_diagram().automorphism_group() - sage: Q.combinatorial_automorphism_group().is_isomorphic(QG) - True - - """ - if vertex_graph_only: - G = self.graph() - else: - G = self.vertex_facet_graph() - return G.automorphism_group(edge_labels=True) - - @cached_method - def restricted_automorphism_group(self, output="abstract"): - r""" - Return the restricted automorphism group. - - First, let the linear automorphism group be the subgroup of - the affine group `AGL(d,\RR) = GL(d,\RR) \ltimes \RR^d` - preserving the `d`-dimensional polyhedron. The affine group - acts in the usual way `\vec{x}\mapsto A\vec{x}+b` on the - ambient space. - - The restricted automorphism group is the subgroup of the linear - automorphism group generated by permutations of the generators - of the same type. That is, vertices can only be permuted with - vertices, ray generators with ray generators, and line - generators with line generators. - - For example, take the first quadrant - - .. MATH:: - - Q = \Big\{ (x,y) \Big| x\geq 0,\; y\geq0 \Big\} - \subset \QQ^2 - - Then the linear automorphism group is - - .. MATH:: - - \mathrm{Aut}(Q) = - \left\{ - \begin{pmatrix} - a & 0 \\ 0 & b - \end{pmatrix} - ,~ - \begin{pmatrix} - 0 & c \\ d & 0 - \end{pmatrix} - :~ - a, b, c, d \in \QQ_{>0} - \right\} - \subset - GL(2,\QQ) - \subset - E(d) - - Note that there are no translations that map the quadrant `Q` - to itself, so the linear automorphism group is contained in - the general linear group (the subgroup of transformations - preserving the origin). The restricted automorphism group is - - .. MATH:: - - \mathrm{Aut}(Q) = - \left\{ - \begin{pmatrix} - 1 & 0 \\ 0 & 1 - \end{pmatrix} - ,~ - \begin{pmatrix} - 0 & 1 \\ 1 & 0 - \end{pmatrix} - \right\} - \simeq \ZZ_2 - - INPUT: - - - ``output`` -- how the group should be represented: - - - ``"abstract"`` (default) -- return an abstract permutation - group without further meaning. - - - ``"permutation"`` -- return a permutation group on the - indices of the polyhedron generators. For example, the - permutation ``(0,1)`` would correspond to swapping - ``self.Vrepresentation(0)`` and ``self.Vrepresentation(1)``. - - - ``"matrix"`` -- return a matrix group representing affine - transformations. When acting on affine vectors, you should - append a `1` to every vector. If the polyhedron is not full - dimensional, the returned matrices act as the identity on - the orthogonal complement of the affine space spanned by - the polyhedron. - - - ``"matrixlist"`` -- like ``matrix``, but return the list of - elements of the matrix group. Useful for fields without a - good implementation of matrix groups or to avoid the - overhead of creating the group. - - OUTPUT: - - - For ``output="abstract"`` and ``output="permutation"``: - a :class:`PermutationGroup`. - - - For ``output="matrix"``: a :class:`MatrixGroup`. - - - For ``output="matrixlist"``: a list of matrices. - - REFERENCES: - - - [BSS2009]_ - - EXAMPLES: - - A cross-polytope example:: - - sage: P = polytopes.cross_polytope(3) - sage: P.restricted_automorphism_group() == PermutationGroup([[(3,4)], [(2,3),(4,5)],[(2,5)],[(1,2),(5,6)],[(1,6)]]) - True - sage: P.restricted_automorphism_group(output="permutation") == PermutationGroup([[(2,3)],[(1,2),(3,4)],[(1,4)],[(0,1),(4,5)],[(0,5)]]) - True - sage: mgens = [[[1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,1]], [[1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]], [[0,1,0,0],[1,0,0,0],[0,0,1,0],[0,0,0,1]]] - - We test groups for equality in a fool-proof way; they can have different generators, etc:: - - sage: poly_g = P.restricted_automorphism_group(output="matrix") - sage: matrix_g = MatrixGroup([matrix(QQ,t) for t in mgens]) - sage: all(t.matrix() in poly_g for t in matrix_g.gens()) - True - sage: all(t.matrix() in matrix_g for t in poly_g.gens()) - True - - 24-cell example:: - - sage: P24 = polytopes.twenty_four_cell() - sage: AutP24 = P24.restricted_automorphism_group() - sage: PermutationGroup([ - ....: '(1,20,2,24,5,23)(3,18,10,19,4,14)(6,21,11,22,7,15)(8,12,16,17,13,9)', - ....: '(1,21,8,24,4,17)(2,11,6,15,9,13)(3,20)(5,22)(10,16,12,23,14,19)' - ....: ]).is_isomorphic(AutP24) - True - sage: AutP24.order() - 1152 - - Here is the quadrant example mentioned in the beginning:: - - sage: P = Polyhedron(rays=[(1,0),(0,1)]) - sage: P.Vrepresentation() - (A vertex at (0, 0), A ray in the direction (0, 1), A ray in the direction (1, 0)) - sage: P.restricted_automorphism_group(output="permutation") - Permutation Group with generators [(1,2)] - - Also, the polyhedron need not be full-dimensional:: - - sage: P = Polyhedron(vertices=[(1,2,3,4,5),(7,8,9,10,11)]) - sage: P.restricted_automorphism_group() - Permutation Group with generators [(1,2)] - sage: G = P.restricted_automorphism_group(output="matrixlist") - sage: G - ( - [1 0 0 0 0 0] [ -87/55 -82/55 -2/5 38/55 98/55 12/11] - [0 1 0 0 0 0] [-142/55 -27/55 -2/5 38/55 98/55 12/11] - [0 0 1 0 0 0] [-142/55 -82/55 3/5 38/55 98/55 12/11] - [0 0 0 1 0 0] [-142/55 -82/55 -2/5 93/55 98/55 12/11] - [0 0 0 0 1 0] [-142/55 -82/55 -2/5 38/55 153/55 12/11] - [0 0 0 0 0 1], [ 0 0 0 0 0 1] - ) - sage: g = AffineGroup(5, QQ)(G[1]) - sage: g - [ -87/55 -82/55 -2/5 38/55 98/55] [12/11] - [-142/55 -27/55 -2/5 38/55 98/55] [12/11] - x |-> [-142/55 -82/55 3/5 38/55 98/55] x + [12/11] - [-142/55 -82/55 -2/5 93/55 98/55] [12/11] - [-142/55 -82/55 -2/5 38/55 153/55] [12/11] - sage: g^2 - [1 0 0 0 0] [0] - [0 1 0 0 0] [0] - x |-> [0 0 1 0 0] x + [0] - [0 0 0 1 0] [0] - [0 0 0 0 1] [0] - sage: g(list(P.vertices()[0])) - (7, 8, 9, 10, 11) - sage: g(list(P.vertices()[1])) - (1, 2, 3, 4, 5) - - Affine transformations do not change the restricted automorphism - group. For example, any non-degenerate triangle has the - dihedral group with 6 elements, `D_6`, as its automorphism - group:: - - sage: initial_points = [vector([1,0]), vector([0,1]), vector([-2,-1])] - sage: points = initial_points - sage: Polyhedron(vertices=points).restricted_automorphism_group() - Permutation Group with generators [(2,3), (1,2)] - sage: points = [pt - initial_points[0] for pt in initial_points] - sage: Polyhedron(vertices=points).restricted_automorphism_group() - Permutation Group with generators [(2,3), (1,2)] - sage: points = [pt - initial_points[1] for pt in initial_points] - sage: Polyhedron(vertices=points).restricted_automorphism_group() - Permutation Group with generators [(2,3), (1,2)] - sage: points = [pt - 2*initial_points[1] for pt in initial_points] - sage: Polyhedron(vertices=points).restricted_automorphism_group() - Permutation Group with generators [(2,3), (1,2)] - - The ``output="matrixlist"`` can be used over fields without a - complete implementation of matrix groups:: - - sage: P = polytopes.dodecahedron(); P - A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^3 defined as the convex hull of 20 vertices - sage: G = P.restricted_automorphism_group(output="matrixlist") - sage: len(G) - 120 - - Floating-point computations are supported with a simple fuzzy - zero implementation:: - - sage: P = Polyhedron(vertices=[(1/3,0,0,1),(0,1/4,0,1),(0,0,1/5,1)], base_ring=RDF) - sage: P.restricted_automorphism_group() - Permutation Group with generators [(2,3), (1,2)] - sage: len(P.restricted_automorphism_group(output="matrixlist")) - 6 - - TESTS:: - - sage: P = Polyhedron(vertices=[(1,0), (1,1)], rays=[(1,0)]) - sage: P.restricted_automorphism_group(output="permutation") - Permutation Group with generators [(1,2)] - sage: P.restricted_automorphism_group(output="matrix") - Matrix group over Rational Field with 1 generators ( - [ 1 0 0] - [ 0 -1 1] - [ 0 0 1] - ) - sage: P.restricted_automorphism_group(output="foobar") - Traceback (most recent call last): - ... - ValueError: unknown output 'foobar', valid values are ('abstract', 'permutation', 'matrix', 'matrixlist') - - Check that :trac:`28828` is fixed:: - - sage: P.restricted_automorphism_group(output="matrixlist")[0].is_immutable() - True - """ - # The algorithm works as follows: - # - # Let V be the matrix where every column is a homogeneous - # coordinate of a V-representation object (vertex, ray, line). - # Let us assume that V has full rank, that the polyhedron is - # full dimensional. - # - # Let Q = V Vt and C = Vt Q^-1 V. The rows and columns of C - # can be thought of as being indexed by the V-rep objects of the - # polytope. - # - # It turns out that we can identify the restricted automorphism - # group with the automorphism group of the edge-colored graph - # on the V-rep objects with colors determined by the symmetric - # matrix C. - # - # An automorphism of this graph is equivalent to a permutation - # matrix P such that C = Pt C P. If we now define - # A = V P Vt Q^-1, then one can check that V P = A V. - # In other words: permuting the generators is the same as - # applying the affine transformation A on the generators. - # - # If the given polyhedron is not fully-dimensional, - # then Q will be not invertible. In this case, we use a - # pseudoinverse Q+ instead of Q^-1. The formula for A acting on - # the space spanned by V then simplifies to A = V P V+ where V+ - # denotes the pseudoinverse of V, which also equals V+ = Vt Q+. - # - # If we are asked to return the (group of) transformation - # matrices to the user, we also require that those - # transformations act as the identity on the orthogonal - # complement of the space spanned by V. This complement is the - # space spanned by the columns of W = 1 - V V+. One can check - # that B = (V P V+) + W is the correct matrix: it acts the same - # as A on V and it satisfies B W = W. - - outputs = ("abstract", "permutation", "matrix", "matrixlist") - if output not in outputs: - raise ValueError("unknown output {!r}, valid values are {}".format(output, outputs)) - - # For backwards compatibility, we treat "abstract" as - # "permutation", but where we add 1 to the indices of the - # permutations. - index0 = 0 - if output == "abstract": - index0 = 1 - output = "permutation" - - if self.base_ring().is_exact(): - def rational_approximation(c): - return c - else: - c_list = [] - - def rational_approximation(c): - # Implementation detail: Return unique integer if two - # c-values are the same up to machine precision. But - # you can think of it as a uniquely-chosen rational - # approximation. - for i, x in enumerate(c_list): - if self._is_zero(x - c): - return i - c_list.append(c) - return len(c_list) - 1 - - if self.is_compact(): - def edge_label(i, j, c_ij): - return c_ij - else: - # In the non-compact case, we also label the edges by the - # type of the V-representation object. This ensures that - # vertices, rays, and lines are only permuted amongst - # themselves. - def edge_label(i, j, c_ij): - return (self.Vrepresentation(i).type(), c_ij, self.Vrepresentation(j).type()) - - # Homogeneous coordinates for the V-representation objects. - # Mathematically, V is a matrix. For efficiency however, we - # represent it as a list of column vectors. - V = [v.homogeneous_vector() for v in self.Vrepresentation()] - - # Pseudoinverse of V Vt - Qplus = sum(v.column() * v.row() for v in V).pseudoinverse() - - # Construct the graph. - from sage.graphs.graph import Graph - G = Graph() - for i in range(len(V)): - for j in range(i+1, len(V)): - c_ij = rational_approximation(V[i] * Qplus * V[j]) - G.add_edge(index0+i, index0+j, edge_label(i, j, c_ij)) - - permgroup = G.automorphism_group(edge_labels=True) - if output == "permutation": - return permgroup - elif output == "matrix": - permgroup = permgroup.gens() - - # Compute V+ = Vt Q+ as list of row vectors - Vplus = list(matrix(V) * Qplus) # matrix(V) is Vt - - # Compute W = 1 - V V+ - W = 1 - sum(V[i].column() * Vplus[i].row() for i in range(len(V))) - - # Convert the permutation group to a matrix group. - # If P is a permutation, then we return the matrix - # B = (V P V+) + W. - # - # If output == "matrix", we loop over the generators of the group. - # Otherwise, we loop over all elements. - matrices = [] - for perm in permgroup: - A = sum(V[perm(i)].column() * Vplus[i].row() for i in range(len(V))) - matrices.append(A + W) - - for mat in matrices: - mat.set_immutable() - - if output == "matrixlist": - return tuple(matrices) - else: - return MatrixGroup(matrices) - - def is_combinatorially_isomorphic(self, other, algorithm='bipartite_graph'): - r""" - Return whether the polyhedron is combinatorially isomorphic to another polyhedron. - - We only consider bounded polyhedra. By definition, they are - combinatorially isomorphic if their face lattices are isomorphic. - - INPUT: - - - ``other`` -- a polyhedron object - - ``algorithm`` (default = ``bipartite_graph``) -- the algorithm to use. - The other possible value is ``face_lattice``. - - OUTPUT: - - - ``True`` if the two polyhedra are combinatorially isomorphic - - ``False`` otherwise - - .. SEEALSO:: - - :meth:`combinatorial_automorphism_group`, - :meth:`vertex_facet_graph`. - - REFERENCES: - - For the equivalence of the two algorithms see [KK1995]_, p. 877-878 - - EXAMPLES: - - The square is combinatorially isomorphic to the 2-dimensional cube:: - - sage: polytopes.hypercube(2).is_combinatorially_isomorphic(polytopes.regular_polygon(4)) - True - - All the faces of the 3-dimensional permutahedron are either - combinatorially isomorphic to a square or a hexagon:: - - sage: H = polytopes.regular_polygon(6) # optional - sage.rings.number_field - sage: S = polytopes.hypercube(2) - sage: P = polytopes.permutahedron(4) - sage: all(F.as_polyhedron().is_combinatorially_isomorphic(S) # optional - sage.rings.number_field - ....: or F.as_polyhedron().is_combinatorially_isomorphic(H) - ....: for F in P.faces(2)) - True - - Checking that a regular simplex intersected with its reflection - through the origin is combinatorially isomorphic to the intersection - of a cube with a hyperplane perpendicular to its long diagonal:: - - sage: def simplex_intersection(k): - ....: S1 = Polyhedron([vector(v)-vector(polytopes.simplex(k).center()) for v in polytopes.simplex(k).vertices_list()]) - ....: S2 = Polyhedron([-vector(v) for v in S1.vertices_list()]) - ....: return S1.intersection(S2) - sage: def cube_intersection(k): - ....: C = polytopes.hypercube(k+1) - ....: H = Polyhedron(eqns=[[0]+[1 for i in range(k+1)]]) - ....: return C.intersection(H) - sage: [simplex_intersection(k).is_combinatorially_isomorphic(cube_intersection(k)) for k in range(2,5)] - [True, True, True] - sage: simplex_intersection(2).is_combinatorially_isomorphic(polytopes.regular_polygon(6)) # optional - sage.rings.number_field - True - sage: simplex_intersection(3).is_combinatorially_isomorphic(polytopes.octahedron()) - True - - Two polytopes with the same `f`-vector, but different combinatorial types:: - - sage: P = Polyhedron([[-605520/1525633, -605520/1525633, -1261500/1525633, -52200/1525633, 11833/1525633],\ - [-720/1769, -600/1769, 1500/1769, 0, -31/1769], [-216/749, 240/749, -240/749, -432/749, 461/749], \ - [-50/181, 50/181, 60/181, -100/181, -119/181], [-32/51, -16/51, -4/51, 12/17, 1/17],\ - [1, 0, 0, 0, 0], [16/129, 128/129, 0, 0, 1/129], [64/267, -128/267, 24/89, -128/267, 57/89],\ - [1200/3953, -1200/3953, -1440/3953, -360/3953, -3247/3953], [1512/5597, 1512/5597, 588/5597, 4704/5597, 2069/5597]]) - sage: C = polytopes.cyclic_polytope(5,10) - sage: C.f_vector() == P.f_vector(); C.f_vector() - True - (1, 10, 45, 100, 105, 42, 1) - sage: C.is_combinatorially_isomorphic(P) - False - - sage: S = polytopes.simplex(3) - sage: S = S.face_truncation(S.faces(0)[3]) - sage: S = S.face_truncation(S.faces(0)[4]) - sage: S = S.face_truncation(S.faces(0)[5]) - sage: T = polytopes.simplex(3) - sage: T = T.face_truncation(T.faces(0)[3]) - sage: T = T.face_truncation(T.faces(0)[4]) - sage: T = T.face_truncation(T.faces(0)[4]) - sage: T.is_combinatorially_isomorphic(S) - False - sage: T.f_vector(), S.f_vector() - ((1, 10, 15, 7, 1), (1, 10, 15, 7, 1)) - - sage: C = polytopes.hypercube(5) - sage: C.is_combinatorially_isomorphic(C) - True - sage: C.is_combinatorially_isomorphic(C, algorithm='magic') - Traceback (most recent call last): - ... - AssertionError: `algorithm` must be 'bipartite graph' or 'face_lattice' - - sage: G = Graph() - sage: C.is_combinatorially_isomorphic(G) - Traceback (most recent call last): - ... - AssertionError: input `other` must be a polyhedron - - sage: H = Polyhedron(eqns=[[0,1,1,1,1]]); H - A 3-dimensional polyhedron in QQ^4 defined as the convex hull of 1 vertex and 3 lines - sage: C.is_combinatorially_isomorphic(H) - Traceback (most recent call last): - ... - AssertionError: polyhedron `other` must be bounded - - """ - assert isinstance(other, Polyhedron_base), "input `other` must be a polyhedron" - assert self.is_compact(), "polyhedron `self` must be bounded" - assert other.is_compact(), "polyhedron `other` must be bounded" - assert algorithm in ['bipartite_graph', 'face_lattice'], "`algorithm` must be 'bipartite graph' or 'face_lattice'" - - # For speed, we check if the polyhedra have the same number of facets and vertices. - # This is faster than building the bipartite graphs first and - # then check that they won't be isomorphic. - if self.n_vertices() != other.n_vertices() or self.n_facets() != other.n_facets(): - return False - - if algorithm == 'bipartite_graph': - G_self = self.vertex_facet_graph(False) - G_other = other.vertex_facet_graph(False) - - return G_self.is_isomorphic(G_other) - else: - return self.face_lattice().is_isomorphic(other.face_lattice()) - - def _test_is_combinatorially_isomorphic(self, tester=None, **options): - """ - Run tests on the method :meth:`.is_combinatorially_isomorphic`. - - TESTS:: - - sage: polytopes.cross_polytope(3)._test_is_combinatorially_isomorphic() - """ - if tester is None: - tester = self._tester(**options) - - if not self.is_compact(): - with tester.assertRaises(AssertionError): - self.is_combinatorially_isomorphic(self) - return - - if self.n_vertices() > 200 or self.n_facets() > 200: - # Avoid very long doctests. - return - - try: - import sage.graphs.graph - except ImportError: - return - - tester.assertTrue(self.is_combinatorially_isomorphic(ZZ(4)*self)) - if self.n_vertices(): - tester.assertTrue(self.is_combinatorially_isomorphic(self + self.center())) - - if self.n_vertices() < 20 and self.n_facets() < 20 and self.is_immutable(): - tester.assertTrue(self.is_combinatorially_isomorphic(ZZ(4)*self, algorithm='face_lattice')) - if self.n_vertices(): - tester.assertTrue(self.is_combinatorially_isomorphic(self + self.center(), algorithm='face_lattice')) - def affine_hull(self, *args, **kwds): r""" Return the affine hull of ``self`` as a polyhedron. diff --git a/src/sage/geometry/polyhedron/base0.py b/src/sage/geometry/polyhedron/base0.py index c9bbd20dd81..ee113536673 100644 --- a/src/sage/geometry/polyhedron/base0.py +++ b/src/sage/geometry/polyhedron/base0.py @@ -260,6 +260,75 @@ def _init_empty_polyhedron(self): self._Vrepresentation = tuple(self._Vrepresentation) self._Hrepresentation = tuple(self._Hrepresentation) + def _delete(self): + """ + Delete this polyhedron. + + This speeds up creation of new polyhedra by reusing + objects. After recycling a polyhedron object, it is not in a + consistent state any more and neither the polyhedron nor its + H/V-representation objects may be used any more. + + .. SEEALSO:: + + :meth:`~sage.geometry.polyhedron.parent.Polyhedra_base.recycle` + + EXAMPLES:: + + sage: p = Polyhedron([(0,0),(1,0),(0,1)]) + sage: p._delete() + + sage: vertices = [(0,0,0,0),(1,0,0,0),(0,1,0,0),(1,1,0,0),(0,0,1,0),(0,0,0,1)] + sage: def loop_polyhedra(): + ....: for i in range(100): + ....: p = Polyhedron(vertices) + + sage: timeit('loop_polyhedra()') # not tested - random + 5 loops, best of 3: 79.5 ms per loop + + sage: def loop_polyhedra_with_recycling(): + ....: for i in range(100): + ....: p = Polyhedron(vertices) + ....: p._delete() + + sage: timeit('loop_polyhedra_with_recycling()') # not tested - random + 5 loops, best of 3: 57.3 ms per loop + """ + self.parent().recycle(self) + + def _sage_input_(self, sib, coerced): + """ + Return Sage command to reconstruct ``self``. + + See :mod:`sage.misc.sage_input` for details. + + .. TODO:: + + Add the option ``preparse`` to the method. + + EXAMPLES:: + + sage: P = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='ppl') + sage: sage_input(P) + Polyhedron(backend='ppl', base_ring=QQ, rays=[(QQ(1), QQ(1))], vertices=[(QQ(0), QQ(1)), (QQ(1), QQ(0))]) + sage: P = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='normaliz') # optional - pynormaliz + sage: sage_input(P) # optional - pynormaliz + Polyhedron(backend='normaliz', base_ring=QQ, rays=[(QQ(1), QQ(1))], vertices=[(QQ(0), QQ(1)), (QQ(1), QQ(0))]) + sage: P = Polyhedron(vertices = [[1, 0], [0, 1]], rays = [[1, 1]], backend='polymake') # optional - polymake + sage: sage_input(P) # optional - polymake + Polyhedron(backend='polymake', base_ring=QQ, rays=[(QQ(1), QQ(1))], vertices=[(QQ(1), QQ(0)), (QQ(0), QQ(1))]) + """ + kwds = dict() + kwds['base_ring'] = sib(self.base_ring()) + kwds['backend'] = sib(self.backend()) + if self.n_vertices() > 0: + kwds['vertices'] = [sib(tuple(v)) for v in self.vertices()] + if self.n_rays() > 0: + kwds['rays'] = [sib(tuple(r)) for r in self.rays()] + if self.n_lines() > 0: + kwds['lines'] = [sib(tuple(l)) for l in self.lines()] + return sib.name('Polyhedron')(**kwds) + def base_extend(self, base_ring, backend=None): """ Return a new polyhedron over a larger base ring. @@ -1025,6 +1094,69 @@ def vertices(self): """ return tuple(self.vertex_generator()) + @cached_method + def vertices_matrix(self, base_ring=None): + """ + Return the coordinates of the vertices as the columns of a matrix. + + INPUT: + + - ``base_ring`` -- A ring or ``None`` (default). The base ring + of the returned matrix. If not specified, the base ring of + the polyhedron is used. + + OUTPUT: + + A matrix over ``base_ring`` whose columns are the coordinates + of the vertices. A ``TypeError`` is raised if the coordinates + cannot be converted to ``base_ring``. + + .. WARNING:: + + If the polyhedron has lines, return the coordinates of the vertices + of the ``Vrepresentation``. However, the represented polyhedron + has no 0-dimensional faces (i.e. vertices):: + + sage: P = Polyhedron(rays=[[1,0,0]],lines=[[0,1,0]]) + sage: P.vertices_matrix() + [0] + [0] + [0] + sage: P.faces(0) + () + + EXAMPLES:: + + sage: triangle = Polyhedron(vertices=[[1,0],[0,1],[1,1]]) + sage: triangle.vertices_matrix() + [0 1 1] + [1 0 1] + sage: (triangle/2).vertices_matrix() + [ 0 1/2 1/2] + [1/2 0 1/2] + sage: (triangle/2).vertices_matrix(ZZ) + Traceback (most recent call last): + ... + TypeError: no conversion of this rational to integer + + TESTS: + + Check that :trac:`28828` is fixed:: + + sage: P.vertices_matrix().is_immutable() + True + """ + from sage.matrix.constructor import matrix + + if base_ring is None: + base_ring = self.base_ring() + m = matrix(base_ring, self.ambient_dim(), self.n_vertices()) + for i, v in enumerate(self.vertices()): + for j in range(self.ambient_dim()): + m[j, i] = v[j] + m.set_immutable() + return m + def ray_generator(self): """ Return a generator for the rays of the polyhedron. diff --git a/src/sage/geometry/polyhedron/base3.py b/src/sage/geometry/polyhedron/base3.py new file mode 100644 index 00000000000..2f056dee15b --- /dev/null +++ b/src/sage/geometry/polyhedron/base3.py @@ -0,0 +1,1946 @@ +r""" +Base class for polyhedra, part 3 + +Define methods related to the combinatorics of a polyhedron +excluding methods relying on :mod:`sage.graphs`. +""" + +# **************************************************************************** +# Copyright (C) 2008-2012 Marshall Hampton +# Copyright (C) 2011-2015 Volker Braun +# Copyright (C) 2012-2018 Frederic Chapoton +# Copyright (C) 2013 Andrey Novoseltsev +# Copyright (C) 2014-2017 Moritz Firsching +# Copyright (C) 2014-2019 Thierry Monteil +# Copyright (C) 2015 Nathann Cohen +# Copyright (C) 2015-2017 Jeroen Demeyer +# Copyright (C) 2015-2017 Vincent Delecroix +# Copyright (C) 2015-2018 Dima Pasechnik +# Copyright (C) 2015-2020 Jean-Philippe Labbe +# Copyright (C) 2015-2021 Matthias Koeppe +# Copyright (C) 2016-2019 Daniel Krenn +# Copyright (C) 2017 Marcelo Forets +# Copyright (C) 2017-2018 Mark Bell +# Copyright (C) 2019 Julian Ritter +# Copyright (C) 2019-2020 Laith Rastanawi +# Copyright (C) 2019-2020 Sophia Elia +# Copyright (C) 2019-2021 Jonathan Kliem +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.misc.cachefunc import cached_method +from sage.matrix.constructor import matrix +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from .base2 import Polyhedron_base2 + +class Polyhedron_base3(Polyhedron_base2): + """ + Methods related to the combinatorics of a polyhedron. + + See :class:`sage.geometry.polyhedron.base.Polyhedron_base`. + + TESTS:: + + sage: from sage.geometry.polyhedron.base3 import Polyhedron_base3 + sage: P = polytopes.cube() + sage: Polyhedron_base3.is_simple(P) + True + sage: Polyhedron_base3.is_simplicial(P) + False + sage: Polyhedron_base3.is_prism(P) + True + sage: Polyhedron_base3.is_pyramid(P) + False + sage: Polyhedron_base3.combinatorial_polyhedron.f(P) + A 3-dimensional combinatorial polyhedron with 6 facets + sage: Polyhedron_base3.facets(P) + (A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices) + sage: Polyhedron_base3.f_vector.f(P) + (1, 8, 12, 6, 1) + sage: next(Polyhedron_base3.face_generator(P)) + A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 8 vertices + """ + + def _init_empty_polyhedron(self): + """ + Initializes an empty polyhedron. + + TESTS:: + + sage: Polyhedron().vertex_adjacency_matrix() # indirect doctest + [] + sage: Polyhedron().facet_adjacency_matrix() + [0] + """ + Polyhedron_base2._init_empty_polyhedron(self) + + V_matrix = matrix(ZZ, 0, 0, 0) + V_matrix.set_immutable() + self.vertex_adjacency_matrix.set_cache(V_matrix) + + H_matrix = matrix(ZZ, 1, 1, 0) + H_matrix.set_immutable() + self.facet_adjacency_matrix.set_cache(H_matrix) + + @cached_method + def slack_matrix(self): + r""" + Return the slack matrix. + + The entries correspond to the evaluation of the Hrepresentation + elements on the Vrepresentation elements. + + .. NOTE:: + + The columns correspond to inequalities/equations in the + order :meth:`Hrepresentation`, the rows correspond to + vertices/rays/lines in the order + :meth:`Vrepresentation`. + + .. SEEALSO:: + + :meth:`incidence_matrix`. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: P.slack_matrix() + [0 2 2 2 0 0] + [0 0 2 2 0 2] + [0 0 0 2 2 2] + [0 2 0 2 2 0] + [2 2 0 0 2 0] + [2 2 2 0 0 0] + [2 0 2 0 0 2] + [2 0 0 0 2 2] + + sage: P = polytopes.cube(intervals='zero_one') + sage: P.slack_matrix() + [0 1 1 1 0 0] + [0 0 1 1 0 1] + [0 0 0 1 1 1] + [0 1 0 1 1 0] + [1 1 0 0 1 0] + [1 1 1 0 0 0] + [1 0 1 0 0 1] + [1 0 0 0 1 1] + + sage: P = polytopes.dodecahedron().faces(2)[0].as_polyhedron() + sage: P.slack_matrix() + [1/2*sqrt5 - 1/2 0 0 1 1/2*sqrt5 - 1/2 0] + [ 0 0 1/2*sqrt5 - 1/2 1/2*sqrt5 - 1/2 1 0] + [ 0 1/2*sqrt5 - 1/2 1 0 1/2*sqrt5 - 1/2 0] + [ 1 1/2*sqrt5 - 1/2 0 1/2*sqrt5 - 1/2 0 0] + [1/2*sqrt5 - 1/2 1 1/2*sqrt5 - 1/2 0 0 0] + + sage: P = Polyhedron(rays=[[1, 0], [0, 1]]) + sage: P.slack_matrix() + [0 0] + [0 1] + [1 0] + + TESTS:: + + sage: Polyhedron().slack_matrix() + [] + sage: Polyhedron(base_ring=QuadraticField(2)).slack_matrix().base_ring() + Number Field in a with defining polynomial x^2 - 2 with a = 1.41... + """ + if not self.n_Vrepresentation() or not self.n_Hrepresentation(): + slack_matrix = matrix(self.base_ring(), self.n_Vrepresentation(), + self.n_Hrepresentation(), 0) + else: + Vrep_matrix = matrix(self.base_ring(), self.Vrepresentation()) + Hrep_matrix = matrix(self.base_ring(), self.Hrepresentation()) + + # Getting homogeneous coordinates of the Vrepresentation. + hom_helper = matrix(self.base_ring(), [1 if v.is_vertex() else 0 for v in self.Vrepresentation()]) + hom_Vrep = hom_helper.stack(Vrep_matrix.transpose()) + + slack_matrix = (Hrep_matrix * hom_Vrep).transpose() + + slack_matrix.set_immutable() + return slack_matrix + + @cached_method + def incidence_matrix(self): + """ + Return the incidence matrix. + + .. NOTE:: + + The columns correspond to inequalities/equations in the + order :meth:`Hrepresentation`, the rows correspond to + vertices/rays/lines in the order + :meth:`Vrepresentation`. + + .. SEEALSO:: + + :meth:`slack_matrix`. + + EXAMPLES:: + + sage: p = polytopes.cuboctahedron() + sage: p.incidence_matrix() + [0 0 1 1 0 1 0 0 0 0 1 0 0 0] + [0 0 0 1 0 0 1 0 1 0 1 0 0 0] + [0 0 1 1 1 0 0 1 0 0 0 0 0 0] + [1 0 0 1 1 0 1 0 0 0 0 0 0 0] + [0 0 0 0 0 1 0 0 1 1 1 0 0 0] + [0 0 1 0 0 1 0 1 0 0 0 1 0 0] + [1 0 0 0 0 0 1 0 1 0 0 0 1 0] + [1 0 0 0 1 0 0 1 0 0 0 0 0 1] + [0 1 0 0 0 1 0 0 0 1 0 1 0 0] + [0 1 0 0 0 0 0 0 1 1 0 0 1 0] + [0 1 0 0 0 0 0 1 0 0 0 1 0 1] + [1 1 0 0 0 0 0 0 0 0 0 0 1 1] + sage: v = p.Vrepresentation(0) + sage: v + A vertex at (-1, -1, 0) + sage: h = p.Hrepresentation(2) + sage: h + An inequality (1, 1, -1) x + 2 >= 0 + sage: h.eval(v) # evaluation (1, 1, -1) * (-1/2, -1/2, 0) + 1 + 0 + sage: h*v # same as h.eval(v) + 0 + sage: p.incidence_matrix() [0,2] # this entry is (v,h) + 1 + sage: h.contains(v) + True + sage: p.incidence_matrix() [2,0] # note: not symmetric + 0 + + The incidence matrix depends on the ambient dimension:: + + sage: simplex = polytopes.simplex(); simplex + A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 4 vertices + sage: simplex.incidence_matrix() + [1 1 1 1 0] + [1 1 1 0 1] + [1 1 0 1 1] + [1 0 1 1 1] + sage: simplex = simplex.affine_hull_projection(); simplex + A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 4 vertices + sage: simplex.incidence_matrix() + [1 1 1 0] + [1 1 0 1] + [1 0 1 1] + [0 1 1 1] + + An incidence matrix does not determine a unique + polyhedron:: + + sage: P = Polyhedron(vertices=[[0,1],[1,1],[1,0]]) + sage: P.incidence_matrix() + [1 1 0] + [1 0 1] + [0 1 1] + + sage: Q = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]]) + sage: Q.incidence_matrix() + [1 1 0] + [1 0 1] + [0 1 1] + + + An example of two polyhedra with isomorphic face lattices + but different incidence matrices:: + + sage: Q.incidence_matrix() + [1 1 0] + [1 0 1] + [0 1 1] + + sage: R = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,3/2], [3/2,1]]) + sage: R.incidence_matrix() + [1 1 0] + [1 0 1] + [0 1 0] + [0 0 1] + + The incidence matrix has base ring integers. This way one can express various + counting questions:: + + sage: P = polytopes.twenty_four_cell() + sage: M = P.incidence_matrix() + sage: sum(sum(x) for x in M) == P.flag_f_vector(0,3) + True + + TESTS: + + Check that :trac:`28828` is fixed:: + + sage: R.incidence_matrix().is_immutable() + True + + Test that this method works for inexact base ring + (``cdd`` sets the cache already):: + + sage: P = polytopes.dodecahedron(exact=False) + sage: M = P.incidence_matrix.cache + sage: P.incidence_matrix.clear_cache() + sage: M == P.incidence_matrix() + True + """ + if self.base_ring() in (ZZ, QQ): + # Much faster for integers or rationals. + incidence_matrix = self.slack_matrix().zero_pattern_matrix(ZZ) + incidence_matrix.set_immutable() + return incidence_matrix + + incidence_matrix = matrix(ZZ, self.n_Vrepresentation(), + self.n_Hrepresentation(), 0) + + Vvectors_vertices = tuple((v.vector(), v.index()) + for v in self.Vrep_generator() + if v.is_vertex()) + Vvectors_rays_lines = tuple((v.vector(), v.index()) + for v in self.Vrep_generator() + if not v.is_vertex()) + + # Determine ``is_zero`` to save lots of time. + if self.base_ring().is_exact(): + def is_zero(x): + return not x + else: + is_zero = self._is_zero + + for H in self.Hrep_generator(): + Hconst = H.b() + Hvec = H.A() + Hindex = H.index() + for Vvec, Vindex in Vvectors_vertices: + if is_zero(Hvec*Vvec + Hconst): + incidence_matrix[Vindex, Hindex] = 1 + + # A ray or line is considered incident with a hyperplane, + # if it is orthogonal to the normal vector of the hyperplane. + for Vvec, Vindex in Vvectors_rays_lines: + if is_zero(Hvec*Vvec): + incidence_matrix[Vindex, Hindex] = 1 + + incidence_matrix.set_immutable() + return incidence_matrix + + @cached_method + def combinatorial_polyhedron(self): + r""" + Return the combinatorial type of ``self``. + + See :class:`sage.geometry.polyhedron.combinatorial_polyhedron.base.CombinatorialPolyhedron`. + + EXAMPLES:: + + sage: polytopes.cube().combinatorial_polyhedron() + A 3-dimensional combinatorial polyhedron with 6 facets + + sage: polytopes.cyclic_polytope(4,10).combinatorial_polyhedron() + A 4-dimensional combinatorial polyhedron with 35 facets + + sage: Polyhedron(rays=[[0,1], [1,0]]).combinatorial_polyhedron() + A 2-dimensional combinatorial polyhedron with 2 facets + """ + from sage.geometry.polyhedron.combinatorial_polyhedron.base import CombinatorialPolyhedron + return CombinatorialPolyhedron(self) + + def _test_combinatorial_polyhedron(self, tester=None, **options): + """ + Run test suite of combinatorial polyhedron. + + TESTS:: + + sage: polytopes.cross_polytope(3)._test_combinatorial_polyhedron() + """ + from sage.misc.sage_unittest import TestSuite + + tester = self._tester(tester=tester, **options) + tester.info("\n Running the test suite of self.combinatorial_polyhedron()") + TestSuite(self.combinatorial_polyhedron()).run(verbose=tester._verbose, + prefix=tester._prefix+" ") + tester.info(tester._prefix+" ", newline = False) + + def face_generator(self, face_dimension=None, dual=None): + r""" + Return an iterator over the faces of given dimension. + + If dimension is not specified return an iterator over all faces. + + INPUT: + + - ``face_dimension`` -- integer (default ``None``), + yield only faces of this dimension if specified + - ``dual`` -- boolean (default ``None``); + if ``True``, generate the faces using the vertices; + if ``False``, generate the faces using the facets; + if ``None``, pick automatically + + OUTPUT: + + A :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator_geom`. + This class iterates over faces as + :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. See + :mod:`~sage.geometry.polyhedron.face` for details. The order + is random but fixed. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: it = P.face_generator() + sage: it + Iterator over the faces of a 3-dimensional polyhedron in ZZ^3 + sage: list(it) + [A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 8 vertices, + A -1-dimensional face of a Polyhedron in ZZ^3, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices] + + sage: P = polytopes.hypercube(4) + sage: list(P.face_generator(2))[:4] + [A 2-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices, + A 2-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices] + + If a polytope has more facets than vertices, the dual mode is chosen:: + + sage: P = polytopes.cross_polytope(3) + sage: list(P.face_generator()) + [A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 6 vertices, + A -1-dimensional face of a Polyhedron in ZZ^3, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 3 vertices, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices] + + The face iterator can also be slightly modified. + In non-dual mode we can skip subfaces of the current (proper) face:: + + sage: P = polytopes.cube() + sage: it = P.face_generator(dual=False) + sage: _ = next(it), next(it) + sage: face = next(it) + sage: face.ambient_H_indices() + (5,) + sage: it.ignore_subfaces() + sage: face = next(it) + sage: face.ambient_H_indices() + (4,) + sage: it.ignore_subfaces() + sage: [face.ambient_H_indices() for face in it] + [(3,), + (2,), + (1,), + (0,), + (2, 3), + (1, 3), + (1, 2, 3), + (1, 2), + (0, 2), + (0, 1, 2), + (0, 1)] + + In dual mode we can skip supfaces of the current (proper) face:: + + sage: P = polytopes.cube() + sage: it = P.face_generator(dual=True) + sage: _ = next(it), next(it) + sage: face = next(it) + sage: face.ambient_V_indices() + (7,) + sage: it.ignore_supfaces() + sage: next(it) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + sage: face = next(it) + sage: face.ambient_V_indices() + (5,) + sage: it.ignore_supfaces() + sage: [face.ambient_V_indices() for face in it] + [(4,), + (3,), + (2,), + (1,), + (0,), + (1, 6), + (3, 4), + (2, 3), + (0, 3), + (0, 1, 2, 3), + (1, 2), + (0, 1)] + + In non-dual mode, we cannot skip supfaces:: + + sage: it = P.face_generator(dual=False) + sage: _ = next(it), next(it) + sage: next(it) + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices + sage: it.ignore_supfaces() + Traceback (most recent call last): + ... + ValueError: only possible when in dual mode + + In dual mode, we cannot skip subfaces:: + + sage: it = P.face_generator(dual=True) + sage: _ = next(it), next(it) + sage: next(it) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + sage: it.ignore_subfaces() + Traceback (most recent call last): + ... + ValueError: only possible when not in dual mode + + We can only skip sub-/supfaces of proper faces:: + + sage: it = P.face_generator(dual=False) + sage: next(it) + A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 8 vertices + sage: it.ignore_subfaces() + Traceback (most recent call last): + ... + ValueError: iterator not set to a face yet + + .. SEEALSO:: + + :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator_geom`. + + ALGORITHM: + + See :class:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator`. + + TESTS:: + + sage: P = polytopes.simplex() + sage: list(P.face_generator(-2)) + [] + sage: list(P.face_generator(-1)) + [A -1-dimensional face of a Polyhedron in ZZ^4] + sage: list(P.face_generator(3)) + [A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 4 vertices] + + sage: list(Polyhedron().face_generator()) + [A -1-dimensional face of a Polyhedron in ZZ^0] + + Check that :trac:`29155` is fixed:: + + sage: P = polytopes.permutahedron(3) + sage: [f] = P.face_generator(2) + sage: f.ambient_Hrepresentation() + (An equation (1, 1, 1) x - 6 == 0,) + """ + from sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator import FaceIterator_geom + return FaceIterator_geom(self, output_dimension=face_dimension, dual=dual) + + def faces(self, face_dimension): + """ + Return the faces of given dimension + + INPUT: + + - ``face_dimension`` -- integer. The dimension of the faces + whose representation will be returned. + + OUTPUT: + + A tuple of + :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. See + :mod:`~sage.geometry.polyhedron.face` for details. The order + is random but fixed. + + .. SEEALSO:: + + :meth:`face_generator`, + :meth:`facet`. + + EXAMPLES: + + Here we find the vertex and face indices of the eight three-dimensional + facets of the four-dimensional hypercube:: + + sage: p = polytopes.hypercube(4) + sage: list(f.ambient_V_indices() for f in p.faces(3)) + [(0, 5, 6, 7, 8, 9, 14, 15), + (1, 4, 5, 6, 10, 13, 14, 15), + (1, 2, 6, 7, 8, 10, 11, 15), + (8, 9, 10, 11, 12, 13, 14, 15), + (0, 3, 4, 5, 9, 12, 13, 14), + (0, 2, 3, 7, 8, 9, 11, 12), + (1, 2, 3, 4, 10, 11, 12, 13), + (0, 1, 2, 3, 4, 5, 6, 7)] + + sage: face = p.faces(3)[3] + sage: face.ambient_Hrepresentation() + (An inequality (1, 0, 0, 0) x + 1 >= 0,) + sage: face.vertices() + (A vertex at (-1, -1, 1, -1), + A vertex at (-1, -1, 1, 1), + A vertex at (-1, 1, -1, -1), + A vertex at (-1, 1, 1, -1), + A vertex at (-1, 1, 1, 1), + A vertex at (-1, 1, -1, 1), + A vertex at (-1, -1, -1, 1), + A vertex at (-1, -1, -1, -1)) + + You can use the + :meth:`~sage.geometry.polyhedron.representation.PolyhedronRepresentation.index` + method to enumerate vertices and inequalities:: + + sage: def get_idx(rep): return rep.index() + sage: [get_idx(_) for _ in face.ambient_Hrepresentation()] + [4] + sage: [get_idx(_) for _ in face.ambient_Vrepresentation()] + [8, 9, 10, 11, 12, 13, 14, 15] + + sage: [ ([get_idx(_) for _ in face.ambient_Vrepresentation()], + ....: [get_idx(_) for _ in face.ambient_Hrepresentation()]) + ....: for face in p.faces(3) ] + [([0, 5, 6, 7, 8, 9, 14, 15], [7]), + ([1, 4, 5, 6, 10, 13, 14, 15], [6]), + ([1, 2, 6, 7, 8, 10, 11, 15], [5]), + ([8, 9, 10, 11, 12, 13, 14, 15], [4]), + ([0, 3, 4, 5, 9, 12, 13, 14], [3]), + ([0, 2, 3, 7, 8, 9, 11, 12], [2]), + ([1, 2, 3, 4, 10, 11, 12, 13], [1]), + ([0, 1, 2, 3, 4, 5, 6, 7], [0])] + + TESTS:: + + sage: pr = Polyhedron(rays = [[1,0,0],[-1,0,0],[0,1,0]], vertices = [[-1,-1,-1]], lines=[(0,0,1)]) + sage: pr.faces(4) + () + sage: pr.faces(3)[0].ambient_V_indices() + (0, 1, 2, 3) + sage: pr.facets()[0].ambient_V_indices() + (0, 1, 2) + sage: pr.faces(1) + () + sage: pr.faces(0) + () + sage: pr.faces(-1) + (A -1-dimensional face of a Polyhedron in QQ^3,) + """ + return tuple(self.face_generator(face_dimension)) + + def facets(self): + r""" + Return the facets of the polyhedron. + + Facets are the maximal nontrivial faces of polyhedra. + The empty face and the polyhedron itself are trivial. + + A facet of a `d`-dimensional polyhedron is a face of dimension + `d-1`. For `d \neq 0` the converse is true as well. + + OUTPUT: + + A tuple of + :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. See + :mod:`~sage.geometry.polyhedron.face` for details. The order + is random but fixed. + + .. SEEALSO:: :meth:`facets` + + EXAMPLES: + + Here we find the eight three-dimensional facets of the + four-dimensional hypercube:: + + sage: p = polytopes.hypercube(4) + sage: p.facets() + (A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices) + + This is the same result as explicitly finding the + three-dimensional faces:: + + sage: dim = p.dimension() + sage: p.faces(dim-1) + (A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices, + A 3-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 8 vertices) + + The ``0``-dimensional polyhedron does not have facets:: + + sage: P = Polyhedron([[0]]) + sage: P.facets() + () + """ + if self.dimension() == 0: + return () + return self.faces(self.dimension()-1) + + @cached_method(do_pickle=True) + def f_vector(self, num_threads=None, parallelization_depth=None): + r""" + Return the f-vector. + + INPUT: + + - ``num_threads`` -- integer (optional); specify the number of threads; + otherwise determined by :func:`~sage.parallel.ncpus.ncpus` + + - ``parallelization_depth`` -- integer (optional); specify + how deep in the lattice the parallelization is done + + OUTPUT: + + Return a vector whose `i`-th entry is the number of + `i-2`-dimensional faces of the polytope. + + .. NOTE:: + + The ``vertices`` as given by :meth:`Polyhedron_base.vertices` + do not need to correspond to `0`-dimensional faces. If a polyhedron + contains `k` lines they correspond to `k`-dimensional faces. + See example below + + EXAMPLES:: + + sage: p = Polyhedron(vertices=[[1, 2, 3], [1, 3, 2], + ....: [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1], [0, 0, 0]]) + sage: p.f_vector() + (1, 7, 12, 7, 1) + + sage: polytopes.cyclic_polytope(4,10).f_vector() + (1, 10, 45, 70, 35, 1) + + sage: polytopes.hypercube(5).f_vector() + (1, 32, 80, 80, 40, 10, 1) + + Polyhedra with lines do not have `0`-faces:: + + sage: Polyhedron(ieqs=[[1,-1,0,0],[1,1,0,0]]).f_vector() + (1, 0, 0, 2, 1) + + However, the method :meth:`Polyhedron_base.vertices` returns + two points that belong to the ``Vrepresentation``:: + + sage: P = Polyhedron(ieqs=[[1,-1,0],[1,1,0]]) + sage: P.vertices() + (A vertex at (1, 0), A vertex at (-1, 0)) + sage: P.f_vector() + (1, 0, 2, 1) + + TESTS: + + Check that :trac:`28828` is fixed:: + + sage: P.f_vector().is_immutable() + True + + The cache of the f-vector is being pickled:: + + sage: P = polytopes.cube() + sage: P.f_vector() + (1, 8, 12, 6, 1) + sage: Q = loads(dumps(P)) + sage: Q.f_vector.is_in_cache() + True + """ + return self.combinatorial_polyhedron().f_vector(num_threads, parallelization_depth) + + def bounded_edges(self): + """ + Return the bounded edges (excluding rays and lines). + + OUTPUT: + + A generator for pairs of vertices, one pair per edge. + + EXAMPLES:: + + sage: p = Polyhedron(vertices=[[1,0],[0,1]], rays=[[1,0],[0,1]]) + sage: [ e for e in p.bounded_edges() ] + [(A vertex at (0, 1), A vertex at (1, 0))] + sage: for e in p.bounded_edges(): print(e) + (A vertex at (0, 1), A vertex at (1, 0)) + """ + obj = self.Vrepresentation() + for i in range(len(obj)): + if not obj[i].is_vertex(): + continue + for j in range(i+1, len(obj)): + if not obj[j].is_vertex(): + continue + if self.vertex_adjacency_matrix()[i, j] == 0: + continue + yield (obj[i], obj[j]) + + @cached_method + def vertex_adjacency_matrix(self): + """ + Return the binary matrix of vertex adjacencies. + + EXAMPLES:: + + sage: polytopes.simplex(4).vertex_adjacency_matrix() + [0 1 1 1 1] + [1 0 1 1 1] + [1 1 0 1 1] + [1 1 1 0 1] + [1 1 1 1 0] + + The rows and columns of the vertex adjacency matrix correspond + to the :meth:`Vrepresentation` objects: vertices, rays, and + lines. The `(i,j)` matrix entry equals `1` if the `i`-th and + `j`-th V-representation object are adjacent. + + Two vertices are adjacent if they are the endpoints of an + edge, that is, a one-dimensional face. For unbounded polyhedra + this clearly needs to be generalized and we define two + V-representation objects (see + :mod:`sage.geometry.polyhedron.constructor`) to be adjacent if + they together generate a one-face. There are three possible + combinations: + + * Two vertices can bound a finite-length edge. + + * A vertex and a ray can generate a half-infinite edge + starting at the vertex and with the direction given by the + ray. + + * A vertex and a line can generate an infinite edge. The + position of the vertex on the line is arbitrary in this + case, only its transverse position matters. The direction of + the edge is given by the line generator. + + For example, take the half-plane:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: half_plane.Hrepresentation() + (An inequality (1, 0) x + 0 >= 0,) + + Its (non-unique) V-representation consists of a vertex, a ray, + and a line. The only edge is spanned by the vertex and the + line generator, so they are adjacent:: + + sage: half_plane.Vrepresentation() + (A line in the direction (0, 1), A ray in the direction (1, 0), A vertex at (0, 0)) + sage: half_plane.vertex_adjacency_matrix() + [0 0 1] + [0 0 0] + [1 0 0] + + In one dimension higher, that is for a half-space in 3 + dimensions, there is no one-dimensional face. Hence nothing is + adjacent:: + + sage: Polyhedron(ieqs=[(0,1,0,0)]).vertex_adjacency_matrix() + [0 0 0 0] + [0 0 0 0] + [0 0 0 0] + [0 0 0 0] + + EXAMPLES: + + In a bounded polygon, every vertex has precisely two adjacent ones:: + + sage: P = Polyhedron(vertices=[(0, 1), (1, 0), (3, 0), (4, 1)]) + sage: for v in P.Vrep_generator(): + ....: print("{} {}".format(P.adjacency_matrix().row(v.index()), v)) + (0, 1, 0, 1) A vertex at (0, 1) + (1, 0, 1, 0) A vertex at (1, 0) + (0, 1, 0, 1) A vertex at (3, 0) + (1, 0, 1, 0) A vertex at (4, 1) + + If the V-representation of the polygon contains vertices and + one ray, then each V-representation object is adjacent to two + V-representation objects:: + + sage: P = Polyhedron(vertices=[(0, 1), (1, 0), (3, 0), (4, 1)], + ....: rays=[(0,1)]) + sage: for v in P.Vrep_generator(): + ....: print("{} {}".format(P.adjacency_matrix().row(v.index()), v)) + (0, 1, 0, 0, 1) A ray in the direction (0, 1) + (1, 0, 1, 0, 0) A vertex at (0, 1) + (0, 1, 0, 1, 0) A vertex at (1, 0) + (0, 0, 1, 0, 1) A vertex at (3, 0) + (1, 0, 0, 1, 0) A vertex at (4, 1) + + If the V-representation of the polygon contains vertices and + two distinct rays, then each vertex is adjacent to two + V-representation objects (which can now be vertices or + rays). The two rays are not adjacent to each other:: + + sage: P = Polyhedron(vertices=[(0, 1), (1, 0), (3, 0), (4, 1)], + ....: rays=[(0,1), (1,1)]) + sage: for v in P.Vrep_generator(): + ....: print("{} {}".format(P.adjacency_matrix().row(v.index()), v)) + (0, 1, 0, 0, 0) A ray in the direction (0, 1) + (1, 0, 1, 0, 0) A vertex at (0, 1) + (0, 1, 0, 0, 1) A vertex at (1, 0) + (0, 0, 0, 0, 1) A ray in the direction (1, 1) + (0, 0, 1, 1, 0) A vertex at (3, 0) + + The vertex adjacency matrix has base ring integers. This way one can express various + counting questions:: + + sage: P = polytopes.cube() + sage: Q = P.stack(P.faces(2)[0]) + sage: M = Q.vertex_adjacency_matrix() + sage: sum(M) + (4, 4, 3, 3, 4, 4, 4, 3, 3) + sage: G = Q.vertex_graph() # optional - sage.graphs + sage: G.degree() # optional - sage.graphs + [4, 4, 3, 3, 4, 4, 4, 3, 3] + + TESTS: + + Check that :trac:`28828` is fixed:: + + sage: P.adjacency_matrix().is_immutable() + True + """ + return self.combinatorial_polyhedron().vertex_adjacency_matrix() + + adjacency_matrix = vertex_adjacency_matrix + + @cached_method + def facet_adjacency_matrix(self): + """ + Return the adjacency matrix for the facets and hyperplanes. + + EXAMPLES:: + + sage: s4 = polytopes.simplex(4, project=True) + sage: s4.facet_adjacency_matrix() + [0 1 1 1 1] + [1 0 1 1 1] + [1 1 0 1 1] + [1 1 1 0 1] + [1 1 1 1 0] + + The facet adjacency matrix has base ring integers. This way one can express various + counting questions:: + + sage: P = polytopes.cube() + sage: Q = P.stack(P.faces(2)[0]) + sage: M = Q.facet_adjacency_matrix() + sage: sum(M) + (4, 4, 4, 4, 3, 3, 3, 3, 4) + + TESTS: + + Check that :trac:`28828` is fixed:: + + sage: s4.facet_adjacency_matrix().is_immutable() + True + """ + return self._facet_adjacency_matrix() + + def _facet_adjacency_matrix(self): + """ + Compute the facet adjacency matrix in case it has not been + computed during initialization. + + EXAMPLES:: + + sage: p = Polyhedron(vertices=[(0,0),(1,0),(0,1)]) + sage: p._facet_adjacency_matrix() + [0 1 1] + [1 0 1] + [1 1 0] + + Checks that :trac:`22455` is fixed:: + + sage: s = polytopes.simplex(2) + sage: s._facet_adjacency_matrix() + [0 1 1] + [1 0 1] + [1 1 0] + + """ + # TODO: This implementation computes the whole face lattice, + # which is much more information than necessary. + M = matrix(ZZ, self.n_facets(), self.n_facets(), 0) + codim = self.ambient_dim()-self.dim() + + def set_adjacent(h1, h2): + if h1 is h2: + return + i = h1.index() - codim + j = h2.index() - codim + M[i, j] = 1 + M[j, i] = 1 + + for face in self.faces(self.dim()-2): + Hrep = face.ambient_Hrepresentation() + assert(len(Hrep) == codim+2) + set_adjacent(Hrep[-2], Hrep[-1]) + M.set_immutable() + return M + + def a_maximal_chain(self): + r""" + Return a maximal chain of the face lattice in increasing order. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: P.a_maximal_chain() + [A -1-dimensional face of a Polyhedron in ZZ^3, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 8 vertices] + sage: P = polytopes.cube() + sage: chain = P.a_maximal_chain(); chain + [A -1-dimensional face of a Polyhedron in ZZ^3, + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex, + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices, + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices, + A 3-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 8 vertices] + sage: [face.ambient_V_indices() for face in chain] + [(), (5,), (0, 5), (0, 3, 4, 5), (0, 1, 2, 3, 4, 5, 6, 7)] + + TESTS:: + + Check output for the empty polyhedron:: + + sage: P = Polyhedron() + sage: P.a_maximal_chain() + [A -1-dimensional face of a Polyhedron in ZZ^0] + """ + comb_chain = self.combinatorial_polyhedron().a_maximal_chain() + + from sage.geometry.polyhedron.face import combinatorial_face_to_polyhedral_face + empty_face = self.faces(-1)[0] + universe = self.faces(self.dim())[0] + + if self.dim() == -1: + return [empty_face] + + return [empty_face] + \ + [combinatorial_face_to_polyhedral_face(self, face) + for face in comb_chain] + \ + [universe] + + def is_simplex(self): + r""" + Return whether the polyhedron is a simplex. + + A simplex is a bounded polyhedron with `d+1` vertices, where + `d` is the dimension. + + EXAMPLES:: + + sage: Polyhedron([(0,0,0), (1,0,0), (0,1,0)]).is_simplex() + True + sage: polytopes.simplex(3).is_simplex() + True + sage: polytopes.hypercube(3).is_simplex() + False + """ + return self.is_compact() and (self.dim()+1 == self.n_vertices()) + + def simplicity(self): + r""" + Return the largest integer `k` such that the polytope is `k`-simple. + + A polytope `P` is `k`-simple, if every `(d-1-k)`-face + is contained in exactly `k+1` facets of `P` for `1 \leq k \leq d-1`. + Equivalently it is `k`-simple if the polar/dual polytope is `k`-simplicial. + If `self` is a simplex, it returns its dimension. + + EXAMPLES:: + + sage: polytopes.hypersimplex(4,2).simplicity() + 1 + sage: polytopes.hypersimplex(5,2).simplicity() + 2 + sage: polytopes.hypersimplex(6,2).simplicity() + 3 + sage: polytopes.simplex(3).simplicity() + 3 + sage: polytopes.simplex(1).simplicity() + 1 + + The method is not implemented for unbounded polyhedra:: + + sage: p = Polyhedron(vertices=[(0,0)],rays=[(1,0),(0,1)]) + sage: p.simplicity() + Traceback (most recent call last): + ... + NotImplementedError: this function is implemented for polytopes only + """ + if not(self.is_compact()): + raise NotImplementedError("this function is implemented for polytopes only") + return self.combinatorial_polyhedron().simplicity() + + def is_simple(self): + """ + Test for simplicity of a polytope. + + See :wikipedia:`Simple_polytope` + + EXAMPLES:: + + sage: p = Polyhedron([[0,0,0],[1,0,0],[0,1,0],[0,0,1]]) + sage: p.is_simple() + True + sage: p = Polyhedron([[0,0,0],[4,4,0],[4,0,0],[0,4,0],[2,2,2]]) + sage: p.is_simple() + False + """ + if not self.is_compact(): + return False + return self.combinatorial_polyhedron().is_simple() + + def simpliciality(self): + r""" + Return the largest integer `k` such that the polytope is `k`-simplicial. + + A polytope is `k`-simplicial, if every `k`-face is a simplex. + If `self` is a simplex, returns its dimension. + + EXAMPLES:: + + sage: polytopes.cyclic_polytope(10,4).simpliciality() + 3 + sage: polytopes.hypersimplex(5,2).simpliciality() + 2 + sage: polytopes.cross_polytope(4).simpliciality() + 3 + sage: polytopes.simplex(3).simpliciality() + 3 + sage: polytopes.simplex(1).simpliciality() + 1 + + The method is not implemented for unbounded polyhedra:: + + sage: p = Polyhedron(vertices=[(0,0)],rays=[(1,0),(0,1)]) + sage: p.simpliciality() + Traceback (most recent call last): + ... + NotImplementedError: this function is implemented for polytopes only + """ + if not(self.is_compact()): + raise NotImplementedError("this function is implemented for polytopes only") + return self.combinatorial_polyhedron().simpliciality() + + def is_simplicial(self): + """ + Tests if the polytope is simplicial + + A polytope is simplicial if every facet is a simplex. + + See :wikipedia:`Simplicial_polytope` + + EXAMPLES:: + + sage: p = polytopes.hypercube(3) + sage: p.is_simplicial() + False + sage: q = polytopes.simplex(5, project=True) + sage: q.is_simplicial() + True + sage: p = Polyhedron([[0,0,0],[1,0,0],[0,1,0],[0,0,1]]) + sage: p.is_simplicial() + True + sage: q = Polyhedron([[1,1,1],[-1,1,1],[1,-1,1],[-1,-1,1],[1,1,-1]]) + sage: q.is_simplicial() + False + sage: P = polytopes.simplex(); P + A 3-dimensional polyhedron in ZZ^4 defined as the convex hull of 4 vertices + sage: P.is_simplicial() + True + + The method is not implemented for unbounded polyhedra:: + + sage: p = Polyhedron(vertices=[(0,0)],rays=[(1,0),(0,1)]) + sage: p.is_simplicial() + Traceback (most recent call last): + ... + NotImplementedError: this function is implemented for polytopes only + """ + if not(self.is_compact()): + raise NotImplementedError("this function is implemented for polytopes only") + return self.combinatorial_polyhedron().is_simplicial() + + def is_pyramid(self, certificate=False): + """ + Test whether the polytope is a pyramid over one of its facets. + + INPUT: + + - ``certificate`` -- boolean (default: ``False``); specifies whether + to return a vertex of the polytope which is the apex of a pyramid, + if found + + OUTPUT: + + If ``certificate`` is ``True``, returns a tuple containing: + + 1. Boolean. + 2. The apex of the pyramid or ``None``. + + If ``certificate`` is ``False`` returns a boolean. + + EXAMPLES:: + + sage: P = polytopes.simplex(3) + sage: P.is_pyramid() + True + sage: P.is_pyramid(certificate=True) + (True, A vertex at (1, 0, 0, 0)) + sage: egyptian_pyramid = polytopes.regular_polygon(4).pyramid() # optional - sage.rings.number_field + sage: egyptian_pyramid.is_pyramid() # optional - sage.rings.number_field + True + sage: Q = polytopes.octahedron() + sage: Q.is_pyramid() + False + + For the `0`-dimensional polyhedron, the output is ``True``, + but it cannot be constructed as a pyramid over the empty polyhedron:: + + sage: P = Polyhedron([[0]]) + sage: P.is_pyramid() + True + sage: Polyhedron().pyramid() + Traceback (most recent call last): + ... + ZeroDivisionError: rational division by zero + """ + if not self.is_compact(): + raise ValueError("polyhedron has to be compact") + + return self.combinatorial_polyhedron().is_pyramid(certificate) + + def is_bipyramid(self, certificate=False): + r""" + Test whether the polytope is combinatorially equivalent to a + bipyramid over some polytope. + + INPUT: + + - ``certificate`` -- boolean (default: ``False``); specifies whether + to return two vertices of the polytope which are the apices of a + bipyramid, if found + + OUTPUT: + + If ``certificate`` is ``True``, returns a tuple containing: + + 1. Boolean. + 2. ``None`` or a tuple containing: + a. The first apex. + b. The second apex. + + If ``certificate`` is ``False`` returns a boolean. + + EXAMPLES:: + + sage: P = polytopes.octahedron() + sage: P.is_bipyramid() + True + sage: P.is_bipyramid(certificate=True) + (True, [A vertex at (-1, 0, 0), A vertex at (1, 0, 0)]) + sage: Q = polytopes.cyclic_polytope(3,7) + sage: Q.is_bipyramid() + False + sage: R = Q.bipyramid() + sage: R.is_bipyramid(certificate=True) + (True, [A vertex at (-1, 3, 13, 63), A vertex at (1, 3, 13, 63)]) + + TESTS:: + + sage: P = polytopes.permutahedron(4).bipyramid() + sage: P.is_bipyramid() + True + + sage: P = polytopes.cube() + sage: P.is_bipyramid() + False + + sage: P = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]]) + sage: P.is_bipyramid() + Traceback (most recent call last): + ... + ValueError: polyhedron has to be compact + + ALGORITHM: + + Assume all faces of a polyhedron to be given as lists of vertices. + + A polytope is a bipyramid with apexes `v`, `w` if and only if for each + proper face `v \in F` there exists a face `G` with + `G \setminus \{w\} = F \setminus \{v\}` + and vice versa (for each proper face + `w \in F` there exists ...). + + To check this property it suffices to check for all facets of the polyhedron. + """ + if not self.is_compact(): + raise ValueError("polyhedron has to be compact") + + from sage.misc.functional import is_odd + n_verts = self.n_vertices() + n_facets = self.n_facets() + if is_odd(n_facets): + if certificate: + return (False, None) + return False + + IM = self.incidence_matrix() + if self.n_equations(): + # Remove equations from the incidence matrix, + # such that this is the vertex-facet incidences matrix. + I1 = IM.transpose() + I2 = I1[[i for i in range(self.n_Hrepresentation()) + if not self.Hrepresentation()[i].is_equation()]] + IM = I2.transpose() + + facets_incidences = [set(column.nonzero_positions()) for column in IM.columns()] + verts_incidences = dict() + for i in range(n_verts): + v_i = set(IM.row(i).nonzero_positions()) + if len(v_i) == n_facets/2: + verts_incidences[i] = v_i + + # Find two vertices ``vert1`` and ``vert2`` such that one of them + # lies on exactly half of the facets, and the other one lies on + # exactly the other half. + from itertools import combinations + for index1, index2 in combinations(verts_incidences, 2): + vert1_incidences = verts_incidences[index1] + vert2_incidences = verts_incidences[index2] + vert1and2 = vert1_incidences.union(vert2_incidences) + if len(vert1and2) == n_facets: + # We have found two candidates for apexes. + # Remove from each facet ``index1`` resp. ``index2``. + test_facets = set(frozenset(facet_inc.difference({index1, index2})) + for facet_inc in facets_incidences) + if len(test_facets) == n_facets/2: + # For each `F` containing `index1` there is + # `G` containing `index2` such that + # `F \setminus \{index1\} = G \setminus \{index2\} + # and vice versa. + if certificate: + V = self.vertices() + return (True, [V[index1], V[index2]]) + return True + + if certificate: + return (False, None) + return False + + def is_prism(self, certificate=False): + """ + Test whether the polytope is combinatorially equivalent to a prism of + some polytope. + + INPUT: + + - ``certificate`` -- boolean (default: ``False``); specifies whether + to return two facets of the polytope which are the bases of a prism, + if found + + OUTPUT: + + If ``certificate`` is ``True``, returns a tuple containing: + + 1. Boolean. + 2. ``None`` or a tuple containing: + a. List of the vertices of the first base facet. + b. List of the vertices of the second base facet. + + If ``certificate`` is ``False`` returns a boolean. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: P.is_prism() + True + sage: P.is_prism(certificate=True) + (True, + [[A vertex at (1, -1, -1), + A vertex at (1, 1, -1), + A vertex at (1, 1, 1), + A vertex at (1, -1, 1)], + [A vertex at (-1, -1, 1), + A vertex at (-1, -1, -1), + A vertex at (-1, 1, -1), + A vertex at (-1, 1, 1)]]) + sage: Q = polytopes.cyclic_polytope(3,8) + sage: Q.is_prism() + False + sage: R = Q.prism() + sage: R.is_prism(certificate=True) + (True, + [[A vertex at (1, 6, 36, 216), + A vertex at (1, 0, 0, 0), + A vertex at (1, 7, 49, 343), + A vertex at (1, 5, 25, 125), + A vertex at (1, 1, 1, 1), + A vertex at (1, 2, 4, 8), + A vertex at (1, 4, 16, 64), + A vertex at (1, 3, 9, 27)], + [A vertex at (0, 3, 9, 27), + A vertex at (0, 6, 36, 216), + A vertex at (0, 0, 0, 0), + A vertex at (0, 7, 49, 343), + A vertex at (0, 5, 25, 125), + A vertex at (0, 1, 1, 1), + A vertex at (0, 2, 4, 8), + A vertex at (0, 4, 16, 64)]]) + + TESTS:: + + sage: P = polytopes.cross_polytope(5) + sage: P.is_prism() + False + + sage: P = polytopes.permutahedron(4).prism() + sage: P.is_prism() + True + + sage: P = Polyhedron(vertices=[[0,1], [1,0]], rays=[[1,1]]) + sage: P.is_prism() + Traceback (most recent call last): + ... + NotImplementedError: polyhedron has to be compact + + ALGORITHM: + + See :meth:`Polyhedron_base.is_bipyramid`. + """ + if not self.is_compact(): + raise NotImplementedError("polyhedron has to be compact") + + from sage.misc.functional import is_odd + n_verts = self.n_vertices() + n_facets = self.n_facets() + if is_odd(n_verts): + if certificate: + return (False, None) + return False + + IM = self.incidence_matrix() + if self.n_equations(): + # Remove equations from the incidence matrix, + # such that this is the vertex-facet incidences matrix. + I1 = IM.transpose() + I2 = I1[[i for i in range(self.n_Hrepresentation()) + if not self.Hrepresentation()[i].is_equation()]] + IM = I2.transpose() + + verts_incidences = [set(row.nonzero_positions()) for row in IM.rows()] + facets_incidences = dict() + for j in range(n_facets): + F_j = set(IM.column(j).nonzero_positions()) + if len(F_j) == n_verts/2: + facets_incidences[j] = F_j + + # Find two vertices ``facet1`` and ``facet2`` such that one of them + # contains exactly half of the vertices, and the other one contains + # exactly the other half. + from itertools import combinations + for index1, index2 in combinations(facets_incidences, 2): + facet1_incidences = facets_incidences[index1] + facet2_incidences = facets_incidences[index2] + facet1and2 = facet1_incidences.union(facet2_incidences) + if len(facet1and2) == n_verts: + # We have found two candidates for base faces. + # Remove from each vertex ``index1`` resp. ``index2``. + test_verts = set(frozenset(vert_inc.difference({index1, index2})) + for vert_inc in verts_incidences) + if len(test_verts) == n_verts/2: + # For each vertex containing `index1` there is + # another one contained in `index2` + # and vice versa. + # Other than `index1` and `index2` both are contained in + # exactly the same facets. + if certificate: + V = self.vertices() + facet1_vertices = [V[i] for i in facet1_incidences] + facet2_vertices = [V[i] for i in facet2_incidences] + return (True, [facet1_vertices, facet2_vertices]) + return True + + if certificate: + return (False, None) + return False + + def is_lawrence_polytope(self): + """ + Return ``True`` if ``self`` is a Lawrence polytope. + + A polytope is called a Lawrence polytope if it has a centrally + symmetric (normalized) Gale diagram. + + EXAMPLES:: + + sage: P = polytopes.hypersimplex(5,2) + sage: L = P.lawrence_polytope() + sage: L.is_lattice_polytope() + True + sage: egyptian_pyramid = polytopes.regular_polygon(4).pyramid() + sage: egyptian_pyramid.is_lawrence_polytope() + True + sage: polytopes.octahedron().is_lawrence_polytope() + False + + REFERENCES: + + For more information, see [BaSt1990]_. + """ + if not self.is_compact(): + raise NotImplementedError("self must be a polytope") + + return self.combinatorial_polyhedron().is_lawrence_polytope() + + def neighborliness(self): + r""" + Return the largest ``k``, such that the polyhedron is ``k``-neighborly. + + A polyhedron is `k`-neighborly if every set of `n` vertices forms a face + for `n` up to `k`. + + In case of the `d`-dimensional simplex, it returns `d + 1`. + + .. SEEALSO:: + + :meth:`is_neighborly` + + EXAMPLES:: + + sage: cube = polytopes.cube() + sage: cube.neighborliness() + 1 + sage: P = Polyhedron(); P + The empty polyhedron in ZZ^0 + sage: P.neighborliness() + 0 + sage: P = Polyhedron([[0]]); P + A 0-dimensional polyhedron in ZZ^1 defined as the convex hull of 1 vertex + sage: P.neighborliness() + 1 + sage: S = polytopes.simplex(5); S + A 5-dimensional polyhedron in ZZ^6 defined as the convex hull of 6 vertices + sage: S.neighborliness() + 6 + sage: C = polytopes.cyclic_polytope(7,10); C + A 7-dimensional polyhedron in QQ^7 defined as the convex hull of 10 vertices + sage: C.neighborliness() + 3 + sage: C = polytopes.cyclic_polytope(6,11); C + A 6-dimensional polyhedron in QQ^6 defined as the convex hull of 11 vertices + sage: C.neighborliness() + 3 + sage: [polytopes.cyclic_polytope(5,n).neighborliness() for n in range(6,10)] + [6, 2, 2, 2] + + """ + return self.combinatorial_polyhedron().neighborliness() + + def is_neighborly(self, k=None): + r""" + Return whether the polyhedron is neighborly. + + If the input ``k`` is provided, then return whether the polyhedron is ``k``-neighborly + + A polyhedron is neighborly if every set of `n` vertices forms a face + for `n` up to floor of half the dimension of the polyhedron. + It is `k`-neighborly if this is true for `n` up to `k`. + + INPUT: + + - ``k`` -- the dimension up to which to check if every set of ``k`` + vertices forms a face. If no ``k`` is provided, check up to floor + of half the dimension of the polyhedron. + + OUTPUT: + + - ``True`` if every set of up to ``k`` vertices forms a face, + - ``False`` otherwise + + .. SEEALSO:: + + :meth:`neighborliness` + + EXAMPLES:: + + sage: cube = polytopes.hypercube(3) + sage: cube.is_neighborly() + True + sage: cube = polytopes.hypercube(4) + sage: cube.is_neighborly() + False + + Cyclic polytopes are neighborly:: + + sage: all(polytopes.cyclic_polytope(i, i + 1 + j).is_neighborly() for i in range(5) for j in range(3)) + True + + The neighborliness of a polyhedron equals floor of dimension half + (or larger in case of a simplex) if and only if the polyhedron + is neighborly:: + + sage: testpolys = [polytopes.cube(), polytopes.cyclic_polytope(6, 9), polytopes.simplex(6)] + sage: [(P.neighborliness()>=floor(P.dim()/2)) == P.is_neighborly() for P in testpolys] + [True, True, True] + + """ + return self.combinatorial_polyhedron().is_neighborly() + + def join_of_Vrep(self, *Vrepresentatives): + r""" + Return the smallest face that contains ``Vrepresentatives``. + + INPUT: + + - ``Vrepresentatives`` -- vertices/rays/lines of ``self`` or indices of such + + OUTPUT: a :class:`~sage.geometry.polyhedron.face.PolyhedronFace` + + .. NOTE:: + + In the case of unbounded polyhedra, the join of rays etc. may not be well-defined. + + EXAMPLES:: + + sage: P = polytopes.permutahedron(5) + sage: P.join_of_Vrep(1) + A 0-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 1 vertex + sage: P.join_of_Vrep() + A -1-dimensional face of a Polyhedron in ZZ^5 + sage: P.join_of_Vrep(0,12,13).ambient_V_indices() + (0, 12, 13, 68) + + The input is flexible:: + + sage: P.join_of_Vrep(2, P.vertices()[3], P.Vrepresentation(4)) + A 2-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 6 vertices + + :: + + sage: P = polytopes.cube() + sage: a, b = P.faces(0)[:2] + sage: P.join_of_Vrep(a, b) + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices + + In the case of an unbounded polyhedron, the join may not be well-defined:: + + sage: P = Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,1]]) + sage: P.join_of_Vrep(0) + A 0-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex + sage: P.join_of_Vrep(0,1) + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 2 vertices + sage: P.join_of_Vrep(0,2) + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 ray + sage: P.join_of_Vrep(1,2) + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 ray + sage: P.join_of_Vrep(2) + Traceback (most recent call last): + ... + ValueError: the join is not well-defined + + The ``Vrepresentatives`` must be of ``self``:: + + sage: P = polytopes.cube(backend='ppl') + sage: Q = polytopes.cube(backend='field') + sage: v = P.vertices()[0] + sage: P.join_of_Vrep(v) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + sage: Q.join_of_Vrep(v) + Traceback (most recent call last): + ... + ValueError: not a Vrepresentative of ``self`` + sage: f = P.faces(0)[0] + sage: P.join_of_Vrep(v) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + sage: Q.join_of_Vrep(v) + Traceback (most recent call last): + ... + ValueError: not a Vrepresentative of ``self`` + + TESTS: + + ``least_common_superface_of_Vrep`` is an alias:: + + sage: P.least_common_superface_of_Vrep(v) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + sage: P.least_common_superface_of_Vrep == P.join_of_Vrep + True + + Error message for invalid input:: + + sage: P.join_of_Vrep('foo') + Traceback (most recent call last): + ... + ValueError: foo is not a Vrepresentative + """ + from sage.geometry.polyhedron.representation import Vrepresentation + from sage.geometry.polyhedron.face import PolyhedronFace + + new_indices = [0]*len(Vrepresentatives) + for i, v in enumerate(Vrepresentatives): + if isinstance(v, PolyhedronFace) and v.dim() == 0: + if v.polyhedron() is not self: + raise ValueError("not a Vrepresentative of ``self``") + new_indices[i] = v.ambient_V_indices()[0] + elif v in ZZ: + new_indices[i] = v + elif isinstance(v, Vrepresentation): + if v.polyhedron() is not self: + raise ValueError("not a Vrepresentative of ``self``") + new_indices[i] = v.index() + else: + raise ValueError("{} is not a Vrepresentative".format(v)) + + return self.face_generator().join_of_Vrep(*new_indices) + + least_common_superface_of_Vrep = join_of_Vrep + + def meet_of_Hrep(self, *Hrepresentatives): + r""" + Return the largest face that is contained in ``Hrepresentatives``. + + INPUT: + + - ``Hrepresentatives`` -- facets or indices of Hrepresentatives; + the indices are assumed to be the indices of the Hrepresentation + + OUTPUT: a :class:`~sage.geometry.polyhedron.face.PolyhedronFace` + + EXAMPLES:: + + sage: P = polytopes.permutahedron(5) + sage: P.meet_of_Hrep() + A 4-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 120 vertices + sage: P.meet_of_Hrep(1) + A 3-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 24 vertices + sage: P.meet_of_Hrep(4) + A 3-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 12 vertices + sage: P.meet_of_Hrep(1,3,7) + A 1-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 2 vertices + sage: P.meet_of_Hrep(1,3,7).ambient_H_indices() + (0, 1, 3, 7) + + The indices are the indices of the Hrepresentation. + ``0`` corresponds to an equation and is ignored:: + + sage: P.meet_of_Hrep(0) + A 4-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 120 vertices + + The input is flexible:: + + sage: P.meet_of_Hrep(P.facets()[-1], P.inequalities()[2], 7) + A 1-dimensional face of a Polyhedron in ZZ^5 defined as the convex hull of 2 vertices + + The ``Hrepresentatives`` must belong to ``self``:: + + sage: P = polytopes.cube(backend='ppl') + sage: Q = polytopes.cube(backend='field') + sage: f = P.facets()[0] + sage: P.meet_of_Hrep(f) + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices + sage: Q.meet_of_Hrep(f) + Traceback (most recent call last): + ... + ValueError: not a facet of ``self`` + sage: f = P.inequalities()[0] + sage: P.meet_of_Hrep(f) + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices + sage: Q.meet_of_Hrep(f) + Traceback (most recent call last): + ... + ValueError: not a facet of ``self`` + + TESTS: + + Equations are not considered by the combinatorial polyhedron. + We check that the index corresponds to the Hrepresentation index:: + + sage: P = polytopes.permutahedron(3, backend='field') + sage: P.Hrepresentation() + (An inequality (0, 0, 1) x - 1 >= 0, + An inequality (0, 1, 0) x - 1 >= 0, + An inequality (0, 1, 1) x - 3 >= 0, + An inequality (1, 0, 0) x - 1 >= 0, + An inequality (1, 0, 1) x - 3 >= 0, + An inequality (1, 1, 0) x - 3 >= 0, + An equation (1, 1, 1) x - 6 == 0) + sage: P.meet_of_Hrep(0).ambient_Hrepresentation() + (An inequality (0, 0, 1) x - 1 >= 0, An equation (1, 1, 1) x - 6 == 0) + + sage: P = polytopes.permutahedron(3, backend='ppl') + sage: P.Hrepresentation() + (An equation (1, 1, 1) x - 6 == 0, + An inequality (1, 1, 0) x - 3 >= 0, + An inequality (-1, -1, 0) x + 5 >= 0, + An inequality (0, 1, 0) x - 1 >= 0, + An inequality (-1, 0, 0) x + 3 >= 0, + An inequality (1, 0, 0) x - 1 >= 0, + An inequality (0, -1, 0) x + 3 >= 0) + sage: P.meet_of_Hrep(1).ambient_Hrepresentation() + (An equation (1, 1, 1) x - 6 == 0, An inequality (1, 1, 0) x - 3 >= 0) + + ``greatest_common_subface_of_Hrep`` is an alias:: + + sage: P.greatest_common_subface_of_Hrep(1).ambient_Hrepresentation() + (An equation (1, 1, 1) x - 6 == 0, An inequality (1, 1, 0) x - 3 >= 0) + sage: P.greatest_common_subface_of_Hrep == P.meet_of_Hrep + True + + Error message for invalid input:: + + sage: P.meet_of_Hrep('foo') + Traceback (most recent call last): + ... + ValueError: foo is not a Hrepresentative + """ + from sage.geometry.polyhedron.representation import Inequality, Equation + from sage.geometry.polyhedron.face import PolyhedronFace + + # Equations are ignored by combinatorial polyhedron for indexing. + offset = 0 + if self.n_equations() and self.Hrepresentation(0).is_equation(): + offset = self.n_equations() + + new_indices = [] + for i, facet in enumerate(Hrepresentatives): + if isinstance(facet, PolyhedronFace) and facet.dim() + 1 == self.dim(): + if facet.polyhedron() is not self: + raise ValueError("not a facet of ``self``") + H_indices = facet.ambient_H_indices() + facet = H_indices[0] if H_indices[0] >= offset else H_indices[-1] + + if facet in ZZ and facet >= offset: + # Note that ``CombinatorialPolyhedron`` ignores indices of equations + # and has equations last. + new_indices.append(facet - offset) + elif isinstance(facet, Inequality): + if facet.polyhedron() is not self: + raise ValueError("not a facet of ``self``") + new_indices.append(facet.index() - offset) + elif isinstance(facet, Equation): + # Ignore equations. + continue + elif facet in ZZ and 0 <= facet < offset: + # Ignore equations. + continue + else: + raise ValueError("{} is not a Hrepresentative".format(facet)) + + return self.face_generator().meet_of_Hrep(*new_indices) + + greatest_common_subface_of_Hrep = meet_of_Hrep + + def _test_combinatorial_face_as_combinatorial_polyhedron(self, tester=None, **options): + """ + Run tests on obtaining the combinatorial face as combinatorial polyhedron. + + TESTS:: + + sage: polytopes.cross_polytope(3)._test_combinatorial_face_as_combinatorial_polyhedron() + """ + if not self.is_compact(): + return + if self.dim() > 7 or self.n_vertices() > 100 or self.n_facets() > 100: + # Avoid very long tests. + return + if self.dim() < 1: + # Avoid trivial cases. + return + + from sage.misc.prandom import random + + if tester is None: + tester = self._tester(**options) + + it = self.face_generator() + _ = next(it), next(it) # get rid of non_proper faces + C1 = self.combinatorial_polyhedron() + it1 = C1.face_iter() + C2 = C1.dual() + it2 = C2.face_iter(dual=not it1.dual) + + for f in it: + f1 = next(it1) + f2 = next(it2) + if random() < 0.95: + # Only test a random 5 percent of the faces. + continue + + P = f.as_polyhedron() + D1 = f1.as_combinatorial_polyhedron() + D2 = f2.as_combinatorial_polyhedron(quotient=True).dual() + D1._test_bitsets(tester, **options) + D2._test_bitsets(tester, **options) + try: + import sage.graphs.graph + except ImportError: + pass + else: + tester.assertTrue(P.combinatorial_polyhedron().vertex_facet_graph().is_isomorphic(D1.vertex_facet_graph())) + tester.assertTrue(P.combinatorial_polyhedron().vertex_facet_graph().is_isomorphic(D2.vertex_facet_graph())) diff --git a/src/sage/geometry/polyhedron/base4.py b/src/sage/geometry/polyhedron/base4.py new file mode 100644 index 00000000000..51efa9586c0 --- /dev/null +++ b/src/sage/geometry/polyhedron/base4.py @@ -0,0 +1,1233 @@ +# sage.doctest: optional - sage.graphs +r""" +Base class for polyhedra, part 4 + +Define methods relying on :mod:`sage.graphs`. +""" + +# **************************************************************************** +# Copyright (C) 2008-2012 Marshall Hampton +# Copyright (C) 2011-2015 Volker Braun +# Copyright (C) 2012-2018 Frederic Chapoton +# Copyright (C) 2013 Andrey Novoseltsev +# Copyright (C) 2014-2017 Moritz Firsching +# Copyright (C) 2014-2019 Thierry Monteil +# Copyright (C) 2015 Nathann Cohen +# Copyright (C) 2015-2017 Jeroen Demeyer +# Copyright (C) 2015-2017 Vincent Delecroix +# Copyright (C) 2015-2018 Dima Pasechnik +# Copyright (C) 2015-2020 Jean-Philippe Labbe +# Copyright (C) 2015-2021 Matthias Koeppe +# Copyright (C) 2016-2019 Daniel Krenn +# Copyright (C) 2017 Marcelo Forets +# Copyright (C) 2017-2018 Mark Bell +# Copyright (C) 2019 Julian Ritter +# Copyright (C) 2019-2020 Laith Rastanawi +# Copyright (C) 2019-2020 Sophia Elia +# Copyright (C) 2019-2021 Jonathan Kliem +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# https://www.gnu.org/licenses/ +# **************************************************************************** + +from sage.misc.cachefunc import cached_method +from .base3 import Polyhedron_base3 + +class Polyhedron_base4(Polyhedron_base3): + """ + Methods relying on :mod:`sage.graphs`. + + See :class:`sage.geometry.polyhedron.base.Polyhedron_base`. + + TESTS:: + + sage: from sage.geometry.polyhedron.base4 import Polyhedron_base4 + sage: P = polytopes.cube() + sage: Polyhedron_base4.vertex_facet_graph.f(P) + Digraph on 14 vertices + sage: Polyhedron_base4.vertex_graph(P) + Graph on 8 vertices + sage: Polyhedron_base4.face_lattice(P) + Finite lattice containing 28 elements + sage: Polyhedron_base4.flag_f_vector(P, 0, 2) + 24 + sage: Polyhedron_base4.is_self_dual(P) + False + sage: Q = polytopes.cube(intervals='zero_one') + sage: P == Q + False + sage: Polyhedron_base4.is_combinatorially_isomorphic(P, Q) + True + """ + + @cached_method + def vertex_facet_graph(self, labels=True): + r""" + Return the vertex-facet graph. + + This function constructs a directed bipartite graph. + The nodes of the graph correspond to the vertices of the polyhedron + and the facets of the polyhedron. There is an directed edge + from a vertex to a face if and only if the vertex is incident to the face. + + INPUT: + + - ``labels`` -- boolean (default: ``True``); decide how the nodes + of the graph are labelled. Either with the original vertices/facets + of the Polyhedron or with integers. + + OUTPUT: + + - a bipartite DiGraph. If ``labels`` is ``True``, then the nodes + of the graph will actually be the vertices and facets of ``self``, + otherwise they will be integers. + + .. SEEALSO:: + + :meth:`combinatorial_automorphism_group`, + :meth:`is_combinatorially_isomorphic`. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: G = P.vertex_facet_graph(); G + Digraph on 14 vertices + sage: G.vertices(key = lambda v: str(v)) + [A vertex at (-1, -1, -1), + A vertex at (-1, -1, 1), + A vertex at (-1, 1, -1), + A vertex at (-1, 1, 1), + A vertex at (1, -1, -1), + A vertex at (1, -1, 1), + A vertex at (1, 1, -1), + A vertex at (1, 1, 1), + An inequality (-1, 0, 0) x + 1 >= 0, + An inequality (0, -1, 0) x + 1 >= 0, + An inequality (0, 0, -1) x + 1 >= 0, + An inequality (0, 0, 1) x + 1 >= 0, + An inequality (0, 1, 0) x + 1 >= 0, + An inequality (1, 0, 0) x + 1 >= 0] + sage: G.automorphism_group().is_isomorphic(P.hasse_diagram().automorphism_group()) + True + sage: O = polytopes.octahedron(); O + A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 6 vertices + sage: O.vertex_facet_graph() + Digraph on 14 vertices + sage: H = O.vertex_facet_graph() + sage: G.is_isomorphic(H) + False + sage: G2 = copy(G) + sage: G2.reverse_edges(G2.edges()) + sage: G2.is_isomorphic(H) + True + + TESTS: + + Check that :trac:`28828` is fixed:: + + sage: G._immutable + True + + Check that :trac:`29188` is fixed:: + + sage: P = polytopes.cube() + sage: P.vertex_facet_graph().is_isomorphic(P.vertex_facet_graph(False)) + True + """ + return self.combinatorial_polyhedron().vertex_facet_graph(names=labels) + + def vertex_graph(self): + """ + Return a graph in which the vertices correspond to vertices + of the polyhedron, and edges to edges. + + ..NOTE:: + + The graph of a polyhedron with lines has no vertices, + as the polyhedron has no vertices (`0`-faces). + + The method :meth:`Polyhedron_base:vertices` returns + the defining points in this case. + + EXAMPLES:: + + sage: g3 = polytopes.hypercube(3).vertex_graph(); g3 + Graph on 8 vertices + sage: g3.automorphism_group().cardinality() + 48 + sage: s4 = polytopes.simplex(4).vertex_graph(); s4 + Graph on 5 vertices + sage: s4.is_eulerian() + True + + The graph of an unbounded polyhedron + is the graph of the bounded complex:: + + sage: open_triangle = Polyhedron(vertices=[[1,0], [0,1]], + ....: rays =[[1,1]]) + sage: open_triangle.vertex_graph() + Graph on 2 vertices + + The graph of a polyhedron with lines has no vertices:: + + sage: line = Polyhedron(lines=[[0,1]]) + sage: line.vertex_graph() + Graph on 0 vertices + + TESTS: + + Check for a line segment (:trac:`30545`):: + + sage: polytopes.simplex(1).graph().edges() + [(A vertex at (0, 1), A vertex at (1, 0), None)] + """ + return self.combinatorial_polyhedron().vertex_graph() + + graph = vertex_graph + + def vertex_digraph(self, f, increasing=True): + r""" + Return the directed graph of the polyhedron according to a linear form. + + The underlying undirected graph is the graph of vertices and edges. + + INPUT: + + - ``f`` -- a linear form. The linear form can be provided as: + + - a vector space morphism with one-dimensional codomain, (see + :meth:`sage.modules.vector_space_morphism.linear_transformation` + and + :class:`sage.modules.vector_space_morphism.VectorSpaceMorphism`) + - a vector ; in this case the linear form is obtained by duality + using the dot product: ``f(v) = v.dot_product(f)``. + + - ``increasing`` -- boolean (default ``True``) whether to orient + edges in the increasing or decreasing direction. + + By default, an edge is oriented from `v` to `w` if + `f(v) \leq f(w)`. + + If `f(v)=f(w)`, then two opposite edges are created. + + EXAMPLES:: + + sage: penta = Polyhedron([[0,0],[1,0],[0,1],[1,2],[3,2]]) + sage: G = penta.vertex_digraph(vector([1,1])); G + Digraph on 5 vertices + sage: G.sinks() + [A vertex at (3, 2)] + + sage: A = matrix(ZZ, [[1], [-1]]) + sage: f = linear_transformation(A) + sage: G = penta.vertex_digraph(f) ; G + Digraph on 5 vertices + sage: G.is_directed_acyclic() + False + + .. SEEALSO:: + + :meth:`vertex_graph` + """ + from sage.modules.vector_space_morphism import VectorSpaceMorphism + if isinstance(f, VectorSpaceMorphism): + if f.codomain().dimension() == 1: + orientation_check = lambda v: f(v) >= 0 + else: + raise TypeError('the linear map f must have ' + 'one-dimensional codomain') + else: + try: + if f.is_vector(): + orientation_check = lambda v: v.dot_product(f) >= 0 + else: + raise TypeError('f must be a linear map or a vector') + except AttributeError: + raise TypeError('f must be a linear map or a vector') + if not increasing: + f = -f + from sage.graphs.digraph import DiGraph + dg = DiGraph() + for j in range(self.n_vertices()): + vj = self.Vrepresentation(j) + for vi in vj.neighbors(): + if orientation_check(vj.vector() - vi.vector()): + dg.add_edge(vi, vj) + return dg + + def face_lattice(self): + """ + Return the face-lattice poset. + + OUTPUT: + + A :class:`~sage.combinat.posets.posets.FinitePoset`. Elements + are given as + :class:`~sage.geometry.polyhedron.face.PolyhedronFace`. + + In the case of a full-dimensional polytope, the faces are + pairs (vertices, inequalities) of the spanning vertices and + corresponding saturated inequalities. In general, a face is + defined by a pair (V-rep. objects, H-rep. objects). The + V-representation objects span the face, and the corresponding + H-representation objects are those inequalities and equations + that are saturated on the face. + + The bottom-most element of the face lattice is the "empty + face". It contains no V-representation object. All + H-representation objects are incident. + + The top-most element is the "full face". It is spanned by all + V-representation objects. The incident H-representation + objects are all equations and no inequalities. + + In the case of a full-dimensional polytope, the "empty face" + and the "full face" are the empty set (no vertices, all + inequalities) and the full polytope (all vertices, no + inequalities), respectively. + + ALGORITHM: + + See :mod:`sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator`. + + .. NOTE:: + + The face lattice is not cached, as long as this creates a memory leak, see :trac:`28982`. + + EXAMPLES:: + + sage: square = polytopes.hypercube(2) + sage: fl = square.face_lattice();fl + Finite lattice containing 10 elements + sage: list(f.ambient_V_indices() for f in fl) + [(), (0,), (1,), (0, 1), (2,), (1, 2), (3,), (0, 3), (2, 3), (0, 1, 2, 3)] + sage: poset_element = fl[5] + sage: a_face = poset_element + sage: a_face + A 1-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: a_face.ambient_V_indices() + (1, 2) + sage: set(a_face.ambient_Vrepresentation()) == \ + ....: set([square.Vrepresentation(1), square.Vrepresentation(2)]) + True + sage: a_face.ambient_Vrepresentation() + (A vertex at (1, 1), A vertex at (-1, 1)) + sage: a_face.ambient_Hrepresentation() + (An inequality (0, -1) x + 1 >= 0,) + + A more complicated example:: + + sage: c5_10 = Polyhedron(vertices = [[i,i^2,i^3,i^4,i^5] for i in range(1,11)]) + sage: c5_10_fl = c5_10.face_lattice() + sage: [len(x) for x in c5_10_fl.level_sets()] + [1, 10, 45, 100, 105, 42, 1] + + Note that if the polyhedron contains lines then there is a + dimension gap between the empty face and the first non-empty + face in the face lattice:: + + sage: line = Polyhedron(vertices=[(0,)], lines=[(1,)]) + sage: [ fl.dim() for fl in line.face_lattice() ] + [-1, 1] + + TESTS:: + + sage: c5_20 = Polyhedron(vertices = [[i,i^2,i^3,i^4,i^5] + ....: for i in range(1,21)]) + sage: c5_20_fl = c5_20.face_lattice() # long time + sage: [len(x) for x in c5_20_fl.level_sets()] # long time + [1, 20, 190, 580, 680, 272, 1] + sage: polytopes.hypercube(2).face_lattice().plot() # optional - sage.plot + Graphics object consisting of 27 graphics primitives + sage: level_sets = polytopes.cross_polytope(2).face_lattice().level_sets() + sage: level_sets[0][0].ambient_V_indices(), level_sets[-1][0].ambient_V_indices() + ((), (0, 1, 2, 3)) + + Various degenerate polyhedra:: + + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[[0,0,0],[1,0,0],[0,1,0]]).face_lattice().level_sets()] + [[()], [(0,), (1,), (2,)], [(0, 1), (0, 2), (1, 2)], [(0, 1, 2)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(1,0,0),(0,1,0)], rays=[(0,0,1)]).face_lattice().level_sets()] + [[()], [(1,), (2,)], [(0, 1), (0, 2), (1, 2)], [(0, 1, 2)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(rays=[(1,0,0),(0,1,0)], vertices=[(0,0,1)]).face_lattice().level_sets()] + [[()], [(0,)], [(0, 1), (0, 2)], [(0, 1, 2)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(rays=[(1,0),(0,1)], vertices=[(0,0)]).face_lattice().level_sets()] + [[()], [(0,)], [(0, 1), (0, 2)], [(0, 1, 2)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(1,),(0,)]).face_lattice().level_sets()] + [[()], [(0,), (1,)], [(0, 1)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(1,0,0),(0,1,0)], lines=[(0,0,1)]).face_lattice().level_sets()] + [[()], [(0, 1), (0, 2)], [(0, 1, 2)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0,0)], vertices=[(0,0,1)]).face_lattice().level_sets()] + [[()], [(0, 1)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0),(0,1)], vertices=[(0,0)]).face_lattice().level_sets()] + [[()], [(0, 1, 2)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0)], rays=[(0,1)], vertices=[(0,0)]).face_lattice().level_sets()] + [[()], [(0, 1)], [(0, 1, 2)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(0,)], lines=[(1,)]).face_lattice().level_sets()] + [[()], [(0, 1)]] + sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0)], vertices=[(0,0)]).face_lattice().level_sets()] + [[()], [(0, 1)]] + + """ + from sage.combinat.posets.lattices import FiniteLatticePoset + return FiniteLatticePoset(self.hasse_diagram()) + + @cached_method + def hasse_diagram(self): + r""" + Return the Hasse diagram of the face lattice of ``self``. + + This is the Hasse diagram of the poset of the faces of ``self``. + + OUTPUT: a directed graph + + EXAMPLES:: + + sage: P = polytopes.regular_polygon(4).pyramid() # optional - sage.rings.number_field + sage: D = P.hasse_diagram(); D # optional - sage.rings.number_field + Digraph on 20 vertices + sage: D.degree_polynomial() # optional - sage.rings.number_field + x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3 + + Faces of an mutable polyhedron are not hashable. Hence those are not suitable as + vertices of the hasse diagram. Use the combinatorial polyhedron instead:: + + sage: P = polytopes.regular_polygon(4).pyramid() # optional - sage.rings.number_field + sage: parent = P.parent() # optional - sage.rings.number_field + sage: parent = parent.change_ring(QQ, backend='ppl') # optional - sage.rings.number_field + sage: Q = parent._element_constructor_(P, mutable=True) # optional - sage.rings.number_field + sage: Q.hasse_diagram() # optional - sage.rings.number_field + Traceback (most recent call last): + ... + TypeError: mutable polyhedra are unhashable + sage: C = Q.combinatorial_polyhedron() # optional - sage.rings.number_field + sage: D = C.hasse_diagram() # optional - sage.rings.number_field + sage: set(D.vertices()) == set(range(20)) # optional - sage.rings.number_field + True + sage: def index_to_combinatorial_face(n): + ....: return C.face_by_face_lattice_index(n) + sage: D.relabel(index_to_combinatorial_face, inplace=True) # optional - sage.rings.number_field + sage: D.vertices() # optional - sage.rings.number_field + [A -1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 0-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 1-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 2-dimensional face of a 3-dimensional combinatorial polyhedron, + A 3-dimensional face of a 3-dimensional combinatorial polyhedron] + sage: D.degree_polynomial() # optional - sage.rings.number_field + x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3 + """ + + from sage.geometry.polyhedron.face import combinatorial_face_to_polyhedral_face + C = self.combinatorial_polyhedron() + D = C.hasse_diagram() + + def index_to_polyhedron_face(n): + return combinatorial_face_to_polyhedral_face( + self, C.face_by_face_lattice_index(n)) + + return D.relabel(index_to_polyhedron_face, inplace=False, immutable=True) + + def flag_f_vector(self, *args): + r""" + Return the flag f-vector. + + For each `-1 < i_0 < \dots < i_n < d` the flag f-vector + counts the number of flags `F_0 \subset \dots \subset F_n` + with `F_j` of dimension `i_j` for each `0 \leq j \leq n`, + where `d` is the dimension of the polyhedron. + + INPUT: + + - ``args`` -- integers (optional); specify an entry of the + flag-f-vector; must be an increasing sequence of integers + + OUTPUT: + + - a dictionary, if no arguments were given + + - an Integer, if arguments were given + + EXAMPLES: + + Obtain the entire flag-f-vector:: + + sage: P = polytopes.twenty_four_cell() + sage: P.flag_f_vector() + {(-1,): 1, + (0,): 24, + (0, 1): 192, + (0, 1, 2): 576, + (0, 1, 2, 3): 1152, + (0, 1, 3): 576, + (0, 2): 288, + (0, 2, 3): 576, + (0, 3): 144, + (1,): 96, + (1, 2): 288, + (1, 2, 3): 576, + (1, 3): 288, + (2,): 96, + (2, 3): 192, + (3,): 24, + (4,): 1} + + Specify an entry:: + + sage: P.flag_f_vector(0,3) + 144 + sage: P.flag_f_vector(2) + 96 + + Leading ``-1`` and trailing entry of dimension are allowed:: + + sage: P.flag_f_vector(-1,0,3) + 144 + sage: P.flag_f_vector(-1,0,3,4) + 144 + + One can get the number of trivial faces:: + + sage: P.flag_f_vector(-1) + 1 + sage: P.flag_f_vector(4) + 1 + + Polyhedra with lines, have ``0`` entries accordingly:: + + sage: P = (Polyhedron(lines=[[1]]) * polytopes.cross_polytope(3)) + sage: P.flag_f_vector() + {(-1,): 1, + (0, 1): 0, + (0, 1, 2): 0, + (0, 1, 3): 0, + (0, 2): 0, + (0, 2, 3): 0, + (0, 3): 0, + (0,): 0, + (1, 2): 24, + (1, 2, 3): 48, + (1, 3): 24, + (1,): 6, + (2, 3): 24, + (2,): 12, + (3,): 8, + 4: 1} + + If the arguments are not stricly increasing or out of range, a key error is raised:: + + sage: P.flag_f_vector(-1,0,3,6) + Traceback (most recent call last): + ... + KeyError: (0, 3, 6) + sage: P.flag_f_vector(-1,3,0) + Traceback (most recent call last): + ... + KeyError: (3, 0) + """ + flag = self._flag_f_vector() + if len(args) == 0: + return flag + elif len(args) == 1: + return flag[(args[0],)] + else: + dim = self.dimension() + if args[0] == -1: + args = args[1:] + if args[-1] == dim: + args = args[:-1] + return flag[tuple(args)] + + @cached_method(do_pickle=True) + def _flag_f_vector(self): + r""" + Return the flag-f-vector. + + See :meth:`flag_f_vector`. + + TESTS:: + + sage: polytopes.hypercube(4)._flag_f_vector() + {(-1,): 1, + (0,): 16, + (0, 1): 64, + (0, 1, 2): 192, + (0, 1, 2, 3): 384, + (0, 1, 3): 192, + (0, 2): 96, + (0, 2, 3): 192, + (0, 3): 64, + (1,): 32, + (1, 2): 96, + (1, 2, 3): 192, + (1, 3): 96, + (2,): 24, + (2, 3): 48, + (3,): 8, + (4,): 1} + """ + return self.combinatorial_polyhedron()._flag_f_vector() + + @cached_method + def combinatorial_automorphism_group(self, vertex_graph_only=False): + """ + Computes the combinatorial automorphism group. + + If ``vertex_graph_only`` is ``True``, the automorphism group + of the vertex-edge graph of the polyhedron is returned. Otherwise + the automorphism group of the vertex-facet graph, which is + isomorphic to the automorphism group of the face lattice is returned. + + INPUT: + + - ``vertex_graph_only`` -- boolean (default: ``False``); whether + to return the automorphism group of the vertex edges graph or + of the lattice + + OUTPUT: + + A + :class:`PermutationGroup` + that is isomorphic to the combinatorial automorphism group is + returned. + + - if ``vertex_graph_only`` is ``True``: + The automorphism group of the vertex-edge graph of the polyhedron + + - if ``vertex_graph_only`` is ``False`` (default): + The automorphism group of the vertex-facet graph of the polyhedron, + see :meth:`vertex_facet_graph`. This group is isomorphic to the + automorphism group of the face lattice of the polyhedron. + + NOTE: + + Depending on ``vertex_graph_only``, this method returns groups + that are not necessarily isomorphic, see the examples below. + + .. SEEALSO:: + + :meth:`is_combinatorially_isomorphic`, + :meth:`graph`, + :meth:`vertex_facet_graph`. + + EXAMPLES:: + + sage: quadrangle = Polyhedron(vertices=[(0,0),(1,0),(0,1),(2,3)]) + sage: quadrangle.combinatorial_automorphism_group().is_isomorphic(groups.permutation.Dihedral(4)) + True + sage: quadrangle.restricted_automorphism_group() + Permutation Group with generators [()] + + Permutations of the vertex graph only exchange vertices with vertices:: + + sage: P = Polyhedron(vertices=[(1,0), (1,1)], rays=[(1,0)]) + sage: P.combinatorial_automorphism_group(vertex_graph_only=True) + Permutation Group with generators [(A vertex at (1,0),A vertex at (1,1))] + + This shows an example of two polytopes whose vertex-edge graphs are isomorphic, + but their face_lattices are not isomorphic:: + + sage: Q=Polyhedron([[-123984206864/2768850730773, -101701330976/922950243591, -64154618668/2768850730773, -2748446474675/2768850730773], + ....: [-11083969050/98314591817, -4717557075/98314591817, -32618537490/98314591817, -91960210208/98314591817], + ....: [-9690950/554883199, -73651220/554883199, 1823050/554883199, -549885101/554883199], [-5174928/72012097, 5436288/72012097, -37977984/72012097, 60721345/72012097], + ....: [-19184/902877, 26136/300959, -21472/902877, 899005/902877], [53511524/1167061933, 88410344/1167061933, 621795064/1167061933, 982203941/1167061933], + ....: [4674489456/83665171433, -4026061312/83665171433, 28596876672/83665171433, -78383796375/83665171433], [857794884940/98972360190089, -10910202223200/98972360190089, 2974263671400/98972360190089, -98320463346111/98972360190089]]) + sage: C = polytopes.cyclic_polytope(4,8) + sage: C.is_combinatorially_isomorphic(Q) + False + sage: C.combinatorial_automorphism_group(vertex_graph_only=True).is_isomorphic(Q.combinatorial_automorphism_group(vertex_graph_only=True)) + True + sage: C.combinatorial_automorphism_group(vertex_graph_only=False).is_isomorphic(Q.combinatorial_automorphism_group(vertex_graph_only=False)) + False + + The automorphism group of the face lattice is isomorphic to the combinatorial automorphism group:: + + sage: CG = C.hasse_diagram().automorphism_group() + sage: C.combinatorial_automorphism_group().is_isomorphic(CG) + True + sage: QG = Q.hasse_diagram().automorphism_group() + sage: Q.combinatorial_automorphism_group().is_isomorphic(QG) + True + + """ + if vertex_graph_only: + G = self.graph() + else: + G = self.vertex_facet_graph() + return G.automorphism_group(edge_labels=True) + + @cached_method + def restricted_automorphism_group(self, output="abstract"): + r""" + Return the restricted automorphism group. + + First, let the linear automorphism group be the subgroup of + the affine group `AGL(d,\RR) = GL(d,\RR) \ltimes \RR^d` + preserving the `d`-dimensional polyhedron. The affine group + acts in the usual way `\vec{x}\mapsto A\vec{x}+b` on the + ambient space. + + The restricted automorphism group is the subgroup of the linear + automorphism group generated by permutations of the generators + of the same type. That is, vertices can only be permuted with + vertices, ray generators with ray generators, and line + generators with line generators. + + For example, take the first quadrant + + .. MATH:: + + Q = \Big\{ (x,y) \Big| x\geq 0,\; y\geq0 \Big\} + \subset \QQ^2 + + Then the linear automorphism group is + + .. MATH:: + + \mathrm{Aut}(Q) = + \left\{ + \begin{pmatrix} + a & 0 \\ 0 & b + \end{pmatrix} + ,~ + \begin{pmatrix} + 0 & c \\ d & 0 + \end{pmatrix} + :~ + a, b, c, d \in \QQ_{>0} + \right\} + \subset + GL(2,\QQ) + \subset + E(d) + + Note that there are no translations that map the quadrant `Q` + to itself, so the linear automorphism group is contained in + the general linear group (the subgroup of transformations + preserving the origin). The restricted automorphism group is + + .. MATH:: + + \mathrm{Aut}(Q) = + \left\{ + \begin{pmatrix} + 1 & 0 \\ 0 & 1 + \end{pmatrix} + ,~ + \begin{pmatrix} + 0 & 1 \\ 1 & 0 + \end{pmatrix} + \right\} + \simeq \ZZ_2 + + INPUT: + + - ``output`` -- how the group should be represented: + + - ``"abstract"`` (default) -- return an abstract permutation + group without further meaning. + + - ``"permutation"`` -- return a permutation group on the + indices of the polyhedron generators. For example, the + permutation ``(0,1)`` would correspond to swapping + ``self.Vrepresentation(0)`` and ``self.Vrepresentation(1)``. + + - ``"matrix"`` -- return a matrix group representing affine + transformations. When acting on affine vectors, you should + append a `1` to every vector. If the polyhedron is not full + dimensional, the returned matrices act as the identity on + the orthogonal complement of the affine space spanned by + the polyhedron. + + - ``"matrixlist"`` -- like ``matrix``, but return the list of + elements of the matrix group. Useful for fields without a + good implementation of matrix groups or to avoid the + overhead of creating the group. + + OUTPUT: + + - For ``output="abstract"`` and ``output="permutation"``: + a :class:`PermutationGroup`. + + - For ``output="matrix"``: a :class:`MatrixGroup`. + + - For ``output="matrixlist"``: a list of matrices. + + REFERENCES: + + - [BSS2009]_ + + EXAMPLES: + + A cross-polytope example:: + + sage: P = polytopes.cross_polytope(3) + sage: P.restricted_automorphism_group() == PermutationGroup([[(3,4)], [(2,3),(4,5)],[(2,5)],[(1,2),(5,6)],[(1,6)]]) + True + sage: P.restricted_automorphism_group(output="permutation") == PermutationGroup([[(2,3)],[(1,2),(3,4)],[(1,4)],[(0,1),(4,5)],[(0,5)]]) + True + sage: mgens = [[[1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,1]], [[1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]], [[0,1,0,0],[1,0,0,0],[0,0,1,0],[0,0,0,1]]] + + We test groups for equality in a fool-proof way; they can have different generators, etc:: + + sage: poly_g = P.restricted_automorphism_group(output="matrix") + sage: matrix_g = MatrixGroup([matrix(QQ,t) for t in mgens]) + sage: all(t.matrix() in poly_g for t in matrix_g.gens()) + True + sage: all(t.matrix() in matrix_g for t in poly_g.gens()) + True + + 24-cell example:: + + sage: P24 = polytopes.twenty_four_cell() + sage: AutP24 = P24.restricted_automorphism_group() + sage: PermutationGroup([ + ....: '(1,20,2,24,5,23)(3,18,10,19,4,14)(6,21,11,22,7,15)(8,12,16,17,13,9)', + ....: '(1,21,8,24,4,17)(2,11,6,15,9,13)(3,20)(5,22)(10,16,12,23,14,19)' + ....: ]).is_isomorphic(AutP24) + True + sage: AutP24.order() + 1152 + + Here is the quadrant example mentioned in the beginning:: + + sage: P = Polyhedron(rays=[(1,0),(0,1)]) + sage: P.Vrepresentation() + (A vertex at (0, 0), A ray in the direction (0, 1), A ray in the direction (1, 0)) + sage: P.restricted_automorphism_group(output="permutation") + Permutation Group with generators [(1,2)] + + Also, the polyhedron need not be full-dimensional:: + + sage: P = Polyhedron(vertices=[(1,2,3,4,5),(7,8,9,10,11)]) + sage: P.restricted_automorphism_group() + Permutation Group with generators [(1,2)] + sage: G = P.restricted_automorphism_group(output="matrixlist") + sage: G + ( + [1 0 0 0 0 0] [ -87/55 -82/55 -2/5 38/55 98/55 12/11] + [0 1 0 0 0 0] [-142/55 -27/55 -2/5 38/55 98/55 12/11] + [0 0 1 0 0 0] [-142/55 -82/55 3/5 38/55 98/55 12/11] + [0 0 0 1 0 0] [-142/55 -82/55 -2/5 93/55 98/55 12/11] + [0 0 0 0 1 0] [-142/55 -82/55 -2/5 38/55 153/55 12/11] + [0 0 0 0 0 1], [ 0 0 0 0 0 1] + ) + sage: g = AffineGroup(5, QQ)(G[1]) + sage: g + [ -87/55 -82/55 -2/5 38/55 98/55] [12/11] + [-142/55 -27/55 -2/5 38/55 98/55] [12/11] + x |-> [-142/55 -82/55 3/5 38/55 98/55] x + [12/11] + [-142/55 -82/55 -2/5 93/55 98/55] [12/11] + [-142/55 -82/55 -2/5 38/55 153/55] [12/11] + sage: g^2 + [1 0 0 0 0] [0] + [0 1 0 0 0] [0] + x |-> [0 0 1 0 0] x + [0] + [0 0 0 1 0] [0] + [0 0 0 0 1] [0] + sage: g(list(P.vertices()[0])) + (7, 8, 9, 10, 11) + sage: g(list(P.vertices()[1])) + (1, 2, 3, 4, 5) + + Affine transformations do not change the restricted automorphism + group. For example, any non-degenerate triangle has the + dihedral group with 6 elements, `D_6`, as its automorphism + group:: + + sage: initial_points = [vector([1,0]), vector([0,1]), vector([-2,-1])] + sage: points = initial_points + sage: Polyhedron(vertices=points).restricted_automorphism_group() + Permutation Group with generators [(2,3), (1,2)] + sage: points = [pt - initial_points[0] for pt in initial_points] + sage: Polyhedron(vertices=points).restricted_automorphism_group() + Permutation Group with generators [(2,3), (1,2)] + sage: points = [pt - initial_points[1] for pt in initial_points] + sage: Polyhedron(vertices=points).restricted_automorphism_group() + Permutation Group with generators [(2,3), (1,2)] + sage: points = [pt - 2*initial_points[1] for pt in initial_points] + sage: Polyhedron(vertices=points).restricted_automorphism_group() + Permutation Group with generators [(2,3), (1,2)] + + The ``output="matrixlist"`` can be used over fields without a + complete implementation of matrix groups:: + + sage: P = polytopes.dodecahedron(); P + A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^3 defined as the convex hull of 20 vertices + sage: G = P.restricted_automorphism_group(output="matrixlist") + sage: len(G) + 120 + + Floating-point computations are supported with a simple fuzzy + zero implementation:: + + sage: P = Polyhedron(vertices=[(1/3,0,0,1),(0,1/4,0,1),(0,0,1/5,1)], base_ring=RDF) + sage: P.restricted_automorphism_group() + Permutation Group with generators [(2,3), (1,2)] + sage: len(P.restricted_automorphism_group(output="matrixlist")) + 6 + + TESTS:: + + sage: P = Polyhedron(vertices=[(1,0), (1,1)], rays=[(1,0)]) + sage: P.restricted_automorphism_group(output="permutation") + Permutation Group with generators [(1,2)] + sage: P.restricted_automorphism_group(output="matrix") + Matrix group over Rational Field with 1 generators ( + [ 1 0 0] + [ 0 -1 1] + [ 0 0 1] + ) + sage: P.restricted_automorphism_group(output="foobar") + Traceback (most recent call last): + ... + ValueError: unknown output 'foobar', valid values are ('abstract', 'permutation', 'matrix', 'matrixlist') + + Check that :trac:`28828` is fixed:: + + sage: P.restricted_automorphism_group(output="matrixlist")[0].is_immutable() + True + """ + # The algorithm works as follows: + # + # Let V be the matrix where every column is a homogeneous + # coordinate of a V-representation object (vertex, ray, line). + # Let us assume that V has full rank, that the polyhedron is + # full dimensional. + # + # Let Q = V Vt and C = Vt Q^-1 V. The rows and columns of C + # can be thought of as being indexed by the V-rep objects of the + # polytope. + # + # It turns out that we can identify the restricted automorphism + # group with the automorphism group of the edge-colored graph + # on the V-rep objects with colors determined by the symmetric + # matrix C. + # + # An automorphism of this graph is equivalent to a permutation + # matrix P such that C = Pt C P. If we now define + # A = V P Vt Q^-1, then one can check that V P = A V. + # In other words: permuting the generators is the same as + # applying the affine transformation A on the generators. + # + # If the given polyhedron is not fully-dimensional, + # then Q will be not invertible. In this case, we use a + # pseudoinverse Q+ instead of Q^-1. The formula for A acting on + # the space spanned by V then simplifies to A = V P V+ where V+ + # denotes the pseudoinverse of V, which also equals V+ = Vt Q+. + # + # If we are asked to return the (group of) transformation + # matrices to the user, we also require that those + # transformations act as the identity on the orthogonal + # complement of the space spanned by V. This complement is the + # space spanned by the columns of W = 1 - V V+. One can check + # that B = (V P V+) + W is the correct matrix: it acts the same + # as A on V and it satisfies B W = W. + + outputs = ("abstract", "permutation", "matrix", "matrixlist") + if output not in outputs: + raise ValueError("unknown output {!r}, valid values are {}".format(output, outputs)) + + # For backwards compatibility, we treat "abstract" as + # "permutation", but where we add 1 to the indices of the + # permutations. + index0 = 0 + if output == "abstract": + index0 = 1 + output = "permutation" + + if self.base_ring().is_exact(): + def rational_approximation(c): + return c + else: + c_list = [] + + def rational_approximation(c): + # Implementation detail: Return unique integer if two + # c-values are the same up to machine precision. But + # you can think of it as a uniquely-chosen rational + # approximation. + for i, x in enumerate(c_list): + if self._is_zero(x - c): + return i + c_list.append(c) + return len(c_list) - 1 + + if self.is_compact(): + def edge_label(i, j, c_ij): + return c_ij + else: + # In the non-compact case, we also label the edges by the + # type of the V-representation object. This ensures that + # vertices, rays, and lines are only permuted amongst + # themselves. + def edge_label(i, j, c_ij): + return (self.Vrepresentation(i).type(), c_ij, self.Vrepresentation(j).type()) + + # Homogeneous coordinates for the V-representation objects. + # Mathematically, V is a matrix. For efficiency however, we + # represent it as a list of column vectors. + V = [v.homogeneous_vector() for v in self.Vrepresentation()] + + # Pseudoinverse of V Vt + Qplus = sum(v.column() * v.row() for v in V).pseudoinverse() + + # Construct the graph. + from sage.graphs.graph import Graph + G = Graph() + for i in range(len(V)): + for j in range(i+1, len(V)): + c_ij = rational_approximation(V[i] * Qplus * V[j]) + G.add_edge(index0+i, index0+j, edge_label(i, j, c_ij)) + + permgroup = G.automorphism_group(edge_labels=True) + if output == "permutation": + return permgroup + elif output == "matrix": + permgroup = permgroup.gens() + + # Compute V+ = Vt Q+ as list of row vectors + from sage.matrix.constructor import matrix + Vplus = list(matrix(V) * Qplus) # matrix(V) is Vt + + # Compute W = 1 - V V+ + W = 1 - sum(V[i].column() * Vplus[i].row() for i in range(len(V))) + + # Convert the permutation group to a matrix group. + # If P is a permutation, then we return the matrix + # B = (V P V+) + W. + # + # If output == "matrix", we loop over the generators of the group. + # Otherwise, we loop over all elements. + matrices = [] + for perm in permgroup: + A = sum(V[perm(i)].column() * Vplus[i].row() for i in range(len(V))) + matrices.append(A + W) + + for mat in matrices: + mat.set_immutable() + + if output == "matrixlist": + return tuple(matrices) + else: + from sage.groups.matrix_gps.finitely_generated import MatrixGroup + return MatrixGroup(matrices) + + def is_combinatorially_isomorphic(self, other, algorithm='bipartite_graph'): + r""" + Return whether the polyhedron is combinatorially isomorphic to another polyhedron. + + We only consider bounded polyhedra. By definition, they are + combinatorially isomorphic if their face lattices are isomorphic. + + INPUT: + + - ``other`` -- a polyhedron object + - ``algorithm`` (default = ``bipartite_graph``) -- the algorithm to use. + The other possible value is ``face_lattice``. + + OUTPUT: + + - ``True`` if the two polyhedra are combinatorially isomorphic + - ``False`` otherwise + + .. SEEALSO:: + + :meth:`combinatorial_automorphism_group`, + :meth:`vertex_facet_graph`. + + REFERENCES: + + For the equivalence of the two algorithms see [KK1995]_, p. 877-878 + + EXAMPLES: + + The square is combinatorially isomorphic to the 2-dimensional cube:: + + sage: polytopes.hypercube(2).is_combinatorially_isomorphic(polytopes.regular_polygon(4)) + True + + All the faces of the 3-dimensional permutahedron are either + combinatorially isomorphic to a square or a hexagon:: + + sage: H = polytopes.regular_polygon(6) # optional - sage.rings.number_field + sage: S = polytopes.hypercube(2) + sage: P = polytopes.permutahedron(4) + sage: all(F.as_polyhedron().is_combinatorially_isomorphic(S) # optional - sage.rings.number_field + ....: or F.as_polyhedron().is_combinatorially_isomorphic(H) + ....: for F in P.faces(2)) + True + + Checking that a regular simplex intersected with its reflection + through the origin is combinatorially isomorphic to the intersection + of a cube with a hyperplane perpendicular to its long diagonal:: + + sage: def simplex_intersection(k): + ....: S1 = Polyhedron([vector(v)-vector(polytopes.simplex(k).center()) for v in polytopes.simplex(k).vertices_list()]) + ....: S2 = Polyhedron([-vector(v) for v in S1.vertices_list()]) + ....: return S1.intersection(S2) + sage: def cube_intersection(k): + ....: C = polytopes.hypercube(k+1) + ....: H = Polyhedron(eqns=[[0]+[1 for i in range(k+1)]]) + ....: return C.intersection(H) + sage: [simplex_intersection(k).is_combinatorially_isomorphic(cube_intersection(k)) for k in range(2,5)] + [True, True, True] + sage: simplex_intersection(2).is_combinatorially_isomorphic(polytopes.regular_polygon(6)) # optional - sage.rings.number_field + True + sage: simplex_intersection(3).is_combinatorially_isomorphic(polytopes.octahedron()) + True + + Two polytopes with the same `f`-vector, but different combinatorial types:: + + sage: P = Polyhedron([[-605520/1525633, -605520/1525633, -1261500/1525633, -52200/1525633, 11833/1525633],\ + [-720/1769, -600/1769, 1500/1769, 0, -31/1769], [-216/749, 240/749, -240/749, -432/749, 461/749], \ + [-50/181, 50/181, 60/181, -100/181, -119/181], [-32/51, -16/51, -4/51, 12/17, 1/17],\ + [1, 0, 0, 0, 0], [16/129, 128/129, 0, 0, 1/129], [64/267, -128/267, 24/89, -128/267, 57/89],\ + [1200/3953, -1200/3953, -1440/3953, -360/3953, -3247/3953], [1512/5597, 1512/5597, 588/5597, 4704/5597, 2069/5597]]) + sage: C = polytopes.cyclic_polytope(5,10) + sage: C.f_vector() == P.f_vector(); C.f_vector() + True + (1, 10, 45, 100, 105, 42, 1) + sage: C.is_combinatorially_isomorphic(P) + False + + sage: S = polytopes.simplex(3) + sage: S = S.face_truncation(S.faces(0)[3]) + sage: S = S.face_truncation(S.faces(0)[4]) + sage: S = S.face_truncation(S.faces(0)[5]) + sage: T = polytopes.simplex(3) + sage: T = T.face_truncation(T.faces(0)[3]) + sage: T = T.face_truncation(T.faces(0)[4]) + sage: T = T.face_truncation(T.faces(0)[4]) + sage: T.is_combinatorially_isomorphic(S) + False + sage: T.f_vector(), S.f_vector() + ((1, 10, 15, 7, 1), (1, 10, 15, 7, 1)) + + sage: C = polytopes.hypercube(5) + sage: C.is_combinatorially_isomorphic(C) + True + sage: C.is_combinatorially_isomorphic(C, algorithm='magic') + Traceback (most recent call last): + ... + AssertionError: `algorithm` must be 'bipartite graph' or 'face_lattice' + + sage: G = Graph() + sage: C.is_combinatorially_isomorphic(G) + Traceback (most recent call last): + ... + AssertionError: input `other` must be a polyhedron + + sage: H = Polyhedron(eqns=[[0,1,1,1,1]]); H + A 3-dimensional polyhedron in QQ^4 defined as the convex hull of 1 vertex and 3 lines + sage: C.is_combinatorially_isomorphic(H) + Traceback (most recent call last): + ... + AssertionError: polyhedron `other` must be bounded + + """ + assert isinstance(other, Polyhedron_base4), "input `other` must be a polyhedron" + assert self.is_compact(), "polyhedron `self` must be bounded" + assert other.is_compact(), "polyhedron `other` must be bounded" + assert algorithm in ['bipartite_graph', 'face_lattice'], "`algorithm` must be 'bipartite graph' or 'face_lattice'" + + # For speed, we check if the polyhedra have the same number of facets and vertices. + # This is faster than building the bipartite graphs first and + # then check that they won't be isomorphic. + if self.n_vertices() != other.n_vertices() or self.n_facets() != other.n_facets(): + return False + + if algorithm == 'bipartite_graph': + G_self = self.vertex_facet_graph(False) + G_other = other.vertex_facet_graph(False) + + return G_self.is_isomorphic(G_other) + else: + return self.face_lattice().is_isomorphic(other.face_lattice()) + + def _test_is_combinatorially_isomorphic(self, tester=None, **options): + """ + Run tests on the method :meth:`.is_combinatorially_isomorphic`. + + TESTS:: + + sage: polytopes.cross_polytope(3)._test_is_combinatorially_isomorphic() + """ + if tester is None: + tester = self._tester(**options) + + if not self.is_compact(): + with tester.assertRaises(AssertionError): + self.is_combinatorially_isomorphic(self) + return + + if self.n_vertices() > 200 or self.n_facets() > 200: + # Avoid very long doctests. + return + + try: + import sage.graphs.graph + except ImportError: + return + + from sage.rings.integer_ring import ZZ + tester.assertTrue(self.is_combinatorially_isomorphic(ZZ(4)*self)) + if self.n_vertices(): + tester.assertTrue(self.is_combinatorially_isomorphic(self + self.center())) + + if self.n_vertices() < 20 and self.n_facets() < 20 and self.is_immutable(): + tester.assertTrue(self.is_combinatorially_isomorphic(ZZ(4)*self, algorithm='face_lattice')) + if self.n_vertices(): + tester.assertTrue(self.is_combinatorially_isomorphic(self + self.center(), algorithm='face_lattice')) + + def is_self_dual(self): + r""" + Return whether the polytope is self-dual. + + A polytope is self-dual if its face lattice is isomorphic to the face + lattice of its dual polytope. + + EXAMPLES:: + + sage: polytopes.simplex().is_self_dual() + True + sage: polytopes.twenty_four_cell().is_self_dual() + True + sage: polytopes.cube().is_self_dual() + False + sage: polytopes.hypersimplex(5,2).is_self_dual() + False + sage: P = Polyhedron(vertices=[[1/2, 1/3]], rays=[[1, 1]]).is_self_dual() + Traceback (most recent call last): + ... + ValueError: polyhedron has to be compact + + """ + if not self.is_compact(): + raise ValueError("polyhedron has to be compact") + + n = self.n_vertices() + m = self.n_facets() + if n != m: + return False + + G1 = self.vertex_facet_graph() + G2 = G1.reverse() + return G1.is_isomorphic(G2) diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd index 2d88b7c7496..94de99c4036 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd @@ -1,5 +1,4 @@ cimport cython -from memory_allocator cimport MemoryAllocator from sage.structure.sage_object cimport SageObject from .face_iterator cimport FaceIterator, CombinatorialFace from .list_of_faces cimport ListOfFaces @@ -57,11 +56,6 @@ cdef class CombinatorialPolyhedron(SageObject): cpdef CombinatorialPolyhedron dual(self) cpdef CombinatorialPolyhedron pyramid(self, new_vertex=*, new_facet=*) - # Space for edges, ridges, etc. is allocated with ``MemoryAllocators``. - # Upon success they are copied to ``_mem_tuple``. - # Thus deallocation (at the correct time) is taken care of. - cdef tuple _mem_tuple - cdef FaceIterator _face_iter(self, bint dual, int dimension) cdef int _compute_f_vector(self, size_t num_threads, size_t parallelization_depth) except -1 @@ -75,8 +69,9 @@ cdef class CombinatorialPolyhedron(SageObject): cdef size_t _compute_edges_or_ridges_with_iterator( self, FaceIterator face_iter, const bint do_atom_rep, const bint do_f_vector, size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt, - size_t* f_vector, MemoryAllocator mem) except -1 + size_t* f_vector) except -1 cdef int _compute_face_lattice_incidences(self) except -1 - cdef inline int _set_edge(self, size_t a, size_t b, size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt, MemoryAllocator mem) except -1 + cdef inline int _set_edge(self, size_t a, size_t b, size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt) except -1 + cdef inline void _free_edges(self, size_t ***edges_pt, size_t counter) cdef inline size_t _get_edge(self, size_t **edges, size_t edge_number, size_t vertex) except -1 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index ff697423d46..6e795febc0e 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -84,6 +84,9 @@ AUTHOR: # **************************************************************************** import numbers +from memory_allocator cimport MemoryAllocator +from cysignals.memory cimport check_malloc, check_allocarray, check_reallocarray, check_calloc, sig_free + from sage.rings.integer import Integer from sage.graphs.graph import Graph from sage.geometry.polyhedron.base import Polyhedron_base @@ -103,12 +106,14 @@ from sage.rings.integer cimport smallInteger from cysignals.signals cimport sig_check, sig_block, sig_unblock from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense -from .face_data_structure cimport face_len_atoms, face_init +from .face_data_structure cimport face_len_atoms, face_init, face_free from .face_iterator cimport iter_t, parallel_f_vector + cdef extern from "Python.h": int unlikely(int) nogil # Defined by Cython + cdef class CombinatorialPolyhedron(SageObject): r""" The class of the Combinatorial Type of a Polyhedron, a Polytope. @@ -313,6 +318,45 @@ cdef class CombinatorialPolyhedron(SageObject): sage: CombinatorialPolyhedron(LatticePolytope([], lattice=ToricLattice(3))) A -1-dimensional combinatorial polyhedron with 0 facets """ + def __cinit__(self): + r""" + TESTS: + + Not initializing the class, does not give segmentation fault:: + + sage: from sage.geometry.polyhedron.combinatorial_polyhedron.base import CombinatorialPolyhedron + sage: C = CombinatorialPolyhedron.__new__(CombinatorialPolyhedron) + sage: C.f_vector() + Traceback (most recent call last): + ... + ValueError: the combinatorial polyhedron was not initialized + sage: C.face_lattice() + Traceback (most recent call last): + ... + ValueError: the combinatorial polyhedron was not initialized + sage: C.face_iter() + Traceback (most recent call last): + ... + ValueError: the combinatorial polyhedron was not initialized + """ + # Note that all values are set to zero at the time ``__cinit__`` is called: + # https://cython.readthedocs.io/en/latest/src/userguide/special_methods.html#initialisation-methods + # In particular, ``__dealloc__`` will not do harm in this case. + + self._dimension = -2 # a "NULL" value + self._equations = () + self._all_faces = None + self._n_facets = -1 + + # ``_length_edges_list`` should not be touched in an instance + # of :class:`CombinatorialPolyhedron`. This number can be altered, + # but should probably be a power of `2` (for memory usage). + # ``_length_edges_list`` shouldn't be too small for speed and + # shouldn't be too large, as ``ridges``, ``edges`` and ``incidences`` + # each have a memory overhead of + # ``self._length_edges_list*2*sizeof(size_t *)``. + self._length_edges_list = 16348 + def __init__(self, data, Vrep=None, facets=None, unbounded=False, far_face=None, Vrepr=None): r""" Initialize :class:`CombinatorialPolyhedron`. @@ -329,29 +373,12 @@ cdef class CombinatorialPolyhedron(SageObject): sage: C = CombinatorialPolyhedron(Matrix([[1,0],[0,1]]), Vrepr=['zero', 'one']) doctest:...: DeprecationWarning: the keyword ``Vrepr`` is deprecated; use ``Vrep`` See https://trac.sagemath.org/28608 for details. + """ if Vrepr: from sage.misc.superseded import deprecation deprecation(28608, "the keyword ``Vrepr`` is deprecated; use ``Vrep``", 3) Vrep = Vrepr - self._dimension = -2 # a "NULL" value - self._edges = NULL - self._ridges = NULL - self._face_lattice_incidences = NULL - self._equations = () - self._all_faces = None - self._mem_tuple = () - cdef MemoryAllocator mem - - # ``_length_edges_list`` should not be touched in an instance - # of :class:`CombinatorialPolyhedron`. This number can be altered, - # but should probably be a power of `2` (for memory usage). - # ``_length_edges_list`` shouldn't be too small for speed and - # shouldn't be too large, as ``ridges``, ``edges`` and ``incidences`` - # each have a memory overhead of - # ``self._length_edges_list*2*sizeof(size_t *)``. - self._length_edges_list = 16348 - data_modified = None if isinstance(data, Polyhedron_base): @@ -397,13 +424,10 @@ cdef class CombinatorialPolyhedron(SageObject): + [[ZZ.one() for _ in range(len(facets))]]) else: # Input is different from ``Polyhedron`` and ``LatticePolytope``. - if not unbounded: - # bounded polyhedron - self._bounded = True - elif not far_face: + if unbounded and not far_face: raise ValueError("must specify far face for unbounded polyhedron") - else: - self._bounded = False + + self._bounded = not unbounded if Vrep: # store vertices names @@ -467,16 +491,14 @@ cdef class CombinatorialPolyhedron(SageObject): # Initialize far_face if unbounded. if not self._bounded: - mem = MemoryAllocator() - self._mem_tuple += (mem,) - face_init(self._far_face, self.bitrep_facets().n_atoms(), self._n_facets, mem) + face_init(self._far_face, self.bitrep_facets().n_atoms(), self._n_facets) Vrep_list_to_bit_rep(tuple(far_face), self._far_face) elif isinstance(data, numbers.Integral): # To construct a trivial polyhedron, equal to its affine hull, # one can give an Integer as Input. if data < -1: - ValueError("any polyhedron must have dimension at least -1") + raise ValueError("any polyhedron must have dimension at least -1") self._dimension = data if self._dimension == 0: @@ -503,9 +525,7 @@ cdef class CombinatorialPolyhedron(SageObject): # Initialize far_face if unbounded. if not self._bounded: - mem = MemoryAllocator() - self._mem_tuple += (mem,) - face_init(self._far_face, self.bitrep_facets().n_atoms(), self._n_facets, mem) + face_init(self._far_face, self.bitrep_facets().n_atoms(), self._n_facets) Vrep_list_to_bit_rep(tuple(far_face), self._far_face) else: @@ -547,9 +567,7 @@ cdef class CombinatorialPolyhedron(SageObject): # Initialize far_face if unbounded. if not self._bounded: - mem = MemoryAllocator() - self._mem_tuple += (mem,) - face_init(self._far_face, self.bitrep_facets().n_atoms(), self._n_facets, mem) + face_init(self._far_face, self.bitrep_facets().n_atoms(), self._n_facets) Vrep_list_to_bit_rep(tuple(far_face), self._far_face) if not self._bounded: @@ -557,6 +575,21 @@ cdef class CombinatorialPolyhedron(SageObject): else: self._far_face_tuple = () + def __dealloc__(self): + """ + TESTS:: + + sage: CombinatorialPolyhedron(-2) # indirect doctest + Traceback (most recent call last): + ... + ValueError: any polyhedron must have dimension at least -1 + """ + if not self._bounded: + face_free(self._far_face) + self._free_edges(&self._edges, self._n_edges) + self._free_edges(&self._ridges, self._n_ridges) + self._free_edges(&self._face_lattice_incidences, self._n_face_lattice_incidences) + def _repr_(self): r""" Return a description of the combinatorial polyhedron. @@ -763,7 +796,9 @@ cdef class CombinatorialPolyhedron(SageObject): """ if self._dimension == -2: # Dimension not computed yet. - if self.n_facets() == 0: + if self.n_facets() == -1: + raise ValueError("the combinatorial polyhedron was not initialized") + elif self.n_facets() == 0: # The dimension of a trivial polyhedron is assumed to contain # exactly one "vertex" and for each dimension one "line" as in # :class:`~sage.geometry.polyhedron.parent.Polyhedron_base` @@ -3268,90 +3303,94 @@ cdef class CombinatorialPolyhedron(SageObject): # In most bounded cases, one should not use the dual. dual = 0 - cdef MemoryAllocator mem = MemoryAllocator() cdef FaceIterator face_iter cdef int dim = self.dimension() - cdef size_t **edges = mem.malloc(sizeof(size_t**)) + cdef size_t **edges = NULL cdef size_t counter = 0 # the number of edges so far cdef size_t current_length = 1 # dynamically enlarge **edges cdef int output_dim_init = 1 if do_edges else dim - 2 cdef bint do_f_vector = False - cdef size_t* f_vector + cdef size_t* f_vector = NULL - if dim == 1 and (do_edges or self.n_facets() > 1): - # In this case there is an edge/ridge, but its not a proper face. - self._set_edge(0, 1, &edges, &counter, ¤t_length, mem) + try: + edges = check_malloc(sizeof(size_t*)) + if dim == 1 and (do_edges or self.n_facets() > 1): + # In this case there is an edge/ridge, but its not a proper face. + self._set_edge(0, 1, &edges, &counter, ¤t_length) - elif dim <= 1 or self.n_facets() == 0: - # There is no edge/ridge. - # Prevent an error when calling the face iterator. - pass + elif dim <= 1 or self.n_facets() == 0: + # There is no edge/ridge. + # Prevent an error when calling the face iterator. + pass - else: - if not self._f_vector and ((dual ^ do_edges)): - # While doing edges in non-dual mode or ridges in dual-mode - # one might as well do the f-vector. - do_f_vector = True - # Initialize ``f_vector``. - f_vector = mem.calloc((dim + 2), sizeof(size_t)) - f_vector[0] = 1 - f_vector[dim + 1] = 1 - face_iter = self._face_iter(dual, -2) else: - do_f_vector = False - face_iter = self._face_iter(dual, output_dim_init) - self._compute_edges_or_ridges_with_iterator(face_iter, (dual ^ do_edges), do_f_vector, - &edges, &counter, ¤t_length, - f_vector, mem) + if not self._f_vector and ((dual ^ do_edges)): + # While doing edges in non-dual mode or ridges in dual-mode + # one might as well do the f-vector. + do_f_vector = True + # Initialize ``f_vector``. + f_vector = check_calloc((dim + 2), sizeof(size_t)) + f_vector[0] = 1 + f_vector[dim + 1] = 1 + face_iter = self._face_iter(dual, -2) + else: + do_f_vector = False + face_iter = self._face_iter(dual, output_dim_init) + self._compute_edges_or_ridges_with_iterator(face_iter, (dual ^ do_edges), do_f_vector, + &edges, &counter, ¤t_length, f_vector) - # Success, copy the data to ``CombinatorialPolyhedron``. + # Success, copy the data to ``CombinatorialPolyhedron``. - # Copy ``f_vector``. - if do_f_vector: - if dual: - if dim > 1 and f_vector[1] < self.n_facets(): - # The input seemed to be wrong. - raise ValueError("not all facets are joins of vertices") + # Copy ``f_vector``. + if do_f_vector: + if dual: + if dim > 1 and f_vector[1] < self.n_facets(): + # The input seemed to be wrong. + raise ValueError("not all facets are joins of vertices") - # We have computed the ``f_vector`` of the dual. - # Reverse it: - self._f_vector = \ - tuple(smallInteger(f_vector[dim+1-i]) for i in range(dim+2)) + # We have computed the ``f_vector`` of the dual. + # Reverse it: + self._f_vector = \ + tuple(smallInteger(f_vector[dim+1-i]) for i in range(dim+2)) + else: + if self.is_bounded() and dim > 1 \ + and f_vector[1] < self.n_Vrepresentation() - len(self.far_face_tuple()): + # The input seemed to be wrong. + raise ValueError("not all vertices are intersections of facets") + + self._f_vector = tuple(smallInteger(f_vector[i]) for i in range(dim+2)) + + # Copy the edge or ridges. + if do_edges: + sig_block() + self._n_edges = counter + self._edges = edges + edges = NULL + counter = 0 + sig_unblock() else: - if self.is_bounded() and dim > 1 \ - and f_vector[1] < self.n_Vrepresentation() - len(self.far_face_tuple()): - # The input seemed to be wrong. - raise ValueError("not all vertices are intersections of facets") - - self._f_vector = tuple(smallInteger(f_vector[i]) for i in range(dim+2)) - - # Copy the edge or ridges. - if do_edges: - sig_block() - self._n_edges = counter - self._edges = edges - self._mem_tuple += (mem,) - sig_unblock() - else: - sig_block() - self._n_ridges = counter - self._ridges = edges - self._mem_tuple += (mem,) - sig_unblock() + sig_block() + self._n_ridges = counter + self._ridges = edges + edges = NULL + counter = 0 + sig_unblock() + finally: + self._free_edges(&edges, counter) + sig_free(f_vector) if do_edges and self._edges is NULL: raise ValueError('could not determine edges') elif not do_edges and self._ridges is NULL: raise ValueError('could not determine ridges') - cdef size_t _compute_edges_or_ridges_with_iterator( self, FaceIterator face_iter, const bint do_atom_rep, const bint do_f_vector, size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt, - size_t* f_vector, MemoryAllocator mem) except -1: + size_t* f_vector) except -1: r""" See :meth:`CombinatorialPolyhedron._compute_edges`. """ @@ -3386,7 +3425,7 @@ cdef class CombinatorialPolyhedron(SageObject): # Copy the information. a = face_iter.structure.coatom_rep[0] b = face_iter.structure.coatom_rep[1] - self._set_edge(a, b, edges_pt, counter_pt, current_length_pt, mem) + self._set_edge(a, b, edges_pt, counter_pt, current_length_pt) d = face_iter.next_dimension() cdef int _compute_face_lattice_incidences(self) except -1: @@ -3401,7 +3440,6 @@ cdef class CombinatorialPolyhedron(SageObject): cdef size_t len_incidence_list = self._length_edges_list cdef int dim = self.dimension() f_vector = self.f_vector() - cdef MemoryAllocator mem = MemoryAllocator() self._record_all_faces() # set up ``self._all_faces`` cdef PolyhedronFaceLattice all_faces = self._all_faces @@ -3425,7 +3463,7 @@ cdef class CombinatorialPolyhedron(SageObject): # For each incidence we determine its location in ``incidences`` # by ``incidences[one][two]``. - cdef size_t **incidences = mem.malloc(sizeof(size_t*)) + cdef size_t **incidences = NULL cdef size_t counter = 0 # the number of incidences so far cdef size_t current_length = 1 # dynamically enlarge **incidences @@ -3441,42 +3479,47 @@ cdef class CombinatorialPolyhedron(SageObject): dimension_one += 1 dimension_two = -1 - while (dimension_one < dim + 1): - already_seen = sum(f_vector[j] for j in range(dimension_two + 1)) - already_seen_next = already_seen + f_vector[dimension_two + 1] - - if all_faces.dual: - # If ``dual``, then ``all_faces`` has the dimensions reversed. - all_faces.incidence_init(dim - 1 - dimension_two, dim - 1 - dimension_one) - else: - all_faces.incidence_init(dimension_one, dimension_two) + try: + incidences = check_malloc(sizeof(size_t*)) + while (dimension_one < dim + 1): + already_seen = sum(f_vector[j] for j in range(dimension_two + 1)) + already_seen_next = already_seen + f_vector[dimension_two + 1] - # Get all incidences for fixed ``[dimension_one, dimension_two]``. - while all_faces.next_incidence(&second, &first): if all_faces.dual: - # If ``dual``, then ``second`` and ``first are flipped. - second += already_seen - first += already_seen_next - self._set_edge(second, first, &incidences, &counter, ¤t_length, mem) + # If ``dual``, then ``all_faces`` has the dimensions reversed. + all_faces.incidence_init(dim - 1 - dimension_two, dim - 1 - dimension_one) else: - second += already_seen_next - first += already_seen - self._set_edge(first, second, &incidences, &counter, ¤t_length, mem) + all_faces.incidence_init(dimension_one, dimension_two) + + # Get all incidences for fixed ``[dimension_one, dimension_two]``. + while all_faces.next_incidence(&second, &first): + if all_faces.dual: + # If ``dual``, then ``second`` and ``first are flipped. + second += already_seen + first += already_seen_next + self._set_edge(second, first, &incidences, &counter, ¤t_length) + else: + second += already_seen_next + first += already_seen + self._set_edge(first, second, &incidences, &counter, ¤t_length) - sig_check() + sig_check() - # Increase dimensions. - dimension_one += 1 - dimension_two = dimension_one - 1 + # Increase dimensions. + dimension_one += 1 + dimension_two = dimension_one - 1 - # Success, copy the data to ``CombinatorialPolyhedron``. - self._n_face_lattice_incidences = counter - sig_block() - self._mem_tuple += (mem,) - self._face_lattice_incidences = incidences - sig_unblock() + # Success, copy the data to ``CombinatorialPolyhedron``. + sig_block() + self._face_lattice_incidences = incidences + self._n_face_lattice_incidences = counter + incidences = NULL + counter = 0 + sig_unblock() + finally: + self._free_edges(&incidences, counter) - cdef inline int _set_edge(self, size_t a, size_t b, size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt, MemoryAllocator mem) except -1: + cdef inline int _set_edge(self, size_t a, size_t b, size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt) except -1: r""" Set an edge in an edge list. @@ -3489,7 +3532,6 @@ cdef class CombinatorialPolyhedron(SageObject): when ``current_length_pt[0] == 0`` - ``counter_pt`` -- pointer to the number of edges - ``current_length_pt`` -- pointer to the length of ``edges_pt[0]`` - - ``mem`` -- ``MemoryAllocator`` used for allocation """ cdef size_t len_edge_list = self._length_edges_list # Determine the position in ``edges``. @@ -3497,7 +3539,7 @@ cdef class CombinatorialPolyhedron(SageObject): cdef size_t two = counter_pt[0] % len_edge_list if unlikely(current_length_pt[0] == 0): - edges_pt[0] = mem.malloc(sizeof(size_t*)) + edges_pt[0] = check_malloc(sizeof(size_t*)) current_length_pt[0] = 1 # Enlarge ``edges`` if needed. @@ -3505,14 +3547,31 @@ cdef class CombinatorialPolyhedron(SageObject): if unlikely(one + 1 > current_length_pt[0]): # enlarge **edges current_length_pt[0] = 2*current_length_pt[0] - edges_pt[0] = mem.reallocarray(edges_pt[0], current_length_pt[0], sizeof(size_t*)) + edges_pt[0] = check_reallocarray(edges_pt[0], current_length_pt[0], sizeof(size_t*)) - edges_pt[0][one] = mem.allocarray(2 * len_edge_list, sizeof(size_t)) + edges_pt[0][one] = check_allocarray(2 * len_edge_list, sizeof(size_t)) edges_pt[0][one][2*two] = a edges_pt[0][one][2*two + 1] = b counter_pt[0] = counter_pt[0] + 1 + cdef inline void _free_edges(self, size_t ***edges_pt, size_t counter): + r""" + Free the memory allocated for the edges. + """ + if edges_pt[0] is NULL: + return + + cdef size_t len_edge_list = self._length_edges_list + # Determine the position in ``edges``. + cdef size_t one = counter // len_edge_list + cdef size_t i + + for i in range(one): + sig_free(edges_pt[0][i]) + + sig_free(edges_pt[0]) + cdef inline size_t _get_edge(self, size_t **edges, size_t edge_number, size_t vertex) except -1: r""" Get a vertex of an edge in an edge list. diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd index f3902b46a91..9193a5417a9 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd @@ -1,5 +1,4 @@ cimport cython -from memory_allocator cimport MemoryAllocator from sage.structure.sage_object cimport SageObject from .list_of_faces cimport ListOfFaces from .face_data_structure cimport face_t @@ -10,7 +9,6 @@ cdef class CombinatorialFace(SageObject): cdef readonly bint _dual # if 1, then iterate over dual Polyhedron cdef face_t face # the face in bit-rep - cdef MemoryAllocator _mem cdef size_t *atom_rep # a place where atom-representation of face will be stored cdef size_t _n_atom_rep cdef size_t *coatom_rep # a place where coatom-representation of face will be stored diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx index 2f300cebfc1..89f1987c5e7 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx @@ -64,6 +64,8 @@ AUTHOR: # https://www.gnu.org/licenses/ # **************************************************************************** +from cysignals.memory cimport check_allocarray, sig_free + from sage.misc.superseded import deprecated_function_alias import numbers @@ -72,13 +74,15 @@ from .conversions cimport bit_rep_to_Vrep_list from .base cimport CombinatorialPolyhedron from .face_iterator cimport FaceIterator_base from .polyhedron_face_lattice cimport PolyhedronFaceLattice -from .face_data_structure cimport face_len_atoms, face_init, face_copy, face_issubset +from .face_data_structure cimport face_len_atoms, face_init, face_free, face_copy, face_issubset from .face_list_data_structure cimport bit_rep_to_coatom_rep from .list_of_faces cimport face_as_combinatorial_polyhedron + cdef extern from "Python.h": int unlikely(int) nogil # Defined by Cython + cdef class CombinatorialFace(SageObject): r""" A class of the combinatorial type of a polyhedral face. @@ -140,7 +144,7 @@ cdef class CombinatorialFace(SageObject): sage: face.n_ambient_Hrepresentation() 11 """ - def __init__(self, data, dimension=None, index=None): + def __cinit__(self, data, dimension=None, index=None): r""" Initialize :class:`CombinatorialFace`. @@ -159,6 +163,10 @@ cdef class CombinatorialFace(SageObject): sage: TestSuite(sage.geometry.polyhedron.combinatorial_polyhedron.combinatorial_face.CombinatorialFace).run() """ + # Note that all values are set to zero at the time ``__cinit__`` is called: + # https://cython.readthedocs.io/en/latest/src/userguide/special_methods.html#initialisation-methods + # In particular, ``__dealloc__`` will not do harm in this case. + cdef FaceIterator_base it cdef PolyhedronFaceLattice all_faces @@ -168,14 +176,13 @@ cdef class CombinatorialFace(SageObject): # Copy data from FaceIterator. it = data self._dual = it.dual - self._mem = MemoryAllocator() self.atoms = it.atoms self.coatoms = it.coatoms if it.structure.face_status == 0: raise LookupError("face iterator not set to a face") - face_init(self.face, self.coatoms.n_atoms(), self.coatoms.n_coatoms(), self._mem) + face_init(self.face, self.coatoms.n_atoms(), self.coatoms.n_coatoms()) face_copy(self.face, it.structure.face) self._dimension = it.structure.current_dimension @@ -199,11 +206,10 @@ cdef class CombinatorialFace(SageObject): # Copy data from PolyhedronFaceLattice. self._dual = all_faces.dual - self._mem = MemoryAllocator() self.atoms = all_faces.atoms self.coatoms = all_faces.coatoms - face_init(self.face, self.coatoms.n_atoms(), self.coatoms.n_coatoms(), self._mem) + face_init(self.face, self.coatoms.n_atoms(), self.coatoms.n_coatoms()) face_copy(self.face, all_faces.faces[dimension+1].faces[index]) self._dimension = dimension @@ -230,12 +236,26 @@ cdef class CombinatorialFace(SageObject): for i in range(-1,self._ambient_dimension+1): self._hash_index += all_faces.f_vector[i+1] else: - raise NotImplementedError("data must be face iterator or a list of all faces") + raise ValueError("data must be face iterator or a list of all faces") if self._dual: # Reverse the hash index in dual mode to respect inclusion of faces. self._hash_index = -self._hash_index - 1 + def __dealloc__(self): + r""" + TESTS:: + + sage: from sage.geometry.polyhedron.combinatorial_polyhedron.combinatorial_face import CombinatorialFace + sage: CombinatorialFace(2) # indirect doctest + Traceback (most recent call last): + ... + ValueError: data must be face iterator or a list of all faces + """ + face_free(self.face) + sig_free(self.atom_rep) + sig_free(self.coatom_rep) + def _repr_(self): r""" Return a description of the combinatorial face. @@ -1085,7 +1105,7 @@ cdef class CombinatorialFace(SageObject): Return its length. """ if self.coatom_rep is NULL: - self.coatom_rep = self._mem.allocarray(self.coatoms.n_faces(), sizeof(size_t)) + self.coatom_rep = check_allocarray(self.coatoms.n_faces(), sizeof(size_t)) self._n_coatom_rep = bit_rep_to_coatom_rep(self.face, self.coatoms.data, self.coatom_rep) return self._n_coatom_rep @@ -1095,7 +1115,6 @@ cdef class CombinatorialFace(SageObject): Return its length. """ if self.atom_rep is NULL: - self.atom_rep = self._mem.allocarray(self.coatoms.n_atoms(), sizeof(size_t)) + self.atom_rep = check_allocarray(self.coatoms.n_atoms(), sizeof(size_t)) self._n_atom_rep = bit_rep_to_Vrep_list(self.face, self.atom_rep) return self._n_atom_rep - diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd index a81db2a1390..eea0e3b4da8 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd @@ -31,23 +31,30 @@ ctypedef fused algorithm_variant: # Face Initialization ############################################################################# -cdef inline bint face_init(face_t face, mp_bitcnt_t n_atoms, mp_bitcnt_t n_coatoms, MemoryAllocator mem) except -1: +cdef inline bint face_init(face_t face, mp_bitcnt_t n_atoms, mp_bitcnt_t n_coatoms) except -1: """ - Initialize and clear ``face`` using the memory allocator. + Initialize and clear ``face``. """ if n_coatoms == 0: # Special case for trivial polyhedra. n_coatoms += 1 if n_atoms == 0: n_atoms += 1 - bitset_init_with_allocator(face.atoms, n_atoms, mem) - bitset_init_with_allocator(face.coatoms, n_coatoms, mem) + bitset_init(face.atoms, n_atoms) + bitset_init(face.coatoms, n_coatoms) + +cdef inline void face_free(face_t face): + """ + Free ``face``. + """ + bitset_free(face.atoms) + bitset_free(face.coatoms) cdef inline bint face_check_alignment(face_t face): """ Return whether the data is correctly aligned. """ - return bitset_check_alignment(face.atoms) and bitset_check_alignment(face.coatoms) + return bitset_check_alignment(face.atoms) cdef inline void face_clear(face_t face): """ diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd index 63acf86021e..452b2c9cdc9 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd @@ -1,5 +1,4 @@ cimport cython -from memory_allocator cimport MemoryAllocator from sage.structure.sage_object cimport SageObject from .list_of_faces cimport ListOfFaces from .face_data_structure cimport face_t @@ -54,7 +53,6 @@ ctypedef iter_s iter_t[1] cdef class FaceIterator_base(SageObject): cdef iter_t structure cdef readonly bint dual # if 1, then iterate over dual Polyhedron - cdef MemoryAllocator _mem # some copies from ``CombinatorialPolyhedron`` cdef tuple _Vrep, _facet_names, _equations diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx index 8cdedf29ca6..ebdd559a45a 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx @@ -176,6 +176,10 @@ AUTHOR: # http://www.gnu.org/licenses/ #***************************************************************************** +from cython.parallel cimport prange, threadid +from cysignals.memory cimport check_allocarray, sig_free +from memory_allocator cimport MemoryAllocator + from sage.rings.integer cimport smallInteger from cysignals.signals cimport sig_check from .conversions cimport bit_rep_to_Vrep_list, Vrep_list_to_bit_rep @@ -185,11 +189,11 @@ from .base cimport CombinatorialPolyhedron from sage.geometry.polyhedron.face import combinatorial_face_to_polyhedral_face, PolyhedronFace from .face_list_data_structure cimport * -from cython.parallel cimport prange, threadid cdef extern from "Python.h": int unlikely(int) nogil # Defined by Cython + cdef class FaceIterator_base(SageObject): r""" A base class to iterate over all faces of a polyhedron. @@ -199,7 +203,7 @@ cdef class FaceIterator_base(SageObject): See :class:`FaceIterator`. """ - def __init__(self, CombinatorialPolyhedron C, bint dual, output_dimension=None): + def __cinit__(self, P, dual=None, output_dimension=None): r""" Initialize :class:`FaceIterator_base`. @@ -218,6 +222,29 @@ cdef class FaceIterator_base(SageObject): sage: TestSuite(sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator).run() """ + # Note that all values are set to zero at the time ``__cinit__`` is called: + # https://cython.readthedocs.io/en/latest/src/userguide/special_methods.html#initialisation-methods + # In particular, ``__dealloc__`` will not do harm in this case. + + cdef CombinatorialPolyhedron C + + # Working around that __cinit__ of base and derived class must be the same, + # as extension classes do not yet have __new__ in Cython 0.29. + if isinstance(P, CombinatorialPolyhedron): + C = P + else: + C = P.combinatorial_polyhedron() + if dual is None: + # Determine the (likely) faster way, to iterate through all faces. + if not P.is_compact() or P.n_facets() <= P.n_vertices(): + dual = False + else: + dual = True + + if output_dimension is not None and (output_dimension < 0 or output_dimension >= P.dim()): + # In those cases the output will be completely handled by :meth:`FaceIterator_geom.__next__`. + output_dimension = None + if dual and not C.is_bounded(): raise ValueError("cannot iterate over dual of unbounded Polyedron") cdef int i @@ -225,11 +252,9 @@ cdef class FaceIterator_base(SageObject): self.dual = dual self.structure.dual = dual - self.structure.face_status = 0 self.structure.dimension = C.dimension() self.structure.current_dimension = self.structure.dimension - 1 self.structure.highest_dimension = self.structure.dimension - 1 - self._mem = MemoryAllocator() # We will not yield the empty face. # If there are `n` lines, than there @@ -266,8 +291,8 @@ cdef class FaceIterator_base(SageObject): self._bounded = C.is_bounded() self._far_face[0] = C._far_face[0] - self.structure.atom_rep = self._mem.allocarray(self.coatoms.n_atoms(), sizeof(size_t)) - self.structure.coatom_rep = self._mem.allocarray(self.coatoms.n_faces(), sizeof(size_t)) + self.structure.atom_rep = check_allocarray(self.coatoms.n_atoms(), sizeof(size_t)) + self.structure.coatom_rep = check_allocarray(self.coatoms.n_faces(), sizeof(size_t)) if self.structure.dimension == 0 or self.coatoms.n_faces() == 0: # As we will only yield proper faces, @@ -279,26 +304,19 @@ cdef class FaceIterator_base(SageObject): # We may assume ``dimension > 0`` and ``n_faces > 0``. # Initialize ``new_faces``. - self.structure.new_faces = self._mem.allocarray((self.structure.dimension), sizeof(face_list_t)) - for i in range(self.structure.dimension-1): + self.structure.new_faces = check_calloc((self.structure.dimension), sizeof(face_list_t)) + for i in range(self.structure.dimension): face_list_init(self.structure.new_faces[i], self.coatoms.n_faces(), self.coatoms.n_atoms(), - self.coatoms.n_coatoms(), self._mem) - - # We start with the coatoms - face_list_shallow_init(self.structure.new_faces[self.structure.dimension-1], - self.coatoms.n_faces(), self.coatoms.n_atoms(), - self.coatoms.n_coatoms(), self._mem) - - - face_list_shallow_copy(self.structure.new_faces[self.structure.dimension-1], self.coatoms.data) + self.coatoms.n_coatoms()) + face_list_copy(self.structure.new_faces[self.structure.dimension-1], self.coatoms.data) # Initialize ``visited_all``. - self.structure.visited_all = self._mem.allocarray((self.structure.dimension), sizeof(face_list_t)) + self.structure.visited_all = check_calloc((self.structure.dimension), sizeof(face_list_t)) face_list_shallow_init(self.structure.visited_all[self.structure.dimension-1], self.coatoms.n_faces(), self.coatoms.n_atoms(), - self.coatoms.n_coatoms(), self._mem) + self.coatoms.n_coatoms()) self.structure.visited_all[self.structure.dimension-1].n_faces = 0 if not C.is_bounded(): @@ -313,7 +331,7 @@ cdef class FaceIterator_base(SageObject): add_face_shallow(self.structure.visited_all[self.structure.dimension-1], self._far_face) # Initialize ``first_time``. - self.structure.first_time = self._mem.allocarray(self.structure.dimension, sizeof(bint)) + self.structure.first_time = check_allocarray(self.structure.dimension, sizeof(bint)) self.structure.first_time[self.structure.dimension - 1] = True self.structure.yet_to_visit = self.coatoms.n_faces() @@ -329,6 +347,28 @@ cdef class FaceIterator_base(SageObject): else: self.structure.new_faces[self.structure.dimension -1].polyhedron_is_simple = False + def __dealloc__(self): + """ + TESTS:: + + sage: from sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator import FaceIterator_base + sage: FaceIterator_base(2) # indirect doctest + Traceback (most recent call last): + ... + AttributeError: 'sage.rings.integer.Integer' object has no attribute 'combinatorial_polyhedron' + """ + cdef int i + sig_free(self.structure.atom_rep) + sig_free(self.structure.coatom_rep) + sig_free(self.structure.first_time) + if self.structure.visited_all: + face_list_shallow_free(self.structure.visited_all[self.structure.dimension - 1]) + sig_free(self.structure.visited_all) + if self.structure.new_faces: + for i in range(self.structure.dimension): + face_list_free(self.structure.new_faces[i]) + sig_free(self.structure.new_faces) + def reset(self): r""" Reset the iterator. @@ -386,7 +426,7 @@ cdef class FaceIterator_base(SageObject): self.structure._index = 0 # ``only_subsets`` might have messed up the coatoms. - face_list_shallow_copy(self.structure.new_faces[self.structure.dimension-1], self.coatoms.data) + face_list_copy(self.structure.new_faces[self.structure.dimension-1], self.coatoms.data) def __next__(self): r""" @@ -1268,7 +1308,6 @@ cdef class FaceIterator_base(SageObject): raise ValueError("the face appears to be incorrect") - cdef class FaceIterator(FaceIterator_base): r""" A class to iterate over all combinatorial faces of a polyhedron. @@ -1573,6 +1612,7 @@ cdef class FaceIterator(FaceIterator_base): return face + cdef class FaceIterator_geom(FaceIterator_base): r""" A class to iterate over all geometric faces of a polyhedron. @@ -1750,21 +1790,8 @@ cdef class FaceIterator_geom(FaceIterator_base): sage: TestSuite(sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator.FaceIterator_geom).run() """ self._requested_dim = output_dimension - - if dual is None: - # Determine the (likely) faster way, to iterate through all faces. - if not P.is_compact() or P.n_facets() <= P.n_vertices(): - dual = False - else: - dual = True - self.P = P - - if output_dimension is not None and (output_dimension < 0 or output_dimension >= P.dim()): - # In those cases the output will be completely handled by :meth:`FaceIterator_geom.__next__`. - output_dimension = None - - FaceIterator_base.__init__(self, P.combinatorial_polyhedron(), dual, output_dimension) + # Base class only has __cinit__ and not __init__ self.reset() def reset(self): @@ -1883,6 +1910,7 @@ cdef class FaceIterator_geom(FaceIterator_base): """ return combinatorial_face_to_polyhedral_face(self.P, FaceIterator_base.current(self)) + # Nogil definitions of crucial functions. cdef inline int next_dimension(iter_t structure, size_t parallelization_depth=0) nogil except -1: diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd index 2f680590715..b43e50a6aae 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd @@ -17,6 +17,7 @@ cdef extern from "Python.h": from .face_data_structure cimport * from libc.string cimport memset from cysignals.signals cimport sig_check +from cysignals.memory cimport check_allocarray, check_calloc, sig_free cdef struct face_list_s: face_t* faces @@ -33,17 +34,17 @@ ctypedef face_list_s face_list_t[1] # Face List Initialization ############################################################################# -cdef inline int face_list_init(face_list_t faces, size_t n_faces, size_t n_atoms, size_t n_coatoms, MemoryAllocator mem) except -1: +cdef inline int face_list_init(face_list_t faces, size_t n_faces, size_t n_atoms, size_t n_coatoms) except -1: """ Sets the initial values for a list of faces with given number of faces and number of atoms. """ - face_list_shallow_init(faces, n_faces, n_atoms, n_coatoms, mem) + face_list_shallow_init(faces, n_faces, n_atoms, n_coatoms) cdef size_t i for i in range(n_faces): - face_init(faces.faces[i], n_atoms, n_coatoms, mem) + face_init(faces.faces[i], n_atoms, n_coatoms) -cdef inline int face_list_shallow_init(face_list_t faces, size_t n_faces, size_t n_atoms, size_t n_coatoms, MemoryAllocator mem) except -1: +cdef inline int face_list_shallow_init(face_list_t faces, size_t n_faces, size_t n_atoms, size_t n_coatoms) except -1: """ Initialize ``faces`` completely, but only set up memory for the pointers to the faces. """ @@ -51,10 +52,27 @@ cdef inline int face_list_shallow_init(face_list_t faces, size_t n_faces, size_t faces.total_n_faces = n_faces faces.n_atoms = n_atoms faces.n_coatoms = n_coatoms - faces.faces = mem.allocarray(n_faces, sizeof(face_t)) - faces.is_not_new_face = mem.allocarray(n_faces, sizeof(bint)) + faces.faces = check_calloc(n_faces, sizeof(face_t)) + faces.is_not_new_face = check_allocarray(n_faces, sizeof(bint)) faces.polyhedron_is_simple = False +cdef inline void face_list_free(face_list_t faces): + """ + Free faces. + """ + cdef size_t i + if faces.faces is not NULL: + for i in range(faces.total_n_faces): + face_free(faces.faces[i]) + face_list_shallow_free(faces) + +cdef inline void face_list_shallow_free(face_list_t faces): + """ + Free a shallow list of faces. + """ + sig_free(faces.faces) + sig_free(faces.is_not_new_face) + cdef inline int face_list_copy(face_list_t dst, face_list_t src) except -1: """ This is a deep copy. All the data for the faces is copied. @@ -86,7 +104,7 @@ cdef inline int face_list_shallow_copy(face_list_t dst, face_list_t src) except cdef size_t i for i in range(src.n_faces): - dst.faces[i] = src.faces[i] + dst.faces[i][0] = src.faces[i][0] cdef inline int add_face_shallow(face_list_t faces, face_t face) nogil except -1: """ @@ -120,8 +138,11 @@ cdef inline void face_list_delete_faces_by_array(face_list_t faces, bint *delete if not delete[i]: faces.faces[n_newfaces][0] = faces.faces[i][0] n_newfaces += 1 + else: + face_free(faces.faces[i]) faces.n_faces = n_newfaces + faces.total_n_faces = n_newfaces cdef inline void face_list_delete_faces_by_face(face_list_t faces, face_t face): r""" @@ -138,8 +159,11 @@ cdef inline void face_list_delete_faces_by_face(face_list_t faces, face_t face): if face_atom_in(face, i): faces.faces[n_newfaces][0] = faces.faces[i][0] n_newfaces += 1 + else: + face_free(faces.faces[i]) faces.n_faces = n_newfaces + faces.total_n_faces = n_newfaces ############################################################################# diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd index e8da24c3366..d16065979eb 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd @@ -1,11 +1,8 @@ cimport cython -from memory_allocator cimport MemoryAllocator from .face_list_data_structure cimport face_list_t, face_t @cython.final cdef class ListOfFaces: - cdef MemoryAllocator _mem - # ``data`` points to the raw data. # It will be of "type" ``uint64_t[n_faces][face_length]`` cdef face_list_t data diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx index a7794cb315c..69a659c0690 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx @@ -127,7 +127,7 @@ cdef class ListOfFaces: sage: facets.matrix().dimensions() (5, 13) """ - def __init__(self, size_t n_faces, size_t n_atoms, size_t n_coatoms): + def __cinit__(self, size_t n_faces, size_t n_atoms, size_t n_coatoms): r""" Initialize :class:`ListOfFaces`. @@ -137,8 +137,31 @@ cdef class ListOfFaces: sage: TestSuite(sage.geometry.polyhedron.combinatorial_polyhedron.list_of_faces.ListOfFaces).run() """ - self._mem = MemoryAllocator() - face_list_init(self.data, n_faces, n_atoms, n_coatoms, self._mem) + # Note that all values are set to zero at the time ``__cinit__`` is called: + # https://cython.readthedocs.io/en/latest/src/userguide/special_methods.html#initialisation-methods + # In particular, ``__dealloc__`` will not do harm in this case. + + face_list_init(self.data, n_faces, n_atoms, n_coatoms) + + def __dealloc__(self): + r""" + TESTS:: + + sage: from sage.geometry.polyhedron.combinatorial_polyhedron.list_of_faces import ListOfFaces + sage: ListOfFaces(-1, -1, -1) # indirect doctest + Traceback (most recent call last): + ... + OverflowError: can't convert negative value to size_t + + sage: from memory_allocator.test import TestMemoryAllocator + sage: t = TestMemoryAllocator() + sage: m = t.size_t_max() + sage: ListOfFaces(1, m, 1) + Traceback (most recent call last): + ... + MemoryError: failed to allocate ... + """ + face_list_free(self.data) def _test_alignment(self): r""" diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd index afd731ae9c6..4e7987b0d7a 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd @@ -1,5 +1,4 @@ cimport cython -from memory_allocator cimport MemoryAllocator from .list_of_faces cimport ListOfFaces from .face_data_structure cimport face_t from .face_list_data_structure cimport face_list_t @@ -7,7 +6,6 @@ from .combinatorial_face cimport CombinatorialFace @cython.final cdef class PolyhedronFaceLattice: - cdef MemoryAllocator _mem cdef int dimension # dimension of Polyhedron cdef readonly bint dual # if True, then List of all faces by dual Polyhedron cdef size_t *f_vector # a copy of the f-vector, is reversed if dual diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx index c47952d7527..2f8edd194de 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx @@ -110,25 +110,29 @@ cdef class PolyhedronFaceLattice: by intersecting with all coatoms. Then each intersection is looked up in the sorted level sets. """ - def __init__(self, CombinatorialPolyhedron C): + def __cinit__(self, CombinatorialPolyhedron C): r""" Initialize :class:`PolyhedronFaceLattice`. See :class:`PolyhedronFaceLattice`. - EXAMPLES:: + TESTS: - sage: P = polytopes.cube() - sage: C = CombinatorialPolyhedron(P) - sage: C._record_all_faces() # indirect doctests - sage: C.face_lattice() - Finite lattice containing 28 elements + Not initializing the class, does not give segmentation fault:: - sage: TestSuite(sage.geometry.polyhedron.combinatorial_polyhedron.polyhedron_face_lattice.PolyhedronFaceLattice).run() + sage: from sage.geometry.polyhedron.combinatorial_polyhedron.polyhedron_face_lattice import PolyhedronFaceLattice + sage: P = polytopes.cube() + sage: F = PolyhedronFaceLattice.__new__(PolyhedronFaceLattice, P.combinatorial_polyhedron()) + sage: F.get_face(2, 3) + A 2-dimensional face of a 3-dimensional combinatorial polyhedron """ + # Note that all values are set to zero at the time ``__cinit__`` is called: + # https://cython.readthedocs.io/en/latest/src/userguide/special_methods.html#initialisation-methods + # In particular, ``__dealloc__`` will not do harm in this case. + cdef int i cdef size_t j - self._mem = MemoryAllocator() + self.dimension = C.dimension() self.dual = False if C.bitrep_facets().n_faces() > C.bitrep_Vrep().n_faces(): @@ -143,7 +147,7 @@ cdef class PolyhedronFaceLattice: # copy f_vector for later use f_vector = C.f_vector() - self.f_vector = self._mem.allocarray(self.dimension + 2, sizeof(size_t)) + self.f_vector = check_allocarray(self.dimension + 2, sizeof(size_t)) if self.dual: for i in range(-1, self.dimension + 1): self.f_vector[i+1] = f_vector[-i-2] @@ -162,20 +166,15 @@ cdef class PolyhedronFaceLattice: self.coatoms = face_iter.coatoms cdef size_t n_atoms = self.atoms.n_faces() - self.atom_rep = self._mem.allocarray(self.coatoms.n_atoms(), sizeof(size_t)) - self.coatom_rep = self._mem.allocarray(self.coatoms.n_faces(), sizeof(size_t)) + self.atom_rep = check_allocarray(self.coatoms.n_atoms(), sizeof(size_t)) + self.coatom_rep = check_allocarray(self.coatoms.n_faces(), sizeof(size_t)) # Setting up a pointer to raw data of ``faces``: - self.faces = self._mem.allocarray(self.dimension + 2, sizeof(face_list_t)) + self.faces = check_calloc(self.dimension + 2, sizeof(face_list_t)) + for i in range(self.dimension + 2): - if i == self.dimension and self.dimension > 0: - face_list_shallow_init(self.faces[i], - self.f_vector[i], self.coatoms.n_atoms(), - self.coatoms.n_coatoms(), self._mem) - else: - face_list_init(self.faces[i], - self.f_vector[i], self.coatoms.n_atoms(), - self.coatoms.n_coatoms(), self._mem) + face_list_init(self.faces[i], self.f_vector[i], + self.coatoms.n_atoms(), self.coatoms.n_coatoms()) # The universe. for j in range(self.coatoms.n_atoms()): @@ -184,12 +183,10 @@ cdef class PolyhedronFaceLattice: # The coatoms. if self.dimension > 0: # Note that in the other cases, this was fully initialized above. - # Not just shallow. - face_list_shallow_copy(self.faces[self.dimension], self.coatoms.data) + face_list_copy(self.faces[self.dimension], self.coatoms.data) # Attributes for iterating over the incidences. - self.is_incidence_initialized = 0 - face_init(self.incidence_face, self.coatoms.n_atoms(), self.coatoms.n_coatoms(), self._mem) + face_init(self.incidence_face, self.coatoms.n_atoms(), self.coatoms.n_coatoms()) # Adding all faces, using the iterator. for i in range(1, self.dimension): @@ -206,9 +203,45 @@ cdef class PolyhedronFaceLattice: add_face_deep(self.faces[d+1], face_iter.structure.face) d = face_iter.next_dimension() + def __init__(self, CombinatorialPolyhedron C): + r""" + Initialize :class:`PolyhedronFaceLattice`. + + See :class:`PolyhedronFaceLattice`. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: C = CombinatorialPolyhedron(P) + sage: C._record_all_faces() # indirect doctests + sage: C.face_lattice() + Finite lattice containing 28 elements + + sage: TestSuite(sage.geometry.polyhedron.combinatorial_polyhedron.polyhedron_face_lattice.PolyhedronFaceLattice).run() + """ # Sorting the faces, except for coatoms. self._sort() + def __dealloc__(self): + """ + TESTS:: + + sage: from sage.geometry.polyhedron.combinatorial_polyhedron.polyhedron_face_lattice import PolyhedronFaceLattice + sage: PolyhedronFaceLattice() # indirect doctest + Traceback (most recent call last): + ... + TypeError: __cinit__() takes exactly 1 positional argument (0 given) + """ + cdef int i + sig_free(self.f_vector) + sig_free(self.atom_rep) + sig_free(self.coatom_rep) + if self.faces: + for i in range(self.dimension + 2): + face_list_free(self.faces[i]) + sig_free(self.faces) + face_free(self.incidence_face) + cdef int _sort(self) except -1: r""" Sort each list of ``self.faces`` (except for coatoms). diff --git a/src/sage/graphs/bipartite_graph.py b/src/sage/graphs/bipartite_graph.py index b782aa989ed..94e99c621a5 100644 --- a/src/sage/graphs/bipartite_graph.py +++ b/src/sage/graphs/bipartite_graph.py @@ -1111,14 +1111,14 @@ def matching_polynomial(self, algorithm="Godsil", name=None): sage: x = polygen(QQ) sage: g = BipartiteGraph(graphs.CompleteBipartiteGraph(16, 16)) - sage: bool(factorial(16) * laguerre(16, x^2) == g.matching_polynomial(algorithm='rook')) + sage: bool(factorial(16) * laguerre(16, x^2) == g.matching_polynomial(algorithm='rook')) # optional - sage.symbolic True Compute the matching polynomial of a line with `60` vertices:: - sage: from sage.functions.orthogonal_polys import chebyshev_U + sage: from sage.functions.orthogonal_polys import chebyshev_U # optional - sage.symbolic sage: g = next(graphs.trees(60)) - sage: chebyshev_U(60, x/2) == BipartiteGraph(g).matching_polynomial(algorithm='rook') + sage: chebyshev_U(60, x/2) == BipartiteGraph(g).matching_polynomial(algorithm='rook') # optional - sage.symbolic True The matching polynomial of a tree is equal to its characteristic diff --git a/src/sage/graphs/chrompoly.pyx b/src/sage/graphs/chrompoly.pyx index 07d87a6d509..221caa5431a 100644 --- a/src/sage/graphs/chrompoly.pyx +++ b/src/sage/graphs/chrompoly.pyx @@ -30,10 +30,11 @@ from memory_allocator cimport MemoryAllocator from sage.libs.gmp.mpz cimport * from sage.rings.integer_ring import ZZ from sage.rings.integer cimport Integer -from sage.misc.misc_c import prod +from sage.rings.ring cimport Algebra +from sage.rings.polynomial.polynomial_integer_dense_flint cimport Polynomial_integer_dense_flint +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - -def chromatic_polynomial(G, return_tree_basis=False): +def chromatic_polynomial(G, return_tree_basis=False, algorithm='C', cache=None): """ Compute the chromatic polynomial of the graph G. @@ -46,6 +47,27 @@ def chromatic_polynomial(G, return_tree_basis=False): is the result of contracting e, then the chromatic polynomial of G is equal to that of G' minus that of G''. + INPUT: + + - ``G`` -- a Sage graph + + - ``return_tree_basis`` -- boolean (default: ``False``); not used yet + + - ``algorithm`` -- string (default: ``"C"``); the algorithm to use among + + - ``"C"``, an implementation in C by Robert Miller and Gordon Royle. + + - ``"Python"``, an implementation in Python using caching to avoid + recomputing the chromatic polynomial of a graph that has already been + seen. This seems faster on some dense graphs. + + - ``cache`` -- dictionary (default: ``None``); this parameter is used only + for algorithm ``"Python"``. It is a dictionary keyed by canonical + labelings of graphs and used to cache the chromatic polynomials of the + graphs generated by the algorithm. In other words, it avoids computing + twice the chromatic polynomial of isometric graphs. One will be created + automatically if not provided. + EXAMPLES:: sage: graphs.CycleGraph(4).chromatic_polynomial() @@ -90,6 +112,14 @@ def chromatic_polynomial(G, return_tree_basis=False): sage: min(i for i in range(11) if P(i) > 0) == G.chromatic_number() True + Check that algorithms ``"C"`` and ``"Python"`` return the same results:: + + sage: G = graphs.RandomGNP(8, randint(1, 9)*0.1) + sage: c = G.chromatic_polynomial(algorithm='C') + sage: p = G.chromatic_polynomial(algorithm='Python') + sage: c == p + True + TESTS: Check that :trac:`21502` is solved:: @@ -101,16 +131,27 @@ def chromatic_polynomial(G, return_tree_basis=False): sage: Graph([[1, 1]], multiedges=True, loops=True).chromatic_polynomial() 0 + + Giving a wrong algorithm:: + + sage: Graph().chromatic_polynomial(algorithm="foo") + Traceback (most recent call last): + ... + ValueError: algorithm must be "C" or "Python" """ + algorithm = algorithm.lower() + if algorithm not in ['c', 'python']: + raise ValueError('algorithm must be "C" or "Python"') + if algorithm == 'python': + return chromatic_polynomial_with_cache(G, cache=cache) + + R = ZZ['x'] if not G: - R = ZZ['x'] return R.one() if G.has_loops(): - R = ZZ['x'] return R.zero() if not G.is_connected(): - return prod([chromatic_polynomial(g) for g in G.connected_components_subgraphs()]) - R = ZZ['x'] + return R.prod(chromatic_polynomial(g, algorithm='C') for g in G.connected_components_subgraphs()) x = R.gen() if G.is_tree(): return x * (x - 1) ** (G.num_verts() - 1) @@ -312,3 +353,199 @@ cdef int contract_and_count(int *chords1, int *chords2, int num_chords, int nver j += 1 contract_and_count(new_chords1, new_chords2, num, nverts - 1, tot, parent) mpz_add_ui(tot[nverts], tot[nverts], 1) + + +# +# Chromatic Polynomial with caching +# + +def chromatic_polynomial_with_cache(G, cache=None): + r""" + Return the chromatic polynomial of the graph ``G``. + + The algorithm used is here is the non recursive version of a recursive + algorithm based on the following observations of Read: + + - The chromatic polynomial of a tree on `n` vertices is `x(x-1)^{n-1}`. + + - If `e` is an edge of `G`, `G'` is the result of deleting the edge `e`, + and `G''` is the result of contracting `e`, then the chromatic + polynomial of `G` is equal to that of `G'` minus that of `G''`. + + - If `G` is not connected, its the chromatic polynomial is the product + of the chromatic polynomials of its connected components. + + Since this method makes extensive use of canonical labelings, it is + recommended to install optional package ``bliss``. + + INPUT: + + - ``G`` -- a Sage graph + + - ``cache`` -- dictionary (default: ``None``); dictionary keyed by canonical + labelings of graphs and used to cache the chromatic polynomials of the + graphs generated by the algorithm. In other words, it avoids computing + twice the chromatic polynomial of isometric graphs. One will be created + automatically if not provided. + + EXAMPLES:: + + sage: from sage.graphs.chrompoly import chromatic_polynomial_with_cache + sage: chromatic_polynomial_with_cache(graphs.CycleGraph(4)) + x^4 - 4*x^3 + 6*x^2 - 3*x + sage: chromatic_polynomial_with_cache(graphs.CycleGraph(3)) + x^3 - 3*x^2 + 2*x + sage: chromatic_polynomial_with_cache(graphs.CubeGraph(3)) + x^8 - 12*x^7 + 66*x^6 - 214*x^5 + 441*x^4 - 572*x^3 + 423*x^2 - 133*x + sage: chromatic_polynomial_with_cache(graphs.PetersenGraph()) + x^10 - 15*x^9 + 105*x^8 - 455*x^7 + 1353*x^6 - 2861*x^5 + 4275*x^4 - 4305*x^3 + 2606*x^2 - 704*x + sage: chromatic_polynomial_with_cache(graphs.CompleteBipartiteGraph(3,3)) + x^6 - 9*x^5 + 36*x^4 - 75*x^3 + 78*x^2 - 31*x + + If a cache is provided, it is feeded:: + + sage: cache = {} + sage: G = graphs.CycleGraph(4) + sage: p = chromatic_polynomial_with_cache(graphs.CycleGraph(4), cache=cache) + sage: key = frozenset(G.canonical_label().edges(labels=False, sort=False)) + sage: cache[key] + x^4 - 4*x^3 + 6*x^2 - 3*x + + TESTS: + + Corner cases:: + + sage: from sage.graphs.chrompoly import chromatic_polynomial_with_cache + sage: chromatic_polynomial_with_cache(graphs.EmptyGraph()) + 1 + sage: chromatic_polynomial_with_cache(Graph(1)) + x + sage: chromatic_polynomial_with_cache(Graph(2)) + x^2 + sage: chromatic_polynomial_with_cache(Graph(3)) + x^3 + sage: chromatic_polynomial_with_cache(Graph([[1, 1]], loops=True)) + 0 + + Parameter ``cache`` must be a dictionary:: + + sage: chromatic_polynomial_with_cache(Graph(2), cache=[]) + Traceback (most recent call last): + ... + TypeError: parameter cache must be a dictionary or None + """ + cdef Algebra R = PolynomialRing(ZZ, "x", implementation="FLINT") + cdef Polynomial_integer_dense_flint one = R.one() + cdef Polynomial_integer_dense_flint zero = R.zero() + cdef Polynomial_integer_dense_flint x = R.gen() + + if not G: + return one + if G.has_loops(): + return zero + + # Make a copy of the input graph and ensure that it's labeled [0..n-1] + G = G.relabel(inplace=False) + G.remove_multiple_edges() + + # We use a cache to avoid computing twice the chromatic polynomial of + # isomorphic graphs + if cache is None: + cache = dict() + elif not isinstance(cache, dict): + raise TypeError("parameter cache must be a dictionary or None") + + # We use a digraph to store intermediate values and store the current state + # of a vertex (either a graph, a key or a polynomial) + from sage.graphs.digraph import DiGraph + D = DiGraph(1) + D.set_vertex(0, G) + + cdef int op_none = 0 + cdef int op_mult = 1 + cdef int op_diff = 2 + + # We use a stack to order operations in a depth first search fashion + from collections import deque + stack = deque() + stack.append((0, True, (op_none, ))) + cdef bint firstseen + cdef int u, v, w, a, b + cdef tuple com + + while stack: + + v, firstseen, com = stack.pop() + + if firstseen: + g = D.get_vertex(v) + key = frozenset(g.canonical_label().edges(labels=False, sort=False)) + if key in cache: + D.set_vertex(v, cache[key]) + + elif g.has_loops(): + D.set_vertex(v, zero) + cache[key] = D.get_vertex(v) + + elif not g.is_connected(): + # We have to compute the product of the chromatic polynomials of + # the connected components + D.set_vertex(v, key) + stack.append((v, False, (op_mult, ))) + for h in g.connected_components_subgraphs(): + w = D.add_vertex() + D.set_vertex(w, h) + D.add_edge(v, w) + stack.append((w, True, (op_none, ))) + + elif g.order() == g.size() + 1: + # g is a tree + D.set_vertex(v, x*(x - one)**(g.order() - 1)) + cache[key] = D.get_vertex(v) + + else: + # Otherwise, the chromatic polynomial of g is the chromatic + # polynomial of g without edge e minus the chromatic polynomial + # of g after the contraction of edge e + a = D.add_vertex() + b = D.add_vertex() + D.add_edge(v, a) + D.add_edge(v, b) + D.set_vertex(v, key) + stack.append((v, False, (op_diff, a, b))) + # We try to select an edge that could disconnect the graph + for u, w in g.bridges(labels=False): + break + else: + u, w = next(g.edge_iterator(labels=False)) + + g.delete_edge(u, w) + D.set_vertex(a, g.copy()) + stack.append((a, True, (op_none, ))) + g.add_edge(u, w) + g.merge_vertices([u, w]) + g.remove_multiple_edges() + D.set_vertex(b, g) + stack.append((b, True, (op_none, ))) + + elif com[0] == op_mult: + # We compute the product of the connected components of the graph + # and delete the children from D + key = D.get_vertex(v) + cache[key] = R.prod(D.get_vertex(w) for w in D.neighbor_out_iterator(v)) + D.set_vertex(v, cache[key]) + D.delete_vertices(D.neighbor_out_iterator(v)) + + elif com[0] == op_diff: + # We compute the difference of the chromatic polynomials of the 2 + # children and remove them from D + key = D.get_vertex(v) + cache[key] = D.get_vertex(com[1]) - D.get_vertex(com[2]) + D.set_vertex(v, cache[key]) + D.delete_vertices(D.neighbor_out_iterator(v)) + + else: + # We should never end here + raise ValueError("something goes wrong") + + return D.get_vertex(0) diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index 4047a09c127..77118925586 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -2215,40 +2215,36 @@ def eccentricity(self, v=None, by_weight=False, algorithm=None, ... ValueError: algorithm 'Johnson_Boost' works only if all eccentricities are needed """ - if weight_function is not None: - by_weight = True - elif by_weight: - def weight_function(e): - return 1 if e[2] is None else e[2] - + by_weight, weight_function = self._get_weight_function(by_weight=by_weight, + weight_function=weight_function, + check_weight=check_weight) + + if not by_weight: + # We don't want the default weight function + weight_function = None + elif algorithm in ['BFS', 'Floyd-Warshall-Cython']: + raise ValueError("algorithm '{}' does not work with weights".format(algorithm)) if algorithm is None: if dist_dict is not None: algorithm = 'From_Dictionary' elif not by_weight: algorithm = 'BFS' - else: - for e in self.edge_iterator(): - try: - if float(weight_function(e)) < 0: - algorithm = 'Johnson_Boost' - break - except (ValueError, TypeError): - raise ValueError("the weight function cannot find the" - " weight of " + str(e)) + elif any(float(weight_function(e)) < 0 for e in self.edge_iterator()): + algorithm = 'Johnson_Boost' if algorithm is None: algorithm = 'Dijkstra_Boost' - if v is not None and not isinstance(v, list): - v = [v] + if v is not None: + if not isinstance(v, list): + v = [v] + v_set = set(v) - if v is None or all(u in v for u in self): + if v is None or all(u in v_set for u in self): if v is None: v = list(self) # If we want to use BFS, we use the Cython routine if algorithm == 'BFS': - if by_weight: - raise ValueError("algorithm 'BFS' does not work with weights") from sage.graphs.distances_all_pairs import eccentricity algo = 'standard' if with_labels: @@ -2257,9 +2253,9 @@ def weight_function(e): return eccentricity(self, algorithm=algo) if algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']: - dist_dict = self.shortest_path_all_pairs(by_weight, algorithm, - weight_function, - check_weight)[0] + dist_dict = self.shortest_path_all_pairs(by_weight=by_weight, algorithm=algorithm, + weight_function=weight_function, + check_weight=False)[0] algorithm = 'From_Dictionary' elif algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']: @@ -2279,7 +2275,7 @@ def weight_function(e): length = self.shortest_path_lengths(u, by_weight=by_weight, algorithm=algorithm, weight_function=weight_function, - check_weight=check_weight) + check_weight=False) if len(length) != self.num_verts(): ecc[u] = Infinity @@ -2352,13 +2348,6 @@ def radius(self, by_weight=False, algorithm=None, weight_function=None, if not self.order(): raise ValueError("radius is not defined for the empty DiGraph") - if weight_function is not None: - by_weight = True - - if by_weight and not weight_function: - def weight_function(e): - return 1 if e[2] is None else e[2] - return min(self.eccentricity(v=None, by_weight=by_weight, weight_function=weight_function, check_weight=check_weight, @@ -2452,13 +2441,22 @@ def diameter(self, by_weight=False, algorithm=None, weight_function=None, sage: d2 = max(G.eccentricity(algorithm='Dijkstra_Boost', by_weight=True)) sage: d1 == d2 True + sage: G.diameter(algorithm='BFS', by_weight=True) + Traceback (most recent call last): + ... + ValueError: algorithm 'BFS' does not work with weights + sage: G.diameter(algorithm='Floyd-Warshall-Cython', by_weight=True) + Traceback (most recent call last): + ... + ValueError: algorithm 'Floyd-Warshall-Cython' does not work with weights sage: G = digraphs.Path(5) sage: G.diameter(algorithm = 'DiFUB') +Infinity sage: G = DiGraph([(1,2,4), (2,1,7)]) sage: G.diameter(algorithm='2Dsweep', by_weight=True) 7.0 - sage: G.delete_edge(2,1,7); G.add_edge(2,1,-5); + sage: G.delete_edge(2,1,7) + sage: G.add_edge(2,1,-5) sage: G.diameter(algorithm='2Dsweep', by_weight=True) Traceback (most recent call last): ... @@ -2483,17 +2481,18 @@ def diameter(self, by_weight=False, algorithm=None, weight_function=None, if not self.order(): raise ValueError("diameter is not defined for the empty DiGraph") - if weight_function is not None: - by_weight = True + by_weight, weight_function = self._get_weight_function(by_weight=by_weight, + weight_function=weight_function, + check_weight=check_weight) - if by_weight and not weight_function: - def weight_function(e): - return 1 if e[2] is None else e[2] + if not by_weight: + # We don't want the default weight function + weight_function = None + elif algorithm in ['BFS', 'Floyd-Warshall-Cython']: + raise ValueError("algorithm '{}' does not work with weights".format(algorithm)) if algorithm is None: algorithm = 'DiFUB' - elif algorithm == 'BFS': - algorithm = 'standard' if algorithm in ['2Dsweep', 'DiFUB']: if not by_weight: @@ -2503,18 +2502,15 @@ def weight_function(e): from sage.graphs.base.boost_graph import diameter return diameter(self, algorithm=algorithm, weight_function=weight_function, - check_weight=check_weight) + check_weight=False) - if algorithm == 'standard': - if by_weight: - raise ValueError("algorithm '" + algorithm + "' does not work" + - " on weighted DiGraphs") + if algorithm == 'BFS': from sage.graphs.distances_all_pairs import diameter - return diameter(self, algorithm=algorithm) + return diameter(self, algorithm='standard') return max(self.eccentricity(v=None, by_weight=by_weight, weight_function=weight_function, - check_weight=check_weight, + check_weight=False, algorithm=algorithm)) def center(self, by_weight=False, algorithm=None, weight_function=None, diff --git a/src/sage/graphs/generators/basic.py b/src/sage/graphs/generators/basic.py index 930fe12ed55..21f56b31891 100644 --- a/src/sage/graphs/generators/basic.py +++ b/src/sage/graphs/generators/basic.py @@ -66,7 +66,8 @@ def BullGraph(): the identity matrix of the same dimensions as `A`:: sage: chrompoly = g.chromatic_polynomial() - sage: bool(expand(x * (x - 2) * (x - 1)^3) == chrompoly) + sage: x = chrompoly.parent()('x') + sage: x * (x - 2) * (x - 1)^3 == chrompoly True sage: charpoly = g.characteristic_polynomial() sage: M = g.adjacency_matrix(); M @@ -77,9 +78,9 @@ def BullGraph(): [0 0 1 0 0] sage: Id = identity_matrix(ZZ, M.nrows()) sage: D = x*Id - M - sage: bool(D.determinant() == charpoly) + sage: D.determinant() == charpoly True - sage: bool(expand(x * (x^2 - x - 3) * (x^2 + x - 1)) == charpoly) + sage: x * (x^2 - x - 3) * (x^2 + x - 1) == charpoly True """ edge_list = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 4)] diff --git a/src/sage/graphs/generators/distance_regular.pyx b/src/sage/graphs/generators/distance_regular.pyx index 7b0479bad51..e97241cece8 100644 --- a/src/sage/graphs/generators/distance_regular.pyx +++ b/src/sage/graphs/generators/distance_regular.pyx @@ -998,7 +998,7 @@ def HalfCube(const int n): sage: G1.is_isomorphic(G2) True """ - from sage.functions.trig import cos, sin + from math import cos, sin, pi if n < 2: raise ValueError("the dimension must be n > 1") @@ -1006,7 +1006,7 @@ def HalfCube(const int n): cdef int u, uu, v, i, j cdef list E = [] cdef dict pos = {} # dictionary of positions - cdef float theta = 3.14159265 / (n - 1) + cdef float theta = pi / (n - 1) cdef list cosi = [cos(i*theta) for i in range(n - 1)] cdef list sini = [sin(i*theta) for i in range(n - 1)] diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py index 9bf3508481d..0c69a777250 100644 --- a/src/sage/graphs/generators/families.py +++ b/src/sage/graphs/generators/families.py @@ -2885,14 +2885,13 @@ def HanoiTowerGraph(pegs, disks, labels=True, positions=True): # clockwise/counterclockwise placements, which # works well for three pegs (planar layout) # - from sage.functions.trig import sin, cos, csc if labels or positions: mapping = {} pos = {} a = Integer(-1) one = Integer(1) if positions: - radius_multiplier = 1 + csc(pi/pegs) + radius_multiplier = 1 + 1/sin(pi/pegs) sine = [] cosine = [] for i in range(pegs): @@ -3728,7 +3727,7 @@ def TuranGraph(n,r): sage: n = 13 sage: r = 4 sage: g = graphs.TuranGraph(n,r) - sage: g.size() == floor((r-1)*(n**2)/(2*r)) + sage: g.size() == (r-1) * (n**2) // (2*r) True TESTS:: diff --git a/src/sage/graphs/generators/random.py b/src/sage/graphs/generators/random.py index 927f9681aa7..b6bff1edd0d 100644 --- a/src/sage/graphs/generators/random.py +++ b/src/sage/graphs/generators/random.py @@ -1208,8 +1208,8 @@ def RandomChordalGraph(n, algorithm="growing", k=None, l=None, f=None, s=None): # 2. Generate n non-empty subtrees of T: {T1,...,Tn} if algorithm == "growing": if k is None: - from sage.rings.integer import Integer - k = int(Integer(n).sqrt()) + from sage.misc.functional import isqrt + k = isqrt(n) elif k < 1: raise ValueError("parameter k must be >= 1") diff --git a/src/sage/graphs/generators/smallgraphs.py b/src/sage/graphs/generators/smallgraphs.py index 5a87ae14cf5..26a331eb923 100644 --- a/src/sage/graphs/generators/smallgraphs.py +++ b/src/sage/graphs/generators/smallgraphs.py @@ -1044,7 +1044,9 @@ def BidiakisCube(): sage: g.is_planar() True - sage: bool(g.characteristic_polynomial() == expand((x - 3) * (x - 2) * (x^4) * (x + 1) * (x + 2) * (x^2 + x - 4)^2)) + sage: char_poly = g.characteristic_polynomial() + sage: x = char_poly.parent()('x') + sage: char_poly == (x - 3) * (x - 2) * (x^4) * (x + 1) * (x + 2) * (x^2 + x - 4)^2 True sage: g.chromatic_number() 3 diff --git a/src/sage/graphs/generators/world_map.py b/src/sage/graphs/generators/world_map.py index ba0c52e79f9..d447c67d12b 100644 --- a/src/sage/graphs/generators/world_map.py +++ b/src/sage/graphs/generators/world_map.py @@ -90,7 +90,7 @@ def AfricaMap(continental=False, year=2018): } no_land_border = ['Cape Verde', 'Seychelles', 'Mauritius', - u'São Tomé and Príncipe', 'Madagascar', 'Comoros'] + 'São Tomé and Príncipe', 'Madagascar', 'Comoros'] G = Graph(common_border, format='dict_of_lists') diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 5f838143a1e..920ee2be6af 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -2363,7 +2363,7 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl [-1 2 -1 0] [-1 -1 2 0] [-1 0 0 1] - sage: M = G.laplacian_matrix(normalized=True); M + sage: M = G.laplacian_matrix(normalized=True); M # optional - sage.symbolic [ 1 -1/6*sqrt(3)*sqrt(2) -1/6*sqrt(3)*sqrt(2) -1/3*sqrt(3)] [-1/6*sqrt(3)*sqrt(2) 1 -1/2 0] [-1/6*sqrt(3)*sqrt(2) -1/2 1 0] @@ -2415,7 +2415,6 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl [-4 -3 -1 8] """ from sage.matrix.constructor import diagonal_matrix - from sage.misc.functional import sqrt if weighted is None: weighted = self._weighted @@ -2451,6 +2450,7 @@ def kirchhoff_matrix(self, weighted=None, indegree=True, normalized=False, signl D[i,i] += row_sums[i] if normalized: + from sage.misc.functional import sqrt Dsqrt = diagonal_matrix([1 / sqrt(D[i,i]) if D[i,i] else 1 \ for i in range(D.nrows())]) if signless: @@ -4376,7 +4376,8 @@ def weight_function(e): if not by_weight: weight_function = lambda e: 1 - wfunction_float = lambda e: float(weight_function(e)) + def wfunction_float(e): + return float(weight_function(e)) if algorithm in ["Kruskal", "Filter_Kruskal", "Kruskal_Boost", "Prim_Boost", "Boruvka"]: if self.is_directed(): @@ -4386,13 +4387,13 @@ def weight_function(e): if algorithm == "Kruskal": from .spanning_tree import kruskal - return kruskal(g, wfunction=wfunction_float, check=check) + return kruskal(g, weight_function=wfunction_float, check_weight=False, check=check) if algorithm == "Filter_Kruskal": from .spanning_tree import filter_kruskal - return filter_kruskal(g, weight_function=wfunction_float, check=check) + return filter_kruskal(g, weight_function=wfunction_float, check_weight=False, check=check) elif algorithm == "Boruvka": from .spanning_tree import boruvka - return boruvka(g, wfunction=wfunction_float, check=check) + return boruvka(g, weight_function=wfunction_float, check_weight=False, check=check) else: from sage.graphs.base.boost_graph import min_spanning_tree return min_spanning_tree(g, @@ -20696,12 +20697,12 @@ def graphviz_string(self, **options): A digraph using latex labels for vertices and edges:: - sage: f(x) = -1 / x - sage: g(x) = 1 / (x + 1) - sage: G = DiGraph() - sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4)) - sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4)) - sage: print(G.graphviz_string(labels="latex", edge_labels=True)) # random + sage: f(x) = -1 / x # optional - sage.symbolic + sage: g(x) = 1 / (x + 1) # optional - sage.symbolic + sage: G = DiGraph() # optional - sage.symbolic + sage: G.add_edges((i, f(i), f) for i in (1, 2, 1/2, 1/4)) # optional - sage.symbolic + sage: G.add_edges((i, g(i), g) for i in (1, 2, 1/2, 1/4)) # optional - sage.symbolic + sage: print(G.graphviz_string(labels="latex", edge_labels=True)) # random # optional - sage.symbolic digraph { node [shape="plaintext"]; node_10 [label=" ", texlbl="$1$"]; @@ -20727,7 +20728,7 @@ def graphviz_string(self, **options): node_4 -> node_9 [label=" ", texlbl="$x \ {\mapsto}\ \frac{1}{x + 1}$"]; } - sage: print(G.graphviz_string(labels="latex", color_by_label=True)) # random + sage: print(G.graphviz_string(labels="latex", color_by_label=True)) # random # optional - sage.symbolic digraph { node [shape="plaintext"]; node_10 [label=" ", texlbl="$1$"]; @@ -20753,7 +20754,7 @@ def graphviz_string(self, **options): node_4 -> node_9 [color = "#00ffff"]; } - sage: print(G.graphviz_string(labels="latex", color_by_label={f: "red", g: "blue"})) # random + sage: print(G.graphviz_string(labels="latex", color_by_label={f: "red", g: "blue"})) # random # optional - sage.symbolic digraph { node [shape="plaintext"]; node_10 [label=" ", texlbl="$1$"]; @@ -20840,7 +20841,7 @@ def graphviz_string(self, **options): sage: def edge_options(data): ....: u, v, label = data ....: return {"dir":"back"} if u == 1 else {} - sage: print(G.graphviz_string(edge_options=edge_options)) # random + sage: print(G.graphviz_string(edge_options=edge_options)) # random # optional - sage.symbolic digraph { node_0 [label="-1"]; node_1 [label="-1/2"]; @@ -20874,7 +20875,7 @@ def graphviz_string(self, **options): ....: if (u,v) == (1, -1): options["label_style"] = "latex" ....: if (u,v) == (1, 1/2): options["dir"] = "back" ....: return options - sage: print(G.graphviz_string(edge_options=edge_options)) # random + sage: print(G.graphviz_string(edge_options=edge_options)) # random # optional - sage.symbolic digraph { node_0 [label="-1"]; node_1 [label="-1/2"]; diff --git a/src/sage/graphs/generic_graph_pyx.pyx b/src/sage/graphs/generic_graph_pyx.pyx index d9643cef474..69c980c9b03 100644 --- a/src/sage/graphs/generic_graph_pyx.pyx +++ b/src/sage/graphs/generic_graph_pyx.pyx @@ -421,7 +421,7 @@ cdef inline double sqrt_approx(double x,double y,double xx,double yy): ....: y = abs(y) ....: return max(x,y) + min(x,y)**2/(2*max(x,y)) - sage: polar_plot([1,lambda x:dist(cos(x),sin(x))], (0, 2*pi)) + sage: polar_plot([1,lambda x:dist(cos(x),sin(x))], (0, 2*math.pi)) Graphics object consisting of 2 graphics primitives """ if xx0) + sage: g = graphs.cospectral_graphs(5, matrix_function=DinverseA, graphs=lambda g: min(g.degree()) > 0) sage: sorted(sorted(g.graph6_string() for g in glist) for glist in g) [['Dlg', 'Ds_']] - sage: g[0][1].laplacian_matrix(normalized=True).charpoly()==g[0][1].laplacian_matrix(normalized=True).charpoly() + sage: g[0][1].laplacian_matrix(normalized=True).charpoly()==g[0][1].laplacian_matrix(normalized=True).charpoly() # optional - sage.symbolic True """ from sage.graphs.all import graphs as graph_gen diff --git a/src/sage/graphs/graph_latex.py b/src/sage/graphs/graph_latex.py index eea0d7eb953..d3692c609e1 100644 --- a/src/sage/graphs/graph_latex.py +++ b/src/sage/graphs/graph_latex.py @@ -265,14 +265,14 @@ package. So it is worth viewing this in the notebook to see the effects of various defaults and choices.:: - sage: var('x y u w') + sage: var('x y u w') # optional - sage.symbolic (x, y, u, w) sage: G = Graph(loops=True) - sage: for i in range(5): + sage: for i in range(5): # optional - sage.symbolic ....: for j in range(i+1, 5): ....: G.add_edge((i, j), label=(x^i*y^j).expand()) - sage: G.add_edge((0,0), label=sin(u)) - sage: G.add_edge((4,4), label=w^5) + sage: G.add_edge((0,0), label=sin(u)) # optional - sage.symbolic + sage: G.add_edge((4,4), label=w^5) # optional - sage.symbolic sage: G.set_pos(G.layout_circular()) sage: G.set_latex_options( ....: units='in', @@ -307,7 +307,7 @@ ....: ) sage: from sage.graphs.graph_latex import check_tkz_graph sage: check_tkz_graph() # random - depends on TeX installation - sage: print(latex(G)) + sage: print(latex(G)) # optional - sage.symbolic \begin{tikzpicture} \definecolor{cv0}{rgb}{0.8,0.8,0.8} \definecolor{cfv0}{rgb}{0.0,0.0,1.0} diff --git a/src/sage/graphs/matchpoly.pyx b/src/sage/graphs/matchpoly.pyx index 3226350f7be..fa271a792bd 100644 --- a/src/sage/graphs/matchpoly.pyx +++ b/src/sage/graphs/matchpoly.pyx @@ -328,9 +328,9 @@ def complete_poly(n): Checking the numerical results up to 20:: - sage: from sage.functions.orthogonal_polys import hermite - sage: p = lambda n: 2^(-n/2)*hermite(n, x/sqrt(2)) - sage: all(p(i) == complete_poly(i) for i in range(2, 20)) + sage: from sage.functions.orthogonal_polys import hermite # optional - sage.symbolic + sage: p = lambda n: 2^(-n/2)*hermite(n, x/sqrt(2)) # optional - sage.symbolic + sage: all(p(i) == complete_poly(i) for i in range(2, 20)) # optional - sage.symbolic True """ # global complete_matching_polys # if we do eventually make it a C array... diff --git a/src/sage/graphs/spanning_tree.pyx b/src/sage/graphs/spanning_tree.pyx index 7e13bdf1a6f..4b40e211161 100644 --- a/src/sage/graphs/spanning_tree.pyx +++ b/src/sage/graphs/spanning_tree.pyx @@ -39,9 +39,10 @@ Methods cimport cython from memory_allocator cimport MemoryAllocator from sage.sets.disjoint_set cimport DisjointSet_of_hashables +from sage.misc.decorators import rename_keyword - -cpdef kruskal(G, wfunction=None, bint check=False): +@rename_keyword(deprecation=32805, wfunction='weight_function') +def kruskal(G, by_weight=True, weight_function=None, check_weight=False, check=False): r""" Minimum spanning tree using Kruskal's algorithm. @@ -53,24 +54,19 @@ cpdef kruskal(G, wfunction=None, bint check=False): INPUT: - - ``G`` -- an undirected graph. - - - ``weight_function`` -- function (default: ``None``); a function that - inputs an edge ``e`` and outputs its weight. An edge has the form - ``(u,v,l)``, where ``u`` and ``v`` are vertices, ``l`` is a label (that - can be of any kind). The ``weight_function`` can be used to transform the - label into a weight. In particular: + - ``G`` -- an undirected graph - - if ``weight_function`` is not ``None``, the weight of an edge ``e`` - is ``weight_function(e)``; + - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in + the graph are weighted; if ``False``, all edges have weight 1. - - if ``weight_function`` is ``None`` (default) and ``g`` is weighted - (that is, ``g.weighted()==True``), the weight of an edge - ``e=(u,v,l)`` is ``l``, independently on which kind of object ``l`` - is: the ordering of labels relies on Python's operator ``<``; + - ``weight_function`` -- function (default: ``None``); a function that takes + as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not + ``None``, else ``1`` as a weight. - - if ``weight_function`` is ``None`` and ``g`` is not weighted, we set - all weights to 1 (hence, the output can be any spanning tree). + - ``check_weight`` -- boolean (default: ``False``); whether to check that + the ``weight_function`` outputs a number for each edge - ``check`` -- boolean (default: ``False``); whether to first perform sanity checks on the input graph ``G``. Default: ``check=False``. If we toggle @@ -172,9 +168,9 @@ cpdef kruskal(G, wfunction=None, bint check=False): sage: weight = lambda e:3-e[0]-e[1] sage: sorted(kruskal(G, check=True)) [(0, 1, 1), (1, 2, 1)] - sage: sorted(kruskal(G, wfunction=weight, check=True)) + sage: sorted(kruskal(G, weight_function=weight, check=True)) [(0, 2, 10), (1, 2, 1)] - sage: sorted(kruskal(G, wfunction=weight, check=False)) + sage: sorted(kruskal(G, weight_function=weight, check=False)) [(0, 2, 10), (1, 2, 1)] TESTS: @@ -236,19 +232,65 @@ cpdef kruskal(G, wfunction=None, bint check=False): sage: kruskal("I am not a graph") Traceback (most recent call last): ... - ValueError: The input G must be an undirected graph. + ValueError: the input graph must be undirected sage: kruskal(digraphs.Path(10)) Traceback (most recent call last): ... - ValueError: The input G must be an undirected graph. + ValueError: the input graph must be undirected + + Rename warning for parameter ``wfunction`` (:trac:`32805`):: + + sage: kruskal(Graph(1), wfunction=lambda e: 2) + doctest:...: DeprecationWarning: use the option 'weight_function' instead of 'wfunction' + See https://trac.sagemath.org/32805 for details. + [] """ - return list(kruskal_iterator(G, wfunction=wfunction, check=check)) + return list(kruskal_iterator(G, by_weight=by_weight, weight_function=weight_function, + check_weight=check_weight, check=check)) -def kruskal_iterator(G, wfunction=None, bint check=False): +@rename_keyword(deprecation=32805, wfunction='weight_function') +def kruskal_iterator(G, by_weight=True, weight_function=None, check_weight=False, bint check=False): """ Return an iterator implementation of Kruskal algorithm. + INPUT: + + - ``G`` -- an undirected graph + + - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in + the graph are weighted; if ``False``, all edges have weight 1. + + - ``weight_function`` -- function (default: ``None``); a function that takes + as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not + ``None``, else ``1`` as a weight. + + - ``check_weight`` -- boolean (default: ``False``); whether to check that + the ``weight_function`` outputs a number for each edge + + - ``check`` -- boolean (default: ``False``); whether to first perform sanity + checks on the input graph ``G``. Default: ``check=False``. If we toggle + ``check=True``, the following sanity checks are first performed on ``G`` + prior to running Kruskal's algorithm on that input graph: + + - Is ``G`` the null graph? + - Is ``G`` disconnected? + - Is ``G`` a tree? + - Does ``G`` have self-loops? + - Does ``G`` have multiple edges? + + By default, we turn off the sanity checks for performance reasons. This + means that by default the function assumes that its input graph is + connected, and has at least one vertex. Otherwise, you should set + ``check=True`` to perform some sanity checks and preprocessing on the + input graph. If ``G`` has multiple edges or self-loops, the algorithm + still works, but the running-time can be improved if these edges are + removed. To further improve the runtime of this function, you should call + it directly instead of using it indirectly via + :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`. + OUTPUT: The edges of a minimum spanning tree of ``G``, one by one. @@ -262,10 +304,30 @@ def kruskal_iterator(G, wfunction=None, bint check=False): sage: G.weighted(True) sage: next(kruskal_iterator(G, check=True)) (1, 6, 10) + + TESTS: + + If the input is not a Graph:: + + sage: list(kruskal_iterator("I am not a graph")) + Traceback (most recent call last): + ... + ValueError: the input graph must be undirected + sage: list(kruskal_iterator(digraphs.Path(2))) + Traceback (most recent call last): + ... + ValueError: the input graph must be undirected + + Rename warning for parameter ``wfunction`` (:trac:`32805`):: + + sage: list(kruskal_iterator(Graph(1), wfunction=lambda e: 2)) + doctest:...: DeprecationWarning: use the option 'weight_function' instead of 'wfunction' + See https://trac.sagemath.org/32805 for details. + [] """ from sage.graphs.graph import Graph if not isinstance(G, Graph): - raise ValueError("The input G must be an undirected graph.") + raise ValueError("the input graph must be undirected") # sanity checks if check: @@ -278,17 +340,20 @@ def kruskal_iterator(G, wfunction=None, bint check=False): # G is a tree yield from G.edge_iterator() return - g = G.to_simple(to_undirected=False, keep_label='min') - else: - g = G - cdef DisjointSet_of_hashables union_find = DisjointSet_of_hashables(g) - yield from kruskal_iterator_from_edges(g.edge_iterator(), union_find, - weighted=G.weighted(), - weight_function=wfunction) + cdef DisjointSet_of_hashables union_find = DisjointSet_of_hashables(G) + by_weight, weight_function = G._get_weight_function(by_weight=by_weight, + weight_function=weight_function, + check_weight=check_weight) + yield from kruskal_iterator_from_edges(G.edge_iterator(), union_find, + by_weight=by_weight, + weight_function=weight_function, + check_weight=False) -def kruskal_iterator_from_edges(edges, union_find, weighted=False, weight_function=None): +@rename_keyword(deprecation=32805, weighted='by_weight') +def kruskal_iterator_from_edges(edges, union_find, by_weight=True, + weight_function=None, check_weight=False): """ Return an iterator implementation of Kruskal algorithm on list of edges. @@ -300,12 +365,17 @@ def kruskal_iterator_from_edges(edges, union_find, weighted=False, weight_functi :class:`~sage.sets.disjoint_set.DisjointSet_of_hashables` encoding a forest - - ``weighted`` -- boolean (default: ``False``); whether edges are weighted, - i.e., the label of an edge is a weight + - ``by_weight`` - boolean (default: ``True``); if ``True``, the edges in + the graph are weighted; if ``False``, all edges have weight 1. + + - ``weight_function`` -- function (default: ``None``); a function that takes + as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not + ``None``, else ``1`` as a weight. - - ``weight_function`` -- function (default: ``None``); a function that - inputs an edge ``e`` and outputs its weight. See :func:`kruskal` for more - details. + - ``check_weight`` -- boolean (default: ``False``); whether to check that + the ``weight_function`` outputs a number for each edge OUTPUT: @@ -321,17 +391,29 @@ def kruskal_iterator_from_edges(edges, union_find, weighted=False, weight_functi sage: from sage.graphs.spanning_tree import kruskal_iterator_from_edges sage: G = Graph({1:{2:28, 6:10}, 2:{3:16, 7:14}, 3:{4:12}, 4:{5:22, 7:18}, 5:{6:25, 7:24}}) sage: G.weighted(True) - sage: union_set=DisjointSet(G.order()) - sage: next(kruskal_iterator_from_edges(G.edges(sort=False), union_set, weighted=G.weighted())) + sage: union_set = DisjointSet(G) + sage: next(kruskal_iterator_from_edges(G.edges(sort=False), union_set, by_weight=G.weighted())) (1, 6, 10) + + TESTS: + + Rename warning for parameter ``weighted`` (:trac:`32805`):: + + sage: from sage.graphs.spanning_tree import kruskal_iterator_from_edges + sage: G = Graph([(0, 1)]) + sage: union_set = DisjointSet(G) + sage: next(kruskal_iterator_from_edges(G.edges(), union_set, weighted=False)) + doctest:...: DeprecationWarning: use the option 'by_weight' instead of 'weighted' + See https://trac.sagemath.org/32805 for details. + (0, 1, None) """ # We sort edges, as specified. - if weight_function is None: - if weighted: - from operator import itemgetter - edges = sorted(edges, key=itemgetter(2)) - else: + if weight_function is not None: edges = sorted(edges, key=weight_function) + elif by_weight: + from operator import itemgetter + edges = sorted(edges, key=itemgetter(2)) + # Kruskal's algorithm for e in edges: # acyclic test via union-find @@ -345,7 +427,8 @@ def kruskal_iterator_from_edges(edges, union_find, weighted=False, weight_functi return -def filter_kruskal(G, threshold=10000, weight_function=None, bint check=False): +def filter_kruskal(G, threshold=10000, by_weight=True, weight_function=None, + check_weight=True, bint check=False): """ Minimum spanning tree using Filter Kruskal algorithm. @@ -364,27 +447,22 @@ def filter_kruskal(G, threshold=10000, weight_function=None, bint check=False): - ``G`` -- an undirected graph - - ``weight_function`` -- function (default: ``None``); a function that - inputs an edge ``e`` and outputs its weight. An edge has the form - ``(u,v,l)``, where ``u`` and ``v`` are vertices, ``l`` is a label (that - can be of any kind). The ``weight_function`` can be used to transform the - label into a weight. In particular: - - - if ``weight_function`` is not ``None``, the weight of an edge ``e`` - is ``weight_function(e)``; - - - if ``weight_function`` is ``None`` (default) and ``g`` is weighted - (that is, ``g.weighted()==True``), the weight of an edge - ``e=(u,v,l)`` is ``l``, independently on which kind of object ``l`` - is: the ordering of labels relies on Python's operator ``<``; - - - if ``weight_function`` is ``None`` and ``g`` is not weighted, we set - all weights to 1 (hence, the output can be any spanning tree). - - ``threshold`` -- integer (default: 10000); maximum number of edges on which to run kruskal algorithm. Above that value, edges are partitioned into sets of size at most ``threshold`` + - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in + the graph are weighted; if ``False``, all edges have weight 1. + + - ``weight_function`` -- function (default: ``None``); a function that takes + as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not + ``None``, else ``1`` as a weight. + + - ``check_weight`` -- boolean (default: ``False``); whether to check that + the ``weight_function`` outputs a number for each edge + - ``check`` -- boolean (default: ``False``); whether to first perform sanity checks on the input graph ``G``. Default: ``check=False``. If we toggle ``check=True``, the following sanity checks are first performed on ``G`` @@ -419,13 +497,47 @@ def filter_kruskal(G, threshold=10000, weight_function=None, bint check=False): sage: filter_kruskal(Graph(2), check=True) [] """ - return list(filter_kruskal_iterator(G, threshold=threshold, weight_function=weight_function, check=check)) + return list(filter_kruskal_iterator(G, threshold=threshold, + by_weight=by_weight, weight_function=weight_function, + check_weight=check_weight, check=check)) -def filter_kruskal_iterator(G, threshold=10000, weight_function=None, bint check=False): +def filter_kruskal_iterator(G, threshold=10000, by_weight=True, weight_function=None, + check_weight=True, bint check=False): r""" Return an iterator implementation of Filter Kruskal's algorithm. + INPUT: + + - ``G`` -- an undirected graph + + - ``threshold`` -- integer (default: 10000); maximum number of edges on + which to run kruskal algorithm. Above that value, edges are partitioned + into sets of size at most ``threshold`` + + - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in + the graph are weighted; if ``False``, all edges have weight 1. + + - ``weight_function`` -- function (default: ``None``); a function that takes + as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not + ``None``, else ``1`` as a weight. + + - ``check_weight`` -- boolean (default: ``False``); whether to check that + the ``weight_function`` outputs a number for each edge + + - ``check`` -- boolean (default: ``False``); whether to first perform sanity + checks on the input graph ``G``. Default: ``check=False``. If we toggle + ``check=True``, the following sanity checks are first performed on ``G`` + prior to running Kruskal's algorithm on that input graph: + + - Is ``G`` the null graph? + - Is ``G`` disconnected? + - Is ``G`` a tree? + - Does ``G`` have self-loops? + - Does ``G`` have multiple edges? + OUTPUT: The edges of a minimum spanning tree of ``G``, one by one. @@ -496,8 +608,9 @@ def filter_kruskal_iterator(G, threshold=10000, weight_function=None, bint check if m <= threshold: yield from kruskal_iterator_from_edges(g.edge_iterator(), DisjointSet_of_hashables(g), - weighted=G.weighted(), - weight_function=weight_function) + by_weight=by_weight, + weight_function=weight_function, + check_weight=check_weight) return # @@ -506,11 +619,11 @@ def filter_kruskal_iterator(G, threshold=10000, weight_function=None, bint check cdef list edges = list(g.edge_iterator()) # Precompute edge weights to avoid frequent calls to weight_function cdef list weight + _, weight_function = G._get_weight_function(by_weight=by_weight, + weight_function=weight_function, + check_weight=check_weight) if weight_function is None: - if G.weighted(): - weight = [e[2] for e in edges] - else: - weight = [1 for _ in range(m)] + weight = [1 for _ in range(m)] else: weight = [weight_function(e) for e in edges] @@ -540,8 +653,9 @@ def filter_kruskal_iterator(G, threshold=10000, weight_function=None, bint check L = [edges[e_index[i]] for i in range(begin, end + 1) if union_find.find(edges[e_index[i]][0]) != union_find.find(edges[e_index[i]][1])] yield from kruskal_iterator_from_edges(L, union_find, - weighted=G.weighted(), - weight_function=weight_function) + by_weight=by_weight, + weight_function=weight_function, + check_weight=False) if union_find.number_of_subsets() == 1: return continue @@ -576,7 +690,8 @@ def filter_kruskal_iterator(G, threshold=10000, weight_function=None, bint check stack.append((begin, i - 1)) -cpdef boruvka(G, wfunction=None, bint check=False, bint by_weight=True): +@rename_keyword(deprecation=32805, wfunction='weight_function') +def boruvka(G, by_weight=True, weight_function=None, check_weight=True, check=False): r""" Minimum spanning tree using Boruvka's algorithm. @@ -590,22 +705,17 @@ cpdef boruvka(G, wfunction=None, bint check=False, bint by_weight=True): - ``G`` -- an undirected graph. - - ``wfunction`` -- weight function (default: ``None``); a function that - inputs an edge ``e`` and outputs its weight. An edge has the form - ``(u,v,l)``, where ``u`` and ``v`` are vertices, ``l`` is a label (that - can be of any kind). The ``wfunction`` can be used to transform the label - into a weight. In particular: + - ``by_weight`` -- boolean (default: ``True``); if ``True``, the edges in + the graph are weighted; if ``False``, all edges have weight 1. - - if ``wfunction`` is not ``None``, the weight of an edge ``e`` is - ``wfunction(e)``; - - - if ``wfunction`` is ``None`` (default) and ``g`` is weighted (that is, - ``g.weighted()==True``), the weight of an edge ``e=(u,v,l)`` is ``l``, - independently on which kind of object ``l`` is: the ordering of labels - relies on Python's operator ``<``; + - ``weight_function`` -- function (default: ``None``); a function that takes + as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not + ``None``, else ``1`` as a weight. - - if ``wfunction`` is ``None`` and ``g`` is not weighted, we set all - weights to 1 (hence, the output can be any spanning tree). + - ``check_weight`` -- boolean (default: ``False``); whether to check that + the ``weight_function`` outputs a number for each edge - ``check`` -- boolean (default: ``False``); whether to first perform sanity checks on the input graph ``G``. Default: ``check=False``. If we toggle @@ -622,14 +732,6 @@ cpdef boruvka(G, wfunction=None, bint check=False, bint by_weight=True): ``check=True`` to perform some sanity checks and preprocessing on the input graph. - - ``by_weight`` -- boolean (default: ``False``); whether to find MST by - using weights of edges provided. Default: ``by_weight=True``. If - ``wfunction`` is given, MST is calculated using the weights of edges as - per the function. If ``wfunction`` is ``None``, the weight of an edge - ``e=(u,v,l)`` is ``l`` if graph is weighted, or all edge weights are - considered ``1`` if graph is unweighted. If we toggle ``by_weight=False``, - all weights are considered as ``1`` and MST is calculated. - OUTPUT: The edges of a minimum spanning tree of ``G``, if one exists, otherwise @@ -657,9 +759,9 @@ cpdef boruvka(G, wfunction=None, bint check=False, bint by_weight=True): sage: G = Graph([[0,1,1],[1,2,1],[2,0,10]], weighted=True) sage: weight = lambda e:3-e[0]-e[1] - sage: boruvka(G, wfunction=lambda e:3-e[0]-e[1], by_weight=True) + sage: boruvka(G, weight_function=lambda e:3-e[0]-e[1], by_weight=True) [(0, 2, 10), (1, 2, 1)] - sage: boruvka(G, wfunction=lambda e:float(1/e[2]), by_weight=True) + sage: boruvka(G, weight_function=lambda e:float(1/e[2]), by_weight=True) [(0, 2, 10), (0, 1, 1)] An example of disconnected graph with ``check`` disabled:: @@ -697,6 +799,13 @@ cpdef boruvka(G, wfunction=None, bint check=False, bint by_weight=True): Traceback (most recent call last): ... ValueError: the input graph must be undirected + + Rename warning for parameter ``wfunction`` (:trac:`32805`):: + + sage: boruvka(Graph(1), wfunction=lambda e: 2) + doctest:...: DeprecationWarning: use the option 'weight_function' instead of 'wfunction' + See https://trac.sagemath.org/32805 for details. + [] """ from sage.graphs.graph import Graph if not isinstance(G, Graph): @@ -714,22 +823,20 @@ cpdef boruvka(G, wfunction=None, bint check=False, bint by_weight=True): # G is a tree return G.edges(sort=False) + by_weight, weight_function = G._get_weight_function(by_weight=by_weight, + weight_function=weight_function, + check_weight=check_weight) + # Boruvka's algorithm # Store the list of active edges as (e, e_weight) in a list - if by_weight: - if wfunction is None: - if G.weighted(): - edge_list = [(e, e[2]) for e in G.edge_iterator()] - else: - edge_list = [(e, 1) for e in G.edge_iterator()] - else: - edge_list = [(e, wfunction(e)) for e in G.edge_iterator()] + if weight_function is not None: + edge_list = [(e, weight_function(e)) for e in G.edge_iterator()] else: edge_list = [(e, 1) for e in G.edge_iterator()] # initially, each vertex is a connected component - cdef DisjointSet_of_hashables partitions = DisjointSet_of_hashables(G.vertex_iterator()) + cdef DisjointSet_of_hashables partitions = DisjointSet_of_hashables(G) # a dictionary to store the least weight outgoing edge for each component cdef dict cheapest = {} cdef list T = [] # stores the edges in minimum spanning tree @@ -1081,3 +1188,236 @@ def spanning_trees(g, labels=False): if g.order() and g.is_connected(): forest = Graph([g, g.bridges()], format='vertices_and_edges') yield from _recursive_spanning_trees(Graph(g, immutable=False, loops=False), forest, labels) + +def edge_disjoint_spanning_trees(G, k, by_weight=False, weight_function=None, check_weight=True): + r""" + Return `k` edge-disjoint spanning trees of minimum cost. + + This method implements the Roskind-Tarjan algorithm for finding `k` + minimum-cost edge-disjoint spanning trees in simple undirected graphs + [RT1985]_. When edge weights are taken into account, the algorithm ensures + that the sum of the weights of the returned spanning trees is minimized. The + time complexity of the algorithm is in `O(k^2n^2)` for the unweighted case + and otherwise in `O(m\log{m} + k^2n^2)`. + + This method raises an error if the graph does not contain the requested + number of spanning trees. + + INPUT: + + - ``G`` -- a simple undirected graph + + - ``k`` -- the requested number of edge-disjoint spanning trees + + - ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges in + the graph are weighted, otherwise all edges have weight 1 + + - ``weight_function`` -- function (default: ``None``); a function that takes + as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``, + ``by_weight`` is automatically set to ``True``. If ``None`` and + ``by_weight`` is ``True``, we use the edge label ``l``, if ``l`` is not + ``None``, else ``1`` as a weight. + + - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check + that the ``weight_function`` outputs a number for each edge + + EXAMPLES: + + Example from [RT1985]_:: + + sage: from sage.graphs.spanning_tree import edge_disjoint_spanning_trees + sage: G = Graph({'a': ['b', 'c', 'd', 'e'], 'b': ['c', 'e'], 'c': ['d'], 'd': ['e']}) + sage: F = edge_disjoint_spanning_trees(G, 2) + sage: F + [Graph on 5 vertices, Graph on 5 vertices] + sage: [f.is_tree() for f in F] + [True, True] + + This method raises an error if the graph does not contain the required + number of trees:: + + sage: edge_disjoint_spanning_trees(G, 3) + Traceback (most recent call last): + ... + EmptySetError: this graph does not contain the required number of trees/arborescences + + A clique of order `n` has `\lfloor n/2 \rfloor` edge disjoint spanning + trees:: + + sage: for n in range(1, 10): + ....: g = graphs.CompleteGraph(n) + ....: F = edge_disjoint_spanning_trees(g, n//2) + + The sum of the weights of the returned spanning trees is minimum:: + + sage: g = graphs.CompleteGraph(5) + sage: for u, v in g.edges(labels=False): + ....: g.set_edge_label(u, v, 1) + sage: g.set_edge_label(0, 1, 33) + sage: g.set_edge_label(1, 3, 33) + sage: F = edge_disjoint_spanning_trees(g, 2, by_weight=True) + sage: sum(F[0].edge_labels()) + sum(F[1].edge_labels()) + 8 + + TESTS: + + A graph with a single vertex has a spanning tree:: + + sage: from sage.graphs.spanning_tree import edge_disjoint_spanning_trees + sage: edge_disjoint_spanning_trees(Graph(1), 1) + [Graph on 1 vertex] + + Check parameter `k`:: + + sage: G = graphs.CompleteGraph(4) + sage: edge_disjoint_spanning_trees(G, -1) + Traceback (most recent call last): + ... + ValueError: parameter k must be a non-negative integer + sage: edge_disjoint_spanning_trees(G, 0) + [] + sage: edge_disjoint_spanning_trees(G, 1) + [Graph on 4 vertices] + + This method is for undirected graphs only:: + + sage: edge_disjoint_spanning_trees(DiGraph(), 1) + Traceback (most recent call last): + ... + ValueError: this method is for undirected graphs only + """ + if G.is_directed(): + raise ValueError("this method is for undirected graphs only") + G._scream_if_not_simple() + + from sage.categories.sets_cat import EmptySetError + from sage.graphs.graph import Graph + msg_no_solution = "this graph does not contain the required number of trees/arborescences" + if k < 0: + raise ValueError("parameter k must be a non-negative integer") + elif not k: + return [] + elif k == 1: + E = G.min_spanning_tree() + if not E and G.order() != 1: + raise EmptySetError(msg_no_solution) + return [Graph([G, E], format="vertices_and_edges")] + elif k > 1 + min(G.degree()) // 2: + raise EmptySetError(msg_no_solution) + + # Initialization of data structures + + # - partition[0] is used to maitain known clumps. + # - partition[i], 1 <= i <= k, is used to check if a given edge has both its + # endpoints in the same tree of forest Fi. + partition = [DisjointSet_of_hashables(G) for _ in range(k + 1)] + + # Mapping from edge to forests: + # - edge_index[e] == i if edge e is in Fi, and 0 if not in any Fi + # This mapping is sufficient to extract the spanning trees. + edge_index = {frozenset(e): 0 for e in G.edge_iterator(labels=False)} + + # Data structure to maintain the edge sets of each forest. + # This is not a requirement of the algorithm as we can use the mapping + # edge_index. However, it is convenient to maintain the forest as graphs to + # simplify some operations. + H = Graph([G, []], format="vertices_and_edges") + F = [H.copy() for _ in range(k + 1)] + + # We consider the edges by increasing weight + by_weight, weight_function = G._get_weight_function(by_weight=by_weight, + weight_function=weight_function, + check_weight=check_weight) + if not by_weight: + weight_function = None + + for x, y, _ in G.edges(sort=by_weight, key=weight_function): + # {x, y} is edge e0 in the algorithm + + if partition[0].find(x) == partition[0].find(y): + # x and y are in a same clump. That is x and y are in a same tree + # in every forest Fi. We proceed with the next edge. + continue + + # else, we apply the labeling algorithm + + # Label assigned to each edge by the labeling algorithm + edge_label = {} + + # We use a queue of edges + queue = [(x, y)] + queue_begin = 0 + queue_end = 1 + + # We find the tree Ti in Fi containing x, root Ti at x and + # compute the parent pi(v) of every vertex in Ti + p = [{x: x} for _ in range(k + 1)] + for i in range(1, k + 1): + # BFS will consider only vertices of the tree Ti of Fi containing x + for u, v in F[i].breadth_first_search(x, edges=True): + p[i][v] = u + + # and we search for an augmenting sequence + augmenting_sequence_found = False + while queue_begin < queue_end: + e = queue[queue_begin] + queue_begin += 1 + fe = frozenset(e) + i = (edge_index[fe] % k) + 1 + v, w = e + if partition[i].find(v) != partition[i].find(w): + # v and w are in different subtrees of Fi. We have detected an + # augmenting sequence since we can join the two subtrees. + augmenting_sequence_found = True + break + else: + # One of v and w is in the subtree of labeled edges in Fi + if v == x or (v in p[i] and frozenset((v, p[i][v])) in edge_label): + u = w + else: + u = v + + # Let F(e) be the unique path joining v and w. + # We find the unlabeled edges of Fi(e) by ascending through the + # tree one vertex at a time from z toward x, until reaching + # either x or a previously labeled edge. + + # Stack of edges to be labeled + edges_to_label = [] + while u != x and (u in p[i] and frozenset((u, p[i][u])) not in edge_label): + edges_to_label.append((u, p[i][u])) + u = p[i][u] + + # We now label edges + while edges_to_label: + ep = edges_to_label.pop() + edge_label[frozenset(ep)] = fe + queue.append(ep) + queue_end += 1 + + if augmenting_sequence_found: + # We perform the corresponding augmentation + partition[i].union(v, w) + + while fe in edge_label: + F[edge_index[fe]].delete_edge(fe) + F[i].add_edge(fe) + e, edge_index[fe], i = edge_label[fe], i, edge_index[fe] + fe = frozenset(e) + + # Finally, add edge e = e0 = (x, y) to Fi + F[i].add_edge(e) + edge_index[fe] = i + + else: + # x and y are in a same tree in every Fi, so in a same clump + partition[0].union(x, y) + + res = [F[i] for i in range(1, k + 1) if F[i].size() == G.order() - 1] + if len(res) != k: + raise EmptySetError(msg_no_solution) + + for f in res: + for u, v in f.edges(labels=False): + f.set_edge_label(u, v, G.edge_label(u, v)) + return res diff --git a/src/sage/groups/abelian_gps/abelian_group_morphism.py b/src/sage/groups/abelian_gps/abelian_group_morphism.py index 518946ea839..400283827b1 100644 --- a/src/sage/groups/abelian_gps/abelian_group_morphism.py +++ b/src/sage/groups/abelian_gps/abelian_group_morphism.py @@ -19,9 +19,8 @@ # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.interfaces.gap import gap +from sage.libs.gap.libgap import libgap from sage.categories.morphism import Morphism - from sage.misc.misc_c import prod @@ -115,7 +114,7 @@ def __init__(self, G, H, genss, imgss): if (self.domaingens[i]).order() != (self.codomaingens[i]).order(): raise TypeError("Sorry, the orders of the corresponding elements in %s, %s must be equal." % (genss, imgss)) - def _gap_init_(self): + def _libgap_(self): """ Only works for finite groups. @@ -128,31 +127,17 @@ def _gap_init_(self): Multiplicative Abelian group isomorphic to C2 x C3 sage: x,y = H.gens() sage: phi = AbelianGroupMorphism(H,G,[x,y],[a,b]) - sage: phi._gap_init_() - 'phi := GroupHomomorphismByImages(G,H,[x, y],[a, b])' + sage: libgap(phi) + [ f1, f2 ] -> [ f1, f2 ] + sage: phi = AbelianGroupMorphism(H,G,[x,y],[a*c**2,b]) + sage: libgap(phi) + [ f1, f2 ] -> [ f1*f4, f2 ] """ - G = (self.domain())._gap_init_() - H = (self.codomain())._gap_init_() - s3 = 'G:=%s; H:=%s' % (G, H) - gap.eval(s3) - gensG = self.domain().variable_names() # the Sage group generators - gensH = self.codomain().variable_names() - s1 = "gensG := GeneratorsOfGroup(G)" # the GAP group generators - gap.eval(s1) - s2 = "gensH := GeneratorsOfGroup(H)" - gap.eval(s2) - for i in range(len(gensG)): # making the Sage group gens - # correspond to the Sage group gens - cmd = "%s := gensG[%d]" % (gensG[i], i + 1) - gap.eval(cmd) - for i in range(len(gensH)): - cmd = "%s := gensH[%d]" % (gensH[i], i + 1) - gap.eval(cmd) - args = str(self.domaingens) + "," + str(self.codomaingens) - cmd = "phi := GroupHomomorphismByImages(G,H,%s)" % args - gap.eval(cmd) - self.gap_hom_string = cmd - return self.gap_hom_string + G = libgap(self.domain()) + H = libgap(self.codomain()) + in_G = [libgap(g) for g in self.domaingens] + in_H = [libgap(h) for h in self.codomaingens] + return G.GroupHomomorphismByImages(H, in_G, in_H) def _repr_type(self): return "AbelianGroup" @@ -175,7 +160,7 @@ def kernel(self): sage: x,y = G.gens() sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,b]) sage: phi.kernel() - 'Group([ ])' + Group([ ]) sage: H = AbelianGroup(3,[2,2,2],names="abc") sage: a,b,c = H.gens() @@ -183,11 +168,9 @@ def kernel(self): sage: x,y = G.gens() sage: phi = AbelianGroupMorphism(G,H,[x,y],[a,a]) sage: phi.kernel() - 'Group([ f1*f2 ])' + Group([ f1*f2 ]) """ - cmd = self._gap_init_() - gap.eval(cmd) - return gap.eval("Kernel(phi)") + return libgap(self).Kernel() def image(self, S): """ diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py index d31668974a1..879b8136624 100644 --- a/src/sage/interfaces/expect.py +++ b/src/sage/interfaces/expect.py @@ -1198,7 +1198,7 @@ def _expect_expr(self, expr=None, timeout=None): :: sage: singular._expect.before.decode('ascii') - u'...10\r\n> ' + '...10\r\n> ' We test interrupting ``_expect_expr`` using the GP interface, see :trac:`6661`. Unfortunately, this test doesn't work reliably using diff --git a/src/sage/interfaces/fricas.py b/src/sage/interfaces/fricas.py index d1d5fc4cd4a..2da16b6f4de 100644 --- a/src/sage/interfaces/fricas.py +++ b/src/sage/interfaces/fricas.py @@ -1191,7 +1191,7 @@ def _latex_(self): \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4\end{array} \right] sage: latex(fricas("integrate(sin(x+1/x),x)")) # optional - fricas - \int ^{\displaystyle x} {{\sin \left( {{{{{ \%O} ^{2}}+1} \over \%O}} \right)} \ {d \%O}} + \int ^{\displaystyle x} {{\sin \left( {{{{{ \%...} ^{2}}+1} \over \%...}} \right)} \ {d \%...}} """ replacements = [(r'\sp ', '^'), (r'\sp{', '^{'), diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index 5ee4c9edaaf..064297ea92b 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -1516,9 +1516,9 @@ def __getitem__(self, n): """ self._check_valid() if not isinstance(n, tuple): - return self.parent().new('%s[%s]'%(self._name, n)) - else: - return self.parent().new('%s%s'%(self._name, ''.join(['[%s]'%x for x in n]))) + return self.parent().new('%s[%s]' % (self._name, n)) + return self.parent().new('%s%s' % (self._name, + ''.join('[%s]' % x for x in n))) def str(self, use_file=False): """ diff --git a/src/sage/interfaces/gap3.py b/src/sage/interfaces/gap3.py index ba4649341ca..fc21ee46d06 100644 --- a/src/sage/interfaces/gap3.py +++ b/src/sage/interfaces/gap3.py @@ -725,9 +725,9 @@ def __getitem__(self, n): """ gap3_session = self._check_valid() if not isinstance(n, tuple): - return gap3_session.new('%s[%s]'%(self.name(), n)) - else: - return gap3_session.new('%s%s'%(self.name(), ''.join(['[%s]'%x for x in n]))) + return gap3_session.new('%s[%s]' % (self.name(), n)) + return gap3_session.new('%s%s' % (self.name(), + ''.join('[%s]' % x for x in n))) def _latex_(self): r""" diff --git a/src/sage/interfaces/interface.py b/src/sage/interfaces/interface.py index 19c6c7c8aa4..a8d774ed07d 100644 --- a/src/sage/interfaces/interface.py +++ b/src/sage/interfaces/interface.py @@ -126,11 +126,11 @@ def rand_seed(self): sage: from sage.interfaces.interface import Interface sage: i = Interface("") sage: i.rand_seed() # random - 318491487L + 318491487 sage: s = Singular() sage: s.rand_seed() # random - 365260051L + 365260051 """ import sage.doctest if sage.doctest.DOCTEST_MODE: diff --git a/src/sage/interfaces/lie.py b/src/sage/interfaces/lie.py index 43c3ec3fdd2..7e58d7a0ca0 100644 --- a/src/sage/interfaces/lie.py +++ b/src/sage/interfaces/lie.py @@ -717,7 +717,7 @@ def function_call(self, function, args=None, kwds=None): # than a LiEElement if function in ['diagram', 'setdefault', 'print_tab', 'type', 'factor', 'void', 'gcol']: args, kwds = self._convert_args_kwds(args, kwds) - cmd = "%s(%s)" % (function, ",".join([s.name() for s in args])) + cmd = "%s(%s)" % (function, ",".join(s.name() for s in args)) return AsciiArtString(self.eval(cmd)) return Expect.function_call(self, function, args, kwds) diff --git a/src/sage/interfaces/lisp.py b/src/sage/interfaces/lisp.py index bd7c5499950..917e8bece8c 100644 --- a/src/sage/interfaces/lisp.py +++ b/src/sage/interfaces/lisp.py @@ -381,7 +381,7 @@ def function_call(self, function, args=None, kwds=None): """ args, kwds = self._convert_args_kwds(args, kwds) self._check_valid_function_name(function) - return self.new("(%s %s)"%(function, ",".join([s.name() for s in args]))) + return self.new("(%s %s)" % (function, ",".join(s.name() for s in args))) # Inherit from RingElement to make __pow__ work diff --git a/src/sage/interfaces/macaulay2.py b/src/sage/interfaces/macaulay2.py index 07811a8c974..18d891f5fca 100644 --- a/src/sage/interfaces/macaulay2.py +++ b/src/sage/interfaces/macaulay2.py @@ -694,7 +694,7 @@ def ideal(self, *gens): gens2.append(self(g)) else: gens2.append(g) - return self('ideal {%s}'%(",".join([g.name() for g in gens2]))) + return self('ideal {%s}' % (",".join(g.name() for g in gens2))) def ring(self, base_ring='ZZ', vars='[x]', order='Lex'): r""" diff --git a/src/sage/interfaces/magma.py b/src/sage/interfaces/magma.py index 9aed0ea413d..feb50f9dc37 100644 --- a/src/sage/interfaces/magma.py +++ b/src/sage/interfaces/magma.py @@ -1154,10 +1154,10 @@ def function_call(self, function, args=[], params={}, nvals=1): if len(params) == 0: par = '' else: - par = ' : ' + ','.join(['%s:=%s' % (a, b.name()) - for a, b in params.items()]) + par = ' : ' + ','.join('%s:=%s' % (a, b.name()) + for a, b in params.items()) - fun = "%s(%s%s)" % (function, ",".join([s.name() for s in args]), par) + fun = "%s(%s%s)" % (function, ",".join(s.name() for s in args), par) return self._do_call(fun, nvals) @@ -1266,9 +1266,9 @@ def bar_call(self, left, name, gens, nvals=1): magma = self # coerce each arg to be a Magma element if isinstance(gens, (list, tuple)): - gens = [magma(z) for z in gens] + gens = (magma(z) for z in gens) # make comma separated list of names (in Magma) of each of the gens - v = ', '.join([w.name() for w in gens]) + v = ', '.join(w.name() for w in gens) else: gens = magma(gens) v = gens.name() diff --git a/src/sage/interfaces/maple.py b/src/sage/interfaces/maple.py index ff201d48fec..bd6b7a73f93 100644 --- a/src/sage/interfaces/maple.py +++ b/src/sage/interfaces/maple.py @@ -936,12 +936,12 @@ def __hash__(self): sage: m = maple('x^2+y^2') # optional - maple sage: m.__hash__() # optional - maple - 188724254834261060184983038723355865733L + 188724254834261060184983038723355865733 sage: hash(m) # random # optional - maple 5035731711831192733 sage: m = maple('x^2+y^3') # optional - maple sage: m.__hash__() # random # optional - maple - 264835029579301191531663246434344770556L + 264835029579301191531663246434344770556 sage: hash(m) # random # optional - maple -2187277978252104690 """ diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index a853cfe2096..2dd4477249e 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -709,7 +709,7 @@ def plot2d(self, *args): The eps file is saved in the current directory. """ - self('plot2d(%s)'%(','.join([str(x) for x in args]))) + self('plot2d(%s)' % (','.join(str(x) for x in args))) def plot2d_parametric(self, r, var, trange, nticks=50, options=None): r""" @@ -780,7 +780,7 @@ def plot3d(self, *args): The eps file is saved in the current working directory. """ - self('plot3d(%s)'%(','.join([str(x) for x in args]))) + self('plot3d(%s)' % (','.join(str(x) for x in args))) def plot3d_parametric(self, r, vars, urange, vrange, options=None): r""" diff --git a/src/sage/interfaces/maxima_lib.py b/src/sage/interfaces/maxima_lib.py index 3e47b3db029..e572cad5775 100644 --- a/src/sage/interfaces/maxima_lib.py +++ b/src/sage/interfaces/maxima_lib.py @@ -91,8 +91,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.symbolic.ring import SR @@ -105,8 +105,8 @@ from sage.env import MAXIMA_FAS -## We begin here by initializing Maxima in library mode -## i.e. loading it into ECL +# We begin here by initializing Maxima in library mode +# i.e. loading it into ECL ecl_eval("(setf *load-verbose* NIL)") if MAXIMA_FAS: ecl_eval("(require 'maxima \"{}\")".format(MAXIMA_FAS)) @@ -119,7 +119,7 @@ ecl_eval("(set-pathnames)") ecl_eval("(defun add-lineinfo (x) x)") ecl_eval('(defun principal nil (cond ($noprincipal (diverg)) ((not pcprntd) (merror "Divergent Integral"))))') -ecl_eval("(remprop 'mfactorial 'grind)") # don't use ! for factorials (#11539) +ecl_eval("(remprop 'mfactorial 'grind)") # don't use ! for factorials (#11539) ecl_eval("(setf $errormsg nil)") # The following is an adaptation of the "retrieve" function in maxima @@ -1695,7 +1695,7 @@ def max_to_sr(expr): max_sym_dict[expr]=sage_symbol return max_sym_dict[expr] else: - e=expr.python() - if isinstance(e,float): + e = expr.python() + if isinstance(e, float): return sage.rings.real_double.RealDoubleElement(e) return e diff --git a/src/sage/interfaces/phc.py b/src/sage/interfaces/phc.py index bfd0806c97c..4a078fc71d9 100644 --- a/src/sage/interfaces/phc.py +++ b/src/sage/interfaces/phc.py @@ -38,7 +38,7 @@ from sage.misc.all import tmp_filename from sage.rings.real_mpfr import RR -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.integer import Integer from sage.plot.line import line from sage.plot.point import point diff --git a/src/sage/interfaces/primecount.py b/src/sage/interfaces/primecount.py new file mode 100644 index 00000000000..e037cb2794d --- /dev/null +++ b/src/sage/interfaces/primecount.py @@ -0,0 +1,6 @@ +from sage.misc.superseded import deprecation +deprecation(32894, "the module sage.interfaces.primecount is deprecated - use primecountpy.primecount instead") +from sage.misc.lazy_import import lazy_import +lazy_import("primecountpy.primecount", ['phi', 'nth_prime', 'prime_pi', 'prime_pi_128'], + deprecation=(32894, "the module sage.interfaces.primecount is deprecated - use primecountpy.primecount instead")) + diff --git a/src/sage/interfaces/primecount.pyx b/src/sage/interfaces/primecount.pyx deleted file mode 100644 index 5fa1441411d..00000000000 --- a/src/sage/interfaces/primecount.pyx +++ /dev/null @@ -1,103 +0,0 @@ -r""" -Interface to the primecount library -""" -#***************************************************************************** -# Copyright (C) 2018 Vincent Delecroix <20100.delecroix@gmail.com> -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - -from libc.stdint cimport int64_t -from libcpp.string cimport string as cppstring -from cpython.int cimport PyInt_FromString - -from cysignals.signals cimport sig_on, sig_off -cimport sage.libs.primecount as primecount - -cdef inline int _do_sig(int64_t n): - "threshold for sig_on/sig_off" - return n >> 26 - -cpdef int64_t prime_pi(int64_t n, method=None) except -1: - r""" - Return the number of prime numbers smaller or equal than ``n``. - - INPUT: - - - ``n`` - an integer - - EXAMPLES:: - - sage: from sage.interfaces.primecount import prime_pi - sage: prime_pi(1000) == 168 - True - sage: prime_pi(1000, method='deleglise_rivat') == 168 - True - """ - cdef int64_t ans - if _do_sig(n): sig_on() - ans = primecount.pi(n) - if _do_sig(n): sig_off() - return ans - -cpdef prime_pi_128(n): - r""" - Return the number of prime number smaller than ``n``. - - EXAMPLES:: - - sage: from sage.interfaces.primecount import prime_pi_128 - - sage: prime_pi_128(1000) - 168 - sage: nth_prime_128(2**65) # not tested - ? - """ - cdef cppstring s = str(n).encode('ascii') - cdef bytes ans - sig_on() - ans = primecount.pi(s) - sig_off() - return PyInt_FromString(ans, NULL, 10) - -cpdef int64_t nth_prime(int64_t n) except -1: - r""" - Return the ``n``-th prime integer. - - EXAMPLES:: - - sage: from sage.interfaces.primecount import nth_prime - - sage: nth_prime(168) == 997 - True - """ - if n <= 0: - raise ValueError("n must be positive") - - cdef int64_t ans - if _do_sig(n): sig_on() - ans = primecount.nth_prime(n) - if _do_sig(n): sig_off() - return ans - -cpdef int64_t phi(int64_t x, int64_t a): - r""" - Return the number of integers smaller or equal than ``x`` by any of the - first ``a`` primes. - - This is sometimes called a "partial sieve function" or "Legendre-sum". - - EXAMPLES:: - - sage: from sage.interfaces.primecount import phi - - sage: phi(1000, 3) == 266 - True - sage: phi(2**30, 100) == 95446716 - True - """ - return primecount.phi(x, a) - diff --git a/src/sage/interfaces/qsieve.py b/src/sage/interfaces/qsieve.py index ebfd475cf23..a9e40cc1b6a 100644 --- a/src/sage/interfaces/qsieve.py +++ b/src/sage/interfaces/qsieve.py @@ -116,7 +116,7 @@ def data_to_list(out, n, time): break if i < len(w): t = w[i].strip() - out = '\n'.join([w[j] for j in range(i)]) + out = '\n'.join(w[j] for j in range(i)) else: t = '' else: diff --git a/src/sage/interfaces/rubik.py b/src/sage/interfaces/rubik.py index 50873d51fe1..263b85c58a5 100644 --- a/src/sage/interfaces/rubik.py +++ b/src/sage/interfaces/rubik.py @@ -154,17 +154,17 @@ def solve(self, facets): self.ready() self.child.sendline(self.format_cube(facets)) self.child.expect(r"([LRUDBF'2 ]+)\s+\((\d+)q\*?, (\d+)f\*?\)") - self.child.sendline(chr(3)) # send ctrl-c + self.child.sendline(chr(3)) # send ctrl-c return bytes_to_str(self.child.match.groups()[0]).strip() def format_cube(self, facets): L = [] optimal_solver_list = [SingNot(x) for x in optimal_solver_tokens] for f in optimal_solver_format.split(" "): - ix = facets[singmaster_list.index(SingNot(f))-1] + ix = facets[singmaster_list.index(SingNot(f)) - 1] facet = singmaster_list[ix] L.append(optimal_solver_list[optimal_solver_list.index(facet)]) - return " ".join([str(f) for f in L]) + return " ".join(str(f) for f in L) move_map = { @@ -287,7 +287,8 @@ def solve(self, facets, timeout=10, extra_time=2): # format the string into our notation child.close(True) sol = bytes_to_str(sol) - return ' '.join([self.rot_map[m[0]]+str(4-int(m[1])) for m in reversed(sol.split(' '))]).replace('1', '').replace('3',"'") + return ' '.join(self.rot_map[m[0]] + str(4 - int(m[1])) + for m in reversed(sol.split(' '))).replace('1', '').replace('3', "'") elif ix == 1: # invalid format child.close(True) diff --git a/src/sage/interfaces/sagespawn.pyx b/src/sage/interfaces/sagespawn.pyx index 469befbc66e..136a42810ed 100644 --- a/src/sage/interfaces/sagespawn.pyx +++ b/src/sage/interfaces/sagespawn.pyx @@ -146,7 +146,7 @@ class SageSpawn(spawn): sage: E = SageSpawn("sh", ["-c", "echo hello world"]) sage: _ = E.expect_peek("w") sage: E.read().decode('ascii') - u'hello world\r\n' + 'hello world\r\n' """ ret = self.expect(*args, **kwds) self._before = self.buffer_type() @@ -165,7 +165,7 @@ class SageSpawn(spawn): sage: E = SageSpawn("sh", ["-c", "echo hello world"]) sage: _ = E.expect_upto("w") sage: E.read().decode('ascii') - u'world\r\n' + 'world\r\n' """ ret = self.expect(*args, **kwds) self._before = self.buffer_type() diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index 023b9e3d42a..ee0576f1bcd 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -893,7 +893,7 @@ def ideal(self, *gens): gens2.append(self.new(g)) else: gens2.append(g) - return self(",".join([g.name() for g in gens2]), 'ideal') + return self(",".join(g.name() for g in gens2), 'ideal') def list(self, x): r""" @@ -971,18 +971,18 @@ def list(self, x): singular_elements = [] def strify(x): - if isinstance(x, (list, tuple, Sequence_generic)): - return 'list(' + ','.join([strify(i) for i in x]) + ')' - elif isinstance(x, SingularElement): - return x.name() - elif isinstance(x, (int, sage.rings.integer.Integer)): - return repr(x) - elif hasattr(x, '_singular_'): - e = x._singular_() - singular_elements.append(e) - return e.name() - else: - return str(x) + if isinstance(x, (list, tuple, Sequence_generic)): + return 'list(' + ','.join(strify(i) for i in x) + ')' + elif isinstance(x, SingularElement): + return x.name() + elif isinstance(x, (int, sage.rings.integer.Integer)): + return repr(x) + elif hasattr(x, '_singular_'): + e = x._singular_() + singular_elements.append(e) + return e.name() + else: + return str(x) return self(strify(x), 'list') @@ -1097,16 +1097,16 @@ def ring(self, char=0, vars='(x)', order='lp', check=True): 3*a """ if len(vars) > 2: - s = '; '.join(['if(defined(%s)>0){kill %s;};'%(x,x) - for x in vars[1:-1].split(',')]) + s = '; '.join('if(defined(%s)>0){kill %s;};' % (x, x) + for x in vars[1:-1].split(',')) self.eval(s) if check and isinstance(char, (int, sage.rings.integer.Integer)): - if char != 0: + if char: n = sage.rings.integer.Integer(char) if not n.is_prime(): raise ValueError("the characteristic must be 0 or prime") - R = self('%s,%s,%s'%(char, vars, order), 'ring') + R = self('%s,%s,%s' % (char, vars, order), 'ring') self.eval('short=0') # make output include *'s for multiplication for *THIS* ring. return R diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 049ab672415..e6a7b3800e9 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1541,7 +1541,7 @@ cdef class GapElement_Integer(GapElement): sage: int(libgap(2)**128) - 340282366920938463463374607431768211456L + 340282366920938463463374607431768211456 sage: type(_) """ @@ -3183,9 +3183,9 @@ cdef class GapElement_Record(GapElement): sage: rec = libgap.eval('rec(first:=123, second:=456)') sage: rec.record_name_to_index('first') # random output - 1812L + 1812 sage: rec.record_name_to_index('no_such_name') # random output - 3776L + 3776 """ name = str_to_bytes(name) return RNamName(name) diff --git a/src/sage/libs/mpmath/utils.pyx b/src/sage/libs/mpmath/utils.pyx index ed771bb1958..f7b50db3c36 100644 --- a/src/sage/libs/mpmath/utils.pyx +++ b/src/sage/libs/mpmath/utils.pyx @@ -14,7 +14,6 @@ from sage.structure.element cimport Element from sage.libs.mpfr cimport * from sage.libs.gmp.all cimport * -from sage.rings.complex_mpfr import ComplexField from sage.rings.real_mpfr cimport RealField cpdef int bitcount(n): @@ -272,6 +271,7 @@ def mpmath_to_sage(x, prec): mpfr_from_mpfval(y.value, x._mpf_) return y elif hasattr(x, "_mpc_"): + from sage.rings.complex_mpfr import ComplexField z = ComplexField(prec)(0) re, im = x._mpc_ mpfr_from_mpfval(z.__re, re) @@ -331,6 +331,7 @@ def sage_to_mpmath(x, prec): if isinstance(x, ComplexNumber): return x._mpmath_() else: + from sage.rings.complex_mpfr import ComplexField x = ComplexField(prec)(x) return x._mpmath_() if isinstance(x, tuple) or isinstance(x, list): diff --git a/src/sage/libs/ntl/ntl_ZZ.pyx b/src/sage/libs/ntl/ntl_ZZ.pyx index e132e997e01..e488b7adc43 100644 --- a/src/sage/libs/ntl/ntl_ZZ.pyx +++ b/src/sage/libs/ntl/ntl_ZZ.pyx @@ -257,7 +257,7 @@ cdef class ntl_ZZ(object): <... 'int'> sage: ntl.ZZ(10^30).__int__() - 1000000000000000000000000000000L + 1000000000000000000000000000000 sage: type(ntl.ZZ(10^30).__int__()) """ diff --git a/src/sage/libs/pari/tests.py b/src/sage/libs/pari/tests.py index f34b30146d4..9b5bff74eea 100644 --- a/src/sage/libs/pari/tests.py +++ b/src/sage/libs/pari/tests.py @@ -94,10 +94,10 @@ [4, 2] sage: int(pari(RealField(63)(2^63-1))) - 9223372036854775807L # 32-bit + 9223372036854775807 # 32-bit 9223372036854775807 # 64-bit sage: int(pari(RealField(63)(2^63+2))) - 9223372036854775810L + 9223372036854775810 sage: K = Qp(11,5) sage: x = K(11^-10 + 5*11^-7 + 11^-6) diff --git a/src/sage/libs/primecount.pxd b/src/sage/libs/primecount.pxd deleted file mode 100644 index a65efd8d9bb..00000000000 --- a/src/sage/libs/primecount.pxd +++ /dev/null @@ -1,24 +0,0 @@ -# distutils: libraries = primecount primesieve -# distutils: language = c++ - -# Use of this file is deprecated. - -from libc.stdint cimport int64_t -from libcpp.string cimport string as cppstring - -cdef extern from "primecount.hpp" namespace "primecount": - int64_t pi(int64_t x) - - cppstring pi(const cppstring& x) - - int64_t nth_prime(int64_t n) - - int64_t phi(int64_t x, int64_t a) - - void set_num_threads(int num_threads) - int get_num_threads() - - cppstring get_max_x() - cppstring get_max_x(double alpha) - - cppstring primecount_version() diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 659c84b2d0c..eefb08ae4e8 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -441,7 +441,7 @@ from sage.categories.manifolds import Manifolds from sage.categories.homset import Hom -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.real_mpfr import RR from sage.rings.infinity import infinity, minus_infinity from sage.rings.integer import Integer diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index 5aff97e65b8..5cb0ca72ccc 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -4293,7 +4293,7 @@ def dalembertian(self, metric=None): sage: e[1] = cos(t-z) sage: e.display() # plane wave propagating in the z direction e = cos(t - z) ∂/∂x - sage: De = e.dalembertian(); De + sage: De = e.dalembertian(); De # long time Vector field Box(e) on the 4-dimensional Lorentzian manifold M The function :func:`~sage.manifolds.operators.dalembertian` from the @@ -4301,12 +4301,12 @@ def dalembertian(self, metric=None): method :meth:`dalembertian`:: sage: from sage.manifolds.operators import dalembertian - sage: dalembertian(e) == De + sage: dalembertian(e) == De # long time True We check that the electric field obeys the wave equation:: - sage: De.display() + sage: De.display() # long time Box(e) = 0 """ diff --git a/src/sage/manifolds/differentiable/vector_bundle.py b/src/sage/manifolds/differentiable/vector_bundle.py index b23207a6d3d..4177a406cac 100644 --- a/src/sage/manifolds/differentiable/vector_bundle.py +++ b/src/sage/manifolds/differentiable/vector_bundle.py @@ -31,7 +31,7 @@ #****************************************************************************** from sage.categories.vector_bundles import VectorBundles -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.real_mpfr import RR from sage.manifolds.vector_bundle import TopologicalVectorBundle from sage.rings.infinity import infinity diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py index 230f54f905a..a049eac7b56 100644 --- a/src/sage/manifolds/manifold.py +++ b/src/sage/manifolds/manifold.py @@ -329,7 +329,7 @@ from sage.categories.manifolds import Manifolds from sage.categories.homset import Hom import sage.rings.abc -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.real_mpfr import RR from sage.misc.prandom import getrandbits from sage.misc.cachefunc import cached_method diff --git a/src/sage/manifolds/vector_bundle.py b/src/sage/manifolds/vector_bundle.py index 49c0b72f1bd..b851fa64fe7 100644 --- a/src/sage/manifolds/vector_bundle.py +++ b/src/sage/manifolds/vector_bundle.py @@ -38,7 +38,7 @@ from sage.categories.vector_bundles import VectorBundles from sage.structure.unique_representation import UniqueRepresentation import sage.rings.abc -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.real_mpfr import RR from sage.rings.integer import Integer from sage.manifolds.vector_bundle_fiber import VectorBundleFiber diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index 50b38a67534..ecdcd3da94f 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -5009,8 +5009,8 @@ cdef class Matrix(sage.structure.element.Matrix): (1 + O(5^5), O(5)) """ - M = sage.modules.free_module.FreeModule(self._base_ring, self.ncols(), sparse=self.is_sparse()) - if self.nrows() != v.degree(): + M = self._row_ambient_module() + if self._nrows != v._degree: raise ArithmeticError("number of rows of matrix must equal degree of vector") cdef Py_ssize_t i return sum([v[i] * self.row(i, from_list=True) @@ -5043,8 +5043,8 @@ cdef class Matrix(sage.structure.element.Matrix): (1 + O(5^5), O(5)) """ - M = sage.modules.free_module.FreeModule(self._base_ring, self.nrows(), sparse=self.is_sparse()) - if self.ncols() != v.degree(): + M = self._column_ambient_module() + if self._ncols != v._degree: raise ArithmeticError("number of columns of matrix must equal degree of vector") cdef Py_ssize_t i return sum([self.column(i, from_list=True) * v[i] diff --git a/src/sage/matrix/matrix1.pyx b/src/sage/matrix/matrix1.pyx index 044fcb67029..7b032bc5ede 100644 --- a/src/sage/matrix/matrix1.pyx +++ b/src/sage/matrix/matrix1.pyx @@ -1286,8 +1286,7 @@ cdef class Matrix(Matrix0): if from_list: return self.columns(copy=False)[i] cdef Py_ssize_t j - V = sage.modules.free_module.FreeModule(self._base_ring, - self._nrows, sparse=self.is_sparse()) + V = self._column_ambient_module() tmp = [self.get_unsafe(j, i) for j in range(self._nrows)] return V(tmp, coerce=False, copy=False, check=False) @@ -1344,8 +1343,7 @@ cdef class Matrix(Matrix0): if from_list: return self.rows(copy=False)[i] cdef Py_ssize_t j - V = sage.modules.free_module.FreeModule(self._base_ring, - self._ncols, sparse=self.is_sparse()) + V = self._row_ambient_module() tmp = [self.get_unsafe(i,j) for j in range(self._ncols)] return V(tmp, coerce=False, copy=False, check=False) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index cf6b29351af..7d319bd54b9 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -5500,8 +5500,7 @@ cdef class Matrix(Matrix1): Edual = decomp_seq([]) F = f.factor() if len(F) == 1: - V = sage.modules.free_module.FreeModule( - self.base_ring(), self.nrows(), sparse=self.is_sparse()) + V = self._column_ambient_module() m = F[0][1] if dual: return decomp_seq([(V, m==1)]), decomp_seq([(V, m==1)]) diff --git a/src/sage/matrix/matrix_double_sparse.pyx b/src/sage/matrix/matrix_double_sparse.pyx index 2e8635a3fcb..7d023645914 100644 --- a/src/sage/matrix/matrix_double_sparse.pyx +++ b/src/sage/matrix/matrix_double_sparse.pyx @@ -79,11 +79,11 @@ cdef class Matrix_double_sparse(Matrix_generic_sparse): sage: A = matrix.random(RDF, n, sparse=True) sage: I = matrix.identity(RDF, n, sparse=True) sage: A = A*A.transpose() + I - sage: L = A.cholesky() - sage: (A - L*L.T).norm(1) < 1e-10 + sage: L = A.cholesky() # known bug, 33031 + sage: (A - L*L.T).norm(1) < 1e-10 # known bug, 33031 True - sage: B = A.change_ring(RR) - sage: (B.cholesky() - L).norm(1) < 1e-10 + sage: B = A.change_ring(RR) # known bug, 33031 + sage: (B.cholesky() - L).norm(1) < 1e-10 # known bug, 33031 True :: @@ -92,11 +92,11 @@ cdef class Matrix_double_sparse(Matrix_generic_sparse): sage: A = matrix.random(CDF, n, sparse=True) sage: I = matrix.identity(CDF, n, sparse=True) sage: A = A*A.conjugate_transpose() + I - sage: L = A.cholesky() - sage: (A - L*L.H).norm(1) < 1e-10 + sage: L = A.cholesky() # known bug, 33031 + sage: (A - L*L.H).norm(1) < 1e-10 # known bug, 33031 True - sage: B = A.change_ring(CC) - sage: (B.cholesky() - L).norm(1) < 1e-10 + sage: B = A.change_ring(CC) # known bug, 33031 + sage: (B.cholesky() - L).norm(1) < 1e-10 # known bug, 33031 True """ cdef Matrix L # output matrix @@ -116,7 +116,7 @@ cdef class Matrix_double_sparse(Matrix_generic_sparse): # handle the case where cvxopt is not present. The # superclass method is slow, but no longer raises an # error, so let's try that. - L = super().cholesky() + return super().cholesky() cdef list idx_pairs = self.nonzero_positions(copy=False) cdef list row_idxs = [r for (r, c) in idx_pairs] diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index 018eba50c1a..dd75c5cb934 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -5156,6 +5156,93 @@ cdef class Matrix_integer_dense(Matrix_dense): ################################################################# # operations with matrices ################################################################# + + def row(self, Py_ssize_t i, from_list=False): + """ + Return the i-th row of this matrix as a dense vector. + + INPUT: + + - ``i`` - integer + + - ``from_list`` - ignored + + EXAMPLES:: + + sage: m = matrix(ZZ, 2, [1, -2, 3, 4]) + sage: m.row(0) + (1, -2) + sage: m.row(1) + (3, 4) + sage: m.row(1, from_list=True) + (3, 4) + sage: m.row(-2) + (1, -2) + + sage: m.row(2) + Traceback (most recent call last): + ... + IndexError: row index out of range + sage: m.row(-3) + Traceback (most recent call last): + ... + IndexError: row index out of range + """ + if i < 0: + i = i + self._nrows + if i < 0 or i >= self._nrows: + raise IndexError("row index out of range") + + cdef Py_ssize_t j + parent = self._row_ambient_module() + cdef Vector_integer_dense v = parent.zero_vector() + for j in range(self._ncols): + fmpz_get_mpz(v._entries[j], fmpz_mat_entry(self._matrix, i, j)) + return v + + def column(self, Py_ssize_t i, from_list=False): + """ + Return the i-th column of this matrix as a dense vector. + + INPUT: + + - ``i`` - integer + + - ``from_list`` - ignored + + EXAMPLES:: + + sage: m = matrix(ZZ, 3, 2, [1, -2, 3, 4, -1, 0]) + sage: m.column(1) + (-2, 4, 0) + sage: m.column(1, from_list=True) + (-2, 4, 0) + sage: m.column(-1) + (-2, 4, 0) + sage: m.column(-2) + (1, 3, -1) + + sage: m.column(2) + Traceback (most recent call last): + ... + IndexError: column index out of range + sage: m.column(-3) + Traceback (most recent call last): + ... + IndexError: column index out of range + """ + if i < 0: + i += self._ncols + if i < 0 or i >= self._ncols: + raise IndexError("column index out of range") + + cdef Py_ssize_t j + parent = self._column_ambient_module() + cdef Vector_integer_dense v = parent.zero_vector() + for j in range(self._nrows): + fmpz_get_mpz(v._entries[j], fmpz_mat_entry(self._matrix, j, i)) + return v + cdef _stack_impl(self, bottom): r""" Return the matrix ``self`` on top of ``bottom``:: diff --git a/src/sage/matrix/matrix_mod2_dense.pyx b/src/sage/matrix/matrix_mod2_dense.pyx index 4b7ae2d67d2..564b21e02ca 100644 --- a/src/sage/matrix/matrix_mod2_dense.pyx +++ b/src/sage/matrix/matrix_mod2_dense.pyx @@ -1965,17 +1965,17 @@ for i from 0 <= i < 256: # assembly instructions, could be faster cpdef inline unsigned long parity(m4ri_word a): """ - Returns the parity of the number of bits in a. + Return the parity of the number of bits in a. EXAMPLES:: sage: from sage.matrix.matrix_mod2_dense import parity sage: parity(1) - 1L + 1 sage: parity(3) - 0L + 0 sage: parity(0x10000101011) - 1L + 1 """ if sizeof(m4ri_word) == 8: a ^= a >> 32 diff --git a/src/sage/matrix/matrix_rational_dense.pyx b/src/sage/matrix/matrix_rational_dense.pyx index 25976778ec7..fa4d2ae51ff 100644 --- a/src/sage/matrix/matrix_rational_dense.pyx +++ b/src/sage/matrix/matrix_rational_dense.pyx @@ -2889,8 +2889,7 @@ cdef class Matrix_rational_dense(Matrix_dense): raise IndexError("row index out of range") cdef Py_ssize_t j - from sage.modules.free_module import FreeModule - parent = FreeModule(self._base_ring, self._ncols) + parent = self._row_ambient_module() cdef Vector_rational_dense v = Vector_rational_dense.__new__(Vector_rational_dense) v._init(self._ncols, parent) for j in range(self._ncols): @@ -2934,8 +2933,7 @@ cdef class Matrix_rational_dense(Matrix_dense): raise IndexError("column index out of range") cdef Py_ssize_t j - from sage.modules.free_module import FreeModule - parent = FreeModule(self._base_ring, self._nrows) + parent = self._column_ambient_module() cdef Vector_rational_dense v = Vector_rational_dense.__new__(Vector_rational_dense) v._init(self._nrows, parent) for j in range(self._nrows): diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 68e42719782..a8b63866843 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -139,9 +139,9 @@ def get_matrix_class(R, nrows, ncols, sparse, implementation): sage: get_matrix_class(CDF, 2, 3, False, 'numpy') - sage: get_matrix_class(GF(25,'x'), 4, 4, False, 'meataxe') # optional: meataxe + sage: get_matrix_class(GF(25,'x'), 4, 4, False, 'meataxe') # optional - meataxe - sage: get_matrix_class(IntegerModRing(3), 4, 4, False, 'meataxe') # optional: meataxe + sage: get_matrix_class(IntegerModRing(3), 4, 4, False, 'meataxe') # optional - meataxe sage: get_matrix_class(IntegerModRing(4), 4, 4, False, 'meataxe') Traceback (most recent call last): @@ -181,7 +181,7 @@ def get_matrix_class(R, nrows, ncols, sparse, implementation): sage: type(matrix(GF(64,'z'), 2, range(4))) - sage: type(matrix(GF(125,'z'), 2, range(4))) # optional: meataxe + sage: type(matrix(GF(125,'z'), 2, range(4))) # optional - meataxe """ diff --git a/src/sage/matrix/matrix_sparse.pyx b/src/sage/matrix/matrix_sparse.pyx index 0d8c4abb46e..9a9114a082b 100644 --- a/src/sage/matrix/matrix_sparse.pyx +++ b/src/sage/matrix/matrix_sparse.pyx @@ -1141,10 +1141,14 @@ cdef class Matrix_sparse(matrix.Matrix): True """ cdef int i, j - from sage.modules.free_module import FreeModule - if self.nrows() != v.degree(): + if self._nrows != v._degree: raise ArithmeticError("number of rows of matrix must equal degree of vector") - s = FreeModule(self.base_ring(), self.ncols(), sparse=v.is_sparse()).zero_vector() + if v.is_sparse_c(): + parent = self._row_ambient_module() + else: + from sage.modules.free_module import FreeModule + parent = FreeModule(self._base_ring, self._ncols, sparse=False) + s = parent.zero_vector() for (i, j), a in self._dict().iteritems(): s[j] += v[i] * a return s @@ -1194,9 +1198,14 @@ cdef class Matrix_sparse(matrix.Matrix): """ cdef int i, j from sage.modules.free_module import FreeModule - if self.ncols() != v.degree(): + if self._ncols != v._degree: raise ArithmeticError("number of columns of matrix must equal degree of vector") - s = FreeModule(v.base_ring(), self.nrows(), sparse=v.is_sparse()).zero_vector() + if v.is_sparse_c(): + parent = self._column_ambient_module() + else: + from sage.modules.free_module import FreeModule + parent = FreeModule(self._base_ring, self._nrows, sparse=False) + s = parent.zero_vector() for (i, j), a in self._dict().iteritems(): s[i] += a * v[j] return s diff --git a/src/sage/matroids/basis_exchange_matroid.pyx b/src/sage/matroids/basis_exchange_matroid.pyx index a061b01c8cf..7b72cd245df 100644 --- a/src/sage/matroids/basis_exchange_matroid.pyx +++ b/src/sage/matroids/basis_exchange_matroid.pyx @@ -1055,7 +1055,7 @@ cdef class BasisExchangeMatroid(Matroid): i=i+1 cdef bitset_t active_rows - bitset_init(active_rows,self.full_rank()+1) + bitset_init(active_rows, self.full_rank()+1) bitset_set_first_n(active_rows, self.full_rank()) i=0 while i>=0: diff --git a/src/sage/matroids/lean_matrix.pyx b/src/sage/matroids/lean_matrix.pyx index ef0b5574cf8..8ea413b9dd8 100644 --- a/src/sage/matroids/lean_matrix.pyx +++ b/src/sage/matroids/lean_matrix.pyx @@ -1745,6 +1745,7 @@ cdef class TernaryMatrix(LeanMatrix): Change number of rows to ``k``. Preserves data. """ cdef long i + cdef mp_bitcnt_t c if k < self._nrows: for i from k <= i < self._nrows: bitset_free(self._M0[i]) @@ -2346,6 +2347,7 @@ cdef class QuaternaryMatrix(LeanMatrix): """ Change number of rows to ``k``. Preserves data. """ + cdef mp_bitcnt_t c if k < self._nrows: for i from k <= i < self._nrows: bitset_free(self._M0[i]) diff --git a/src/sage/misc/dev_tools.py b/src/sage/misc/dev_tools.py index 9864633df04..b08b6857623 100644 --- a/src/sage/misc/dev_tools.py +++ b/src/sage/misc/dev_tools.py @@ -479,7 +479,7 @@ def import_statements(*objects, **kwds): sage: import_statements(sage.combinat.partition_algebra.SetPartitionsAk) from sage.combinat.partition_algebra import SetPartitionsAk sage: import_statements(CIF) - from sage.rings.all import CIF + from sage.rings.cif import CIF sage: import_statements(NaN) from sage.symbolic.constants import NaN sage: import_statements(pi) diff --git a/src/sage/misc/latex.py b/src/sage/misc/latex.py index 5d5ac4e6fb3..1b2e9c65479 100644 --- a/src/sage/misc/latex.py +++ b/src/sage/misc/latex.py @@ -1084,7 +1084,7 @@ def eval(self, x, globals, strip=False, filename=None, debug=None, sage: fn = tmp_filename() sage: latex.eval("$\\ZZ[x]$", locals(), filename=fn) # not tested '' - sage: latex.eval(r"\ThisIsAnInvalidCommand", {}) # optional -- ImageMagick + sage: latex.eval(r"\ThisIsAnInvalidCommand", {}) # optional -- latex ImageMagick An error occurred... No pages of output... """ @@ -1544,10 +1544,10 @@ def add_package_to_preamble_if_available(self, package_name): TESTS:: - sage: latex.add_package_to_preamble_if_available("xypic") + sage: latex.add_package_to_preamble_if_available("tkz-graph") sage: latex.add_package_to_preamble_if_available("nonexistent_package") - sage: latex.extra_preamble() # optional - latex - '\\usepackage{xypic}\n' + sage: latex.extra_preamble() # optional - latex latex_package_tkz_graph + '\\usepackage{tkz-graph}\n' sage: latex.extra_preamble('') """ assert isinstance(package_name, str) @@ -1852,11 +1852,11 @@ def view(objects, title='Sage', debug=False, sep='', tiny=False, sage: from sage.misc.latex import _run_latex_, _latex_file_ sage: g = sage.misc.latex.latex_examples.graph() - sage: latex.add_to_preamble(r"\usepackage{tkz-graph}") + sage: latex.add_to_preamble(r"\usepackage{tkz-graph}") # optional - latex_package_tkz_graph sage: file = os.path.join(SAGE_TMP, "temp.tex") sage: with open(file, 'w') as O: ....: _ = O.write(_latex_file_(g)) - sage: _run_latex_(file, engine="pdflatex") # optional - latex + sage: _run_latex_(file, engine="pdflatex") # optional - latex latex_package_tkz_graph 'pdf' sage: view(4, margin=5, debug=True) # not tested @@ -1965,7 +1965,7 @@ def png(x, filename, density=150, debug=False, EXAMPLES:: sage: from sage.misc.latex import png - sage: png(ZZ[x], os.path.join(SAGE_TMP, "zz.png")) # random, optional - latex + sage: png(ZZ[x], os.path.join(SAGE_TMP, "zz.png")) # random, optional - latex imagemagick """ if not pdflatex: engine = "latex" @@ -2085,7 +2085,7 @@ def repr_lincomb(symbols, coeffs): first = True i = 0 - from sage.rings.all import CC + from sage.rings.cc import CC for c in coeffs: bv = symbols[i] diff --git a/src/sage/misc/lazy_import.pyx b/src/sage/misc/lazy_import.pyx index fcc4291ebc3..2e7f4eaa786 100644 --- a/src/sage/misc/lazy_import.pyx +++ b/src/sage/misc/lazy_import.pyx @@ -416,17 +416,6 @@ cdef class LazyImport(object): """ return str(self.get_object()) - def __unicode__(self): - """ - TESTS:: - - sage: from sage.misc.lazy_import import LazyImport - sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') - sage: str(lazy_ZZ) == str(ZZ) - True - """ - return unicode(self.get_object()) - def __bool__(self): """ TESTS:: diff --git a/src/sage/misc/lazy_string.pyx b/src/sage/misc/lazy_string.pyx index a731d861981..de2878017fa 100644 --- a/src/sage/misc/lazy_string.pyx +++ b/src/sage/misc/lazy_string.pyx @@ -114,13 +114,10 @@ def lazy_string(f, *args, **kwargs): sage: s == 'this is a test' determining string representation True - sage: unicode(s) # py2 - determining string representation - u'this is a test' - """ return _LazyString(f, args, kwargs) + def _make_lazy_string(ftype, fpickle, args, kwargs): """ Used for pickling. @@ -187,12 +184,7 @@ cdef class _LazyString(object): sage: s == 'this is a test' determining string representation True - sage: unicode(s) # py2 - determining string representation - u'this is a test' - """ - def __init__(self, f, args, kwargs): """ INPUT: @@ -211,8 +203,6 @@ cdef class _LazyString(object): l'laziness5' sage: lazy_string("This is %s", ZZ) l'This is Integer Ring' - sage: lazy_string(u"This is %s", ZZ) - lu'This is Integer Ring' """ self.func = f self.args = args @@ -340,18 +330,6 @@ cdef class _LazyString(object): """ return str(self) - def __unicode__(self): - """ - EXAMPLES:: - - sage: from sage.misc.lazy_string import lazy_string - sage: f = lambda: "laziness" - sage: s = lazy_string(f) - sage: unicode(s) # indirect doctest py2 only - u'laziness' - """ - return unicode(self.val()) - def __add__(self, other): """ EXAMPLES:: diff --git a/src/sage/misc/package.py b/src/sage/misc/package.py index d5665f34c3f..84f3d50de72 100644 --- a/src/sage/misc/package.py +++ b/src/sage/misc/package.py @@ -90,7 +90,7 @@ def pip_remote_version(pkg, pypi_url=DEFAULT_PYPI, ignore_URLError=False): sage: from sage.misc.package import pip_remote_version sage: pip_remote_version('beautifulsoup4') # optional - internet # not tested - u'...' + '...' These tests are reliable since the tested package does not exist:: diff --git a/src/sage/misc/prandom.py b/src/sage/misc/prandom.py index 4ec77446cf1..751d5b35129 100644 --- a/src/sage/misc/prandom.py +++ b/src/sage/misc/prandom.py @@ -71,7 +71,7 @@ def _pyrand(): sage: _pyrand() <...random.Random object at 0x...> sage: _pyrand().getrandbits(10) - 114L + 114 """ return current_randstate().python_random() diff --git a/src/sage/misc/randstate.pyx b/src/sage/misc/randstate.pyx index f5c282bdb65..4af9306cb63 100644 --- a/src/sage/misc/randstate.pyx +++ b/src/sage/misc/randstate.pyx @@ -78,11 +78,11 @@ random numbers are generated.) :: sage: set_random_seed(12345) sage: initial_seed() - 12345L + 12345 sage: print(rtest()) (720, -0.612180244315804, 0, (1,3), [ 1, 0, 1, 1, 0 ], 1911581957, 65175, 0.8043027951758298) sage: initial_seed() - 12345L + 12345 If :func:`set_random_seed` is called with no arguments, then a new seed is automatically selected. On operating systems that support it, @@ -107,7 +107,7 @@ random number sequence. :: sage: s = initial_seed() sage: s # random - 336237747258024892084418842839280045662L + 336237747258024892084418842839280045662 sage: set_random_seed(s) sage: r2 = rtest() sage: r == r2 @@ -141,13 +141,13 @@ line, and these wrappers are properly affected by :meth:`set_random_seed`. :: sage: set_random_seed(0) sage: random(), getrandbits(20), uniform(5.0, 10.0), normalvariate(0, 1) - (0.111439293741037, 539332L, 8.26785106378383, 1.3893337539828183) + (0.111439293741037, 539332, 8.26785106378383, 1.3893337539828183) sage: set_random_seed(1) sage: random(), getrandbits(20), uniform(5.0, 10.0), normalvariate(0, 1) - (0.8294022851874259, 624859L, 5.77894484361117, -0.4201366826308758) + (0.8294022851874259, 624859, 5.77894484361117, -0.4201366826308758) sage: set_random_seed(0) sage: random(), getrandbits(20), uniform(5.0, 10.0), normalvariate(0, 1) - (0.111439293741037, 539332L, 8.26785106378383, 1.3893337539828183) + (0.111439293741037, 539332, 8.26785106378383, 1.3893337539828183) That pretty much covers what you need to know for command-line use of this module. Now let's move to what authors of Sage library code @@ -509,11 +509,11 @@ cdef class randstate: sage: r = randstate(54321); r sage: r.seed() - 54321L + 54321 sage: r = randstate(); r sage: r.seed() # random - 305866218880103397618377824640007711767L + 305866218880103397618377824640007711767 Note that creating a :class:`randstate` with a seed of 0 is vastly faster than any other seed (over a thousand times @@ -556,11 +556,11 @@ cdef class randstate: sage: from sage.misc.randstate import randstate sage: r = randstate(314159) sage: r.seed() - 314159L + 314159 sage: r.python_random().random() 0.111439293741037 sage: r.seed() - 314159L + 314159 """ return self._seed @@ -634,7 +634,7 @@ cdef class randstate: sage: set_random_seed(1618) sage: current_randstate().long_seed() - 256056279774514099508607350947089272595L + 256056279774514099508607350947089272595 """ from sage.rings.integer_ring import ZZ return long(ZZ.random_element(long(1)<<128)) @@ -945,7 +945,7 @@ cpdef set_random_seed(seed=None): sage: set_random_seed(5) sage: initial_seed() - 5L + 5 """ global _current_randstate _current_randstate = randstate(seed) @@ -977,7 +977,7 @@ def initial_seed(): sage: set_random_seed(42) sage: initial_seed() - 42L + 42 If you set a random seed (by failing to specify the seed), this is how you retrieve the seed actually chosen by Sage. This can also be @@ -986,7 +986,7 @@ def initial_seed(): sage: set_random_seed() sage: initial_seed() # random - 121030915255244661507561642968348336774L + 121030915255244661507561642968348336774 """ return _current_randstate._seed diff --git a/src/sage/misc/sage_input.py b/src/sage/misc/sage_input.py index 0f0d0a788fb..958a3205b2b 100644 --- a/src/sage/misc/sage_input.py +++ b/src/sage/misc/sage_input.py @@ -418,9 +418,9 @@ def __call__(self, x, coerced=False): sage: sage_input('Icky chars: \0\n\t\b\'\"\200\300\234', verify=True) # Verified 'Icky chars: \x00\n\t\x08\'"\x80\xc0\x9c' - sage: sage_input(u'unicode with spectral: \u1234\U00012345', verify=True) + sage: sage_input('unicode with spectral: \u1234\U00012345', verify=True) # Verified - u'unicode with spectral: \u1234\U00012345' + 'unicode with spectral: \u1234\U00012345' sage: sage_input((2, 3.5, 'Hi'), verify=True) # Verified (2, 3.5, 'Hi') diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index fb2073f7c44..a76f41a8ffb 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -521,15 +521,14 @@ def visit_NameConstant(self, node): EXAMPLES:: - sage: import ast, sage.misc.sageinspect as sms # py3 - sage: visitor = sms.SageArgSpecVisitor() # py3 - sage: vis = lambda x: visitor.visit_NameConstant(ast.parse(x).body[0].value) # py3 - sage: [vis(n) for n in ['True', 'False', 'None']] # py3 + sage: import ast, sage.misc.sageinspect as sms + sage: visitor = sms.SageArgSpecVisitor() + sage: vis = lambda x: visitor.visit_NameConstant(ast.parse(x).body[0].value) + sage: [vis(n) for n in ['True', 'False', 'None']] [True, False, None] - sage: [type(vis(n)) for n in ['True', 'False', 'None']] # py3 + sage: [type(vis(n)) for n in ['True', 'False', 'None']] [, , ] """ - return node.value def visit_arg(self, node): @@ -552,11 +551,11 @@ def visit_arg(self, node): EXAMPLES:: - sage: import ast, sage.misc.sageinspect as sms # py3 - sage: s = "def f(a, b=2, c={'a': [4, 5.5, False]}, d=(None, True)):\n return" # py3 - sage: visitor = sms.SageArgSpecVisitor() # py3 - sage: args = ast.parse(s).body[0].args.args # py3 - sage: [visitor.visit_arg(n) for n in args] # py3 + sage: import ast, sage.misc.sageinspect as sms + sage: s = "def f(a, b=2, c={'a': [4, 5.5, False]}, d=(None, True)):\n return" + sage: visitor = sms.SageArgSpecVisitor() + sage: args = ast.parse(s).body[0].args.args + sage: [visitor.visit_arg(n) for n in args] ['a', 'b', 'c', 'd'] """ return node.arg @@ -1570,10 +1569,6 @@ def foo(x, a='\')"', b={not (2+1==3):'bar'}): return inspect module does):: sage: import inspect - sage: inspect.getargspec(range) # py2 - Traceback (most recent call last): - ... - TypeError: is not a Python function sage: sage_getargspec(range) ArgSpec(args=[], varargs='args', keywords='kwds', defaults=None) @@ -1585,6 +1580,8 @@ def foo(x, a='\')"', b={not (2+1==3):'bar'}): return ....: 'class Foo:\n' ....: ' def __call__(self):\n' ....: ' return None\n' + ....: ' def __module__(self):\n' + ....: ' return "sage.misc.sageinspect"\n' ....: ' def _sage_src_(self):\n' ....: ' return "the source code string"') sage: shell.run_cell('f = Foo()') @@ -1702,20 +1699,20 @@ def formatannotation(annotation, base_module=None): sage: from sage.misc.sageinspect import formatannotation sage: import inspect - sage: def foo(a, *, b:int, **kwargs): # py3 + sage: def foo(a, *, b:int, **kwargs): ....: pass - sage: s = inspect.signature(foo) # py3 + sage: s = inspect.signature(foo) - sage: a = s.parameters['a'].annotation # py3 - sage: a # py3 + sage: a = s.parameters['a'].annotation + sage: a - sage: formatannotation(a) # py3 + sage: formatannotation(a) 'inspect._empty' - sage: b = s.parameters['b'].annotation # py3 - sage: b # py3 + sage: b = s.parameters['b'].annotation + sage: b - sage: formatannotation(b) # py3 + sage: formatannotation(b) 'int' """ if getattr(annotation, '__module__', None) == 'typing': @@ -1723,7 +1720,7 @@ def formatannotation(annotation, base_module=None): if isinstance(annotation, type): if annotation.__module__ in ('builtins', base_module): return annotation.__qualname__ - return annotation.__module__+'.'+annotation.__qualname__ + return annotation.__module__ + '.' + annotation.__qualname__ return repr(annotation) diff --git a/src/sage/modular/abvar/lseries.py b/src/sage/modular/abvar/lseries.py index 11ca70142a5..48c3377a4c5 100644 --- a/src/sage/modular/abvar/lseries.py +++ b/src/sage/modular/abvar/lseries.py @@ -26,7 +26,7 @@ from sage.rings.rational_field import QQ from sage.rings.integer import Integer from sage.rings.infinity import infinity -from sage.rings.all import CC +from sage.rings.cc import CC from sage.modules.free_module import span from sage.misc.misc_c import prod diff --git a/src/sage/modular/arithgroup/farey_symbol.pyx b/src/sage/modular/arithgroup/farey_symbol.pyx index 83689f81bc5..c4e8f9439f0 100644 --- a/src/sage/modular/arithgroup/farey_symbol.pyx +++ b/src/sage/modular/arithgroup/farey_symbol.pyx @@ -27,7 +27,8 @@ from cysignals.signals cimport sig_on, sig_off from sage.libs.gmpxx cimport * -from sage.rings.all import CC, RR +from sage.rings.real_mpfr import RR +from sage.rings.cc import CC from sage.rings.integer cimport Integer from sage.rings.infinity import infinity from .congroup_gammaH import is_GammaH diff --git a/src/sage/modular/hecke/algebra.py b/src/sage/modular/hecke/algebra.py index ffd6364c8bc..5bf05da9a37 100644 --- a/src/sage/modular/hecke/algebra.py +++ b/src/sage/modular/hecke/algebra.py @@ -27,11 +27,11 @@ # **************************************************************************** import sage.rings.infinity -import sage.rings.commutative_algebra from sage.matrix.constructor import matrix from sage.arith.all import lcm, gcd from sage.misc.latex import latex from sage.matrix.matrix_space import MatrixSpace +from sage.rings.ring import CommutativeAlgebra from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.structure.element import Element @@ -103,7 +103,7 @@ def _heckebasis(M): @richcmp_method -class HeckeAlgebra_base(CachedRepresentation, sage.rings.commutative_algebra.CommutativeAlgebra): +class HeckeAlgebra_base(CachedRepresentation, CommutativeAlgebra): """ Base class for algebras of Hecke operators on a fixed Hecke module. @@ -179,7 +179,7 @@ def __init__(self, M): if not module.is_HeckeModule(M): raise TypeError("M (=%s) must be a HeckeModule" % M) self.__M = M - sage.rings.commutative_algebra.CommutativeAlgebra.__init__(self, M.base_ring()) + CommutativeAlgebra.__init__(self, M.base_ring()) def _an_element_impl(self): r""" diff --git a/src/sage/modular/hypergeometric_motive.py b/src/sage/modular/hypergeometric_motive.py index 5a6289355be..9a8cac396b9 100644 --- a/src/sage/modular/hypergeometric_motive.py +++ b/src/sage/modular/hypergeometric_motive.py @@ -1409,14 +1409,14 @@ def H_value(self, p, f, t, ring=None): if ring is None: ring = UniversalCyclotomicField() gamma = self.gamma_array() - q = p ** f + q = p**f m = {r: beta.count(QQ((r, q - 1))) for r in range(q - 1)} D = -min(self.zigzag(x, flip_beta=True) for x in alpha + beta) # also: D = (self.weight() + 1 - m[0]) // 2 M = self.M_value() - Fq = GF(q) + Fq = GF((p, f)) gen = Fq.multiplicative_generator() zeta_q = ring.zeta(q - 1) diff --git a/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py b/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py index cf230339323..fcefc755a72 100644 --- a/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py +++ b/src/sage/modular/modform_hecketriangle/hecke_triangle_group_element.py @@ -22,7 +22,8 @@ from sage.rings.integer_ring import ZZ from sage.rings.infinity import infinity -from sage.rings.all import AA, QQbar, CC +from sage.rings.cc import CC +from sage.rings.qqbar import AA, QQbar from sage.groups.matrix_gps.group_element import MatrixGroupElement_generic from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane diff --git a/src/sage/plot/colors.py b/src/sage/plot/colors.py index 6fdba0f0da0..f46b73699af 100644 --- a/src/sage/plot/colors.py +++ b/src/sage/plot/colors.py @@ -918,12 +918,12 @@ def hls(self): sage: Color(0.3, 0.5, 0.7, space='hls').hls() (0.30000000000000004, 0.5, 0.7) - sage: Color(0.3, 0.5, 0.7, space='hsl').hls() + sage: Color(0.3, 0.5, 0.7, space='hsl').hls() # abs tol 1e-15 (0.30000000000000004, 0.7, 0.5000000000000001) - sage: Color('#aabbcc').hls() + sage: Color('#aabbcc').hls() # abs tol 1e-15 (0.5833333333333334, 0.7333333333333334, 0.25000000000000017) sage: from sage.plot.colors import orchid - sage: orchid.hls() + sage: orchid.hls() # abs tol 1e-15 (0.8396226415094339, 0.6470588235294117, 0.5888888888888889) """ return tuple(map(float, rgb_to_hls(*self._rgb))) @@ -942,9 +942,9 @@ def hsl(self): sage: Color(1,0,0).hsl() (0.0, 1.0, 0.5) sage: from sage.plot.colors import orchid - sage: orchid.hsl() + sage: orchid.hsl() # abs tol 1e-15 (0.8396226415094339, 0.5888888888888889, 0.6470588235294117) - sage: Color('#aabbcc').hsl() + sage: Color('#aabbcc').hsl() # abs tol 1e-15 (0.5833333333333334, 0.25000000000000017, 0.7333333333333334) """ h, l, s = tuple(map(float, rgb_to_hls(*self._rgb))) diff --git a/src/sage/plot/contour_plot.py b/src/sage/plot/contour_plot.py index a7de8a7c912..9e0a2cb6337 100644 --- a/src/sage/plot/contour_plot.py +++ b/src/sage/plot/contour_plot.py @@ -912,7 +912,6 @@ def f(x,y): return cos(x) + sin(y) g._set_extra_kwds(Graphics._extract_kwds_for_show(options, ignore=['xmin', 'xmax'])) - # Was a single contour level explicitly given? If "contours" is # the integer 1, then there will be a single level, but we can't # know what it is because it's determined within matplotlib's @@ -920,11 +919,9 @@ def f(x,y): return cos(x) + sin(y) # there's a single contour and fill=True, we fall through to let # matplotlib complain that "Filled contours require at least 2 # levels." - if ( isinstance(options["contours"], (list, tuple)) - and - len(options["contours"]) == 1 - and - options.get("fill") == False): + if (isinstance(options["contours"], (list, tuple)) + and len(options["contours"]) == 1 + and options.get("fill") is False): # When there's only one level (say, zero), matplotlib doesn't # handle it well. If all of the data lie on one side of that # level -- for example, if f(x,y) >= 0 for all x,y -- then it diff --git a/src/sage/plot/ellipse.py b/src/sage/plot/ellipse.py index 33bdff48d3f..ddba88a5c49 100644 --- a/src/sage/plot/ellipse.py +++ b/src/sage/plot/ellipse.py @@ -272,23 +272,51 @@ def ellipse(center, r1, r2, angle=0, **options): sage: ellipse((0,0),2,1) Graphics object consisting of 1 graphics primitive + .. PLOT:: + + E=ellipse((0,0),2,1) + sphinx_plot(E) + More complicated examples with tilted axes and drawing options:: sage: ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3,linestyle="dashed") Graphics object consisting of 1 graphics primitive + + .. PLOT:: + + E = ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3,linestyle="dashed") + sphinx_plot(E) + + other way to indicate dashed linestyle:: + sage: ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3,linestyle="--") Graphics object consisting of 1 graphics primitive - :: + .. PLOT:: + + E =ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3,linestyle='--') + sphinx_plot(E) + + with colors :: sage: ellipse((0,0),3,1,pi/6,fill=True,edgecolor='black',facecolor='red') Graphics object consisting of 1 graphics primitive + + .. PLOT:: + + E=ellipse((0,0),3,1,pi/6,fill=True,edgecolor='black',facecolor='red') + sphinx_plot(E) We see that ``rgbcolor`` overrides these other options, as this plot is green:: sage: ellipse((0,0),3,1,pi/6,fill=True,edgecolor='black',facecolor='red',rgbcolor='green') Graphics object consisting of 1 graphics primitive + + .. PLOT:: + + E=ellipse((0,0),3,1,pi/6,fill=True,edgecolor='black',facecolor='red',rgbcolor='green') + sphinx_plot(E) The default aspect ratio for ellipses is 1.0:: @@ -306,6 +334,12 @@ def ellipse(center, r1, r2, angle=0, **options): sage: ellipse((0,0),2,1,legend_label="My ellipse", legend_color='green') Graphics object consisting of 1 graphics primitive + + .. PLOT:: + + E=ellipse((0,0),2,1,legend_label="My ellipse", legend_color='green') + sphinx_plot(E) + """ from sage.plot.all import Graphics g = Graphics() diff --git a/src/sage/plot/hyperbolic_arc.py b/src/sage/plot/hyperbolic_arc.py index 1acc1e1fdba..890644345eb 100644 --- a/src/sage/plot/hyperbolic_arc.py +++ b/src/sage/plot/hyperbolic_arc.py @@ -24,7 +24,7 @@ from sage.plot.bezier_path import BezierPath from sage.misc.decorators import options, rename_keyword -from sage.rings.all import CC +from sage.rings.cc import CC class HyperbolicArc(BezierPath): diff --git a/src/sage/plot/hyperbolic_polygon.py b/src/sage/plot/hyperbolic_polygon.py index 3b1f2693fdb..8cf05c7ea4c 100644 --- a/src/sage/plot/hyperbolic_polygon.py +++ b/src/sage/plot/hyperbolic_polygon.py @@ -25,7 +25,7 @@ from sage.plot.bezier_path import BezierPath from sage.misc.decorators import options, rename_keyword -from sage.rings.all import CC +from sage.rings.cc import CC class HyperbolicPolygon(BezierPath): diff --git a/src/sage/plot/hyperbolic_regular_polygon.py b/src/sage/plot/hyperbolic_regular_polygon.py index aafea99814c..1a1010ae99e 100644 --- a/src/sage/plot/hyperbolic_regular_polygon.py +++ b/src/sage/plot/hyperbolic_regular_polygon.py @@ -18,7 +18,7 @@ from sage.plot.hyperbolic_polygon import HyperbolicPolygon from sage.plot.all import Graphics -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.integer import Integer from sage.misc.decorators import options, rename_keyword from sage.symbolic.constants import pi, e diff --git a/src/sage/plot/plot.py b/src/sage/plot/plot.py index 7bec1ca4dce..1bf0034b6b3 100644 --- a/src/sage/plot/plot.py +++ b/src/sage/plot/plot.py @@ -3047,8 +3047,7 @@ def list_plot(data, plotjoined=False, **kwargs): # Need to catch IndexError because if data is, say, [(0, 1), (1, I)], # point3d() throws an IndexError on the (0,1) before it ever # gets to (1, I). - from sage.rings.complex_mpfr import ComplexField - CC = ComplexField() + from sage.rings.cc import CC # if we get here, we already did "list(enumerate(data))", # so look at z[1] in inner list data = [(z.real(), z.imag()) for z in [CC(z[1]) for z in data]] diff --git a/src/sage/quadratic_forms/quadratic_form__local_density_congruence.py b/src/sage/quadratic_forms/quadratic_form__local_density_congruence.py index 8864130c618..f02417cf4f5 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_density_congruence.py +++ b/src/sage/quadratic_forms/quadratic_form__local_density_congruence.py @@ -1,14 +1,11 @@ """ Local Density Congruence """ - - ########################################################################## -## Methods which compute the local densities for representing a number -## by a quadratic form at a prime (possibly subject to additional -## congruence conditions). +# Methods which compute the local densities for representing a number +# by a quadratic form at a prime (possibly subject to additional +# congruence conditions). ########################################################################## - from copy import deepcopy from sage.sets.set import Set @@ -19,30 +16,27 @@ from sage.quadratic_forms.count_local_2 import count_modp__by_gauss_sum - - def count_modp_solutions__by_Gauss_sum(self, p, m): """ - Returns the number of solutions of `Q(x) = m (mod p)` of a + Return the number of solutions of `Q(x) = m (mod p)` of a non-degenerate quadratic form over the finite field `Z/pZ`, where `p` is a prime number > 2. - Note: We adopt the useful convention that a zero-dimensional - quadratic form has exactly one solution always (i.e. the empty - vector). + .. NOTE:: - These are defined in Table 1 on p363 of Hanke's "Local - Densities..." paper. + We adopt the useful convention that a zero-dimensional + quadratic form has exactly one solution always (i.e. the empty + vector). - INPUT: + These are defined in Table 1 on p363 of Hanke's "Local Densities..." paper. - `p` -- a prime number > 2 + INPUT: - `m` -- an integer + - `p` -- a prime number > 2 - OUTPUT: + - `m` -- an integer - an integer >= 0 + OUTPUT: an integer >= 0 EXAMPLES:: @@ -50,25 +44,18 @@ def count_modp_solutions__by_Gauss_sum(self, p, m): sage: [Q.count_modp_solutions__by_Gauss_sum(3, m) for m in range(3)] [9, 6, 12] - :: - sage: Q = DiagonalQuadraticForm(ZZ, [1,1,2]) sage: [Q.count_modp_solutions__by_Gauss_sum(3, m) for m in range(3)] [9, 12, 6] - """ if self.dim() == 0: return 1 - else: - return count_modp__by_gauss_sum(self.dim(), p, m, self.Gram_det()) - - - + return count_modp__by_gauss_sum(self.dim(), p, m, self.Gram_det()) def local_good_density_congruence_odd(self, p, m, Zvec, NZvec): """ - Finds the Good-type local density of Q representing `m` at `p`. + Find the Good-type local density of Q representing `m` at `p`. (Assuming that `p` > 2 and Q is given in local diagonal form.) The additional congruence condition arguments Zvec and NZvec can @@ -77,22 +64,22 @@ def local_good_density_congruence_odd(self, p, m, Zvec, NZvec): = [] returns no solutions always while NZvec = None imposes no additional condition. - TO DO: Add type checking for Zvec, NZvec, and that Q is in local - normal form. + .. TODO:: - INPUT: + Add type checking for Zvec, NZvec, and that Q is in local + normal form. - Q -- quadratic form assumed to be diagonal and p-integral + INPUT: - `p` -- a prime number + - Q -- quadratic form assumed to be diagonal and p-integral - `m` -- an integer + - `p` -- a prime number - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None + - `m` -- an integer - OUTPUT: + - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None - a rational number + OUTPUT: a rational number EXAMPLES:: @@ -109,62 +96,55 @@ def local_good_density_congruence_odd(self, p, m, Zvec, NZvec): """ n = self.dim() - ## Put the Zvec congruence condition in a standard form + # Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] - - ## Sanity Check on Zvec and NZvec: - ## ------------------------------- + # Sanity Check on Zvec and NZvec: + # ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") + # Assuming Q is diagonal, find the indices of the p-unit (diagonal) entries + UnitVec = Set(i for i in range(n) if self[i, i] % p) + NonUnitVec = Set(range(n)) - UnitVec - - ## Assuming Q is diagonal, find the indices of the p-unit (diagonal) entries - UnitVec = [i for i in range(n) if (self[i,i] % p) != 0] - NonUnitVec = list(Set(range(n)) - Set(UnitVec)) - - - ## Take cases on the existence of additional non-zero congruence conditions (mod p) - UnitVec_minus_Zvec = list(Set(UnitVec) - Set(Zvec)) - NonUnitVec_minus_Zvec = list(Set(NonUnitVec) - Set(Zvec)) + # Take cases on the existence of additional non-zero congruence conditions (mod p) + UnitVec_minus_Zvec = list(UnitVec - Set(Zvec)) + NonUnitVec_minus_Zvec = list(NonUnitVec - Set(Zvec)) Q_Unit_minus_Zvec = self.extract_variables(UnitVec_minus_Zvec) - if (NZvec is None): - if m % p != 0: - total = Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum(p, m) * p**len(NonUnitVec_minus_Zvec) ## m != 0 (mod p) + if NZvec is None: + if m % p: + total = Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum(p, m) * p**len(NonUnitVec_minus_Zvec) else: - total = (Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum(p, m) - 1) * p**len(NonUnitVec_minus_Zvec) ## m == 0 (mod p) + total = (Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum(p, m) - 1) * p**len(NonUnitVec_minus_Zvec) else: - UnitVec_minus_ZNZvec = list(Set(UnitVec) - (Set(Zvec) + Set(NZvec))) - NonUnitVec_minus_ZNZvec = list(Set(NonUnitVec) - (Set(Zvec) + Set(NZvec))) + UnitVec_minus_ZNZvec = list(UnitVec - (Set(Zvec) + Set(NZvec))) + NonUnitVec_minus_ZNZvec = list(NonUnitVec - (Set(Zvec) + Set(NZvec))) Q_Unit_minus_ZNZvec = self.extract_variables(UnitVec_minus_ZNZvec) - if m % p != 0: ## m != 0 (mod p) + if m % p: total = Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum(p, m) * p**len(NonUnitVec_minus_Zvec) \ - - Q_Unit_minus_ZNZvec.count_modp_solutions__by_Gauss_sum(p, m) * p**len(NonUnitVec_minus_ZNZvec) - else: ## m == 0 (mod p) + - Q_Unit_minus_ZNZvec.count_modp_solutions__by_Gauss_sum(p, m) * p**len(NonUnitVec_minus_ZNZvec) + else: total = (Q_Unit_minus_Zvec.count_modp_solutions__by_Gauss_sum(p, m) - 1) * p**len(NonUnitVec_minus_Zvec) \ - - (Q_Unit_minus_ZNZvec.count_modp_solutions__by_Gauss_sum(p, m) - 1) * p**len(NonUnitVec_minus_ZNZvec) + - (Q_Unit_minus_ZNZvec.count_modp_solutions__by_Gauss_sum(p, m) - 1) * p**len(NonUnitVec_minus_ZNZvec) - ## Return the Good-type representation density - good_density = QQ(total) / p**(n-1) + # Return the Good-type representation density + good_density = QQ(total) / p**(n - 1) return good_density - - def local_good_density_congruence_even(self, m, Zvec, NZvec): """ - Finds the Good-type local density of Q representing `m` at `p=2`. + Find the Good-type local density of Q representing `m` at `p=2`. (Assuming Q is given in local diagonal form.) - The additional congruence condition arguments Zvec and NZvec can be either a list of indices or None. Zvec = [] is equivalent to Zvec = None which both impose no additional conditions, but NZvec @@ -179,23 +159,22 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec): otherwise all Jordan blocks are 1x1, and so there the indices and Jordan blocks coincide. - TO DO: Add type checking for Zvec, NZvec, and that Q is in local - normal form. + .. TODO:: + Add type checking for Zvec, NZvec, and that Q is in local + normal form. INPUT: - Q -- quadratic form assumed to be block diagonal and 2-integral + - Q -- quadratic form assumed to be block diagonal and 2-integral - `p` -- a prime number + - `p` -- a prime number - `m` -- an integer + - `m` -- an integer - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None + - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None - OUTPUT: - - a rational number + OUTPUT: a rational number EXAMPLES:: @@ -246,63 +225,59 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec): """ n = self.dim() - ## Put the Zvec congruence condition in a standard form + # Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] - - ## Sanity Check on Zvec and NZvec: - ## ------------------------------- + # Sanity Check on Zvec and NZvec: + # ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") - - - ## Find the indices of x for which the associated Jordan blocks are non-zero mod 8 TO DO: Move this to special Jordan block code separately! - ## ------------------------------------------------------------------------------- + # Find the indices of x for which the associated Jordan blocks are non-zero mod 8 TODO: Move this to special Jordan block code separately! + # ------------------------------------------------------------------------------- Not8vec = [] for i in range(n): - ## DIAGNOSTIC + # DIAGNOSTIC verbose(" i = " + str(i)) verbose(" n = " + str(n)) verbose(" Not8vec = " + str(Not8vec)) nz_flag = False - ## Check if the diagonal entry isn't divisible 8 - if ((self[i,i] % 8) != 0): + # Check if the diagonal entry isn't divisible 8 + if self[i, i] % 8: nz_flag = True - ## Check appropriate off-diagonal entries aren't divisible by 8 + # Check appropriate off-diagonal entries aren't divisible by 8 else: - ## Special check for first off-diagonal entry - if ((i == 0) and ((self[i,i+1] % 8) != 0)): + # Special check for first off-diagonal entry + if i == 0 and self[i, i + 1] % 8: nz_flag = True - ## Special check for last off-diagonal entry - elif ((i == n-1) and ((self[i-1,i] % 8) != 0)): + # Special check for last off-diagonal entry + elif i == n - 1 and self[i - 1, i] % 8: nz_flag = True - ## Check for the middle off-diagonal entries + # Check for the middle off-diagonal entries else: - if ( (i > 0) and (i < n-1) and (((self[i,i+1] % 8) != 0) or ((self[i-1,i] % 8) != 0)) ): + if (i > 0) and (i < n - 1) and (self[i, i + 1] % 8 or + self[i - 1, i] % 8): nz_flag = True - ## Remember the (vector) index if it's not part of a Jordan block of norm divisible by 8 + # Remember the (vector) index if it's not part of a Jordan block of norm divisible by 8 if nz_flag: Not8vec += [i] + # Compute the number of Good-type solutions mod 8: + # ------------------------------------------------ - - ## Compute the number of Good-type solutions mod 8: - ## ------------------------------------------------ - - ## Setup the indexing sets for additional zero congruence solutions + # Setup the indexing sets for additional zero congruence solutions Q_Not8 = self.extract_variables(Not8vec) Not8 = Set(Not8vec) Is8 = Set(range(n)) - Not8 @@ -312,15 +287,13 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec): Z_Is8 = Is8.intersection(Z) Is8_minus_Z = Is8 - Z_Is8 - - ## DIAGNOSTIC + # DIAGNOSTIC verbose("Z = " + str(Z)) verbose("Z_Not8 = " + str(Z_Not8)) verbose("Z_Is8 = " + str(Z_Is8)) verbose("Is8_minus_Z = " + str(Is8_minus_Z)) - - ## Take cases on the existence of additional non-zero congruence conditions (mod 2) + # Take cases on the existence of additional non-zero congruence conditions (mod 2) if NZvec is None: total = (4 ** len(Z_Is8)) * (8 ** len(Is8_minus_Z)) \ * Q_Not8.count_congruence_solutions__good_type(2, 3, m, list(Z_Not8), None) @@ -330,7 +303,7 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec): ZNZ_Is8 = Is8.intersection(ZNZ) Is8_minus_ZNZ = Is8 - ZNZ_Is8 - ## DIAGNOSTIC + # DIAGNOSTIC verbose("ZNZ = " + str(ZNZ)) verbose("ZNZ_Not8 = " + str(ZNZ_Not8)) verbose("ZNZ_Is8 = " + str(ZNZ_Is8)) @@ -341,45 +314,34 @@ def local_good_density_congruence_even(self, m, Zvec, NZvec): - (4 ** len(ZNZ_Is8)) * (8 ** len(Is8_minus_ZNZ)) \ * Q_Not8.count_congruence_solutions__good_type(2, 3, m, list(ZNZ_Not8), None) - - ## DIAGNOSTIC + # DIAGNOSTIC verbose("total = " + str(total)) - - ## Return the associated Good-type representation density - good_density = QQ(total) / 8**(n-1) - return good_density - - - - - - + # Return the associated Good-type representation density + return QQ(total) / 8**(n - 1) def local_good_density_congruence(self, p, m, Zvec=None, NZvec=None): """ - Finds the Good-type local density of Q representing `m` at `p`. + Find the Good-type local density of Q representing `m` at `p`. (Front end routine for parity specific routines for p.) - TO DO: Add Documentation about the additional congruence - conditions Zvec and NZvec. - + .. TODO:: + Add Documentation about the additional congruence + conditions Zvec and NZvec. INPUT: - Q -- quadratic form assumed to be block diagonal and p-integral + - Q -- quadratic form assumed to be block diagonal and p-integral - `p` -- a prime number + - `p` -- a prime number - `m` -- an integer + - `m` -- an integer - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None + - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None - OUTPUT: - - a rational number + OUTPUT: a rational number EXAMPLES:: @@ -398,7 +360,7 @@ def local_good_density_congruence(self, p, m, Zvec=None, NZvec=None): 8/9 """ - ## DIAGNOSTIC + # DIAGNOSTIC verbose(" In local_good_density_congruence with ") verbose(" Q is: \n" + str(self)) verbose(" p = " + str(p)) @@ -406,67 +368,51 @@ def local_good_density_congruence(self, p, m, Zvec=None, NZvec=None): verbose(" Zvec = " + str(Zvec)) verbose(" NZvec = " + str(NZvec)) - ## Put the Zvec congruence condition in a standard form + # Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] - n = self.dim() - ## Sanity Check on Zvec and NZvec: - ## ------------------------------- + # Sanity Check on Zvec and NZvec: + # ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") + # There was here a commented-out check that Q is in local normal form + # (it often may not be since the reduction procedure + # often mixes up the order of the valuations...) + # This commented-out code was removed in ticket #32960 - - ## Check that Q is in local normal form -- should replace this with a diagonalization check? - ## (it often may not be since the reduction procedure - ## often mixes up the order of the valuations...) - # - #if (self != self.local_normal_form(p)) - # print "Warning in local_good_density_congruence: Q is not in local normal form! \n"; - - - - - ## Decide which routine to use to compute the Good-type density - if (p > 2): + # Decide which routine to use to compute the Good-type density + if p > 2: return self.local_good_density_congruence_odd(p, m, Zvec, NZvec) - if (p == 2): + if p == 2: return self.local_good_density_congruence_even(m, Zvec, NZvec) raise RuntimeError("\n Error in Local_Good_Density: The 'prime' p = " + str(p) + " is < 2. \n") - - - - - def local_zero_density_congruence(self, p, m, Zvec=None, NZvec=None): """ - Finds the Zero-type local density of Q representing `m` at `p`, + Find the Zero-type local density of Q representing `m` at `p`, allowing certain congruence conditions mod p. - INPUT: - Q -- quadratic form assumed to be block diagonal and `p`-integral - - `p` -- a prime number + - Q -- quadratic form assumed to be block diagonal and `p`-integral - `m` -- an integer + - `p` -- a prime number - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None + - `m` -- an integer - OUTPUT: + - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None - a rational number + OUTPUT: a rational number EXAMPLES:: @@ -493,7 +439,7 @@ def local_zero_density_congruence(self, p, m, Zvec=None, NZvec=None): 8/81 """ - ## DIAGNOSTIC + # DIAGNOSTIC verbose(" In local_zero_density_congruence with ") verbose(" Q is: \n" + str(self)) verbose(" p = " + str(p)) @@ -501,56 +447,46 @@ def local_zero_density_congruence(self, p, m, Zvec=None, NZvec=None): verbose(" Zvec = " + str(Zvec)) verbose(" NZvec = " + str(NZvec)) - ## Put the Zvec congruence condition in a standard form + # Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] - n = self.dim() - ## Sanity Check on Zvec and NZvec: - ## ------------------------------- + # Sanity Check on Zvec and NZvec: + # ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") - p2 = p * p - ## Check some conditions for no zero-type solutions to exist - if ((m % (p2) != 0) or (NZvec is not None)): + # Check some conditions for no zero-type solutions to exist + if m % p2 or NZvec is not None: return 0 - ## Use the reduction procedure to return the result + # Use the reduction procedure to return the result return self.local_density_congruence(p, m / p2, None, None) / p**(self.dim() - 2) - - - - - def local_badI_density_congruence(self, p, m, Zvec=None, NZvec=None): """ - Finds the Bad-type I local density of Q representing `m` at `p`. + Find the Bad-type I local density of Q representing `m` at `p`. (Assuming that p > 2 and Q is given in local diagonal form.) - INPUT: - Q -- quadratic form assumed to be block diagonal and `p`-integral + - Q -- quadratic form assumed to be block diagonal and `p`-integral - `p` -- a prime number + - `p` -- a prime number - `m` -- an integer + - `m` -- an integer - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None + - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None - OUTPUT: - - a rational number + OUTPUT: a rational number EXAMPLES:: @@ -600,7 +536,7 @@ def local_badI_density_congruence(self, p, m, Zvec=None, NZvec=None): """ - ## DIAGNOSTIC + # DIAGNOSTIC verbose(" In local_badI_density_congruence with ") verbose(" Q is: \n" + str(self)) verbose(" p = " + str(p)) @@ -608,140 +544,114 @@ def local_badI_density_congruence(self, p, m, Zvec=None, NZvec=None): verbose(" Zvec = " + str(Zvec)) verbose(" NZvec = " + str(NZvec)) - ## Put the Zvec congruence condition in a standard form + # Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] - n = self.dim() - - - ## Sanity Check on Zvec and NZvec: - ## ------------------------------- + # Sanity Check on Zvec and NZvec: + # ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") - - - ## Define the indexing set S_0, and determine if S_1 is empty: - ## ----------------------------------------------------------- + # Define the indexing set S_0, and determine if S_1 is empty: + # ----------------------------------------------------------- S0 = [] - S1_empty_flag = True ## This is used to check if we should be computing BI solutions at all! - ## (We should really to this earlier, but S1 must be non-zero to proceed.) + S1_empty_flag = True + # This is used to check if we should be computing BI solutions at all! + # (We should really to this earlier, but S1 must be non-zero to proceed.) - ## Find the valuation of each variable (which will be the same over 2x2 blocks), - ## remembering those of valuation 0 and if an entry of valuation 1 exists. + # Find the valuation of each variable (which will be the same over 2x2 blocks), + # remembering those of valuation 0 and if an entry of valuation 1 exists. for i in range(n): - ## Compute the valuation of each index, allowing for off-diagonal terms - if (self[i,i] == 0): - if (i == 0): - val = valuation(self[i,i+1], p) ## Look at the term to the right + # Compute the valuation of each index, allowing for off-diagonal terms + if self[i, i] == 0: + if i == 0: + val = valuation(self[i, i + 1], p) # Look at the term to the right else: - if (i == n-1): - val = valuation(self[i-1,i], p) ## Look at the term above + if i == n - 1: + val = valuation(self[i - 1, i], p) # Look at the term above else: - val = valuation(self[i,i+1] + self[i-1,i], p) ## Finds the valuation of the off-diagonal term since only one isn't zero + val = valuation(self[i, i + 1] + self[i - 1, i], p) # Finds the valuation of the off-diagonal term since only one isn't zero else: - val = valuation(self[i,i], p) + val = valuation(self[i, i], p) - if (val == 0): + if val == 0: S0 += [i] - elif (val == 1): - S1_empty_flag = False ## Need to have a non-empty S1 set to proceed with Bad-type I reduction... + elif val == 1: + S1_empty_flag = False # Need to have a non-empty S1 set to proceed with Bad-type I reduction... - - - - - ## Check that S1 is non-empty and p|m to proceed, otherwise return no solutions. - if S1_empty_flag or m % p != 0: + # Check that S1 is non-empty and p|m to proceed, otherwise return no solutions. + if S1_empty_flag or m % p: return 0 - ## Check some conditions for no bad-type I solutions to exist + # Check some conditions for no bad-type I solutions to exist if (NZvec is not None) and (len(Set(S0).intersection(Set(NZvec))) != 0): return 0 - - - ## Check that the form is primitive... WHY DO WE NEED TO DO THIS?!? - if (S0 == []): + # Check that the form is primitive... WHY DO WE NEED TO DO THIS?!? + if not S0: print(" Using Q = " + str(self)) print(" and p = " + str(p)) raise RuntimeError("Oops! The form is not primitive!") - - - ## DIAGNOSTIC + # DIAGNOSTIC verbose(" m = " + str(m) + " p = " + str(p)) verbose(" S0 = " + str(S0)) verbose(" len(S0) = " + str(len(S0))) - - - ## Make the form Qnew for the reduction procedure: - ## ----------------------------------------------- - Qnew = deepcopy(self) ## TO DO: DO THIS WITHOUT A copy(). =) + # Make the form Qnew for the reduction procedure: + # ----------------------------------------------- + Qnew = deepcopy(self) # TODO: DO THIS WITHOUT A copy() for i in range(n): if i in S0: - Qnew[i,i] = p * Qnew[i,i] - if ((p == 2) and (i < n-1)): - Qnew[i,i+1] = p * Qnew[i,i+1] + Qnew[i, i] = p * Qnew[i, i] + if ((p == 2) and (i < n - 1)): + Qnew[i, i + 1] = p * Qnew[i, i + 1] else: - Qnew[i,i] = Qnew[i,i] / p - if ((p == 2) and (i < n-1)): - Qnew[i,i+1] = Qnew[i,i+1] / p - - + Qnew[i, i] = Qnew[i, i] / p + if ((p == 2) and (i < n - 1)): + Qnew[i, i + 1] = Qnew[i, i + 1] / p - ## DIAGNOSTIC + # DIAGNOSTIC verbose("\n\n Check of Bad-type I reduction: \n") verbose(" Q is " + str(self)) verbose(" Qnew is " + str(Qnew)) verbose(" p = " + str(p)) - verbose(" m / p = " + str(m/p)) + verbose(" m / p = " + str(m / p)) verbose(" NZvec " + str(NZvec)) - - - ## Do the reduction - Zvec_geq_1 = list(Set([i for i in Zvec if i not in S0])) + # Do the reduction + Zvec_geq_1 = list(Set([i for i in Zvec if i not in S0])) if NZvec is None: NZvec_geq_1 = NZvec else: - NZvec_geq_1 = list(Set([i for i in NZvec if i not in S0])) + NZvec_geq_1 = list(Set([i for i in NZvec if i not in S0])) return QQ(p**(1 - len(S0))) * Qnew.local_good_density_congruence(p, m / p, Zvec_geq_1, NZvec_geq_1) - - - - - def local_badII_density_congruence(self, p, m, Zvec=None, NZvec=None): """ - Finds the Bad-type II local density of Q representing `m` at `p`. + Find the Bad-type II local density of Q representing `m` at `p`. (Assuming that `p` > 2 and Q is given in local diagonal form.) + INPUT: - INPUT: - - Q -- quadratic form assumed to be block diagonal and p-integral - - `p` -- a prime number + - Q -- quadratic form assumed to be block diagonal and p-integral - `m` -- an integer + - `p` -- a prime number - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None + - `m` -- an integer - OUTPUT: + - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None - a rational number + OUTPUT: a rational number EXAMPLES:: @@ -776,7 +686,7 @@ def local_badII_density_congruence(self, p, m, Zvec=None, NZvec=None): 4/9 """ - ## DIAGNOSTIC + # DIAGNOSTIC verbose(" In local_badII_density_congruence with ") verbose(" Q is: \n" + str(self)) verbose(" p = " + str(p)) @@ -784,43 +694,40 @@ def local_badII_density_congruence(self, p, m, Zvec=None, NZvec=None): verbose(" Zvec = " + str(Zvec)) verbose(" NZvec = " + str(NZvec)) - ## Put the Zvec congruence condition in a standard form + # Put the Zvec congruence condition in a standard form if Zvec is None: Zvec = [] - n = self.dim() - - ## Sanity Check on Zvec and NZvec: - ## ------------------------------- + # Sanity Check on Zvec and NZvec: + # ------------------------------- Sn = Set(range(n)) if (Zvec is not None) and (len(Set(Zvec) + Sn) > n): raise RuntimeError("Zvec must be a subset of {0, ..., n-1}.") if (NZvec is not None) and (len(Set(NZvec) + Sn) > n): raise RuntimeError("NZvec must be a subset of {0, ..., n-1}.") - - ## Define the indexing sets S_i: - ## ----------------------------- + # Define the indexing sets S_i: + # ----------------------------- S0 = [] S1 = [] S2plus = [] for i in range(n): - ## Compute the valuation of each index, allowing for off-diagonal terms - if (self[i,i] == 0): - if (i == 0): - val = valuation(self[i,i+1], p) ## Look at the term to the right - elif (i == n-1): - val = valuation(self[i-1,i], p) ## Look at the term above + # Compute the valuation of each index, allowing for off-diagonal terms + if self[i, i] == 0: + if i == 0: + val = valuation(self[i, i + 1], p) # Look at the term to the right + elif i == n - 1: + val = valuation(self[i - 1, i], p) # Look at the term above else: - val = valuation(self[i,i+1] + self[i-1,i], p) ## Finds the valuation of the off-diagonal term since only one isn't zero + val = valuation(self[i, i + 1] + self[i - 1, i], p) # Finds the valuation of the off-diagonal term since only one isn't zero else: - val = valuation(self[i,i], p) + val = valuation(self[i, i], p) - ## Sort the indices into disjoint sets by their valuation + # Sort the indices into disjoint sets by their valuation if (val == 0): S0 += [i] elif (val == 1): @@ -828,92 +735,70 @@ def local_badII_density_congruence(self, p, m, Zvec=None, NZvec=None): elif (val >= 2): S2plus += [i] - - - ## Check that S2 is non-empty and p^2 divides m to proceed, otherwise return no solutions. + # Check that S2 is non-empty and p^2 divides m to proceed, otherwise return no solutions. p2 = p * p - if (S2plus == []) or (m % p2 != 0): + if not S2plus or m % p2: return 0 - - - - ## Check some conditions for no bad-type II solutions to exist + # Check some conditions for no bad-type II solutions to exist if (NZvec is not None) and (len(Set(S2plus).intersection(Set(NZvec))) == 0): return 0 - - - ## Check that the form is primitive... WHY IS THIS NECESSARY? - if (S0 == []): + # Check that the form is primitive... WHY IS THIS NECESSARY? + if not S0: print(" Using Q = " + str(self)) print(" and p = " + str(p)) raise RuntimeError("Oops! The form is not primitive!") - - - - ## DIAGNOSTIC + # DIAGNOSTIC verbose("\n Entering BII routine ") verbose(" S0 is " + str(S0)) verbose(" S1 is " + str(S1)) verbose(" S2plus is " + str(S2plus)) verbose(" m = " + str(m) + " p = " + str(p)) - - - - - ## Make the form Qnew for the reduction procedure: - ## ----------------------------------------------- - Qnew = deepcopy(self) ## TO DO: DO THIS WITHOUT A copy(). =) + # Make the form Qnew for the reduction procedure: + # ----------------------------------------------- + Qnew = deepcopy(self) # TODO: DO THIS WITHOUT A copy() for i in range(n): if i in S2plus: - Qnew[i,i] = Qnew[i,i] / p2 - if (p == 2) and (i < n-1): - Qnew[i,i+1] = Qnew[i,i+1] / p2 + Qnew[i, i] = Qnew[i, i] / p2 + if (p == 2) and (i < n - 1): + Qnew[i, i + 1] = Qnew[i, i + 1] / p2 - ## DIAGNOSTIC + # DIAGNOSTIC verbose("\n\n Check of Bad-type II reduction: \n") verbose(" Q is " + str(self)) verbose(" Qnew is " + str(Qnew)) - - - ## Perform the reduction formula - Zvec_geq_2 = list(Set([i for i in Zvec if i in S2plus])) + # Perform the reduction formula + Zvec_geq_2 = list(Set([i for i in Zvec if i in S2plus])) if NZvec is None: NZvec_geq_2 = NZvec else: - NZvec_geq_2 = list(Set([i for i in NZvec if i in S2plus])) - - return QQ(p**(len(S2plus) + 2 - n)) \ - * (Qnew.local_density_congruence(p, m / p2, Zvec_geq_2, NZvec_geq_2) \ - - Qnew.local_density_congruence(p, m / p2, S2plus , NZvec_geq_2)) - - - + NZvec_geq_2 = list(Set([i for i in NZvec if i in S2plus])) + diff = Qnew.local_density_congruence(p, m / p2, Zvec_geq_2, NZvec_geq_2) + diff -= Qnew.local_density_congruence(p, m / p2, S2plus, NZvec_geq_2) + return QQ(p**(len(S2plus) + 2 - n)) * diff def local_bad_density_congruence(self, p, m, Zvec=None, NZvec=None): """ - Finds the Bad-type local density of Q representing + Find the Bad-type local density of Q representing `m` at `p`, allowing certain congruence conditions mod `p`. INPUT: - Q -- quadratic form assumed to be block diagonal and p-integral - - `p` -- a prime number + - Q -- quadratic form assumed to be block diagonal and p-integral - `m` -- an integer + - `p` -- a prime number - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None + - `m` -- an integer - OUTPUT: + - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None - a rational number + OUTPUT: a rational number EXAMPLES:: @@ -952,30 +837,27 @@ def local_bad_density_congruence(self, p, m, Zvec=None, NZvec=None): """ return self.local_badI_density_congruence(p, m, Zvec, NZvec) + self.local_badII_density_congruence(p, m, Zvec, NZvec) - - ######################################################### -## local_density and local_density_congruence routines ## +# local_density and local_density_congruence routines ## ######################################################### + def local_density_congruence(self, p, m, Zvec=None, NZvec=None): """ - Finds the local density of Q representing `m` at `p`, + Find the local density of Q representing `m` at `p`, allowing certain congruence conditions mod `p`. INPUT: - Q -- quadratic form assumed to be block diagonal and p-integral - - `p` -- a prime number + - Q -- quadratic form assumed to be block diagonal and p-integral - `m` -- an integer + - `p` -- a prime number - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None + - `m` -- an integer - OUTPUT: + - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None - a rational number + OUTPUT: a rational number EXAMPLES:: @@ -1022,34 +904,32 @@ def local_density_congruence(self, p, m, Zvec=None, NZvec=None): 2/9 sage: Q.local_density_congruence(3, 18, None, None) 4/9 - """ return self.local_good_density_congruence(p, m, Zvec, NZvec) \ - + self.local_zero_density_congruence(p, m, Zvec, NZvec) \ - + self.local_bad_density_congruence(p, m, Zvec, NZvec) - + + self.local_zero_density_congruence(p, m, Zvec, NZvec) \ + + self.local_bad_density_congruence(p, m, Zvec, NZvec) def local_primitive_density_congruence(self, p, m, Zvec=None, NZvec=None): """ - Finds the primitive local density of Q representing + Find the primitive local density of Q representing `m` at `p`, allowing certain congruence conditions mod `p`. - Note: The following routine is not used internally, but is included for consistency. + .. NOTE:: - INPUT: + The following routine is not used internally, but is included for consistency. - Q -- quadratic form assumed to be block diagonal and p-integral + INPUT: - `p` -- a prime number + - Q -- quadratic form assumed to be block diagonal and p-integral - `m` -- an integer + - `p` -- a prime number - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None + - `m` -- an integer - OUTPUT: + - Zvec, NZvec -- non-repeating lists of integers in range(self.dim()) or None - a rational number + OUTPUT: a rational number EXAMPLES:: @@ -1105,6 +985,4 @@ def local_primitive_density_congruence(self, p, m, Zvec=None, NZvec=None): """ return self.local_good_density_congruence(p, m, Zvec, NZvec) \ - + self.local_bad_density_congruence(p, m, Zvec, NZvec) - - + + self.local_bad_density_congruence(p, m, Zvec, NZvec) diff --git a/src/sage/rings/all.py b/src/sage/rings/all.py index a8a1da9ff3d..5b72a6d139d 100644 --- a/src/sage/rings/all.py +++ b/src/sage/rings/all.py @@ -149,8 +149,8 @@ from .monomials import monomials -CC = ComplexField() -CIF = ComplexIntervalField() +from .cc import CC +from .cif import CIF # invariant theory from .invariants.all import * diff --git a/src/sage/rings/asymptotic/asymptotic_expansion_generators.py b/src/sage/rings/asymptotic/asymptotic_expansion_generators.py index 86efd79c913..3100ed5356f 100644 --- a/src/sage/rings/asymptotic/asymptotic_expansion_generators.py +++ b/src/sage/rings/asymptotic/asymptotic_expansion_generators.py @@ -168,7 +168,18 @@ def Stirling(var, precision=None, skip_constant_factor=False): Traceback (most recent call last): ... ValueError: precision must be at least 3 + + Check that :trac:`20066` is resolved:: + + sage: set_series_precision(5) + sage: asymptotic_expansions.Stirling('n') + sqrt(2)*sqrt(pi)*e^(n*log(n))*(e^n)^(-1)*n^(1/2) + + ... + O(e^(n*log(n))*(e^n)^(-1)*n^(-5/2)) + sage: set_series_precision(20) # restore series precision default """ + if precision is None: + precision = series_precision() + if precision < 3: raise ValueError("precision must be at least 3") log_Stirling = AsymptoticExpansionGenerators.log_Stirling( @@ -550,6 +561,13 @@ def Binomial_kn_over_n(var, k, precision=None, skip_constant_factor=False): ....: SR(S.subs(n=k*n) / (S.subs(n=(k-1)*n) * S)).canonicalize_radical() ....: for k in [2, 3, 4]) True + + Check that :trac:`20066` is resolved:: + + sage: set_series_precision(3) + sage: asymptotic_expansions.Binomial_kn_over_n('n', k=2) + 1/sqrt(pi)*4^n*n^(-1/2) - 1/8/sqrt(pi)*4^n*n^(-3/2) + ... + O(4^n*n^(-7/2)) + sage: set_series_precision(20) # restore series precision default """ from sage.symbolic.ring import SR SCR = SR.subring(no_variables=True) @@ -560,6 +578,9 @@ def Binomial_kn_over_n(var, k, precision=None, skip_constant_factor=False): raise combine_exceptions( TypeError('Cannot use k={}.'.format(k)), e) + if precision is None: + precision = series_precision() + S = AsymptoticExpansionGenerators._log_StirlingNegativePowers_( var, precision=max(precision - 2,0)) n = S.parent().gen() diff --git a/src/sage/rings/asymptotic/asymptotic_ring.py b/src/sage/rings/asymptotic/asymptotic_ring.py index e0357d29a5a..46d50c65c51 100644 --- a/src/sage/rings/asymptotic/asymptotic_ring.py +++ b/src/sage/rings/asymptotic/asymptotic_ring.py @@ -3362,6 +3362,59 @@ def limit(self): else: raise ValueError("Cannot determine limit of {}".format(self)) + def B(self, valid_from=0): + r""" + Convert all terms in this asymptotic expansion to `B`-terms. + + INPUT: + + - ``valid_from`` -- dictionary mapping variable names to lower bounds + for the corresponding variable. The bound implied by this term is valid when + all variables are at least their corresponding lower bound. If a number + is passed to ``valid_from``, then the lower bounds for all variables of + the asymptotic expansion are set to this number + + OUTPUT: + + An asymptotic expansion + + EXAMPLES:: + + sage: AR. = AsymptoticRing(growth_group='x^ZZ * z^ZZ', coefficient_ring=ZZ) + sage: AR.B(2*x^2, {x: 10}) # indirect doctest + doctest:warning + ... + FutureWarning: This class/method/function is marked as experimental. + It, its functionality or its interface might change without a formal deprecation. + See https://trac.sagemath.org/31922 for details. + B(2*x^2, x >= 10) + sage: expr = 42*x^42 + x^10 + AR.B(x^2, 20); expr # indirect doctest + 42*x^42 + x^10 + B(x^2, x >= 20, z >= 20) + sage: type(AR.B(x, 10)) # indirect doctest + + sage: 2*z^3 + AR.B(5*z^2, {z: 20}) # indirect doctest + 2*z^3 + B(5*z^2, z >= 20) + sage: (2*x).B({x: 20}) + B(2*x, x >= 20) + sage: AR.B(4*x^2*z^3, valid_from=10) # indirect doctest + B(4*x^2*z^3, x >= 10, z >= 10) + sage: AR.B(42*x^2) # indirect doctest + B(42*x^2, x >= 0, z >= 0) + + TESTS:: + sage: AR(0).B(20) # indirect doctest + Traceback (most recent call last): + ... + NotImplementedBZero: got B(0) + The error term B(0) means 0 for sufficiently large x, z. + """ + if not self.summands: + from .misc import NotImplementedBZero + raise NotImplementedBZero(self.parent(), exact_part=self.parent().zero()) + return sum(self.parent().create_summand('B', growth=element, valid_from=valid_from) + for element in self.summands.elements()) + + class AsymptoticRing(Algebra, UniqueRepresentation, WithLocals): r""" A ring consisting of :class:`asymptotic expansions `. @@ -4633,6 +4686,31 @@ def construction(self): cls=self._underlying_class()), self.coefficient_ring) + @staticmethod + def B(self, valid_from=0): + r"""" + Create a B-term. + + INPUT: + + - ``valid_from`` -- dictionary mapping variable names to lower bounds + for the corresponding variable. The bound implied by this term is valid when + all variables are at least their corresponding lower bound. If a number + is passed to ``valid_from``, then the lower bounds for all variables of + the asymptotic expansion are set to this number + + OUTPUT: + + A B-term + + EXAMPLES:: + + sage: A. = AsymptoticRing(growth_group='x^ZZ * QQ^y', coefficient_ring=QQ) + sage: A.B(2*x^3, {x: 5}) + B(2*x^3, x >= 5) + """ + return self.B(valid_from) + from sage.categories.pushout import ConstructionFunctor diff --git a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py index 7c308fd2460..990ac0a43c3 100644 --- a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +++ b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py @@ -1765,7 +1765,7 @@ def asymptotics_smooth(self, p, alpha, N, asy_var, coordinate=None, from sage.modules.free_module_element import vector from sage.symbolic.constants import pi from sage.symbolic.relation import solve - from sage.rings.all import CC + from sage.rings.cc import CC from sage.rings.rational_field import QQ R = self.denominator_ring @@ -2144,7 +2144,7 @@ def asymptotics_multiple(self, p, alpha, N, asy_var, coordinate=None, from sage.matrix.constructor import matrix from sage.misc.mrange import xmrange from sage.modules.free_module_element import vector - from sage.rings.all import CC + from sage.rings.cc import CC from sage.arith.misc import binomial from sage.rings.rational_field import QQ from sage.symbolic.constants import pi diff --git a/src/sage/rings/asymptotic/growth_group.py b/src/sage/rings/asymptotic/growth_group.py index ba7e6848ea3..a91ec314486 100644 --- a/src/sage/rings/asymptotic/growth_group.py +++ b/src/sage/rings/asymptotic/growth_group.py @@ -5434,7 +5434,7 @@ def create_key_and_extra_args(self, specification, **kwds): describing a growth group. > *previous* ValueError: Cannot create a parent out of 'as'. >> *previous* ValueError: unknown specification as - >> *and* SyntaxError: unexpected EOF while parsing (, line 1) + >> *and* SyntaxError: ... (, line 1) > *and* ValueError: Cannot create a parent out of 'df'. >> *previous* ValueError: unknown specification df >> *and* NameError: name 'df' is not defined diff --git a/src/sage/rings/asymptotic/misc.py b/src/sage/rings/asymptotic/misc.py index b2d7ad27fae..32ca1e90c1e 100644 --- a/src/sage/rings/asymptotic/misc.py +++ b/src/sage/rings/asymptotic/misc.py @@ -840,6 +840,76 @@ def __init__(self, asymptotic_ring=None, var=None, exact_part=0): super(NotImplementedOZero, self).__init__(message) +class NotImplementedBZero(NotImplementedError): + r""" + A special :python:`NotImplementedError` + which is raised when the result is B(0) which means 0 + for sufficiently large values of the variable. + """ + def __init__(self, asymptotic_ring=None, var=None, exact_part=0): + r""" + INPUT: + + - ``asymptotic_ring`` -- (default: ``None``) an :class:`AsymptoticRing` or ``None``. + + - ``var`` -- (default: ``None``) a string. + + Either ``asymptotic_ring`` or ``var`` has to be specified. + + - ``exact_part`` -- (default: ``0``) asymptotic expansion + + EXAMPLES:: + + sage: A = AsymptoticRing('n^ZZ', ZZ) + sage: from sage.rings.asymptotic.misc import NotImplementedBZero + + sage: raise NotImplementedBZero(A) + Traceback (most recent call last): + ... + NotImplementedBZero: got B(0) + The error term B(0) means 0 for sufficiently large n. + + sage: raise NotImplementedBZero(var='m') + Traceback (most recent call last): + ... + NotImplementedBZero: got B(0) + The error term B(0) means 0 for sufficiently large m. + + sage: AR. = AsymptoticRing('n^QQ', QQ) + sage: AR(0).B(42) + Traceback (most recent call last): + ... + NotImplementedBZero: got B(0) + The error term B(0) means 0 for sufficiently large n. + + TESTS:: + + sage: raise NotImplementedBZero(A, var='m') + Traceback (most recent call last): + ... + ValueError: specify either 'asymptotic_ring' or 'var' + sage: raise NotImplementedBZero() + Traceback (most recent call last): + ... + ValueError: specify either 'asymptotic_ring' or 'var' + """ + if (asymptotic_ring is None) == (var is None): + raise ValueError("specify either 'asymptotic_ring' or 'var'") + + if var is None: + var = ', '.join(str(g) for g in asymptotic_ring.gens()) + message = ('got {}\n'.format(('{} + '.format(exact_part) if exact_part else '') + + 'B(0)') + + 'The error term B(0) ' + 'means 0 for sufficiently large {}.'.format(var)) + + if asymptotic_ring is not None and isinstance(exact_part, int) and exact_part == 0: + exact_part = asymptotic_ring.zero() + self.exact_part = exact_part + + super(NotImplementedBZero, self).__init__(message) + + def transform_category(category, subcategory_mapping, axiom_mapping, initial_category=None): diff --git a/src/sage/rings/asymptotic/term_monoid.py b/src/sage/rings/asymptotic/term_monoid.py index b7288c2e9e6..35064d10b18 100644 --- a/src/sage/rings/asymptotic/term_monoid.py +++ b/src/sage/rings/asymptotic/term_monoid.py @@ -2101,6 +2101,12 @@ def _convert_construction_(self, kwds_construction): raise ValueError('Coefficient %s is not 1, but %s does not ' 'support coefficients.' % (coefficient, self)) + if 'parent' in kwds_construction and isinstance(kwds_construction['parent'], BTermMonoid): + try: + del kwds_construction['valid_from'] + except KeyError: + pass + def from_construction(self, construction, **kwds_overrides): r""" Create a term from the construction of another term. @@ -3006,12 +3012,30 @@ def _convert_construction_(self, kwds_construction): {'growth': x} sage: kwds = {'growth': x, 'coefficient': 3/2}; T._convert_construction_(kwds); kwds {'growth': x} + + :: + + sage: T = TermMonoid('O', G, ZZ) + sage: T(TermMonoid('exact', G, QQ)(x, coefficient=42)) + O(x) + sage: T(TermMonoid('O', G, QQ)(x)) + O(x) + sage: T(TermMonoid('B', G, QQ)(x, coefficient=42)) + O(x) + sage: T(TermMonoid('B', G, QQ)(x, coefficient=42, valid_from={'x': 7})) + O(x) """ try: del kwds_construction['coefficient'] except KeyError: pass + if 'parent' in kwds_construction and isinstance(kwds_construction['parent'], BTermMonoid): + try: + del kwds_construction['valid_from'] + except KeyError: + pass + def _coerce_map_from_(self, S): r""" Return whether ``S`` coerces into this term monoid. @@ -3063,7 +3087,7 @@ def _coerce_map_from_(self, S): sage: OT_ZZ.has_coerce_map_from(ET) # indirect doctest True """ - if isinstance(S, (ExactTermMonoid,)): + if isinstance(S, (ExactTermMonoid, BTermMonoid,)): if self.growth_group.has_coerce_map_from(S.growth_group) and \ self.coefficient_ring.has_coerce_map_from(S.coefficient_ring): return True @@ -3677,7 +3701,11 @@ def _convert_construction_(self, kwds_construction): sage: kwds = {'growth': x, 'coefficient': 3/2}; T._convert_construction_(kwds); kwds {'coefficient': 3/2, 'growth': x} """ - pass + if 'parent' in kwds_construction and isinstance(kwds_construction['parent'], BTermMonoid): + try: + del kwds_construction['valid_from'] + except KeyError: + pass def _an_element_(self): r""" @@ -4447,8 +4475,24 @@ def _convert_construction_(self, kwds_construction): {'coefficient': None, 'growth': x} sage: kwds = {'growth': x, 'coefficient': 3/2}; T._convert_construction_(kwds); kwds {'coefficient': 3/2, 'growth': x} + + :: + + sage: T = TermMonoid('exact', G, ZZ) + sage: T(TermMonoid('exact', G, QQ)(x, coefficient=42)) + 42*x + sage: T(TermMonoid('O', G, QQ)(x)) + x + sage: T(TermMonoid('B', G, QQ)(x, coefficient=42)) + 42*x + sage: T(TermMonoid('B', G, QQ)(x, coefficient=42, valid_from={'x': 7})) + 42*x """ - pass + if 'parent' in kwds_construction and isinstance(kwds_construction['parent'], BTermMonoid): + try: + del kwds_construction['valid_from'] + except KeyError: + pass def _repr_(self): r""" @@ -4500,7 +4544,9 @@ class BTerm(TermWithCoefficient): - ``valid_from`` -- dictionary mapping variable names to lower bounds for the corresponding variable. The bound implied by this term is valid when - all variables are at least their corresponding lower bound + all variables are at least their corresponding lower bound. If a number + is passed to ``valid_from``, then the lower bounds for all variables of + the asymptotic expansion are set to this number EXAMPLES: @@ -4555,8 +4601,9 @@ def __init__(self, parent, growth, valid_from, **kwds): sage: BT_ZZ(x, coefficient=1/2, valid_from={'x': 20}) Traceback (most recent call last): ... - ValueError: Cannot create BTerm(x) since given coefficient 1/2 is not - valid in B-Term Monoid x^ZZ with coefficients in Integer Ring. + ValueError: Cannot create BTerm(x) + since given coefficient 1/2 is not valid in + B-Term Monoid x^ZZ with coefficients in Integer Ring. > *previous* TypeError: no conversion of this rational to integer sage: B = GrowthGroup('x^ZZ * y^ZZ'); sage: x, y = B('x'), B('y') @@ -4573,8 +4620,18 @@ def __init__(self, parent, growth, valid_from, **kwds): ... ValueError: B-Term has valid_from variables defined which do not occur in the term. """ - super().__init__(parent=parent, growth=growth, coefficient=kwds['coefficient']) - self.coefficient = kwds['coefficient'] + # BTerms must have positive cofficients + coefficient = abs(kwds['coefficient']) + + super().__init__(parent=parent, growth=growth, coefficient=coefficient) + self.coefficient = coefficient + if not isinstance(valid_from, dict): + valid_from = dict.fromkeys(parent.growth_group.variable_names(), valid_from) + + for variable_name in valid_from.keys(): + if not isinstance(variable_name, str): + valid_from = {f'{variable_name}': valid_from[variable_name]} + for variable_name in valid_from.keys(): if variable_name not in parent.growth_group.variable_names(): raise ValueError('B-Term has valid_from variables defined which do not occur in the term.') @@ -4584,6 +4641,47 @@ def __init__(self, parent, growth, valid_from, **kwds): raise ValueError('B-Term has not defined all variables which occur in the term in valid_from.') self.valid_from = valid_from + + def construction(self): + r""" + Return a construction of this term. + + INPUT: + + Nothing. + + OUTPUT: + + A pair ``(cls, kwds)`` such that ``cls(**kwds)`` equals this term. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoidFactory + sage: TermMonoid = TermMonoidFactory('__main__.TermMonoid') + + sage: T = TermMonoid('B', GrowthGroup('x^ZZ'), QQ) + sage: a = T.an_element(); a + B(1/2*x, x >= 42) + sage: cls, kwds = a.construction(); cls, kwds + (, + {'coefficient': 1/2, + 'growth': x, + 'parent': B-Term Monoid x^ZZ with coefficients in Rational Field, + 'valid_from': {'x': 42}}) + sage: cls(**kwds) == a + True + + .. SEEALSO:: + + :meth:`GenericTerm.construction`, + :meth:`TermWithCoefficient.construction`, + :meth:`GenericTermMonoid.from_construction` + """ + cls, kwds = super().construction() + kwds.update({'valid_from': self.valid_from}) + return cls, kwds + def _repr_(self, latex=False): r""" A representation string for this B-term. @@ -4643,6 +4741,47 @@ def _latex_(self): """ return self._repr_(latex=True) + def _mul_(self, other): + r""" + Multiplication method for asymptotic B-terms. + + INPUT: + + - ``other`` -- an asymptotic B-term + + OUTPUT: + + An asymptotic B-term representing the product of this element + and ``other``. + + .. NOTE:: + + This method is called by the coercion framework, thus, + it can be assumed that this element and ``other`` have + the same parent. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.term_monoid import DefaultTermMonoidFactory as TM + sage: BTM = TM('B', 'n^QQ', QQ) + sage: ETM = TM('exact', 'n^QQ', QQ) + sage: OTM = TM('O', 'n^QQ', QQ) + sage: n = BTM.growth_group.gen() + sage: BTM(n^2, coefficient=42, valid_from={'n': 3}) * BTM(n^5, valid_from={'n': 5}) + B(42*n^7, n >= 5) + sage: BTM(n^5, coefficient=21, valid_from={'n': 3}) * ETM(n^2, coefficient=2) + B(42*n^7, n >= 3) + sage: BTM(n^5, coefficient=21, valid_from={'n': 3}) * OTM(n) + O(n^6) + """ + valid_from = { + var: max(self.valid_from.get(var, 0), other.valid_from.get(var, 0)) + for var in set().union(self.valid_from, other.valid_from) + } + return self.parent()(self.growth * other.growth, + coefficient=self.coefficient * other.coefficient, + valid_from=valid_from) + def can_absorb(self, other): r""" Check whether this B-term can absorb ``other``. @@ -4809,39 +4948,109 @@ def _repr_(self): return (f'B-Term Monoid {self.growth_group._repr_short_()} with ' f'coefficients in {self.coefficient_ring}') - def _create_element_(self, growth, coefficient, valid_from): + def _default_kwds_construction_(self): r""" - Helper method which creates an element by using the ``element_class``. + Return the default keyword arguments for the construction of a term. INPUT: - - ``growth_group`` -- a growth group + Nothing. + + OUTPUT: + + A dictionary. + + TESTS:: + + sage: from sage.rings.asymptotic.term_monoid import TermMonoidFactory + sage: TermMonoid = TermMonoidFactory('__main__.TermMonoid') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: T = TermMonoid('B', G, ZZ) + sage: T._default_kwds_construction_() + {'coefficient': 1, 'valid_from': {'x': 0}} + sage: T.from_construction((None, {'growth': G.gen()})) # indirect doctest + B(x, x >= 0) + sage: T.from_construction( + ....: (None, {'growth': G.gen(), 'coefficient': 2})) # indirect doctest + B(2*x, x >= 0) + sage: T.from_construction( + ....: (None, {'growth': G.gen(), 'valid_from': {'x': 5}})) # indirect doctest + B(x, x >= 5) + """ + defaults = {} + defaults.update(super()._default_kwds_construction_()) + defaults.update( + {'valid_from': {v: 0 for v in self.growth_group.variable_names()}}) + return defaults - - ``coefficient`` -- an element of the coefficient ring + def _convert_construction_(self, kwds_construction): + r""" + Helper method which converts the given keyword arguments + suitable for the term (in the element construction process). + + This is used e.g. for converting one type of term into another - - ``valid_from`` -- dictionary mapping variable names to lower bounds - for the corresponding variable. The bound implied by this term is valid when - all variables are at least their corresponding lower bound + INPUT: + + - ``kwds_construction`` -- a dictionary representing + the keyword arguments of a term in its construction + (see also :meth:`GenericTerm.construction` and + :meth:`TermWithCoefficient.construction`) OUTPUT: - A B-term + Nothing, but ``kwds_construction`` might be changed. TESTS:: - sage: from sage.rings.asymptotic.growth_group import MonomialGrowthGroup - sage: from sage.rings.asymptotic.term_monoid import DefaultTermMonoidFactory as TermMonoid + sage: from sage.rings.asymptotic.term_monoid import TermMonoidFactory + sage: TermMonoid = TermMonoidFactory('__main__.TermMonoid') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('x^ZZ') + sage: x = G.gen() + sage: T = TermMonoid('B', G, ZZ) + + sage: kwds = {'growth': x}; T._convert_construction_(kwds); kwds + {'growth': x} + sage: kwds = {'growth': x, 'coefficient': QQ(1)}; T._convert_construction_(kwds); kwds + {'coefficient': 1, 'growth': x} + sage: kwds = {'growth': x, 'coefficient': None}; T._convert_construction_(kwds); kwds + {'coefficient': None, 'growth': x} + sage: kwds = {'growth': x, 'coefficient': 3/2}; T._convert_construction_(kwds); kwds + {'coefficient': 3/2, 'growth': x} + + :: + + sage: T = TermMonoid('B', G, ZZ) + sage: T(TermMonoid('exact', G, QQ)(x, coefficient=42)) + B(42*x, x >= 0) + sage: T(TermMonoid('O', G, QQ)(x)) + B(x, x >= 0) + sage: T(TermMonoid('B', G, QQ)(x, coefficient=42)) + B(42*x, x >= 0) + sage: T(TermMonoid('B', G, QQ)(x, coefficient=42, valid_from={'x': 7})) + B(42*x, x >= 7) + + :: + + sage: T(TermMonoid('exact', G, QQ)(x, coefficient=-42)) + B(42*x, x >= 0) + + :: - sage: G = MonomialGrowthGroup(ZZ, 'x') sage: BT = TermMonoid('B', G, QQ) sage: BT(x^3, coefficient=4, valid_from={'x': 10}) B(4*x^3, x >= 10) sage: BT(x^3, coefficient=4, valid_from=10) + B(4*x^3, x >= 10) + sage: BT(x^3, coefficient=4, 10) Traceback (most recent call last): ... - AttributeError: 'sage.rings.integer.Integer' object has no attribute 'keys' + SyntaxError: positional argument follows keyword argument """ - return self.element_class(self, growth, coefficient, valid_from) + # TODO handle negative coefficients of exact terms etc. + pass def _coerce_map_from_(self, S): r""" @@ -4903,6 +5112,78 @@ def _coerce_map_from_(self, S): else: return super()._coerce_map_from_(S) + def _an_element_(self): + r""" + Return an element of this B-term monoid. + + INPUT: + + Nothing. + + OUTPUT: + + An element of this term monoid. + + EXAMPLES:: + + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: from sage.rings.asymptotic.term_monoid import TermMonoidFactory + sage: TermMonoid = TermMonoidFactory('__main__.TermMonoid') + sage: G = GrowthGroup('x^ZZ') + sage: TermMonoid('B', G, ZZ).an_element() # indirect doctest + B(x, x >= 42) + """ + from sage.rings.semirings.non_negative_integer_semiring import NN + return self(self.growth_group.an_element(), + coefficient=self.coefficient_ring.an_element(), + valid_from={v: NN.an_element() + for v in self.growth_group.variable_names()}) + + def some_elements(self): + r""" + Return some elements of this B-term monoid. + + See :class:`TestSuite` for a typical use case. + + INPUT: + + Nothing. + + OUTPUT: + + An iterator. + + EXAMPLES:: + + sage: from itertools import islice + sage: from sage.rings.asymptotic.term_monoid import TermMonoidFactory + sage: TermMonoid = TermMonoidFactory('__main__.TermMonoid') + sage: from sage.rings.asymptotic.growth_group import GrowthGroup + sage: G = GrowthGroup('z^QQ') + sage: T = TermMonoid('B', G, ZZ) + sage: tuple(islice(T.some_elements(), int(10))) + (B(z^(1/2), z >= 0), + B(z^(-1/2), z >= 1), + B(z^(1/2), z >= 3), + B(z^2, z >= 42), + B(z^(-1/2), z >= 0), + B(2*z^(1/2), z >= 1), + B(z^(-2), z >= 3), + B(z^2, z >= 42), + B(2*z^(-1/2), z >= 0), + B(2*z^(1/2), z >= 1)) + """ + from itertools import cycle + from sage.misc.mrange import cantor_product + from sage.rings.semirings.non_negative_integer_semiring import NN + return (self(g, + coefficient=c, + valid_from={v: f for v in self.growth_group.variable_names()}) + for (g, c), f in zip(cantor_product( + self.growth_group.some_elements(), + (c for c in self.coefficient_ring.some_elements() if c != 0)), + cycle(NN.some_elements()))) + class TermMonoidFactory(UniqueRepresentation, UniqueFactory): r""" diff --git a/src/sage/rings/cc.py b/src/sage/rings/cc.py new file mode 100644 index 00000000000..6db89579029 --- /dev/null +++ b/src/sage/rings/cc.py @@ -0,0 +1,3 @@ +from .complex_mpfr import ComplexField + +CC = ComplexField() diff --git a/src/sage/rings/cif.py b/src/sage/rings/cif.py new file mode 100644 index 00000000000..91924f4d26c --- /dev/null +++ b/src/sage/rings/cif.py @@ -0,0 +1,3 @@ +from .complex_interval_field import ComplexIntervalField + +CIF = ComplexIntervalField() diff --git a/src/sage/rings/commutative_algebra.py b/src/sage/rings/commutative_algebra.py index 274ba57bc14..26ab115e52b 100644 --- a/src/sage/rings/commutative_algebra.py +++ b/src/sage/rings/commutative_algebra.py @@ -25,7 +25,9 @@ def is_CommutativeAlgebra(x): EXAMPLES:: - sage: sage.rings.commutative_algebra.is_CommutativeAlgebra(sage.rings.ring.CommutativeAlgebra(ZZ)) + sage: from sage.rings.commutative_algebra import is_CommutativeAlgebra + sage: from sage.rings.ring import CommutativeAlgebra + sage: is_CommutativeAlgebra(CommutativeAlgebra(ZZ)) True """ return isinstance(x, CommutativeAlgebra) diff --git a/src/sage/rings/complex_arb.pyx b/src/sage/rings/complex_arb.pyx index ce9b1b19a95..7e10ec9d2fc 100644 --- a/src/sage/rings/complex_arb.pyx +++ b/src/sage/rings/complex_arb.pyx @@ -4189,7 +4189,7 @@ cdef class ComplexBall(RingElement): TESTS: - sage: CBF(Ei(I)) + sage: CBF(Ei(I)) # abs tol 1e-16 [0.337403922900968 +/- 3.76e-16] + [2.51687939716208 +/- 2.01e-15]*I """ cdef ComplexBall result = self._new() @@ -4239,7 +4239,7 @@ cdef class ComplexBall(RingElement): TESTS: - sage: CBF(Ci(I)) + sage: CBF(Ci(I)) # abs tol 1e-17 [0.837866940980208 +/- 4.72e-16] + [1.570796326794897 +/- 5.54e-16]*I """ cdef ComplexBall result = self._new() @@ -4291,7 +4291,7 @@ cdef class ComplexBall(RingElement): TESTS: - sage: CBF(Chi(I)) + sage: CBF(Chi(I)) # abs tol 1e-16 [0.337403922900968 +/- 3.25e-16] + [1.570796326794897 +/- 5.54e-16]*I """ cdef ComplexBall result = self._new() diff --git a/src/sage/rings/complex_conversion.pxd b/src/sage/rings/complex_conversion.pxd new file mode 100644 index 00000000000..2053005e340 --- /dev/null +++ b/src/sage/rings/complex_conversion.pxd @@ -0,0 +1,7 @@ +from sage.structure.element cimport Element +from sage.categories.map cimport Map + + +cdef class CCtoCDF(Map): + + cpdef Element _call_(self, x) diff --git a/src/sage/rings/complex_conversion.pyx b/src/sage/rings/complex_conversion.pyx new file mode 100644 index 00000000000..a11f86a7735 --- /dev/null +++ b/src/sage/rings/complex_conversion.pyx @@ -0,0 +1,22 @@ +from .complex_double cimport ComplexDoubleElement +from .complex_mpfr cimport ComplexNumber +from sage.libs.mpfr cimport mpfr_get_d, MPFR_RNDN +from sage.libs.gsl.complex cimport GSL_SET_COMPLEX + +cdef class CCtoCDF(Map): + + cpdef Element _call_(self, x): + """ + EXAMPLES:: + sage: from sage.rings.complex_conversion import CCtoCDF + sage: f = CCtoCDF(CC, CDF) # indirect doctest + sage: f(CC.0) + 1.0*I + sage: f(exp(pi*CC.0/4)) + 0.7071067811865476 + 0.7071067811865475*I + """ + z = ComplexDoubleElement.__new__(ComplexDoubleElement) + GSL_SET_COMPLEX(&z._complex, + mpfr_get_d((x).__re, MPFR_RNDN), + mpfr_get_d((x).__im, MPFR_RNDN)) + return z diff --git a/src/sage/rings/complex_double.pyx b/src/sage/rings/complex_double.pyx index 466784c62df..d8dad014081 100644 --- a/src/sage/rings/complex_double.pyx +++ b/src/sage/rings/complex_double.pyx @@ -106,14 +106,7 @@ complex_double_element_gamma = None complex_double_element_gamma_inc = None complex_double_element_zeta = None - -from . import complex_mpfr - -from .complex_mpfr import ComplexField -cdef CC = ComplexField() - -from .real_mpfr import RealField -cdef RR = RealField() +from .complex_conversion cimport CCtoCDF from .real_double cimport RealDoubleElement, double_repr from .real_double import RDF @@ -382,6 +375,7 @@ cdef class ComplexDoubleField_class(sage.rings.abc.ComplexDoubleField): sage: CDF((1,2)) # indirect doctest 1.0 + 2.0*I """ + from . import complex_mpfr if isinstance(x, ComplexDoubleElement): return x elif isinstance(x, tuple): @@ -449,6 +443,8 @@ cdef class ComplexDoubleField_class(sage.rings.abc.ComplexDoubleField): from .rational_field import QQ from .real_lazy import RLF from .real_mpfr import RR + from .cc import CC + if S is ZZ or S is QQ or S is RDF or S is RLF: return FloatToCDF(S) if isinstance(S, sage.rings.abc.RealField): @@ -467,9 +463,9 @@ cdef class ComplexDoubleField_class(sage.rings.abc.ComplexDoubleField): elif RR.has_coerce_map_from(S): return FloatToCDF(RR) * RR._internal_coerce_map_from(S) elif isinstance(S, sage.rings.abc.ComplexField) and S.prec() >= 53: - return complex_mpfr.CCtoCDF(S, self) + return CCtoCDF(S, self) elif CC.has_coerce_map_from(S): - return complex_mpfr.CCtoCDF(CC, self) * CC._internal_coerce_map_from(S) + return CCtoCDF(CC, self) * CC._internal_coerce_map_from(S) def _magma_init_(self, magma): r""" @@ -529,6 +525,7 @@ cdef class ComplexDoubleField_class(sage.rings.abc.ComplexDoubleField): if prec == 53: return self else: + from .complex_mpfr import ComplexField return ComplexField(prec) @@ -993,7 +990,8 @@ cdef class ComplexDoubleElement(FieldElement): True """ # Sending to another computer algebra system is slow anyway, right? - return CC(self)._interface_init_(I) + from .complex_mpfr import ComplexField + return ComplexField()(self)._interface_init_(I) def _mathematica_init_(self): """ @@ -1004,7 +1002,8 @@ cdef class ComplexDoubleElement(FieldElement): sage: mathematica(CDF(1e-25, 1e25)) # optional - mathematica 1.*^-25 + 1.*^25*I """ - return CC(self)._mathematica_init_() + from .complex_mpfr import ComplexField + return ComplexField()(self)._mathematica_init_() def _maxima_init_(self, I=None): """ @@ -1018,7 +1017,8 @@ cdef class ComplexDoubleElement(FieldElement): sage: CDF(.5 + I)._maxima_init_() '0.50000000000000000 + 1.0000000000000000*%i' """ - return CC(self)._maxima_init_(I) + from .complex_mpfr import ComplexField + return ComplexField()(self)._maxima_init_(I) def _sympy_(self): """ @@ -1119,7 +1119,8 @@ cdef class ComplexDoubleElement(FieldElement): sage: format(CDF(0, 0), '+#.4') '+0.000' """ - return complex_mpfr._format_complex_number(GSL_REAL(self._complex), + from .complex_mpfr import _format_complex_number + return _format_complex_number(GSL_REAL(self._complex), GSL_IMAG(self._complex), format_spec) @@ -2406,8 +2407,9 @@ cdef class ComplexDoubleElement(FieldElement): from .infinity import unsigned_infinity return unsigned_infinity try: - from sage.rings.all import Integer, CC + from .integer import Integer if Integer(GSL_REAL(self._complex)) < 0: + from .cc import CC return CC(self).gamma() except TypeError: pass diff --git a/src/sage/rings/complex_mpfr.pyx b/src/sage/rings/complex_mpfr.pyx index 0de4d91878f..de201f8b297 100644 --- a/src/sage/rings/complex_mpfr.pyx +++ b/src/sage/rings/complex_mpfr.pyx @@ -3450,27 +3450,6 @@ cdef class RRtoCC(Map): mpfr_set_ui(z.__im, 0, rnd) return z - -cdef class CCtoCDF(Map): - - cpdef Element _call_(self, x): - """ - EXAMPLES:: - - sage: from sage.rings.complex_mpfr import CCtoCDF - sage: f = CCtoCDF(CC, CDF) # indirect doctest - sage: f(CC.0) - 1.0*I - sage: f(exp(pi*CC.0/4)) - 0.7071067811865476 + 0.7071067811865475*I - """ - z = ComplexDoubleElement.__new__(ComplexDoubleElement) - GSL_SET_COMPLEX(&z._complex, - mpfr_get_d((x).__re, MPFR_RNDN), - mpfr_get_d((x).__im, MPFR_RNDN)) - return z - - cdef inline mp_exp_t min_exp_t(mp_exp_t a, mp_exp_t b): return a if a < b else b diff --git a/src/sage/rings/convert/mpfi.pyx b/src/sage/rings/convert/mpfi.pyx index 2ddd972e221..1f16b866862 100644 --- a/src/sage/rings/convert/mpfi.pyx +++ b/src/sage/rings/convert/mpfi.pyx @@ -21,6 +21,7 @@ from sage.libs.gsl.complex cimport * from sage.arith.long cimport integer_check_long from sage.cpython.string cimport bytes_to_str from sage.structure.element cimport Element, parent + import sage.rings.abc from ..integer cimport Integer from ..rational cimport Rational diff --git a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx index 7092888fddd..bfff1ee1c5d 100644 --- a/src/sage/rings/finite_rings/element_ntl_gf2e.pyx +++ b/src/sage/rings/finite_rings/element_ntl_gf2e.pyx @@ -951,7 +951,7 @@ cdef class FiniteField_ntl_gf2eElement(FinitePolyExtElement): 5 sage: k. = GF(2^70) sage: (a^65 + a^64 + 1).integer_representation() - 55340232221128654849L + 55340232221128654849 """ cdef unsigned int i = 0 ret = int(0) diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index 32f418a12f6..da72407303a 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -1697,13 +1697,13 @@ cdef class FiniteField(Field): inc = self.coerce_map_from(self) elif hasattr(self, '_prefix'): modulus = self.prime_subfield().algebraic_closure(self._prefix)._get_polynomial(degree) - K = GF(p**degree, name=name, prefix=self._prefix, modulus=modulus, check_irreducible=False) + K = GF((p, degree), name=name, prefix=self._prefix, modulus=modulus, check_irreducible=False) a = self.gen()**((p**n-1)//(p**degree - 1)) inc = K.hom([a], codomain=self, check=False) else: fam = self._compatible_family() a, modulus = fam[degree] - K = GF(p**degree, modulus=modulus, name=name) + K = GF((p, degree), modulus=modulus, name=name) inc = K.hom([a], codomain=self, check=False) if fam[n][0] == self.gen(): try: # to register a coercion map, embedding of K to self diff --git a/src/sage/rings/finite_rings/finite_field_constructor.py b/src/sage/rings/finite_rings/finite_field_constructor.py index d7b4e15985f..e6129689ad5 100644 --- a/src/sage/rings/finite_rings/finite_field_constructor.py +++ b/src/sage/rings/finite_rings/finite_field_constructor.py @@ -176,16 +176,17 @@ from sage.rings.integer import Integer -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - # the import below is just a redirection from sage.rings.finite_rings.finite_field_base import is_FiniteField assert is_FiniteField # just to silent pyflakes -# We don't late import this because this means trouble with the Givaro library -# On a Macbook Pro OSX 10.5.8, this manifests as a Bus Error on exiting Sage. -# TODO: figure out why -from .finite_field_givaro import FiniteField_givaro +try: + # We don't late import this because this means trouble with the Givaro library + # On a Macbook Pro OSX 10.5.8, this manifests as a Bus Error on exiting Sage. + # TODO: figure out why + from .finite_field_givaro import FiniteField_givaro +except ImportError: + FiniteField_givaro = None from sage.structure.factory import UniqueFactory @@ -633,6 +634,7 @@ def create_key_and_extra_args(self, order, name=None, modulus=None, names=None, # optimization which we also need to avoid an infinite loop: # a modulus of None is a shorthand for x-1. if modulus is not None or impl != 'modn': + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing R = PolynomialRing(FiniteField(p), 'x') if modulus is None: modulus = R.irreducible_element(n) diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 9dad670c8df..fab88fd9dae 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -1193,20 +1193,9 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): 10 sage: print(Integer(16938402384092843092843098243).hex()) 36bb1e3929d1a8fe2802f083 - - sage: hex(Integer(16)) # py2 - doctest:warning...: - DeprecationWarning: use the method .hex instead - See https://trac.sagemath.org/26756 for details. - '10' """ return self.str(16) - def __hex__(self): - from sage.misc.superseded import deprecation_cython as deprecation - deprecation(26756, 'use the method .hex instead') - return self.hex() - def oct(self): r""" Return the digits of ``self`` in base 8. @@ -1233,34 +1222,20 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): sage: print(Integer(16938402384092843092843098243).oct()) 15535436162247215217705000570203 - Behavior of Sage integers vs. Python integers (Python 2 and Python 3):: + Behavior of Sage integers vs. Python integers:: sage: Integer(10).oct() '12' - sage: oct(Integer(10)) # py2 - doctest:warning...: - DeprecationWarning: use the method .oct instead - See https://trac.sagemath.org/26756 for details. - '12' - sage: oct(int(10)) # py2 - '012' - sage: oct(int(10)) # py3 + sage: oct(int(10)) '0o12' sage: Integer(-23).oct() '-27' - sage: oct(int(-23)) # py2 - '-027' - sage: oct(int(-23)) # py3 + sage: oct(int(-23)) '-0o27' """ return self.str(8) - def __oct__(self): - from sage.misc.superseded import deprecation_cython as deprecation - deprecation(26756, 'use the method .oct instead') - return self.oct() - def binary(self): """ Return the binary digits of ``self`` as a string. @@ -3529,8 +3504,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): def __int__(self): """ - Return the Python int (or long) corresponding to this Sage - integer. + Return the Python int corresponding to this Sage integer. EXAMPLES:: @@ -3543,9 +3517,9 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): <... 'int'> sage: n = 99028390823409823904823098490238409823490820938 sage: int(n) - 99028390823409823904823098490238409823490820938L + 99028390823409823904823098490238409823490820938 sage: int(-n) - -99028390823409823904823098490238409823490820938L + -99028390823409823904823098490238409823490820938 sage: type(n.__int__()) sage: int(-1), int(0), int(1) diff --git a/src/sage/rings/lazy_series.py b/src/sage/rings/lazy_series.py index 0892554f272..35d04f750e0 100644 --- a/src/sage/rings/lazy_series.py +++ b/src/sage/rings/lazy_series.py @@ -3301,7 +3301,7 @@ def __call__(self, p): # Special behavior for finite series if isinstance(coeff_stream, Stream_exact): - from sage.rings.all import CC + from sage.rings.cc import CC if not coeff_stream._constant: try: return sum(self[k] * ~(ZZ(k)**p) diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index 561c575b0a6..138656873b2 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -238,8 +238,8 @@ def proof_flag(t): from sage.rings.rational_field import QQ from sage.rings.integer_ring import ZZ -RIF = sage.rings.real_mpfi.RealIntervalField() -CIF = sage.rings.complex_interval_field.ComplexIntervalField() +from sage.rings.real_mpfi import RIF +from sage.rings.cif import CIF from sage.rings.real_double import RDF from sage.rings.complex_double import CDF from sage.rings.real_lazy import RLF, CLF diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 89242c9fe24..c0fe2091834 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -88,7 +88,8 @@ Integer_sage = sage.rings.integer.Integer from sage.rings.real_mpfi import RealInterval from sage.rings.complex_mpfr import ComplexField -CC = ComplexField(53) +from sage.rings.cc import CC + # this is a threshold for the charpoly() methods in this file # for degrees <= this threshold, pari is used diff --git a/src/sage/rings/number_field/number_field_rel.py b/src/sage/rings/number_field/number_field_rel.py index 71760700442..b0234f03d08 100644 --- a/src/sage/rings/number_field/number_field_rel.py +++ b/src/sage/rings/number_field/number_field_rel.py @@ -101,8 +101,6 @@ from sage.rings.rational_field import QQ from sage.rings.integer_ring import ZZ -import sage.rings.complex_interval_field -CIF = sage.rings.complex_interval_field.ComplexIntervalField() def is_RelativeNumberField(x): diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index abfce6c44c9..6024799ceae 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -1338,21 +1338,21 @@ def Qq(q, prec = None, type = 'capped-rel', modulus = None, names=None, if k == 1: return base - if isinstance(names, (list,tuple)): + if isinstance(names, (list, tuple)): if len(names) != 1: raise ValueError("must provide exactly one generator name") names = names[0] if names is None: raise TypeError("You must specify the name of the generator.") if not isinstance(names, str): - names = str(names) + names = str(names) if res_name is None: res_name = names + '0' if modulus is None: from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF - modulus = GF(p**k, res_name).modulus().change_ring(ZZ) + modulus = GF((p, k), res_name).modulus().change_ring(ZZ) return ExtensionFactory(base=base, modulus=modulus, prec=prec, print_mode=print_mode, names=names, res_name=res_name, ram_name=ram_name, print_pos=print_pos, print_sep=print_sep, print_max_ram_terms=print_max_ram_terms, diff --git a/src/sage/rings/polynomial/binary_form_reduce.py b/src/sage/rings/polynomial/binary_form_reduce.py index e7e8e4a8359..fb60089a751 100644 --- a/src/sage/rings/polynomial/binary_form_reduce.py +++ b/src/sage/rings/polynomial/binary_form_reduce.py @@ -32,7 +32,7 @@ from sage.matrix.constructor import matrix from sage.misc.misc_c import prod from sage.modules.free_module_element import vector -from sage.rings.all import CC +from sage.rings.cc import CC from sage.rings.complex_mpfr import ComplexField from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.integer_ring import ZZ diff --git a/src/sage/rings/polynomial/cyclotomic.pyx b/src/sage/rings/polynomial/cyclotomic.pyx index f9bd920b231..6f972bc5055 100644 --- a/src/sage/rings/polynomial/cyclotomic.pyx +++ b/src/sage/rings/polynomial/cyclotomic.pyx @@ -41,7 +41,7 @@ from sage.libs.pari.all import pari def cyclotomic_coeffs(nn, sparse=None): - u""" + """ Return the coefficients of the n-th cyclotomic polynomial by using the formula diff --git a/src/sage/rings/polynomial/polydict.pyx b/src/sage/rings/polynomial/polydict.pyx index 95785f04521..f2ce00f8568 100644 --- a/src/sage/rings/polynomial/polydict.pyx +++ b/src/sage/rings/polynomial/polydict.pyx @@ -174,9 +174,7 @@ cdef class PolyDict: sage: p2 = PolyDict({(0,): 2}) sage: p1 == p2 False - sage: p1 < p2 # py2 - random - False - sage: p1 < p2 # py3 + sage: p1 < p2 Traceback (most recent call last): ... TypeError: '<' not supported between instances of diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 2edc2ab54ad..4c2f63a28a5 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -88,7 +88,7 @@ cimport sage.rings.abc from sage.rings.real_mpfr import RealField, RR from sage.rings.complex_mpfr import ComplexField -CC = ComplexField() +from sage.rings.cc import CC from sage.rings.real_double import RDF from sage.rings.complex_double import CDF diff --git a/src/sage/rings/polynomial/polynomial_ring.py b/src/sage/rings/polynomial/polynomial_ring.py index e6c8d400c2e..bfb8b863edb 100644 --- a/src/sage/rings/polynomial/polynomial_ring.py +++ b/src/sage/rings/polynomial/polynomial_ring.py @@ -147,8 +147,6 @@ import sage.categories as categories from sage.categories.morphism import IdentityMorphism -import sage.algebras.algebra -import sage.rings.commutative_algebra as commutative_algebra import sage.rings.ring as ring from sage.structure.element import is_RingElement import sage.rings.polynomial.polynomial_element_generic as polynomial_element_generic @@ -226,7 +224,7 @@ def is_PolynomialRing(x): ######################################################################################### -class PolynomialRing_general(sage.algebras.algebra.Algebra): +class PolynomialRing_general(ring.Algebra): """ Univariate polynomial ring over a ring. """ @@ -300,7 +298,7 @@ def __init__(self, base_ring, name=None, sparse=False, element_class=None, categ self.Element = self._polynomial_class self.__cyclopoly_cache = {} self._has_singular = False - sage.algebras.algebra.Algebra.__init__(self, base_ring, names=name, normalize=True, category=category) + ring.Algebra.__init__(self, base_ring, names=name, normalize=True, category=category) self._populate_coercion_lists_(convert_method_name='_polynomial_') def __reduce__(self): @@ -1662,7 +1660,7 @@ def monics( self, of_degree = None, max_degree = None ): raise ValueError("you should pass exactly one of of_degree and max_degree") -class PolynomialRing_commutative(PolynomialRing_general, commutative_algebra.CommutativeAlgebra): +class PolynomialRing_commutative(PolynomialRing_general, ring.CommutativeAlgebra): """ Univariate polynomial ring over a commutative ring. """ diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 25f814a9dbd..cb4c293f88f 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -566,7 +566,8 @@ op_EQ, op_NE, op_GT) from sage.rings.real_mpfr import RR from sage.rings.real_mpfi import RealIntervalField, RIF, is_RealIntervalFieldElement, RealIntervalField_class -from sage.rings.complex_mpfr import ComplexField +from sage.rings.cc import CC +from sage.rings.cif import CIF from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.complex_interval import is_ComplexIntervalFieldElement from sage.rings.polynomial.all import PolynomialRing @@ -581,8 +582,6 @@ from sage.structure.global_options import GlobalOptions -CC = ComplexField() -CIF = ComplexIntervalField() class AlgebraicField_common(sage.rings.abc.AlgebraicField_common): r""" diff --git a/src/sage/rings/quotient_ring.py b/src/sage/rings/quotient_ring.py index 807dce92a9b..70c4afdc283 100644 --- a/src/sage/rings/quotient_ring.py +++ b/src/sage/rings/quotient_ring.py @@ -110,15 +110,22 @@ import sage.misc.latex as latex from . import ring, ideal, quotient_ring_element -import sage.rings.polynomial.multi_polynomial_ideal from sage.structure.category_object import normalize_names from sage.structure.richcmp import richcmp_method, richcmp import sage.structure.parent_gens -from sage.interfaces.singular import singular as singular_default, is_SingularElement from sage.misc.cachefunc import cached_method from sage.categories.rings import Rings from sage.categories.commutative_rings import CommutativeRings + +MPolynomialIdeal = None +try: + from sage.interfaces.singular import singular as singular_default, is_SingularElement +except ImportError: + is_singularElement = lambda x : False + singular_default = None + + def QuotientRing(R, I, names=None, **kwds): r""" Creates a quotient ring of the ring `R` by the twosided ideal `I`. @@ -959,7 +966,11 @@ def ideal(self, *gens, **kwds): gens = [gens] if 'coerce' in kwds and kwds['coerce']: gens = [self(x) for x in gens] # this will even coerce from singular ideals correctly! - return sage.rings.polynomial.multi_polynomial_ideal.MPolynomialIdeal(self, gens, **kwds) + + global MPolynomialIdeal + if MPolynomialIdeal is None: + from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal + return MPolynomialIdeal(self, gens, **kwds) def _element_constructor_(self, x, coerce=True): """ @@ -1006,7 +1017,7 @@ def _element_constructor_(self, x, coerce=True): return x x = x.lift() if is_SingularElement(x): - #self._singular_().set_ring() + # self._singular_().set_ring() x = self.element_class(self, x.sage_poly(self.cover_ring())) return x if coerce: @@ -1171,7 +1182,6 @@ def gen(self, i=0): """ return self(self.__R.gen(i)) - def _singular_(self, singular=singular_default): """ Returns the Singular quotient ring of ``self`` if the base ring is @@ -1203,6 +1213,9 @@ def _singular_(self, singular=singular_default): // quotient ring from ideal _[1]=x2+y2 """ + if singular is None: + raise ImportError("could not import singular") + try: Q = self.__singular if not (Q.parent() is singular): @@ -1212,7 +1225,7 @@ def _singular_(self, singular=singular_default): except (AttributeError, ValueError): return self._singular_init_(singular) - def _singular_init_(self,singular=singular_default): + def _singular_init_(self, singular=None): """ Returns a newly created Singular quotient ring matching ``self`` if the base ring is coercible to Singular. @@ -1229,6 +1242,8 @@ def _singular_init_(self,singular=singular_default): sage: parent(T) Singular """ + if singular is None: + from sage.interfaces.singular import singular self.__R._singular_().set_ring() self.__singular = singular("%s"%self.__I._singular_().name(),"qring") return self.__singular diff --git a/src/sage/rings/quotient_ring_element.py b/src/sage/rings/quotient_ring_element.py index 8d09a28f6ba..337c22c232c 100644 --- a/src/sage/rings/quotient_ring_element.py +++ b/src/sage/rings/quotient_ring_element.py @@ -18,7 +18,12 @@ from sage.structure.element import RingElement from sage.structure.richcmp import richcmp, rich_to_bool -from sage.interfaces.singular import singular as singular_default + + +try: + from sage.interfaces.singular import singular as singular_default +except ImportError: + singular_default = None class QuotientRingElement(RingElement): @@ -797,6 +802,8 @@ def _singular_(self, singular=singular_default): sage: S((a-2/3*b)._singular_()) a - 2/3*b """ + if singular is None: + raise ImportError("could not import singular") return self.__rep._singular_(singular) def _magma_init_(self, magma): diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index e2c09e6d6ce..dfd5923119d 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -65,7 +65,6 @@ from cysignals.signals cimport sig_on, sig_off import operator import fractions -from sage.misc.mathml import mathml from sage.arith.long cimport pyobject_to_long, integer_check_long_py from sage.cpython.string cimport char_to_str, str_to_bytes @@ -1093,6 +1092,7 @@ cdef class Rational(sage.structure.element.FieldElement): if self.denom() == 1: return '%s'%(self.numer()) else: + from sage.misc.mathml import mathml t = '' if self < 0: t = t + '-' @@ -4221,12 +4221,7 @@ cdef class int_to_Q(Morphism): sage: f = sage.rings.rational.int_to_Q() sage: f(int(4)) # indirect doctest 4 - sage: f(4^100) # py2 - this will crash on Python 3 - Traceback (most recent call last): - ... - TypeError: must be a Python int object """ - cdef Rational rat if type(a) is not int: diff --git a/src/sage/rings/real_arb.pyx b/src/sage/rings/real_arb.pyx index 80a840e7868..49caa6305a5 100644 --- a/src/sage/rings/real_arb.pyx +++ b/src/sage/rings/real_arb.pyx @@ -3489,12 +3489,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: RBF(1).Ei() + sage: RBF(1).Ei() # abs tol 5e-16 [1.89511781635594 +/- 4.94e-15] TESTS:: - sage: RBF(Ei(1)) + sage: RBF(Ei(1)) # abs tol 5e-16 [1.89511781635594 +/- 4.94e-15] """ cdef RealBall res = self._new() @@ -3531,12 +3531,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: RBF(1).Ci() + sage: RBF(1).Ci() # abs tol 1e-16 [0.337403922900968 +/- 3.25e-16] TESTS:: - sage: RBF(Ci(1)) + sage: RBF(Ci(1)) # abs tol 1e-16 [0.337403922900968 +/- 3.25e-16] """ cdef RealBall res = self._new() @@ -3575,12 +3575,12 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: RBF(1).Chi() + sage: RBF(1).Chi() # abs tol 1e-17 [0.837866940980208 +/- 4.72e-16] TESTS:: - sage: RBF(Chi(1)) + sage: RBF(Chi(1)) # abs tol 1e-17 [0.837866940980208 +/- 4.72e-16] """ cdef RealBall res = self._new() @@ -3597,7 +3597,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: RBF(3).li() + sage: RBF(3).li() # abs tol 1e-15 [2.16358859466719 +/- 4.72e-15] TESTS:: @@ -3621,7 +3621,7 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: RBF(3).Li() + sage: RBF(3).Li() # abs tol 1e-15 [1.11842481454970 +/- 7.61e-15] """ cdef RealBall res = self._new() @@ -3648,9 +3648,9 @@ cdef class RealBall(RingElement): EXAMPLES:: - sage: RBF(sin(3)).beta(RBF(2/3).sqrt()) + sage: RBF(sin(3)).beta(RBF(2/3).sqrt()) # abs tol 1e-13 [7.407661629415 +/- 1.07e-13] - sage: RealBallField(100)(7/2).beta(1) + sage: RealBallField(100)(7/2).beta(1) # abs tol 1e-30 [0.28571428571428571428571428571 +/- 5.23e-30] sage: RealBallField(100)(7/2).beta(1, 1/2) [0.025253813613805268728601584361 +/- 2.53e-31] @@ -3685,9 +3685,9 @@ cdef class RealBall(RingElement): sage: RBF(1/2).gamma() [1.772453850905516 +/- ...e-16] - sage: RBF(gamma(3/2, RBF(2).sqrt())) + sage: RBF(gamma(3/2, RBF(2).sqrt())) # abs tol 2e-17 [0.37118875695353 +/- 3.00e-15] - sage: RBF(3/2).gamma_inc(RBF(2).sqrt()) + sage: RBF(3/2).gamma_inc(RBF(2).sqrt()) # abs tol 2e-17 [0.37118875695353 +/- 3.00e-15] .. SEEALSO:: diff --git a/src/sage/rings/real_lazy.pyx b/src/sage/rings/real_lazy.pyx index 1ac123cfdb3..4ea1acc0fe1 100644 --- a/src/sage/rings/real_lazy.pyx +++ b/src/sage/rings/real_lazy.pyx @@ -50,7 +50,7 @@ cdef QQ, RR, CC, RealField, ComplexField from sage.rings.rational_field import QQ from sage.rings.real_mpfr import RR, RealField from sage.rings.complex_mpfr import ComplexField -CC = ComplexField(53) +from sage.rings.cc import CC cdef _QQx = None @@ -411,10 +411,11 @@ class ComplexLazyField_class(LazyField): sage: CLF.interval_field() is CIF True """ - from sage.rings.all import CIF, ComplexIntervalField if prec is None: + from sage.rings.cif import CIF return CIF else: + from sage.rings.complex_interval_field import ComplexIntervalField return ComplexIntervalField(prec) def gen(self, i=0): diff --git a/src/sage/rings/real_mpfr.pyx b/src/sage/rings/real_mpfr.pyx index a5bfcc070e8..24abbd3d5ba 100644 --- a/src/sage/rings/real_mpfr.pyx +++ b/src/sage/rings/real_mpfr.pyx @@ -2168,19 +2168,6 @@ cdef class RealNumber(sage.structure.element.RingElement): mpfr_free_str(s) return t - def __hex__(self): - """ - TESTS:: - - sage: hex(RR(-1/3)) # py2 - doctest:...: - DeprecationWarning: use the method .hex instead - See http://trac.sagemath.org/24568 for details. - '-0x5.5555555555554p-4' - """ - deprecation(24568, 'use the method .hex instead') - return self.hex() - def __copy__(self): """ Return copy of ``self`` - since ``self`` is immutable, we just return diff --git a/src/sage/sat/solvers/dimacs.py b/src/sage/sat/solvers/dimacs.py index abc94569f59..d9361d296fe 100644 --- a/src/sage/sat/solvers/dimacs.py +++ b/src/sage/sat/solvers/dimacs.py @@ -517,27 +517,12 @@ def __call__(self, **kwds): ....: break ....: except ZeroDivisionError: ....: pass - sage: solve_sat(F, solver=sage.sat.solvers.Glucose) # optional - glucose - [{k003: 1, - k002: 1, - k001: 0, - k000: 1, - s003: 1, - s002: 0, - s001: 1, - s000: 0, - w103: 1, - w102: 1, - w101: 1, - w100: 1, - x103: 0, - x102: 0, - x101: 0, - x100: 1, - k103: 1, - k102: 0, - k101: 1, - k100: 1}] + sage: [sol] = solve_sat(F, solver=sage.sat.solvers.Glucose) # optional - glucose + sage: Fsol = F.subs(sol) # optional - glucose + sage: Fsol # optional - glucose + Polynomial Sequence with 36 Polynomials in 0 Variables + sage: Fsol.reduced() # optional - glucose + [] :: @@ -604,27 +589,14 @@ def __call__(self, **kwds): ....: break ....: except ZeroDivisionError: ....: pass - sage: solve_sat(F, solver=sage.sat.solvers.GlucoseSyrup) # optional - glucose - [{k003: 1, - k002: 1, - k001: 0, - k000: 1, - s003: 1, - s002: 0, - s001: 1, - s000: 0, - w103: 1, - w102: 1, - w101: 1, - w100: 1, - x103: 0, - x102: 0, - x101: 0, - x100: 1, - k103: 1, - k102: 0, - k101: 1, - k100: 1}] + sage: [sol] = solve_sat(F, solver=sage.sat.solvers.GlucoseSyrup) # optional - glucose + sage: Fsol = F.subs(sol) # optional - glucose + sage: Fsol # optional - glucose + Polynomial Sequence with 36 Polynomials in 0 Variables + sage: Fsol.reduced() # optional - glucose + [] + + :: sage: from sage.sat.solvers.dimacs import GlucoseSyrup sage: solver = GlucoseSyrup() diff --git a/src/sage/schemes/affine/affine_homset.py b/src/sage/schemes/affine/affine_homset.py index f712c0f130d..b9260996204 100644 --- a/src/sage/schemes/affine/affine_homset.py +++ b/src/sage/schemes/affine/affine_homset.py @@ -34,7 +34,9 @@ #***************************************************************************** from sage.misc.verbose import verbose -from sage.rings.all import ZZ, CC, RR +from sage.rings.integer_ring import ZZ +from sage.rings.real_mpfr import RR +from sage.rings.cc import CC from sage.rings.rational_field import is_RationalField from sage.categories.fields import Fields from sage.categories.number_fields import NumberFields @@ -209,7 +211,7 @@ def points(self, **kwds): sage: A. = AffineSpace(CC, 2) sage: E = A.subscheme([y^3 - x^3 - x^2, x*y]) sage: E(A.base_ring()).points() - verbose 0 (124: affine_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. + verbose 0 (...: affine_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. [(-1.00000000000000, 0.000000000000000), (0.000000000000000, 0.000000000000000)] @@ -218,7 +220,7 @@ def points(self, **kwds): sage: A. = AffineSpace(CDF, 2) sage: E = A.subscheme([x1^2 + x2^2 + x1*x2, x1 + x2]) sage: E(A.base_ring()).points() - verbose 0 (124: affine_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. + verbose 0 (...: affine_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. [(0.0, 0.0)] """ from sage.schemes.affine.affine_space import is_AffineSpace diff --git a/src/sage/schemes/cyclic_covers/cycliccover_finite_field.py b/src/sage/schemes/cyclic_covers/cycliccover_finite_field.py index d6faa60101f..1526e6afe3e 100644 --- a/src/sage/schemes/cyclic_covers/cycliccover_finite_field.py +++ b/src/sage/schemes/cyclic_covers/cycliccover_finite_field.py @@ -1205,7 +1205,7 @@ def frobenius_polynomial(self): ....: fail = False ....: p = random_prime(500, lbound=5) ....: for i in range(1, 4): - ....: F = GF(p**i) + ....: F = GF((p, i)) ....: Fx = PolynomialRing(F, 'x') ....: b = F.random_element() ....: while b == 0: diff --git a/src/sage/schemes/elliptic_curves/cardinality.py b/src/sage/schemes/elliptic_curves/cardinality.py index 7016a8c70a7..aeda8a32727 100644 --- a/src/sage/schemes/elliptic_curves/cardinality.py +++ b/src/sage/schemes/elliptic_curves/cardinality.py @@ -576,7 +576,7 @@ def _cardinality_subfield(self, jpol): # Let j be the j-invariant as element of the smallest finite # field over which j is defined. - GFj = GF(p**jdeg, name='j', modulus=jpol) + GFj = GF((p, jdeg), name='j', modulus=jpol) j = GFj.gen() # Use special code for j = 0, 1728 @@ -587,7 +587,7 @@ def _cardinality_subfield(self, jpol): # Recursive call which does all the real work: E0 = EllipticCurve_from_j(j) - N = E0.cardinality(extension_degree=d//jdeg) + N = E0.cardinality(extension_degree=d // jdeg) # Map to the original larger field phi = GFj.hom([self.j_invariant()]) diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index 9945c5bb0c7..2d22b404f4e 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -178,7 +178,7 @@ def points(self): :: - sage: K = GF(p**2,'a') + sage: K = GF((p, 2),'a') sage: E = E.change_ring(K) sage: len(E.points()) 32 @@ -1538,14 +1538,14 @@ def is_j_supersingular(j, proof=True): if degj == 1: j = -jpol(0) # = j, but in GF(p) elif d > 2: - F = GF(p**2, 'a') - j = jpol.roots(F,multiplicities=False)[0] # j, but in GF(p^2) + F = GF((p, 2), 'a') + j = jpol.roots(F, multiplicities=False)[0] # j, but in GF(p^2) E = EllipticCurve(j=j) if degj == 1: for i in range(10): P = E.random_element() - if not ((p+1)*P).is_zero(): + if not ((p + 1) * P).is_zero(): return False else: n = None # will hold either p+1 or p-1 later diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 63b027eae62..dc3e63bedf1 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -38,12 +38,12 @@ (0 : i : 1) sage: P.order() +Infinity - sage: 101*P-100*P==P + sage: 101*P-100*P == P True An example over a finite field:: - sage: K. = GF(101^3) + sage: K. = GF((101,3)) sage: E = EllipticCurve(K,[1,0,0,0,-1]) sage: P = E(40*a^2 + 69*a + 84 , 58*a^2 + 73*a + 45) sage: P.order() @@ -53,7 +53,7 @@ Arithmetic with a point over an extension of a finite field:: - sage: k. = GF(5^2) + sage: k. = GF((5,2)) sage: E = EllipticCurve(k,[1,0]); E Elliptic Curve defined by y^2 = x^3 + x over Finite Field in a of size 5^2 sage: P = E([a,2*a+4]) @@ -87,7 +87,6 @@ ... ZeroDivisionError: Inverse of 1520944668 does not exist (characteristic = 1715761513 = 26927*63719) - AUTHORS: - William Stein (2005) -- Initial version @@ -301,7 +300,7 @@ def __init__(self, curve, v, check=True): raise TypeError("Coordinates %s do not define a point on %s" % (list(v), curve)) SchemeMorphism_point_abelian_variety_field.__init__(self, point_homset, v, check=False) - #AdditiveGroupElement.__init__(self, point_homset) + # AdditiveGroupElement.__init__(self, point_homset) def _repr_(self): """ @@ -448,12 +447,12 @@ def scheme(self): sage: P.scheme() Elliptic Curve defined by y^2 = x^3 + x + 1 over Number Field in a with defining polynomial x^2 - 3 """ - #The following text is just not true: it applies to the class - #EllipticCurvePoint, which appears to be never used, but does - #not apply to EllipticCurvePoint_field which is simply derived - #from AdditiveGroupElement. + # The following text is just not true: it applies to the class + # EllipticCurvePoint, which appears to be never used, but does + # not apply to EllipticCurvePoint_field which is simply derived + # from AdditiveGroupElement. # - #"Technically, points on curves in Sage are scheme maps from + # "Technically, points on curves in Sage are scheme maps from # the domain Spec(F) where F is the base field of the curve to # the codomain which is the curve. See also domain() and # codomain()." @@ -917,7 +916,7 @@ def division_points(self, m, poly_only=False): We create a curve over a non-prime finite field with group of order `18`:: - sage: k. = GF(25) + sage: k. = GF((5,2)) sage: E = EllipticCurve(k, [1,2+a,3,4*a,2]) sage: P = E([3,3*a+4]) sage: factor(E.order()) @@ -1094,23 +1093,23 @@ def division_points(self, m, poly_only=False): elif mQ == nP: ans.append(nQ) - if not ans: + if not ans: return ans # set orders of points found when self's order is known: if self.is_zero(): self._order = Integer(1) try: - n = self._order # do not compute, just use if already known - if n==oo: + n = self._order # do not compute, just use if already known + if n == oo: for Q in ans: Q._order = oo else: mfac = m.factor() for Q in ans: - R = n*Q - Q._order = n*generic.order_from_multiple(R, m, factorization=mfac, operation='+') - except AttributeError: # do nothing about order if self's order unknown + R = n * Q + Q._order = n * generic.order_from_multiple(R, m, factorization=mfac, operation='+') + except AttributeError: # do nothing about order if self's order unknown pass # Finally, sort and return @@ -1256,7 +1255,7 @@ def set_order(self, value): E = self.curve() q = E.base_ring().order() if value <= 0: - raise ValueError('Value %s illegal for point order'%value) + raise ValueError('Value %s illegal for point order' % value) low, hi = Hasse_bounds(q) if value > hi: raise ValueError('Value %s illegal: outside max Hasse bound' % value) @@ -1264,7 +1263,7 @@ def set_order(self, value): raise ValueError('Value %s illegal: %s * %s is not the identity' % (value, value, self)) self._order = value - ############################## end ################################ + # ############################# end ################################ def _line_(self, R, Q): r""" @@ -1282,7 +1281,7 @@ def _line_(self, R, Q): EXAMPLES:: - sage: F.=GF(2^5) + sage: F.=GF((2,5)) sage: E=EllipticCurve(F,[0,0,1,1,1]) sage: P = E(a^4 + 1, a^3) sage: Q = E(a^4, a^4 + a^3) @@ -1360,10 +1359,10 @@ def _miller_(self, Q, n): EXAMPLES:: - sage: F.=GF(2^5) + sage: F.=GF((2,5)) sage: E=EllipticCurve(F,[0,0,1,1,1]) sage: P = E(a^4 + 1, a^3) - sage: Fx.=GF(2^(4*5)) + sage: Fx.=GF((2,(4*5))) sage: Ex=EllipticCurve(Fx,[0,0,1,1,1]) sage: phi=Hom(F,Fx)(F.gen().minpoly().roots(Fx)[0][0]) sage: Px=Ex(phi(P.xy()[0]),phi(P.xy()[1])) @@ -1379,7 +1378,7 @@ def _miller_(self, Q, n): An example of even order:: - sage: F. = GF(19^4) + sage: F. = GF((19,4)) sage: E = EllipticCurve(F,[-1,0]) sage: P = E(15*a^3 + 17*a^2 + 14*a + 13,16*a^3 + 7*a^2 + a + 18) sage: Q = E(10*a^3 + 16*a^2 + 4*a + 2, 6*a^3 + 4*a^2 + 3*a + 2) @@ -1533,10 +1532,10 @@ def weil_pairing(self, Q, n): EXAMPLES:: - sage: F.=GF(2^5) + sage: F.=GF((2,5)) sage: E=EllipticCurve(F,[0,0,1,1,1]) sage: P = E(a^4 + 1, a^3) - sage: Fx.=GF(2^(4*5)) + sage: Fx.=GF((2,4*5)) sage: Ex=EllipticCurve(Fx,[0,0,1,1,1]) sage: phi=Hom(F,Fx)(F.gen().minpoly().roots(Fx)[0][0]) sage: Px=Ex(phi(P.xy()[0]),phi(P.xy()[1])) @@ -1558,7 +1557,7 @@ def weil_pairing(self, Q, n): A larger example (see :trac:`4964`):: - sage: P,Q = EllipticCurve(GF(19^4,'a'),[-1,0]).gens() + sage: P,Q = EllipticCurve(GF((19,4),'a'),[-1,0]).gens() sage: P.order(), Q.order() (360, 360) sage: z = P.weil_pairing(Q,360) @@ -1690,7 +1689,7 @@ def tate_pairing(self, Q, n, k, q=None): the pairing extension field, and we also demonstrate the bilinearity of the pairing:: - sage: K. = GF(p^k) + sage: K. = GF((p,k)) sage: EK = E.base_extend(K); P = EK(P) sage: Qx = 69*a^5 + 96*a^4 + 22*a^3 + 86*a^2 + 6*a + 35 sage: Qy = 34*a^5 + 24*a^4 + 16*a^3 + 41*a^2 + 4*a + 40 @@ -1727,10 +1726,10 @@ def tate_pairing(self, Q, n, k, q=None): An example where we have to pass the base field size (and we again have agreement with the Weil pairing):: - sage: F.=GF(2^5) + sage: F.=GF((2,5)) sage: E=EllipticCurve(F,[0,0,1,1,1]) sage: P = E(a^4 + 1, a^3) - sage: Fx.=GF(2^(4*5)) + sage: Fx.=GF((2,4*5)) sage: Ex=EllipticCurve(Fx,[0,0,1,1,1]) sage: phi=Hom(F,Fx)(F.gen().minpoly().roots(Fx)[0][0]) sage: Px=Ex(phi(P.xy()[0]),phi(P.xy()[1])) @@ -1828,7 +1827,7 @@ def ate_pairing(self, Q, n, k, t, q=None): sage: p = 7549; A = 0; B = 1; n = 157; k = 6; t = 14 sage: F = GF(p); E = EllipticCurve(F, [A, B]) - sage: R. = F[]; K. = GF(p^k, modulus=x^k+2) + sage: R. = F[]; K. = GF((p,k), modulus=x^k+2) sage: EK = E.base_extend(K) sage: P = EK(3050, 5371); Q = EK(6908*a^4, 3231*a^3) sage: P.ate_pairing(Q, n, k, t) @@ -1843,7 +1842,7 @@ def ate_pairing(self, Q, n, k, t, q=None): sage: p = 2213; A = 1; B = 49; n = 1093; k = 7; t = 28 sage: F = GF(p); E = EllipticCurve(F, [A, B]) - sage: R. = F[]; K. = GF(p^k, modulus=x^k+2) + sage: R. = F[]; K. = GF((p,k), modulus=x^k+2) sage: EK = E.base_extend(K) sage: P = EK(1583, 1734) sage: Qx = 1729*a^6+1767*a^5+245*a^4+980*a^3+1592*a^2+1883*a+722 @@ -1861,7 +1860,7 @@ def ate_pairing(self, Q, n, k, t, q=None): sage: p = 2017; A = 1; B = 30; n = 29; k = 7; t = -70 sage: F = GF(p); E = EllipticCurve(F, [A, B]) - sage: R. = F[]; K. = GF(p^k, modulus=x^k+2) + sage: R. = F[]; K. = GF((p,k), modulus=x^k+2) sage: EK = E.base_extend(K) sage: P = EK(369, 716) sage: Qx = 1226*a^6+1778*a^5+660*a^4+1791*a^3+1750*a^2+867*a+770 @@ -1926,7 +1925,7 @@ def ate_pairing(self, Q, n, k, t, q=None): sage: p = 29; A = 1; B = 0; n = 5; k = 2; t = 10 sage: F = GF(p); R. = F[] sage: E = EllipticCurve(F, [A, B]); - sage: K. = GF(p^k, modulus=x^k+2); EK = E.base_extend(K) + sage: K. = GF((p,k), modulus=x^k+2); EK = E.base_extend(K) sage: P = EK(13, 8); Q = EK(13, 21) sage: P.ate_pairing(Q, n, k, t) Traceback (most recent call last): @@ -1938,7 +1937,7 @@ def ate_pairing(self, Q, n, k, t, q=None): sage: p = 29; A = 1; B = 0; n = 5; k = 2; t = 10 sage: F = GF(p); R. = F[] sage: E = EllipticCurve(F, [A, B]); - sage: K. = GF(p^k, modulus=x^k+2); EK = E.base_extend(K) + sage: K. = GF((p,k), modulus=x^k+2); EK = E.base_extend(K) sage: P = EK(14, 10*a); Q = EK(13, 21) sage: P.ate_pairing(Q, n, k, t) Traceback (most recent call last): @@ -2669,7 +2668,7 @@ def height(self, precision=None, normalised=True, algorithm='pari'): height = rings.RealField(precision)(h) else: height = (self.non_archimedean_local_height(prec=precision) - + self.archimedean_local_height(prec=precision)) + + self.archimedean_local_height(prec=precision)) # The cached height is the one that is independent of the base field. self.__height = height @@ -2830,7 +2829,7 @@ def archimedean_local_height(self, v=None, prec=None, weighted=False): working_prec = prec + extra_prec RC = RealField(working_prec) if v_is_real else ComplexField(working_prec) - #print("Using working precision {}, |D| = {}".format(working_prec, RC(D).abs())) + # print("Using working precision {}, |D| = {}".format(working_prec, RC(D).abs())) # NB We risk losing much precision if we compute the embedding # of K into RR or CC to some precision and then apply that to @@ -2848,7 +2847,7 @@ def archimedean_local_height(self, v=None, prec=None, weighted=False): H = max(RC(4).abs(), b2.abs(), 2*b4.abs(), 2*b6.abs(), b8.abs()) absdisc = RC(v_inf(E.discriminant())).abs() - adl3 = 0 if absdisc>=1 else absdisc.log()/3 + adl3 = 0 if absdisc >= 1 else absdisc.log() / 3 nterms = int(math.ceil(0.51*working_prec + 0.5 + 0.75 * (7 + 4*H.log()/3 - adl3).log())) b2p = b2 - 12 @@ -3020,10 +3019,10 @@ def non_archimedean_local_height(self, v=None, prec=None, # the model is not minimal. h = (log(c) + sum(self.non_archimedean_local_height(p, prec, weighted=True, is_minimal=(e < 12)) - for p,e in factorD if not p.divides(c)) + for p, e in factorD if not p.divides(c)) + sum(self.non_archimedean_local_height(p, prec, weighted=True) - c.valuation(p) * log(p) - for p,e in factorD if e >= 12 and p.divides(c))) + for p, e in factorD if e >= 12 and p.divides(c))) else: factorD = K.factor(D) if self[0] == 0: @@ -3034,10 +3033,10 @@ def non_archimedean_local_height(self, v=None, prec=None, # the model is not minimal. h = (log(c.norm()) + sum(self.non_archimedean_local_height(v, prec, weighted=True, is_minimal=(e < 12)) - for v,e in factorD if not v.divides(c)) + for v, e in factorD if not v.divides(c)) + sum(self.non_archimedean_local_height(v, prec, weighted=True) - c.valuation(v) * log(v.norm()) - for v,e in factorD if e >= 12 and v.divides(c))) + for v, e in factorD if e >= 12 and v.divides(c))) if not weighted: h /= K.degree() return h @@ -3465,7 +3464,7 @@ def discrete_log(self, Q, ord=None): EXAMPLES:: - sage: F = GF(3^6,'a') + sage: F = GF((3,6),'a') sage: a = F.gen() sage: E = EllipticCurve([0,1,1,a,a]) sage: E.cardinality() @@ -3511,7 +3510,7 @@ def order(self): EXAMPLES:: - sage: k. = GF(5^5) + sage: k. = GF((5,5)) sage: E = EllipticCurve(k,[2,4]); E Elliptic Curve defined by y^2 = x^3 + 2*x + 4 over Finite Field in a of size 5^5 sage: P = E(3*a^4 + 3*a , 2*a + 1 ) @@ -3536,7 +3535,7 @@ def order(self): The next example has `j(E)=0`:: sage: p = 33554501 - sage: F. = GF(p^2) + sage: F. = GF((p,2)) sage: E = EllipticCurve(F,[0,1]) sage: E.j_invariant() 0 @@ -3547,7 +3546,7 @@ def order(self): Similarly when `j(E)=1728`:: sage: p = 33554473 - sage: F. = GF(p^2) + sage: F. = GF((p,2)) sage: E = EllipticCurve(F,[1,0]) sage: E.j_invariant() 1728 diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index 4ac3bd866db..ae86442455f 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -2875,7 +2875,7 @@ def selmer_rank(self): To establish that the rank is in fact 0 in this case, we would need to carry out a higher descent:: - sage: E.three_selmer_rank() # optional: magma + sage: E.three_selmer_rank() # optional - magma 0 Or use the L-function to compute the analytic rank:: diff --git a/src/sage/schemes/elliptic_curves/ell_torsion.py b/src/sage/schemes/elliptic_curves/ell_torsion.py index 5e1795f088f..f8af5714907 100644 --- a/src/sage/schemes/elliptic_curves/ell_torsion.py +++ b/src/sage/schemes/elliptic_curves/ell_torsion.py @@ -403,7 +403,7 @@ def torsion_bound(E, number_of_places=20): k += 1 for fi, ei in f.factor_mod(p): di = fi.degree() - Fq = GF(p**di) + Fq = GF((p, di)) ai = fi.roots(Fq, multiplicities=False)[0] def red(c): diff --git a/src/sage/schemes/elliptic_curves/height.py b/src/sage/schemes/elliptic_curves/height.py index de9cbc670de..0116d7bd0fe 100644 --- a/src/sage/schemes/elliptic_curves/height.py +++ b/src/sage/schemes/elliptic_curves/height.py @@ -33,7 +33,12 @@ from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.infinity import infinity -from sage.rings.all import RR, RDF, RIF, CC, CDF, CIF +from sage.rings.cif import CIF +from sage.rings.cc import CC +from sage.rings.complex_double import CDF +from sage.rings.real_double import RDF +from sage.rings.real_mpfi import RIF +from sage.rings.real_mpfr import RR from sage.misc.cachefunc import cached_method from sage.misc.all import cartesian_product_iterator diff --git a/src/sage/schemes/elliptic_curves/period_lattice_region.pyx b/src/sage/schemes/elliptic_curves/period_lattice_region.pyx index f308727efdb..18ac90a1eea 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice_region.pyx +++ b/src/sage/schemes/elliptic_curves/period_lattice_region.pyx @@ -28,7 +28,7 @@ AUTHORS: import numpy as np cimport numpy as np -from sage.rings.all import CIF +from sage.rings.cif import CIF from cpython.object cimport Py_EQ, Py_NE diff --git a/src/sage/schemes/projective/projective_homset.py b/src/sage/schemes/projective/projective_homset.py index e3d40a6179f..cb6420d80f1 100644 --- a/src/sage/schemes/projective/projective_homset.py +++ b/src/sage/schemes/projective/projective_homset.py @@ -38,7 +38,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.rings.all import ZZ, CC, RR +from sage.rings.integer_ring import ZZ +from sage.rings.real_mpfr import RR +from sage.rings.cc import CC from sage.schemes.generic.homset import SchemeHomset_points from sage.misc.verbose import verbose @@ -144,7 +146,7 @@ def points(self, **kwds): sage: P. = ProjectiveSpace(CC, 2) sage: E = P.subscheme([y^3 - x^3 - x*z^2, x*y*z]) sage: L=E(P.base_ring()).points(); sorted(L, key=str) - verbose 0 (71: projective_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. + verbose 0 (...: projective_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. [(-0.500000000000000 + 0.866025403784439*I : 1.00000000000000 : 0.000000000000000), (-0.500000000000000 - 0.866025403784439*I : 1.00000000000000 : 0.000000000000000), (-1.00000000000000*I : 0.000000000000000 : 1.00000000000000), @@ -159,7 +161,7 @@ def points(self, **kwds): sage: P. = ProjectiveSpace(CDF, 2) sage: E = P.subscheme([y^2 + x^2 + z^2, x*y*z]) sage: len(E(P.base_ring()).points()) - verbose 0 (71: projective_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. + verbose 0 (...: projective_homset.py, points) Warning: computations in the numerical fields are inexact;points may be computed partially or incorrectly. 6 """ from sage.schemes.projective.projective_space import is_ProjectiveSpace diff --git a/src/sage/schemes/riemann_surfaces/riemann_surface.py b/src/sage/schemes/riemann_surfaces/riemann_surface.py index 3c3d3871286..f9d1e2bdce6 100644 --- a/src/sage/schemes/riemann_surfaces/riemann_surface.py +++ b/src/sage/schemes/riemann_surfaces/riemann_surface.py @@ -1390,7 +1390,7 @@ def make_zw_interpolator(self, upstairs_edge): sage: S = RiemannSurface(f) sage: _ = S.homology_basis() sage: g,d = S.make_zw_interpolator([(0,0),(1,0)]); - sage: all(f(*g(i*0.1)).abs() < 1e-13for i in range(10)) + sage: all(f(*g(i*0.1)).abs() < 1e-13 for i in range(10)) True sage: abs((g(1)[0]-g(0)[0]) - d) < 1e-13 True diff --git a/src/sage/structure/graphics_file.py b/src/sage/structure/graphics_file.py index 0dab46ac32a..5d2a6929851 100644 --- a/src/sage/structure/graphics_file.py +++ b/src/sage/structure/graphics_file.py @@ -11,17 +11,17 @@ class Mime(object): - TEXT = u'text/plain' - HTML = u'text/html' - LATEX = u'text/latex' - JSON = u'application/json' - JAVASCRIPT = u'application/javascript' - PDF = u'application/pdf' - PNG = u'image/png' - JPG = u'image/jpeg' - SVG = u'image/svg+xml' - - JMOL = u'application/jmol' + TEXT = 'text/plain' + HTML = 'text/html' + LATEX = 'text/latex' + JSON = 'application/json' + JAVASCRIPT = 'application/javascript' + PDF = 'application/pdf' + PNG = 'image/png' + JPG = 'image/jpeg' + SVG = 'image/svg+xml' + + JMOL = 'application/jmol' @classmethod def validate(cls, value): @@ -41,7 +41,7 @@ def validate(cls, value): sage: from sage.structure.graphics_file import Mime sage: Mime.validate('image/png') - u'image/png' + 'image/png' sage: Mime.validate('foo/bar') Traceback (most recent call last): ... diff --git a/src/sage/structure/unique_representation.py b/src/sage/structure/unique_representation.py index 9ead7a44be3..5d8d4ad758b 100644 --- a/src/sage/structure/unique_representation.py +++ b/src/sage/structure/unique_representation.py @@ -1221,7 +1221,7 @@ class UniqueRepresentation(CachedRepresentation, WithEqualityById): sage: isinstance(GF(7), GF) Traceback (most recent call last): ... - TypeError: isinstance() arg 2 must be a type or tuple of types + TypeError: isinstance() arg 2 must be a type... sage: isinstance(GF, sage.structure.factory.UniqueFactory) True diff --git a/src/sage/symbolic/assumptions.py b/src/sage/symbolic/assumptions.py index 679978157fd..522edd5ecd3 100644 --- a/src/sage/symbolic/assumptions.py +++ b/src/sage/symbolic/assumptions.py @@ -71,7 +71,10 @@ ValueError: Assumption is inconsistent sage: forget() """ -from sage.rings.all import ZZ, QQ, RR, CC +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.real_mpfr import RR +from sage.rings.cc import CC from sage.symbolic.ring import is_SymbolicVariable from sage.structure.unique_representation import UniqueRepresentation diff --git a/src/sage/symbolic/callable.py b/src/sage/symbolic/callable.py index 5f3a7bea659..4b8efda38aa 100644 --- a/src/sage/symbolic/callable.py +++ b/src/sage/symbolic/callable.py @@ -38,27 +38,27 @@ sage: f(1)=2 Traceback (most recent call last): ... - SyntaxError: can...t assign to function call + SyntaxError: can...t assign to function call... sage: f(x,1)=2 Traceback (most recent call last): ... - SyntaxError: can...t assign to function call + SyntaxError: can...t assign to function call... sage: f(1,2)=3 Traceback (most recent call last): ... - SyntaxError: can...t assign to function call + SyntaxError: can...t assign to function call... sage: f(1,2)=x Traceback (most recent call last): ... - SyntaxError: can...t assign to function call + SyntaxError: can...t assign to function call... sage: f(x,2)=x Traceback (most recent call last): ... - SyntaxError: can...t assign to function call + SyntaxError: can...t assign to function call... """ import sage.rings.abc diff --git a/src/sage/symbolic/constants.py b/src/sage/symbolic/constants.py index 29c43eb64c5..647a36fbd9e 100644 --- a/src/sage/symbolic/constants.py +++ b/src/sage/symbolic/constants.py @@ -46,8 +46,8 @@ 3.14159265358979323846264338328 sage: mathematica(pi) # optional - mathematica Pi - sage: maple(pi) # optional - maple - Pi + sage: pi._maple_init_() + 'Pi' sage: octave(pi) # optional - octave 3.14159 @@ -556,8 +556,9 @@ def __init__(self, name="pi"): sage: mathml(pi) π """ - conversions = dict(axiom='%pi', fricas='%pi', maxima='%pi', giac='pi', gp='Pi', kash='PI', - mathematica='Pi', matlab='pi', maple='pi', + conversions = dict(axiom='%pi', fricas='%pi', maxima='%pi', giac='pi', + gp='Pi', kash='PI', + mathematica='Pi', matlab='pi', maple='Pi', octave='pi', pari='Pi', pynac='Pi') Constant.__init__(self, name, conversions=conversions, latex=r"\pi", mathml="π", diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index 843cfee7f4c..e7c756acf3c 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # distutils: sources = sage/symbolic/ginac/add.cpp sage/symbolic/ginac/archive.cpp sage/symbolic/ginac/assume.cpp sage/symbolic/ginac/basic.cpp sage/symbolic/ginac/cmatcher.cpp sage/symbolic/ginac/constant.cpp sage/symbolic/ginac/context.cpp sage/symbolic/ginac/ex.cpp sage/symbolic/ginac/expair.cpp sage/symbolic/ginac/expairseq.cpp sage/symbolic/ginac/exprseq.cpp sage/symbolic/ginac/fderivative.cpp sage/symbolic/ginac/function.cpp sage/symbolic/ginac/function_info.cpp sage/symbolic/ginac/infinity.cpp sage/symbolic/ginac/infoflagbase.cpp sage/symbolic/ginac/inifcns.cpp sage/symbolic/ginac/inifcns_comb.cpp sage/symbolic/ginac/inifcns_gamma.cpp sage/symbolic/ginac/inifcns_hyperb.cpp sage/symbolic/ginac/inifcns_hyperg.cpp sage/symbolic/ginac/inifcns_nstdsums.cpp sage/symbolic/ginac/inifcns_orthopoly.cpp sage/symbolic/ginac/inifcns_trans.cpp sage/symbolic/ginac/inifcns_trig.cpp sage/symbolic/ginac/inifcns_zeta.cpp sage/symbolic/ginac/lst.cpp sage/symbolic/ginac/matrix.cpp sage/symbolic/ginac/mpoly-giac.cpp sage/symbolic/ginac/mpoly-ginac.cpp sage/symbolic/ginac/mpoly-singular.cpp sage/symbolic/ginac/mpoly.cpp sage/symbolic/ginac/mul.cpp sage/symbolic/ginac/normal.cpp sage/symbolic/ginac/numeric.cpp sage/symbolic/ginac/operators.cpp sage/symbolic/ginac/order.cpp sage/symbolic/ginac/power.cpp sage/symbolic/ginac/print.cpp sage/symbolic/ginac/pseries.cpp sage/symbolic/ginac/py_funcs.cpp sage/symbolic/ginac/registrar.cpp sage/symbolic/ginac/relational.cpp sage/symbolic/ginac/remember.cpp sage/symbolic/ginac/sum.cpp sage/symbolic/ginac/symbol.cpp sage/symbolic/ginac/templates.cpp sage/symbolic/ginac/upoly-ginac.cpp sage/symbolic/ginac/useries.cpp sage/symbolic/ginac/utils.cpp sage/symbolic/ginac/wildcard.cpp # distutils: language = c++ -# distutils: libraries = gmp SINGULAR_LIBRARIES +# distutils: libraries = flint gmp SINGULAR_LIBRARIES # distutils: extra_compile_args = -std=c++11 SINGULAR_CFLAGS # distutils: depends = ginac/add.h ginac/archive.h ginac/assertion.h ginac/assume.h ginac/basic.h ginac/class_info.h ginac/cmatcher.h ginac/compiler.h ginac/constant.h ginac/container.h ginac/context.h ginac/ex.h ginac/ex_utils.h ginac/expair.h ginac/expairseq.h ginac/exprseq.h ginac/extern_templates.h ginac/fderivative.h ginac/flags.h ginac/function.h ginac/ginac.h ginac/infinity.h ginac/infoflagbase.h ginac/inifcns.h ginac/lst.h ginac/matrix.h ginac/mpoly.h ginac/mul.h ginac/normal.h ginac/numeric.h ginac/operators.h ginac/order.h ginac/optional.hpp ginac/power.h ginac/print.h ginac/pseries.h ginac/ptr.h ginac/py_funcs.h ginac/pynac-config.h ginac/registrar.h ginac/relational.h ginac/remember.h ginac/sum.h ginac/symbol.h ginac/templates.h ginac/tostring.h ginac/upoly.h ginac/useries-flint.h ginac/useries.h ginac/utils.h ginac/wildcard.h # distutils: include_dirs = SINGULAR_INCDIR @@ -1124,7 +1124,7 @@ cdef class Expression(Expression_abc): return AsciiArt(self._sympy_character_art(False).splitlines()) def _unicode_art_(self): - u""" + """ Unicode art magic method. See :mod:`sage.typeset.unicode_art` for details. @@ -1240,7 +1240,7 @@ cdef class Expression(Expression_abc): sage: a = (pi + 2).sin() sage: a._maple_init_() - 'sin((pi)+(2))' + 'sin((Pi)+(2))' sage: a = (pi + 2).sin() sage: a._mathematica_init_() @@ -1274,7 +1274,7 @@ cdef class Expression(Expression_abc): sage: gap(e + pi^2 + x^3) x^3 + pi^2 + e """ - return '"%s"'%repr(self) + return '"%s"' % repr(self) def _singular_init_(self): """ @@ -4850,6 +4850,11 @@ cdef class Expression(Expression_abc): sage: exp(log(1+x)*(1/x)).series(x) (e) + (-1/2*e)*x + (11/24*e)*x^2 + (-7/16*e)*x^3 + (2447/5760*e)*x^4 + ... + + Check that :trac:`32640` is fixed:: + + sage: ((1 - x)^-x).series(x, 8) + 1 + 1*x^2 + 1/2*x^3 + 5/6*x^4 + 3/4*x^5 + 33/40*x^6 + 5/6*x^7 + Order(x^8) """ cdef Expression symbol0 = self.coerce_in(symbol) cdef GEx x @@ -10082,6 +10087,8 @@ cdef class Expression(Expression_abc): Traceback (most recent call last): ... TypeError: self is not a rational expression + sage: n = var('n'); assume(n,'integer'); assume(n>0); (e^(2*n)/(e^(2*n) - 1)).numerator() + e^(2*n) """ cdef GExVector vec cdef GEx oper, power, ex diff --git a/src/sage/symbolic/function.pyx b/src/sage/symbolic/function.pyx index 89cdce56d70..480a6019e7c 100644 --- a/src/sage/symbolic/function.pyx +++ b/src/sage/symbolic/function.pyx @@ -626,7 +626,7 @@ cdef class Function(SageObject): sage: b = RBF(3/2, 1e-10) sage: airy_ai(b) airy_ai([1.500000000 +/- 1.01e-10]) - sage: gamma(b, 1) + sage: gamma(b, 1) # abs tol 4.5e-9 [0.50728223 +/- 4.67e-9] sage: hurwitz_zeta(b, b) hurwitz_zeta([1.500000000 +/- 1.01e-10], [1.500000000 +/- 1.01e-10]) diff --git a/src/sage/symbolic/ginac/normal.cpp b/src/sage/symbolic/ginac/normal.cpp index 2a52c9e7e81..2d34d8f16f2 100644 --- a/src/sage/symbolic/ginac/normal.cpp +++ b/src/sage/symbolic/ginac/normal.cpp @@ -695,7 +695,10 @@ ex power::normal(exmap & repl, exmap & rev_lookup, int level, unsigned options) ex n_exponent = ex_to(exponent).normal(repl, rev_lookup, level-1); n_exponent = n_exponent.op(0) / n_exponent.op(1); - if (n_exponent.is_integer()) { + if (n_exponent.is_integer() + // exponents must be numbers, e.g. x^n if n is declared to + // be an integer is replaced by a single symbol instead of sym^n + && is_exactly_a(n_exponent)) { if (n_exponent.is_positive()) { // (a/b)^n -> {a^n, b^n} diff --git a/src/sage/symbolic/ginac/useries.cpp b/src/sage/symbolic/ginac/useries.cpp index 3af04d89c4b..40fbd7ab360 100644 --- a/src/sage/symbolic/ginac/useries.cpp +++ b/src/sage/symbolic/ginac/useries.cpp @@ -521,6 +521,7 @@ void power::useries(flint_series_t& fp, int order) const exponent.useries(fp, order); fmpq_poly_mullow(fp.ft, fp.ft, fp1.ft, order+2); check_poly_ccoeff_zero(fp); + normalize(fp); fmpq_poly_exp_series(fp.ft, fp.ft, order); return; } diff --git a/src/sage/symbolic/pynac_impl.pxi b/src/sage/symbolic/pynac_impl.pxi index 482593b2ef4..8a00839f6b3 100644 --- a/src/sage/symbolic/pynac_impl.pxi +++ b/src/sage/symbolic/pynac_impl.pxi @@ -57,7 +57,7 @@ from sage.rings.rational cimport Rational from sage.rings.real_mpfr import RR, RealField from sage.rings.rational cimport rational_power_parts from sage.rings.real_double cimport RealDoubleElement -from sage.rings.all import CC +from sage.rings.cc import CC from sage.symbolic.function cimport Function diff --git a/src/sage/tests/cmdline.py b/src/sage/tests/cmdline.py index 4f16b0e17d5..ef86dc41814 100644 --- a/src/sage/tests/cmdline.py +++ b/src/sage/tests/cmdline.py @@ -729,7 +729,7 @@ def test_executable(args, input="", timeout=100.0, pydebug_ignore_warnings=False ....: _ = F.write(s) sage: L = ["sage", "--ipynb2rst", input, output] sage: _ = test_executable(L) # optional - pandoc - sage: print(open(output, 'r').read() == t) # optional - pandoc + sage: print(open(output, 'r').read() == t) # optional - pandoc # known bug #32697 True """ pexpect_env = dict(os.environ) @@ -766,6 +766,7 @@ def test_executable(args, input="", timeout=100.0, pydebug_ignore_warnings=False rfd.append(fderr) if len(rfd) == 0: break + timeout = float(timeout) rlist = select.select(rfd, [], [], timeout)[0] if len(rlist) == 0: diff --git a/src/sage/typeset/unicode_art.py b/src/sage/typeset/unicode_art.py index 77f4faf04cd..caed3475afe 100644 --- a/src/sage/typeset/unicode_art.py +++ b/src/sage/typeset/unicode_art.py @@ -51,19 +51,6 @@ class UnicodeArt(CharacterArt): """ _string_type = str - def __unicode__(self): - r""" - Return a unicode representation of ``self``. - - EXAMPLES:: - - sage: i = var('i') - sage: ua = unicode_art(sum(pi^i/factorial(i)*x^i, i, 0, oo)) - sage: str(ua) - ' \u03c0\u22c5x\n\u212f ' - """ - return repr(self).decode("utf-8") - _unicode_art_factory = CharacterArtFactory( UnicodeArt, str, '_unicode_art_', @@ -119,7 +106,7 @@ def unicode_art(*obj, **kwds): If specified, the ``sep_baseline`` overrides the baseline of an unicode art separator:: - sage: sep_line = unicode_art('\n'.join(u' ⎟ ' for _ in range(5)), baseline=5) + sage: sep_line = unicode_art('\n'.join(' ⎟ ' for _ in range(5)), baseline=5) sage: unicode_art(*AlternatingSignMatrices(3), ....: separator=sep_line, sep_baseline=1) ⎟ ⎟ ⎟ ⎟ ⎟ ⎟ @@ -159,14 +146,14 @@ def unicode_art(*obj, **kwds): baseline=baseline) -_subscript_dict = {'0': u'₀', '1': u'₁', '2': u'₂', '3': u'₃', '4': u'₄', - '5': u'₅', '6': u'₆', '7': u'₇', '8': u'₈', '9': u'₉', - '-': u'₋', '+': u'₊'} +_subscript_dict = {'0': '₀', '1': '₁', '2': '₂', '3': '₃', '4': '₄', + '5': '₅', '6': '₆', '7': '₇', '8': '₈', '9': '₉', + '-': '₋', '+': '₊'} -_superscript_dict = {'0': u'⁰', '1': u'¹', '2': u'²', '3': u'³', '4': u'⁴', - '5': u'⁵', '6': u'⁶', '7': u'⁷', '8': u'⁸', '9': u'⁹', - '-': u'⁻', '+': u'⁺', '/': u'ᐟ'} +_superscript_dict = {'0': '⁰', '1': '¹', '2': '²', '3': '³', '4': '⁴', + '5': '⁵', '6': '⁶', '7': '⁷', '8': '⁸', '9': '⁹', + '-': '⁻', '+': '⁺', '/': 'ᐟ'} def unicode_superscript(x): @@ -181,7 +168,7 @@ def unicode_superscript(x): sage: unicode_superscript(-712/5) '⁻⁷¹²ᐟ⁵' """ - return u''.join(_superscript_dict[i] for i in str(x)) + return ''.join(_superscript_dict[i] for i in str(x)) def unicode_subscript(x): @@ -196,4 +183,4 @@ def unicode_subscript(x): sage: unicode_subscript(-712) '₋₇₁₂' """ - return u''.join(_subscript_dict[i] for i in str(x)) + return ''.join(_subscript_dict[i] for i in str(x)) diff --git a/src/sage/version.py b/src/sage/version.py index 1e5a25ab935..92941ff68a2 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 = '9.5.beta8' -date = '2021-12-12' -banner = 'SageMath version 9.5.beta8, Release Date: 2021-12-12' +version = '9.5.beta9' +date = '2021-12-23' +banner = 'SageMath version 9.5.beta9, Release Date: 2021-12-23' diff --git a/src/sage_docbuild/__init__.py b/src/sage_docbuild/__init__.py index 8a5c1a19d24..62570352eac 100644 --- a/src/sage_docbuild/__init__.py +++ b/src/sage_docbuild/__init__.py @@ -266,7 +266,7 @@ def pdf(self): error_message = "failed to run $MAKE %s in %s" command = 'all-pdf' - if subprocess.call(make_target % (tex_dir, command, pdf_dir), shell=True): + if subprocess.call(make_target % (tex_dir, command, pdf_dir), close_fds=False, shell=True): raise RuntimeError(error_message % (command, tex_dir)) logger.warning("Build finished. The built documents can be found in %s", pdf_dir) diff --git a/src/sage_setup/autogen/interpreters/memory.py b/src/sage_setup/autogen/interpreters/memory.py index 70e9d350711..be6ec531722 100644 --- a/src/sage_setup/autogen/interpreters/memory.py +++ b/src/sage_setup/autogen/interpreters/memory.py @@ -107,7 +107,7 @@ def declare_class_members(self): sage: from sage_setup.autogen.interpreters import * sage: mc = MemoryChunkArguments('args', ty_mpfr) sage: mc.declare_class_members() - u' cdef int _n_args\n cdef mpfr_t* _args\n' + ' cdef int _n_args\n cdef mpfr_t* _args\n' """ return self.storage_type.declare_chunk_class_members(self.name) @@ -174,7 +174,7 @@ class using this memory chunk, to allocate local variables. sage: from sage_setup.autogen.interpreters import * sage: mc = MemoryChunkRRRetval('retval', ty_mpfr) sage: mc.declare_call_locals() - u' cdef RealNumber retval = (self.domain)()\n' + ' cdef RealNumber retval = (self.domain)()\n' """ return "" diff --git a/src/sage_setup/autogen/interpreters/specs/cc.py b/src/sage_setup/autogen/interpreters/specs/cc.py index d1c71f87dcb..aa0db45bad1 100644 --- a/src/sage_setup/autogen/interpreters/specs/cc.py +++ b/src/sage_setup/autogen/interpreters/specs/cc.py @@ -1,4 +1,4 @@ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009 Carl Witty # Copyright (C) 2015 Jeroen Demeyer # @@ -6,11 +6,8 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - -from __future__ import print_function, absolute_import - +# https://www.gnu.org/licenses/ +# **************************************************************************** from .base import StackInterpreter from .python import MemoryChunkPyConstant from ..instructions import (params_gen, instr_funcall_1arg_mpc, @@ -19,6 +16,7 @@ from ..storage import ty_mpc, ty_python from ..utils import je, reindent_lines as ri + class MemoryChunkCCRetval(MemoryChunk): r""" A special-purpose memory chunk, for dealing with the return value @@ -49,7 +47,7 @@ class using this memory chunk, to allocate local variables. sage: from sage_setup.autogen.interpreters import * sage: mc = MemoryChunkCCRetval('retval', ty_mpc) sage: mc.declare_call_locals() - u' cdef ComplexNumber retval = (self.domain_element._new())\n' + ' cdef ComplexNumber retval = (self.domain_element._new())\n' """ return je(ri(8, """ @@ -80,7 +78,7 @@ def pass_argument(self): sage: from sage_setup.autogen.interpreters import * sage: mc = MemoryChunkCCRetval('retval', ty_mpc) sage: mc.pass_argument() - u'((retval.__re))' + '((retval.__re))' """ return je("""(({{ myself.name }}.__re))""", myself=self) diff --git a/src/sage_setup/autogen/interpreters/specs/python.py b/src/sage_setup/autogen/interpreters/specs/python.py index ad00c3702e0..42003f62774 100644 --- a/src/sage_setup/autogen/interpreters/specs/python.py +++ b/src/sage_setup/autogen/interpreters/specs/python.py @@ -1,4 +1,4 @@ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009 Carl Witty # Copyright (C) 2015 Jeroen Demeyer # @@ -6,11 +6,8 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - -from __future__ import print_function, absolute_import - +# https://www.gnu.org/licenses/ +# **************************************************************************** from .base import StackInterpreter from ..instructions import (params_gen, instr_funcall_2args, instr_unary, InstrSpec) @@ -118,7 +115,7 @@ def declare_class_members(self): sage: from sage_setup.autogen.interpreters import * sage: mc = MemoryChunkPyConstant('domain') sage: mc.declare_class_members() - u' cdef object _domain\n' + ' cdef object _domain\n' """ return je(ri(4, """ diff --git a/src/sage_setup/autogen/interpreters/specs/rr.py b/src/sage_setup/autogen/interpreters/specs/rr.py index 96edcee3f18..d59e1c2bf8e 100644 --- a/src/sage_setup/autogen/interpreters/specs/rr.py +++ b/src/sage_setup/autogen/interpreters/specs/rr.py @@ -1,4 +1,4 @@ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2009 Carl Witty # Copyright (C) 2015 Jeroen Demeyer # @@ -6,11 +6,8 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - -from __future__ import print_function, absolute_import - +# https://www.gnu.org/licenses/ +# **************************************************************************** from .base import StackInterpreter from .python import MemoryChunkPyConstant from ..instructions import (params_gen, instr_funcall_1arg_mpfr, @@ -50,7 +47,7 @@ class using this memory chunk, to allocate local variables. sage: from sage_setup.autogen.interpreters import * sage: mc = MemoryChunkRRRetval('retval', ty_mpfr) sage: mc.declare_call_locals() - u' cdef RealNumber retval = (self.domain)()\n' + ' cdef RealNumber retval = (self.domain)()\n' """ return je(ri(8, """ @@ -81,7 +78,7 @@ def pass_argument(self): sage: from sage_setup.autogen.interpreters import * sage: mc = MemoryChunkRRRetval('retval', ty_mpfr) sage: mc.pass_argument() - u'retval.value' + 'retval.value' """ return je("""{{ myself.name }}.value""", myself=self) diff --git a/src/sage_setup/autogen/interpreters/storage.py b/src/sage_setup/autogen/interpreters/storage.py index bc556a1341f..d9e0e60273f 100644 --- a/src/sage_setup/autogen/interpreters/storage.py +++ b/src/sage_setup/autogen/interpreters/storage.py @@ -90,7 +90,7 @@ def cheap_copies(self): Returns True or False, depending on whether this StorageType supports cheap copies -- whether it is cheap to copy values of this type from one location to another. This is true for - primitive types, and for types like PyObject* (where you're only + primitive types, and for types like PyObject* (where you are only copying a pointer, and possibly changing some reference counts). It is false for types like mpz_t and mpfr_t, where copying values can involve arbitrarily much work (including memory allocation). @@ -268,11 +268,11 @@ def assign_c_from_py(self, c, py): sage: from sage_setup.autogen.interpreters import * sage: ty_double.assign_c_from_py('foo', 'bar') - u'foo = bar' + 'foo = bar' sage: ty_python.assign_c_from_py('foo[i]', 'bar[j]') - u'foo[i] = bar[j]; Py_INCREF(foo[i])' + 'foo[i] = bar[j]; Py_INCREF(foo[i])' sage: ty_mpfr.assign_c_from_py('foo', 'bar') - u'rn = self.domain(bar)\nmpfr_set(foo, rn.value, MPFR_RNDN)' + 'rn = self.domain(bar)\nmpfr_set(foo, rn.value, MPFR_RNDN)' """ return je("{{ c }} = {{ py }}", c=c, py=py) @@ -286,7 +286,7 @@ def declare_chunk_class_members(self, name): sage: from sage_setup.autogen.interpreters import * sage: ty_mpfr.declare_chunk_class_members('args') - u' cdef int _n_args\n cdef mpfr_t* _args\n' + ' cdef int _n_args\n cdef mpfr_t* _args\n' """ return je(ri(0, """ @@ -457,7 +457,7 @@ def assign_c_from_py(self, c, py): """ sage: from sage_setup.autogen.interpreters import ty_double_complex sage: ty_double_complex.assign_c_from_py('z_c', 'z_py') - u'z_c = CDE_to_dz(z_py)' + 'z_c = CDE_to_dz(z_py)' """ return je("{{ c }} = CDE_to_dz({{ py }})", c=c, py=py) @@ -547,7 +547,7 @@ def declare_chunk_class_members(self, name): sage: from sage_setup.autogen.interpreters import * sage: ty_python.declare_chunk_class_members('args') - u' cdef object _list_args\n cdef int _n_args\n cdef PyObject** _args\n' + ' cdef object _list_args\n cdef int _n_args\n cdef PyObject** _args\n' """ return je(ri(4, """ @@ -619,7 +619,7 @@ def assign_c_from_py(self, c, py): sage: from sage_setup.autogen.interpreters import * sage: ty_python.assign_c_from_py('foo[i]', 'bar[j]') - u'foo[i] = bar[j]; Py_INCREF(foo[i])' + 'foo[i] = bar[j]; Py_INCREF(foo[i])' """ return je("""{{ c }} = {{ py }}; Py_INCREF({{ c }})""", c=c, py=py) @@ -633,7 +633,7 @@ def cython_init(self, loc): sage: from sage_setup.autogen.interpreters import * sage: ty_python.cython_init('foo[i]') - u'foo[i] = NULL' + 'foo[i] = NULL' """ return je("{{ loc }} = NULL", loc=loc) @@ -646,7 +646,7 @@ def cython_clear(self, loc): sage: from sage_setup.autogen.interpreters import * sage: ty_python.cython_clear('foo[i]') - u'Py_CLEAR(foo[i])' + 'Py_CLEAR(foo[i])' """ return je("Py_CLEAR({{ loc }})", loc=loc) @@ -811,7 +811,7 @@ def cython_init(self, loc): sage: from sage_setup.autogen.interpreters import * sage: ty_mpfr.cython_init('foo[i]') - u'mpfr_init2(foo[i], self.domain.prec())' + 'mpfr_init2(foo[i], self.domain.prec())' """ return je("mpfr_init2({{ loc }}, self.domain{{ myself.id }}.prec())", myself=self, loc=loc) @@ -839,7 +839,7 @@ def assign_c_from_py(self, c, py): sage: from sage_setup.autogen.interpreters import * sage: ty_mpfr.assign_c_from_py('foo[i]', 'bar[j]') - u'rn = self.domain(bar[j])\nmpfr_set(foo[i], rn.value, MPFR_RNDN)' + 'rn = self.domain(bar[j])\nmpfr_set(foo[i], rn.value, MPFR_RNDN)' """ return je(ri(0, """ rn{{ myself.id }} = self.domain({{ py }}) @@ -914,7 +914,7 @@ def cython_init(self, loc): sage: from sage_setup.autogen.interpreters import * sage: ty_mpc.cython_init('foo[i]') - u'mpc_init2(foo[i], self.domain_element._prec)' + 'mpc_init2(foo[i], self.domain_element._prec)' """ return je("mpc_init2({{ loc }}, self.domain_element{{ myself.id }}._prec)", myself=self, loc=loc) @@ -942,7 +942,7 @@ def assign_c_from_py(self, c, py): sage: from sage_setup.autogen.interpreters import * sage: ty_mpc.assign_c_from_py('foo[i]', 'bar[j]') - u'cn = self.domain(bar[j])\nmpc_set_fr_fr(foo[i], cn.__re, cn.__im, MPC_RNDNN)' + 'cn = self.domain(bar[j])\nmpc_set_fr_fr(foo[i], cn.__re, cn.__im, MPC_RNDNN)' """ return je(""" cn{{ myself.id }} = self.domain({{ py }}) diff --git a/src/setup.py b/src/setup.py index 36764726468..288f9dfa444 100755 --- a/src/setup.py +++ b/src/setup.py @@ -81,7 +81,7 @@ t = time.time() # Exclude a few files if the corresponding distribution is not loaded - optional_packages = ['mcqd', 'bliss', 'tdlib', 'primecount', + optional_packages = ['mcqd', 'bliss', 'tdlib', 'coxeter3', 'fes', 'sirocco', 'meataxe'] not_installed_packages = [package for package in optional_packages if not is_package_installed_and_updated(package)] diff --git a/src/tox.ini b/src/tox.ini index 08abc70e5f6..eb5f55064db 100644 --- a/src/tox.ini +++ b/src/tox.ini @@ -86,7 +86,7 @@ description = # E721: do not compare types, use isinstance() # See https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes deps = pycodestyle -commands = pycodestyle --select E401,E70,W605,E711,E712,E721 {posargs:{toxinidir}/sage/} +commands = pycodestyle --select E401,E701,E702,E703,W605,E711,E712,E721 {posargs:{toxinidir}/sage/} [pycodestyle] max-line-length = 160 diff --git a/tox.ini b/tox.ini index 154eacd5260..90ec308d82d 100644 --- a/tox.ini +++ b/tox.ini @@ -182,7 +182,7 @@ setenv = docker: BASE_TAG=latest # # https://hub.docker.com/_/ubuntu?tab=description - # as of 2021-06, latest=focal=20.04, rolling=hirsute=21.04, impish=devel=21.10 + # as of 2021-11, latest=focal=20.04, rolling=impish=21.10, devel=jammy=22.04 # ubuntu: SYSTEM=debian ubuntu: BASE_IMAGE=ubuntu @@ -199,6 +199,8 @@ setenv = ubuntu-hirsute: IGNORE_MISSING_SYSTEM_PACKAGES=no ubuntu-impish: BASE_TAG=impish ubuntu-impish: IGNORE_MISSING_SYSTEM_PACKAGES=yes + ubuntu-jammy: BASE_TAG=jammy + ubuntu-jammy: IGNORE_MISSING_SYSTEM_PACKAGES=yes # # https://hub.docker.com/_/debian # debian-bullseye does not have libgiac-dev @@ -212,6 +214,8 @@ setenv = debian-buster: BASE_TAG=buster debian-bullseye: BASE_TAG=bullseye debian-bullseye: IGNORE_MISSING_SYSTEM_PACKAGES=yes + debian-bookworm: BASE_TAG=bookworm + debian-bookworm: IGNORE_MISSING_SYSTEM_PACKAGES=yes debian-sid: BASE_TAG=sid # # https://hub.docker.com/u/linuxmintd @@ -227,9 +231,10 @@ setenv = linuxmint-20: BASE_IMAGE=linuxmintd/mint20 linuxmint-20.1: BASE_IMAGE=linuxmintd/mint20.1 linuxmint-20.2: BASE_IMAGE=linuxmintd/mint20.2 + linuxmint-20.3: BASE_IMAGE=linuxmintd/mint20.3 # # https://hub.docker.com/_/fedora - # as of 2021-06, latest=34, rawhide=35 + # as of 2021-11, latest=35, rawhide=36 fedora: SYSTEM=fedora fedora: BASE_IMAGE=fedora fedora-26: BASE_TAG=26 @@ -250,6 +255,8 @@ setenv = fedora-34: IGNORE_MISSING_SYSTEM_PACKAGES=no fedora-35: BASE_TAG=35 fedora-35: IGNORE_MISSING_SYSTEM_PACKAGES=yes + fedora-36: BASE_TAG=36 + fedora-36: IGNORE_MISSING_SYSTEM_PACKAGES=yes # # https://hub.docker.com/r/scientificlinux/sl # @@ -467,7 +474,8 @@ setenv = local-conda-environment: CONDA_SAGE_ENVIRONMENT=sage-build local-conda-environment: CONDA_SAGE_ENVIRONMENT_FILE=environment.yml local-conda-environment-optional: CONDA_SAGE_ENVIRONMENT_FILE=environment-optional.yml - local-conda-environment: SETENV_CONFIGURE=( {env:CONDA_PREFIX}/bin/conda env create -n {env:CONDA_SAGE_ENVIRONMENT} --file {env:CONDA_SAGE_ENVIRONMENT_FILE} || {env:CONDA_PREFIX}/bin/conda env update -n {env:CONDA_SAGE_ENVIRONMENT} --file {env:CONDA_SAGE_ENVIRONMENT_FILE} ) && . {env:CONDA_PREFIX}/bin/activate {env:CONDA_SAGE_ENVIRONMENT} + local-conda-environment-src: CONDA_SAGE_ENVIRONMENT_DIR=src/ + local-conda-environment: SETENV_CONFIGURE=( {env:CONDA_PREFIX}/bin/conda env create -n {env:CONDA_SAGE_ENVIRONMENT} --file {env:CONDA_SAGE_ENVIRONMENT_DIR:}{env:CONDA_SAGE_ENVIRONMENT_FILE} || {env:CONDA_PREFIX}/bin/conda env update -n {env:CONDA_SAGE_ENVIRONMENT} --file {env:CONDA_SAGE_ENVIRONMENT_DIR:}{env:CONDA_SAGE_ENVIRONMENT_FILE} ) && . {env:CONDA_PREFIX}/bin/activate {env:CONDA_SAGE_ENVIRONMENT} # # Configuration factors #