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
#