diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f778e17ed91..6b08a0fe270 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,7 +30,7 @@ jobs: - name: Install relint run: pip install tox relint - name: Lint using relint - run: tox -e relint src/ + run: tox -e relint src/sage/ lint-pyright: name: Static type check with pyright runs-on: ubuntu-latest diff --git a/.github/workflows/tox-experimental.yml b/.github/workflows/tox-experimental.yml index e8c1fe3ba9e..862f00476e9 100644 --- a/.github/workflows/tox-experimental.yml +++ b/.github/workflows/tox-experimental.yml @@ -38,7 +38,9 @@ jobs: fail-fast: false max-parallel: 6 matrix: - tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, 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, centos-8, gentoo, 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] + # 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 + tox_system_factor: [ubuntu-trusty-gcc_spkg, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie-gcc_spkg, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17-gcc_spkg, 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, 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-gcc_spkg] tox_packages_factor: [maximal] targets_pattern: [0-g, h-o, p, q-z] env: diff --git a/.github/workflows/tox-optional.yml b/.github/workflows/tox-optional.yml index b79ffff8b20..279c6d23feb 100644 --- a/.github/workflows/tox-optional.yml +++ b/.github/workflows/tox-optional.yml @@ -38,7 +38,9 @@ jobs: fail-fast: false max-parallel: 6 matrix: - tox_system_factor: [ubuntu-trusty, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, 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, centos-8, gentoo, 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] + # 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 + tox_system_factor: [ubuntu-trusty-gcc_spkg, ubuntu-xenial, ubuntu-bionic, ubuntu-focal, ubuntu-groovy, ubuntu-hirsute, debian-jessie-gcc_spkg, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17-gcc_spkg, 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, 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-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 767a12e279d..f14be3ee2e0 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, debian-jessie, debian-stretch, debian-buster, debian-bullseye, debian-sid, linuxmint-17, 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, centos-8, gentoo, gentoo-python3.7, 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, 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, gentoo-python3.7, 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/.gitignore b/.gitignore index 70e0765fb73..40cb54fa701 100644 --- a/.gitignore +++ b/.gitignore @@ -114,6 +114,7 @@ src/sage/modular/arithgroup/farey_symbol.h !src/sage/graphs/cliquer/cl.c !src/sage/graphs/graph_decompositions/sage_tdlib.cpp !src/sage/libs/eclib/wrap.cpp +!src/sage/libs/linkages/padics/relaxed/flint_helper.c !src/sage/misc/inherit_comparison_impl.c !src/sage/modular/arithgroup/farey.cpp !src/sage/modular/arithgroup/sl2z.cpp diff --git a/.zenodo.json b/.zenodo.json index f57eda9b908..c40fda4109b 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.4.beta0", - "version": "9.4.beta0", + "title": "sagemath/sage: 9.4.beta4", + "version": "9.4.beta4", "upload_type": "software", - "publication_date": "2021-05-25", + "publication_date": "2021-07-01", "creators": [ { "affiliation": "SageMath.org", @@ -15,7 +15,7 @@ "related_identifiers": [ { "scheme": "url", - "identifier": "https://github.com/sagemath/sage/tree/9.4.beta0", + "identifier": "https://github.com/sagemath/sage/tree/9.4.beta4", "relation": "isSupplementTo" }, { diff --git a/README.md b/README.md index e48e6f1730b..020bfba5a66 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ https://www.sagemath.org -The Sage Library is GPLv2+, and included packages have [compatible OSS -licenses](./COPYING.txt). [Over 400 people](https://www.sagemath.org/development-map.html) -have contributed code to Sage. In many cases, documentation for modules -and functions list the authors. +The Sage Library is GPLv2+, and included packages have +[compatible OSS licenses](./COPYING.txt). +[Over 400 people](https://www.sagemath.org/development-map.html) +have contributed code to Sage. In many cases, documentation +for modules and functions list the authors. Getting Started --------------- @@ -22,7 +23,7 @@ If you downloaded a [binary](https://www.sagemath.org/download.html) Sage is ready to start -- just open a terminal in the directory where you extracted the binary archive and type: - ./sage + $ ./sage (Note that the first run will take more time, as Sage needs to get itself ready.) @@ -118,9 +119,9 @@ virtual appliance](https://wiki.sagemath.org/SageAppliance). ------------------------------ Make sure you have installed the most current version of Xcode -supported on your version of macOS. If you don't, go to +supported on your version of macOS. If you don't, either go to https://developer.apple.com/, sign up, and download the free Xcode -package. +package, or get it from Apple's app store. You also need to install the "command line tools": After installing Xcode, run `xcode-select --install` from a terminal window; then click @@ -131,7 +132,7 @@ and then "Install" the Command Line Tools.) Optionally, you can consider installing Homebrew ("the missing package manager for macOS") from https://brew.sh/, which can provide libraries -such gfortran, gmp, etc. +such as gfortran, gmp, etc. Instructions to Build from Source --------------------------------- @@ -173,11 +174,12 @@ Guide](https://doc.sagemath.org/html/en/installation). - [Git] Alternatively, clone the Sage git repository: - $ git clone -c core.symlinks=true --branch master https://github.com/sagemath/sage.git + $ ORIG=https://github.com/sagemath/sage.git + $ git clone -c core.symlinks=true --branch master $ORIG - This will create the subdirectory `sage`. `cd sage/` and pick the branch you need - by doing `git checkout` - typically you want the latest development branch, thus do - `git checkout develop`. + This will create the subdirectory `sage`. `cd sage/` and pick + the branch you need by doing `git checkout` - typically you want + the latest development branch, thus do `git checkout develop`. - [Windows] The Sage source tree contains symbolic links, and the build will not work if Windows line endings rather than UNIX @@ -186,8 +188,8 @@ Guide](https://doc.sagemath.org/html/en/installation). Therefore it is crucial that you unpack the source tree from the Cygwin (or WSL) `bash` using the Cygwin (or WSL) `tar` utility and not using other Windows tools (including mingw). Likewise, - when using `git`, it is recommended (but not necessary) to use the Cygwin (or WSL) - version of `git`. + when using `git`, it is recommended (but not necessary) to use + the Cygwin (or WSL) version of `git`. 3. `cd` into the source/build directory: @@ -224,11 +226,16 @@ Guide](https://doc.sagemath.org/html/en/installation). avoid having to build Sage's own copy of Python 3. We have collected lists of system packages that provide these build - prerequisites. See [build/pkgs/arch.txt](build/pkgs/arch.txt), - [cygwin.txt](build/pkgs/cygwin.txt), - [debian.txt](build/pkgs/debian.txt) (also for Ubuntu, Linux Mint, - etc.), [fedora.txt](build/pkgs/fedora.txt) (also for Red Hat, - CentOS), and [slackware.txt](build/pkgs/slackware.txt). + prerequisites. See, in the folder + [build/pkgs/_prereq/distros](build/pkgs/_prereq/distros), + the files + [arch.txt](build/pkgs/_prereq/distros/arch.txt), + [cygwin.txt](build/pkgs/_prereq/distros/cygwin.txt), + [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). 7. Optional, but highly recommended: Make sure your system has an SSL library and its development files installed. @@ -245,7 +252,7 @@ Guide](https://doc.sagemath.org/html/en/installation). 9. Optionally, review the configuration options, which includes many optional packages: - ./configure --help + $ ./configure --help 10. Optional, but highly recommended: Set some environment variables to customize the build. @@ -470,7 +477,7 @@ do. 2. (**Obsolete, probably broken**) To make your own source tarball of Sage, type: - sage --sdist + $ sage --sdist The result is placed in the directory `dist/`. diff --git a/VERSION.txt b/VERSION.txt index 32443a2a4c1..2f40e816f49 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -SageMath version 9.4.beta0, Release Date: 2021-05-25 +SageMath version 9.4.beta4, Release Date: 2021-07-01 diff --git a/build/bin/sage-bootstrap-python b/build/bin/sage-bootstrap-python index c3afc1261af..faee444e8dc 100755 --- a/build/bin/sage-bootstrap-python +++ b/build/bin/sage-bootstrap-python @@ -30,6 +30,15 @@ fi # is accessible by this python; this is to guard on Cygwin against Pythons # installed somewhere else in Windows. +# Trac #30008: Make it work even if the environment tries to sabotage UTF-8 +# operation in Python 3.0.x-3.6.x by setting LC_ALL=C or similar. + +if [ "$LC_ALL" = "C" -o "$LANG" = "C" -o "$LC_CTYPE" = "C" ]; then + LC_ALL=$(locale -a | grep -E -i '^(c|en_us)[-.]utf-?8$' | head -n 1) + LANG=$LC_ALL + export LC_ALL + export LANG +fi PYTHONS="python python3 python3.8 python3.7 python2.7 python3.6 python2" for PY in $PYTHONS; do diff --git a/build/bin/sage-dist-helpers b/build/bin/sage-dist-helpers index 95b5770b748..4fc586de5e8 100644 --- a/build/bin/sage-dist-helpers +++ b/build/bin/sage-dist-helpers @@ -39,10 +39,10 @@ # # - sdh_configure [...] # -# Runs `./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib"`, -# (for autoconf'd projects with extra -# --disable-maintainer-mode --disable-dependency-tracking) -# Additional arguments to `./configure` may be given as arguments. +# Runs `./configure --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib"` +# --disable-static, (for autoconf'd projects with extra +# --disable-maintainer-mode --disable-dependency-tracking) Additional +# arguments to `./configure` may be given as arguments. # # - sdh_make [...] # @@ -160,9 +160,16 @@ sdh_configure() { if [ -z "$CONFIG_SHELL" ]; then export CONFIG_SHELL=`command -v bash` fi - ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" --disable-maintainer-mode --disable-dependency-tracking "$@" + if [ "$UNAME" = "CYGWIN" ]; then + # TODO: To use --disable-static for all packages on Cygwin, need + # #30814: Cygwin: Fix remaining packages to build shared libraries, using AM_LDFLAGS=-no-undefined + DISABLE_STATIC= + else + DISABLE_STATIC=--disable-static + fi + ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" $DISABLE_STATIC --disable-maintainer-mode --disable-dependency-tracking "$@" if [ $? -ne 0 ]; then # perhaps it is a non-autoconf'd project - ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" "$@" + ./configure --prefix="$SAGE_INST_LOCAL" --libdir="$SAGE_INST_LOCAL/lib" $DISABLE_STATIC "$@" if [ $? -ne 0 ]; then if [ -f "$(pwd)/config.log" ]; then sdh_die <<_EOF_ @@ -211,14 +218,6 @@ sdh_setup_bdist_wheel() { sdh_pip_install() { echo "Installing $PKG_NAME" - if [ -n "$SAGE_DESTDIR" ]; then - local sudo="" - else - local sudo="$SAGE_SUDO" - fi - $sudo sage-pip-uninstall "$@" || \ - echo 2>&1 "Warning: Failure trying to uninstall a previous version of $PKG_NAME" - mkdir -p dist rm -f dist/*.whl python3 -m pip wheel --wheel-dir=dist --no-binary :all: --verbose --no-deps --no-index --isolated --no-build-isolation "$@" || \ diff --git a/build/bin/sage-pip-uninstall b/build/bin/sage-pip-uninstall deleted file mode 100755 index cf737d0b6bf..00000000000 --- a/build/bin/sage-pip-uninstall +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash -# This command ensures that any previous installations of the same package -# are uninstalled. - -# Only argument must be "." and will be ignored. -if [ $# -gt 1 ]; then - echo >&2 "$0 requires . as only argument" - exit 1 -fi -if [ "$1" != "." ]; then - echo >&2 "$0 requires . as final argument" - exit 1 -fi - -# Note: "sage-pip-uninstall" is meant to be run after $(PYTHON) has -# been installed (either as an spkg or as a venv over a system python3). -# It is then guaranteed by sage-env that PATH is set in a way that "python3" -# refers to the correct python3. -PYTHON=python3 - -# The PIP variable is only used to determine the name of the lock file. -PIP=pip3 - -# For PEP 517 packages, do not try to uninstall -if [ ! -f setup.py ]; then - exit 0 -fi - -# Find out the name of the package that we are installing -name="$($PYTHON setup.py --name)" - -if [ $? -ne 0 ]; then - echo >&2 "Error: could not determine package name" - exit 1 -fi - -if [ $(echo "$name" | wc -l) -gt 1 ]; then - name="$(echo "$name" | tail -1)" - echo >&2 "Warning: This package has a badly-behaved setup.py which outputs" - echo >&2 "more than the package name for 'setup.py --name'; using the last" - echo >&2 "line as the package name: $name" -fi - -# We should avoid running pip while uninstalling a package because that -# is prone to race conditions. Therefore, we use a lockfile while -# running pip. This is implemented in the Python script sage-flock -LOCK="$SAGE_LOCAL/var/lock/$PIP.lock" - -# Keep uninstalling as long as it succeeds -while true; do - out=$(sage-flock -x $LOCK $PYTHON -m pip uninstall --disable-pip-version-check -y "$name" 2>&1) - if [ $? -ne 0 ]; then - # Uninstall failed - echo >&2 "$out" - exit 1 - fi - - # Uninstall succeeded, which may mean that the package was not - # installed to begin with. - if [[ "$out" != *"not installed" ]]; then - break - fi -done diff --git a/build/bin/sage-site b/build/bin/sage-site index b99923d1eff..e0577b6f064 100755 --- a/build/bin/sage-site +++ b/build/bin/sage-site @@ -150,6 +150,7 @@ if [ "$1" = "-docbuild" -o "$1" = "--docbuild" ]; then # Trac #30002: ensure an English locale so that it is possible to # scrape out warnings by pattern matching. export LANG=C + export LANGUAGE=C # See #30351: bugs in macOS implementations of openblas/libgopm can cause # docbuild to hang if multiple OpenMP threads are allowed. diff --git a/build/make/Makefile.in b/build/make/Makefile.in index fb3a6ed5bcf..a249cc3c546 100644 --- a/build/make/Makefile.in +++ b/build/make/Makefile.in @@ -283,6 +283,11 @@ all-toolchain: base-toolchain # given as a prerequisite to any pip-installed packages PYTHON_TOOLCHAIN = setuptools pip setuptools_scm wheel setuptools_wheel +# Trac #32056: Avoid installed setuptools leaking into the build of python3 by uninstalling it. +# It will have to be reinstalled anyway because of its dependency on $(PYTHON). +python3-SAGE_LOCAL-no-deps: setuptools-clean +python3-SAGE_VENV-no-deps: setuptools-clean + # Everything needed to start up Sage using "./sage". Of course, not # every part of Sage will work. It does not include Maxima for example. SAGERUNTIME = sagelib $(inst_ipython) $(inst_pexpect) \ @@ -338,23 +343,23 @@ DOC_DEPENDENCIES = sagelib sage_docbuild $(inst_sphinx) \ doc: doc-html doc-html: $(DOC_DEPENDENCIES) - $(AM_V_at)cd ../.. && sage-logger -p './sage --docbuild --no-pdf-links all html $(SAGE_DOCBUILD_OPTS)' logs/dochtml.log + $(AM_V_at)cd "$(SAGE_SRC)/doc" && sage-logger -p "$(MAKE) doc-html" $(SAGE_ROOT)/logs/dochtml.log # 'doc-html-no-plot': build docs without building the graphics coming # from the '.. plot' directive, in case you want to save a few # megabytes of disk space. 'doc-clean' is a prerequisite because the # presence of graphics is cached in src/doc/output. -doc-html-no-plot: doc-clean $(DOC_DEPENDENCIES) - $(AM_V_at)cd ../.. && sage-logger -p './sage --docbuild --no-pdf-links --no-plot all html $(SAGE_DOCBUILD_OPTS)' logs/dochtml.log +doc-html-no-plot: doc-clean + $(AM_V_at)sage-logger -p "$(MAKE) SAGE_DOCBUILD_OPTS=\"$(SAGE_DOCBUILD_OPTS) --no-plot\" doc-html" $(SAGE_ROOT)/logs/dochtml.log -doc-html-mathjax: $(DOC_DEPENDENCIES) - $(AM_V_at)cd ../.. && sage-logger -p './sage --docbuild --no-pdf-links all html -j $(SAGE_DOCBUILD_OPTS)' logs/dochtml.log +doc-html-mathjax: + $(AM_V_at)sage-logger -p "$(MAKE) SAGE_DOCBUILD_OPTS=\"$(SAGE_DOCBUILD_OPTS) -j\" doc-html" $(SAGE_ROOT)/logs/dochtml.log # Keep target 'doc-html-jsmath' for backwards compatibility. doc-html-jsmath: doc-html-mathjax doc-pdf: $(DOC_DEPENDENCIES) - $(AM_V_at)cd ../.. && sage-logger -p './sage --docbuild all pdf $(SAGE_DOCBUILD_OPTS)' logs/docpdf.log + $(AM_V_at)cd "$(SAGE_SRC)/doc" && sage-logger -p "$(MAKE) doc-pdf" $(SAGE_ROOT)/logs/docpdf.log doc-clean: doc-src-clean doc-output-clean diff --git a/build/pkgs/appdirs/SPKG.rst b/build/pkgs/appdirs/SPKG.rst new file mode 100644 index 00000000000..6018e327f90 --- /dev/null +++ b/build/pkgs/appdirs/SPKG.rst @@ -0,0 +1,18 @@ +appdirs: A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". +========================================================================================================== + +Description +----------- + +A small Python module for determining appropriate platform-specific dirs, e.g. a "user data dir". + +License +------- + +MIT + +Upstream Contact +---------------- + +https://pypi.org/project/appdirs/ + diff --git a/build/pkgs/appdirs/checksums.ini b/build/pkgs/appdirs/checksums.ini new file mode 100644 index 00000000000..7b26025094c --- /dev/null +++ b/build/pkgs/appdirs/checksums.ini @@ -0,0 +1,5 @@ +tarball=appdirs-VERSION.tar.gz +sha1=1fa04e44b1084338cb7b21e9cf44fce5efb81840 +md5=d6bca12613174185dd9abc8a29f4f012 +cksum=1191718163 +upstream_url=https://pypi.io/packages/source/a/appdirs/appdirs-VERSION.tar.gz diff --git a/build/pkgs/appdirs/dependencies b/build/pkgs/appdirs/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/appdirs/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/appdirs/install-requires.txt b/build/pkgs/appdirs/install-requires.txt new file mode 100644 index 00000000000..d64bc321a11 --- /dev/null +++ b/build/pkgs/appdirs/install-requires.txt @@ -0,0 +1 @@ +appdirs diff --git a/build/pkgs/appdirs/package-version.txt b/build/pkgs/appdirs/package-version.txt new file mode 100644 index 00000000000..1c99cf0e809 --- /dev/null +++ b/build/pkgs/appdirs/package-version.txt @@ -0,0 +1 @@ +1.4.4 diff --git a/build/pkgs/appdirs/spkg-configure.m4 b/build/pkgs/appdirs/spkg-configure.m4 new file mode 100644 index 00000000000..098659476dc --- /dev/null +++ b/build/pkgs/appdirs/spkg-configure.m4 @@ -0,0 +1,7 @@ +SAGE_SPKG_CONFIGURE([appdirs], [ + sage_spkg_install_appdirs=yes + ], [dnl REQUIRED-CHECK + AC_REQUIRE([SAGE_SPKG_CONFIGURE_VIRTUALENV]) + dnl only needed as a dependency of virtualenv. + AS_VAR_SET([SPKG_REQUIRE], [$sage_spkg_install_virtualenv]) + ]) diff --git a/build/pkgs/appdirs/spkg-install.in b/build/pkgs/appdirs/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/appdirs/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/appdirs/type b/build/pkgs/appdirs/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/appdirs/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/brial/spkg-configure.m4 b/build/pkgs/brial/spkg-configure.m4 index a0f00838c75..79aaa9027d5 100644 --- a/build/pkgs/brial/spkg-configure.m4 +++ b/build/pkgs/brial/spkg-configure.m4 @@ -1,13 +1,13 @@ SAGE_SPKG_CONFIGURE([brial], [ dnl Trac #31624: Avoid C++ ABI issues - SAGE_SPKG_DEPCHECK([gcc boost m4ri], [ + SAGE_SPKG_DEPCHECK([gcc boost_cropped m4ri], [ # If we're using the system m4ri and boost, ensure that we can # compile and run an executable linked against both libbrial and # libbrial_groebner (both are used by SageMath). AC_LANG_PUSH(C++) SAVED_LIBS=$LIBS LIBS="$LIBS -lbrial -lbrial_groebner" - AC_MSG_CHECKING([if we can link against brial libraries]) + AC_MSG_CHECKING([if we can link against brial libraries and run]) AC_RUN_IFELSE([ AC_LANG_PROGRAM([ #include @@ -48,11 +48,43 @@ SAGE_SPKG_CONFIGURE([brial], [ ], [ AC_MSG_RESULT([yes]) sage_spkg_install_brial=no - ]) + ], [AC_MSG_RESULT([cross compiling. Assume yes]) + sage_spkg_install_brial=no]) ], [ AC_MSG_RESULT([no]) sage_spkg_install_brial=yes + ], + [ + AC_MSG_CHECKING([if we can link against brial libraries]) + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([ + #include + #include + USING_NAMESPACE_PBORI + USING_NAMESPACE_PBORIGB + + class MyConstant : public BooleConstant{ + public: void negate() { this->m_value = !this->m_value; } + }; + ],[ + BoolePolyRing r = BoolePolyRing(2, COrderEnums::dlex); + ReductionStrategy rs = ReductionStrategy(r); + rs.llReduceAll(); // uses groebner lib + if (2 != r.nVariables()) { return 1; } + if (r.constant(true) == r.constant(false)) { return 2; } + MyConstant f = MyConstant(); + f.negate(); // ensures v1.1.0+ if m_value isn't const + if (!f.isOne()) { return 3; } + return 0; + ]) + ],[ + AC_MSG_RESULT([yes]) + sage_spkg_install_brial=yes + ],[ + AC_MSG_RESULT([no]) + sage_spkg_install_brial=no + ]) ]) LIBS=$SAVED_LIBS AC_LANG_POP diff --git a/build/pkgs/cliquer/spkg-install.in b/build/pkgs/cliquer/spkg-install.in index 3a9487dbd22..a863950189e 100644 --- a/build/pkgs/cliquer/spkg-install.in +++ b/build/pkgs/cliquer/spkg-install.in @@ -1,5 +1,5 @@ cd src -sdh_configure --disable-static +sdh_configure sdh_make sdh_make_install diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 5b81d62ba6e..8dfcdb4fa2a 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=fc049bba332acf1d0e40275f20cdbcb734ad5bb3 -md5=971a6e2f0f92060a85aefa4130ddc287 -cksum=2143691469 +sha1=7b88d740d7ed702c15cb4048ae08c2faf2e9852c +md5=6252f2920838a7ea42b17845aa9782a2 +cksum=2536572108 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 0720b669a7c..e457c20289b 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -9e7b0bd3124630ebf368909dbcea7c2480ea1f27 +06ec5d949d4ac37fffbebba5ab2ad5294cb141e1 diff --git a/build/pkgs/curl/spkg-configure.m4 b/build/pkgs/curl/spkg-configure.m4 index d88561511b8..b938bd7547d 100644 --- a/build/pkgs/curl/spkg-configure.m4 +++ b/build/pkgs/curl/spkg-configure.m4 @@ -20,7 +20,12 @@ SAGE_SPKG_CONFIGURE([curl], [ AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include ]],[[ curl_easy_setopt(NULL,CURLOPT_URL,NULL); - ]])], sage_libcurl_cv_lib_curl_executable=yes, sage_libcurl_cv_lib_curl_executable=no) + ]])], sage_libcurl_cv_lib_curl_executable=yes, sage_libcurl_cv_lib_curl_executable=no, [ + dnl cross compiling. link only + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]],[[ + curl_easy_setopt(NULL,CURLOPT_URL,NULL); + ]])], sage_libcurl_cv_lib_curl_executable=yes, sage_libcurl_cv_lib_curl_executable=no)] + ) ]) AS_IF([test "$sage_libcurl_cv_lib_curl_executable" = "no"], [sage_spkg_install_curl=yes]) ]) diff --git a/build/pkgs/database_knotinfo/SPKG.rst b/build/pkgs/database_knotinfo/SPKG.rst new file mode 100644 index 00000000000..83d03495632 --- /dev/null +++ b/build/pkgs/database_knotinfo/SPKG.rst @@ -0,0 +1,18 @@ +database_knotinfo: Content of the KnotInfo and LinkInfo databases as lists of dictionaries +========================================================================================== + +Description +----------- + +Content of the KnotInfo and LinkInfo databases as lists of dictionaries + +License +------- + +GPL + +Upstream Contact +---------------- + +https://pypi.org/project/database-knotinfo/ + diff --git a/build/pkgs/database_knotinfo/checksums.ini b/build/pkgs/database_knotinfo/checksums.ini new file mode 100644 index 00000000000..c0c4e86ceb2 --- /dev/null +++ b/build/pkgs/database_knotinfo/checksums.ini @@ -0,0 +1,5 @@ +tarball=database_knotinfo-VERSION.tar.gz +sha1=2d758c5f8bf346162d13bec1d5bccfec9d27baa1 +md5=ec20d43af0c4ecf59dfd281c6ccc4ef0 +cksum=2792610748 +upstream_url=https://pypi.io/packages/source/d/database_knotinfo/database_knotinfo-VERSION.tar.gz diff --git a/build/pkgs/database_knotinfo/dependencies b/build/pkgs/database_knotinfo/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/database_knotinfo/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/database_knotinfo/install-requires.txt b/build/pkgs/database_knotinfo/install-requires.txt new file mode 100644 index 00000000000..ef7ae65ef46 --- /dev/null +++ b/build/pkgs/database_knotinfo/install-requires.txt @@ -0,0 +1 @@ +database-knotinfo diff --git a/build/pkgs/database_knotinfo/package-version.txt b/build/pkgs/database_knotinfo/package-version.txt new file mode 100644 index 00000000000..eb49d7c7fdc --- /dev/null +++ b/build/pkgs/database_knotinfo/package-version.txt @@ -0,0 +1 @@ +0.7 diff --git a/build/pkgs/database_knotinfo/spkg-check.in b/build/pkgs/database_knotinfo/spkg-check.in new file mode 100644 index 00000000000..6a1eb79a95a --- /dev/null +++ b/build/pkgs/database_knotinfo/spkg-check.in @@ -0,0 +1,10 @@ +cd $SAGE_ROOT/src/sage/ + +echo "Testing databases/knotinfo_db.py" +sage -t --long --optional="sage,database_knotinfo" databases/knotinfo_db.py || sdh_die "Error testing KnotInfo databases" + +echo "Testing knots/knotinfo.py" +sage -t --optional="sage,database_knotinfo" knots/knotinfo.py || sdh_die "Error testing KnotInfo funcionality" + +echo "Testing knots/link.py" +sage -t --optional="sage,database_knotinfo" knots/link.py || sdh_die "Error testing KnotInfo funcionality" diff --git a/build/pkgs/database_knotinfo/spkg-install.in b/build/pkgs/database_knotinfo/spkg-install.in new file mode 100644 index 00000000000..f9667e4a733 --- /dev/null +++ b/build/pkgs/database_knotinfo/spkg-install.in @@ -0,0 +1,9 @@ +cd src +sdh_pip_install . + +FILECACHE="${SAGE_SHARE}/knotinfo" +if [ -d $FILECACHE ] +then + echo "Clearing former filecache of knotinfo" + rm -rf $FILECACHE +fi diff --git a/build/pkgs/database_knotinfo/type b/build/pkgs/database_knotinfo/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/database_knotinfo/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/distlib/SPKG.rst b/build/pkgs/distlib/SPKG.rst new file mode 100644 index 00000000000..a4496c0427a --- /dev/null +++ b/build/pkgs/distlib/SPKG.rst @@ -0,0 +1,18 @@ +distlib: Distribution utilities +=============================== + +Description +----------- + +Distribution utilities + +License +------- + +Python license + +Upstream Contact +---------------- + +https://pypi.org/project/distlib/ + diff --git a/build/pkgs/distlib/checksums.ini b/build/pkgs/distlib/checksums.ini new file mode 100644 index 00000000000..718023bc038 --- /dev/null +++ b/build/pkgs/distlib/checksums.ini @@ -0,0 +1,5 @@ +tarball=distlib-VERSION.zip +sha1=1c575431e31c32d25596c360e81bba7fe4638669 +md5=4baf787d8aceb260d6f77cb31bf27cf6 +cksum=2902365751 +upstream_url=https://pypi.io/packages/source/d/distlib/distlib-VERSION.zip diff --git a/build/pkgs/distlib/dependencies b/build/pkgs/distlib/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/distlib/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/distlib/install-requires.txt b/build/pkgs/distlib/install-requires.txt new file mode 100644 index 00000000000..f68bb07272d --- /dev/null +++ b/build/pkgs/distlib/install-requires.txt @@ -0,0 +1 @@ +distlib diff --git a/build/pkgs/distlib/package-version.txt b/build/pkgs/distlib/package-version.txt new file mode 100644 index 00000000000..9e11b32fcaa --- /dev/null +++ b/build/pkgs/distlib/package-version.txt @@ -0,0 +1 @@ +0.3.1 diff --git a/build/pkgs/distlib/spkg-configure.m4 b/build/pkgs/distlib/spkg-configure.m4 new file mode 100644 index 00000000000..00c32e2e0da --- /dev/null +++ b/build/pkgs/distlib/spkg-configure.m4 @@ -0,0 +1,7 @@ +SAGE_SPKG_CONFIGURE([distlib], [ + AC_REQUIRE([SAGE_SPKG_CONFIGURE_VIRTUALENV]) + sage_spkg_install_distlib=yes + ], [dnl REQUIRED-CHECK + dnl only needed as a dependency of virtualenv. + AS_VAR_SET([SPKG_REQUIRE], [$sage_spkg_install_virtualenv]) + ]) diff --git a/build/pkgs/distlib/spkg-install.in b/build/pkgs/distlib/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/distlib/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/distlib/type b/build/pkgs/distlib/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/distlib/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/docutils/checksums.ini b/build/pkgs/docutils/checksums.ini index 79c8595503c..7eb275b5d93 100644 --- a/build/pkgs/docutils/checksums.ini +++ b/build/pkgs/docutils/checksums.ini @@ -1,4 +1,5 @@ tarball=docutils-VERSION.tar.gz -sha1=32cefb69ac3dab5b04c4d150776f35419cc4c863 -md5=c53768d63db3873b7d452833553469de -cksum=2981697109 +sha1=f423535c12fcd2a68d4fc52525fbe36020a58ab5 +md5=ed810564c25063e9dac10dd0893ead47 +cksum=3160620183 +upstream_url=https://pypi.io/packages/source/d/docutils/docutils-VERSION.tar.gz diff --git a/build/pkgs/docutils/package-version.txt b/build/pkgs/docutils/package-version.txt index 948a5472708..7cca7711a0d 100644 --- a/build/pkgs/docutils/package-version.txt +++ b/build/pkgs/docutils/package-version.txt @@ -1 +1 @@ -0.14 +0.17.1 diff --git a/build/pkgs/ecl/distros/alpine.txt b/build/pkgs/ecl/distros/alpine.txt new file mode 100644 index 00000000000..b58d87094ea --- /dev/null +++ b/build/pkgs/ecl/distros/alpine.txt @@ -0,0 +1 @@ +ecl-dev diff --git a/build/pkgs/ecl/distros/arch.txt b/build/pkgs/ecl/distros/arch.txt new file mode 100644 index 00000000000..100aa2efb32 --- /dev/null +++ b/build/pkgs/ecl/distros/arch.txt @@ -0,0 +1 @@ +ecl diff --git a/build/pkgs/ecl/distros/fedora.txt b/build/pkgs/ecl/distros/fedora.txt new file mode 100644 index 00000000000..100aa2efb32 --- /dev/null +++ b/build/pkgs/ecl/distros/fedora.txt @@ -0,0 +1 @@ +ecl diff --git a/build/pkgs/ecl/distros/gentoo.txt b/build/pkgs/ecl/distros/gentoo.txt new file mode 100644 index 00000000000..416fa20614c --- /dev/null +++ b/build/pkgs/ecl/distros/gentoo.txt @@ -0,0 +1 @@ +dev-lisp/ecls diff --git a/build/pkgs/ecl/spkg-configure.m4 b/build/pkgs/ecl/spkg-configure.m4 new file mode 100644 index 00000000000..bcb3c6c1493 --- /dev/null +++ b/build/pkgs/ecl/spkg-configure.m4 @@ -0,0 +1,44 @@ +SAGE_SPKG_CONFIGURE([ecl], [ + + # Default to installing the SPKG + sage_spkg_install_ecl=yes + + SAGE_SPKG_DEPCHECK([gc gmp mpir], [ + AC_PATH_PROG([ECL_CONFIG], [ecl-config]) + AS_IF([test x$ECL_CONFIG != x], [ + # "CPPFLAGS" is not a typo, the --cflags output from + # ecl-config typically contains -D and -I flags. + saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} $($ECL_CONFIG --cflags)" + + AC_LANG_PUSH([C]) + AC_RUN_IFELSE([AC_LANG_PROGRAM([[ + #include + ]],[[ + if (ECL_VERSION_NUMBER < 210201) { return 1; } + ]])], + [ + sage_spkg_install_ecl=no + ], + [ + CPPFLAGS="${saved_CPPFLAGS}" + AC_MSG_NOTICE([ecl found but too old]) + ]) + AC_LANG_POP([C]) + ]) + ]) +],[],[],[ + # post-check + if test x$sage_spkg_install_ecl = xyes; then + AC_SUBST(SAGE_ECL_CONFIG, ['${prefix}'/bin/ecl-config]) + else + AC_SUBST(SAGE_ECL_CONFIG, [$ECL_CONFIG]) + fi + + # Maxima cannot yet be provided by the system, so we always use + # the SAGE_LOCAL path for now. + AC_SUBST(SAGE_MAXIMA_FAS, ['${prefix}'/lib/ecl/maxima.fas]) + + # Likewise for the optional Kenzo SPKG + AC_SUBST(SAGE_KENZO_FAS, ['${prefix}'/lib/ecl/kenzo.fas]) +]) diff --git a/build/pkgs/eclib/checksums.ini b/build/pkgs/eclib/checksums.ini index 4daaf05d8b5..a76b8f40fde 100644 --- a/build/pkgs/eclib/checksums.ini +++ b/build/pkgs/eclib/checksums.ini @@ -1,4 +1,5 @@ -tarball=eclib-20190909.tar.bz2 -sha1=0e994c0de95ef03ef19ad5030a2cacbb83c76bbd -md5=1a67217a7fa762646d43c7bec8a73028 -cksum=4240278408 +tarball=eclib-VERSION.tar.bz2 +sha1=e25f1aa6b7450f17bcff643fac9473d326012e29 +md5=b1288dc5eb981d45db1db0e11987468a +cksum=2713659223 +upstream_url=https://github.com/JohnCremona/eclib/releases/download/VERSION/eclib-VERSION.tar.bz2 diff --git a/build/pkgs/eclib/package-version.txt b/build/pkgs/eclib/package-version.txt index 4defdea11fc..ed06b627c4d 100644 --- a/build/pkgs/eclib/package-version.txt +++ b/build/pkgs/eclib/package-version.txt @@ -1 +1 @@ -20190909 +20210625 diff --git a/build/pkgs/eclib/spkg-configure.m4 b/build/pkgs/eclib/spkg-configure.m4 index 8cb8ed01ea7..e49e0b74ae3 100644 --- a/build/pkgs/eclib/spkg-configure.m4 +++ b/build/pkgs/eclib/spkg-configure.m4 @@ -1,23 +1,19 @@ SAGE_SPKG_CONFIGURE([eclib], [ - SAGE_SPKG_DEPCHECK([ntl pari flint], [ - dnl header types.h appeared in v20180710 - AC_CHECK_HEADER([eclib/types.h], [ - AC_MSG_CHECKING([whether we can link and run a program using eclib]) - ECLIB_SAVED_LIBS="$LIBS" - LIBS="$LIBS -lec" - AC_RUN_IFELSE([ - AC_LANG_PROGRAM([[#include ] - [#include ]], - [[set_bit_precision(42); /* test for versions >= v20190226 */ - show_version(); - return 0;]] - )], [AC_MSG_RESULT([yes; use eclib from the system])], [ - AC_MSG_RESULT([no; install eclib]) - sage_spkg_install_eclib=yes - LIBS="$ECLIB_SAVED_LIBS" - ]) - ], [sage_spkg_install_eclib=yes]) - AC_PATH_PROG([MWRANK], [mwrank]) + SAGE_SPKG_DEPCHECK([ntl pari flint], [ + dnl Trac #31443: use existing eclib only if the version reported by pkg-config is correct + m4_pushdef([SAGE_ECLIB_VER],["20210625"]) + PKG_CHECK_MODULES([ECLIB], [eclib = SAGE_ECLIB_VER], [ + AC_CACHE_CHECK([for mwrank version == SAGE_ECLIB_VER], [ac_cv_path_MWRANK], [ + AC_PATH_PROGS_FEATURE_CHECK([MWRANK], [mwrank], [ + mwrank_version=`$ac_path_MWRANK -V 2>&1` + AX_COMPARE_VERSION([$mwrank_version], [eq], [SAGE_ECLIB_VER], [ + ac_cv_path_MWRANK="$ac_path_MWRANK" + ]) + ]) + ]) AS_IF([test -z "$ac_cv_path_MWRANK"], [sage_spkg_install_eclib=yes]) - ]) + ], [ + sage_spkg_install_eclib=yes]) + ]) + m4_popdef([SAGE_ECLIB_VER]) ]) diff --git a/build/pkgs/filelock/SPKG.rst b/build/pkgs/filelock/SPKG.rst new file mode 100644 index 00000000000..d39b439be2e --- /dev/null +++ b/build/pkgs/filelock/SPKG.rst @@ -0,0 +1,18 @@ +filelock: A platform independent file lock +========================================== + +Description +----------- + +A platform independent file lock. + +License +------- + +Public Domain + +Upstream Contact +---------------- + +https://pypi.org/project/filelock/ + diff --git a/build/pkgs/filelock/checksums.ini b/build/pkgs/filelock/checksums.ini new file mode 100644 index 00000000000..a7a437ebb3c --- /dev/null +++ b/build/pkgs/filelock/checksums.ini @@ -0,0 +1,5 @@ +tarball=filelock-VERSION.tar.gz +sha1=ca03bf213ee1d7a9b6353cebc265072aae40fdcb +md5=c1fe6d9a7433a7ca6ce4f36e273317d1 +cksum=2927344437 +upstream_url=https://pypi.io/packages/source/f/filelock/filelock-VERSION.tar.gz diff --git a/build/pkgs/filelock/dependencies b/build/pkgs/filelock/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/filelock/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/filelock/install-requires.txt b/build/pkgs/filelock/install-requires.txt new file mode 100644 index 00000000000..83c2e35706e --- /dev/null +++ b/build/pkgs/filelock/install-requires.txt @@ -0,0 +1 @@ +filelock diff --git a/build/pkgs/filelock/package-version.txt b/build/pkgs/filelock/package-version.txt new file mode 100644 index 00000000000..f93fc9f42ea --- /dev/null +++ b/build/pkgs/filelock/package-version.txt @@ -0,0 +1 @@ +3.0.12 diff --git a/build/pkgs/filelock/spkg-configure.m4 b/build/pkgs/filelock/spkg-configure.m4 new file mode 100644 index 00000000000..b14299a9130 --- /dev/null +++ b/build/pkgs/filelock/spkg-configure.m4 @@ -0,0 +1,9 @@ +SAGE_SPKG_CONFIGURE([filelock], [ + sage_spkg_install_filelock=yes + ], [dnl REQUIRED-CHECK + AC_REQUIRE([SAGE_SPKG_CONFIGURE_VIRTUALENV]) + AC_REQUIRE([SAGE_SPKG_CONFIGURE_TOX]) + dnl only needed as a dependency of tox and virtualenv. + AS_IF([test $sage_spkg_install_virtualenv = no -a $sage_spkg_install_tox = no], + AS_VAR_SET([SPKG_REQUIRE], [no])) + ]) diff --git a/build/pkgs/filelock/spkg-install.in b/build/pkgs/filelock/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/filelock/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/filelock/type b/build/pkgs/filelock/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/filelock/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/flint/spkg-configure.m4 b/build/pkgs/flint/spkg-configure.m4 index b697e478d74..de3ad59cc2f 100644 --- a/build/pkgs/flint/spkg-configure.m4 +++ b/build/pkgs/flint/spkg-configure.m4 @@ -22,7 +22,8 @@ SAGE_SPKG_CONFIGURE([flint], [ [#endif]])], [AC_MSG_RESULT([GC not enabled. Good.])], [AC_MSG_RESULT([GC enabled. Incompatible with Sage.]) - sage_spkg_install_flint=yes]) + sage_spkg_install_flint=yes], + [AC_MSG_RESULT(["cross compiling. assuming GC is not enabled"])]) ], [sage_spkg_install_flint=yes]) ], [sage_spkg_install_flint=yes]) ], [sage_spkg_install_flint=yes]) diff --git a/build/pkgs/fplll/checksums.ini b/build/pkgs/fplll/checksums.ini index 2e355eb4de9..c346ac9603d 100644 --- a/build/pkgs/fplll/checksums.ini +++ b/build/pkgs/fplll/checksums.ini @@ -1,5 +1,5 @@ tarball=fplll-VERSION.tar.gz -sha1=a62fc9aa2a4736e9680a336b092f432c31ddac43 -md5=db2f0f46d4de3e8ab8ed73a79d19ffc3 -cksum=1237963825 +sha1=fe1b225f2bff07b7b832ae8b20ffc85acfd231cd +md5=44db0a42c33e5aa60264b32ab0063351 +cksum=3420408626 upstream_url=https://github.com/fplll/fplll/releases/download/VERSION/fplll-VERSION.tar.gz diff --git a/build/pkgs/fplll/package-version.txt b/build/pkgs/fplll/package-version.txt index 8a30e8f94a3..ade65226e0a 100644 --- a/build/pkgs/fplll/package-version.txt +++ b/build/pkgs/fplll/package-version.txt @@ -1 +1 @@ -5.4.0 +5.4.1 diff --git a/build/pkgs/fplll/spkg-configure.m4 b/build/pkgs/fplll/spkg-configure.m4 index f870e4dbf72..83ea99dee4c 100644 --- a/build/pkgs/fplll/spkg-configure.m4 +++ b/build/pkgs/fplll/spkg-configure.m4 @@ -8,7 +8,7 @@ SAGE_SPKG_CONFIGURE([fplll], [ dnl Trac #31025: FPLLL/FPyLLL make no guarantee regarding compatibility dnl other than "whatever versions were released at the same time should work together" PKG_CHECK_MODULES([FPLLL], - [fplll = 5.4.0], + [fplll >= 5.4.0 fplll <= 5.4.1], [sage_spkg_install_fplll=no], [sage_spkg_install_fplll=yes]) ]) diff --git a/build/pkgs/fpylll/checksums.ini b/build/pkgs/fpylll/checksums.ini index 72ca88799e7..5255ba6aec8 100644 --- a/build/pkgs/fpylll/checksums.ini +++ b/build/pkgs/fpylll/checksums.ini @@ -1,5 +1,5 @@ tarball=fpylll-VERSION.tar.gz -sha1=3976fb58ee9520a85f29a2ff041acb171c49fcc0 -md5=c53cc961721322ad8e8f74e41ea5bb37 -cksum=2257998255 +sha1=e2a005b53fcc3a9ec1f1111f144eb75b6de7fb44 +md5=5ea3a5fe30646311ef28ca1f8e9bf2bc +cksum=690866680 upstream_url=https://github.com/fplll/fpylll/releases/download/VERSION/fpylll-VERSION.tar.gz diff --git a/build/pkgs/fpylll/install-requires.txt b/build/pkgs/fpylll/install-requires.txt index 166267d5e85..0461a00816f 100644 --- a/build/pkgs/fpylll/install-requires.txt +++ b/build/pkgs/fpylll/install-requires.txt @@ -1 +1 @@ -fpylll ==0.5.5 +fpylll >=0.5.5, <=0.5.6 diff --git a/build/pkgs/fpylll/package-version.txt b/build/pkgs/fpylll/package-version.txt index d1d899fa33a..b49b25336d4 100644 --- a/build/pkgs/fpylll/package-version.txt +++ b/build/pkgs/fpylll/package-version.txt @@ -1 +1 @@ -0.5.5 +0.5.6 diff --git a/build/pkgs/fricas/package-version.txt b/build/pkgs/fricas/package-version.txt index 95b25aee259..0bfdb062251 100644 --- a/build/pkgs/fricas/package-version.txt +++ b/build/pkgs/fricas/package-version.txt @@ -1 +1 @@ -1.3.6 +1.3.6.p1 diff --git a/build/pkgs/fricas/patches/extdecls.patch b/build/pkgs/fricas/patches/extdecls.patch new file mode 100644 index 00000000000..15772be6715 --- /dev/null +++ b/build/pkgs/fricas/patches/extdecls.patch @@ -0,0 +1,12 @@ +diff --git a/src/include/cfuns-c.H1 b/src/include/cfuns-c.H1 +index 643a532..da22a8d 100644 +--- a/src/include/cfuns-c.H1 ++++ b/src/include/cfuns-c.H1 +@@ -3,5 +3,7 @@ extern int directoryp(char *); + extern int make_path_from_file(char *, char *); + extern int writeablep(char *); + extern int readablep(char *); ++extern int remove_directory(char *); ++extern int makedir(char *); + extern long findString(char *, char *); + extern int copyEnvValue(char *, char *); diff --git a/build/pkgs/fricas/patches/macos_lisp.patch b/build/pkgs/fricas/patches/macos_lisp.patch new file mode 100644 index 00000000000..c1be0ac46fd --- /dev/null +++ b/build/pkgs/fricas/patches/macos_lisp.patch @@ -0,0 +1,31 @@ +diff --git a/src/lisp/Makefile.in b/src/lisp/Makefile.in +index 30f615096..f0b3f0bd4 100644 +--- a/src/lisp/Makefile.in ++++ b/src/lisp/Makefile.in +@@ -118,6 +118,8 @@ do_it.ecl: fricas-lisp.lisp fricas-package.lisp fricas-config.lisp \ + fricas-lisp.o primitives.o) ")))" \ + >> fricas-ecl.lisp + echo "(defvar *fricas-initial-lisp-forms* nil)" >> fricas-ecl.lisp ++ echo "(require :cmp)" ++ echo "(setf c::*user-cc-flags* (concatenate 'string c::*user-cc-flags* \" -I$(BASE)/$(fricas_src_srcdir)/include/ -I$(BASE)/$(fricas_src_srcdir)/../config/\"))" >> fricas-ecl.lisp + echo '(load "fricas-package.lisp")' \ + '(load "fricas-config.lisp")' \ + '(load "fricas-ecl.lisp")' \ +diff --git a/src/lisp/fricas-lisp.lisp b/src/lisp/fricas-lisp.lisp +index d6c7484df..f99e2e75b 100644 +--- a/src/lisp/fricas-lisp.lisp ++++ b/src/lisp/fricas-lisp.lisp +@@ -609,6 +609,13 @@ with this hack and will try to convince the GCL crowd to fix this. + #+(and :clisp :ffi) `(defun clisp-init-foreign-calls () ,@arguments) + ) + ++#+:ecl ++(ext:with-backend :c/c++ ++ (ffi:clines ++ "#include " ++ "#include " ++ "#include ")) ++ + (foreign-defs + + (fricas-foreign-call |writeablep| "writeablep" int diff --git a/build/pkgs/gap/SPKG.rst b/build/pkgs/gap/SPKG.rst index 148babf9e49..8dedb17dccd 100644 --- a/build/pkgs/gap/SPKG.rst +++ b/build/pkgs/gap/SPKG.rst @@ -31,7 +31,7 @@ Dependencies ------------ - Readline -- MPIR +- GMP Special Update/Build Instructions @@ -56,6 +56,3 @@ update GAP, please also update and use the spkg-src script. Patches ~~~~~~~ - -- writeandcheck.patch: fix infinite loop in writeandcheck() when - writing an error message fails. diff --git a/build/pkgs/gap/checksums.ini b/build/pkgs/gap/checksums.ini index 76035c17031..066e943308a 100644 --- a/build/pkgs/gap/checksums.ini +++ b/build/pkgs/gap/checksums.ini @@ -1,5 +1,5 @@ -tarball=gap-VERSION.tar.bz2 -sha1=0998ec7153ead8c6ccfc33e4c20156b7bb2ffb44 -md5=c5544dd73507c01760416ad04acff6f1 -cksum=290729880 -upstream_url=https://www.gap-system.org/pub/gap/gap-4.11/tar.bz2/gap-4.11.0.tar.bz2 +tarball=gap-VERSION.tar.gz +sha1=4ecdd281b8f430282fb9b12690b06e0a26abde10 +md5=85dc9e459d5b6c66fcad9f468afd3e3e +cksum=1351843158 +upstream_url=https://github.com/gap-system/gap/releases/download/vVERSION/gap-VERSION.tar.gz diff --git a/build/pkgs/gap/package-version.txt b/build/pkgs/gap/package-version.txt index 0367118566f..d782fca8f64 100644 --- a/build/pkgs/gap/package-version.txt +++ b/build/pkgs/gap/package-version.txt @@ -1 +1 @@ -4.11.0.p1 +4.11.1 diff --git a/build/pkgs/gap/spkg-install.in b/build/pkgs/gap/spkg-install.in index f17117c847e..32d1fb982cc 100644 --- a/build/pkgs/gap/spkg-install.in +++ b/build/pkgs/gap/spkg-install.in @@ -61,7 +61,7 @@ sdh_install \ pkg/autpgrp-* \ pkg/alnuth-* \ pkg/crisp-* \ - pkg/ctbllib \ + pkg/ctbllib-* \ pkg/FactInt-* \ pkg/fga \ pkg/irredsol-* \ diff --git a/build/pkgs/gap_packages/spkg-install.in b/build/pkgs/gap_packages/spkg-install.in index 42870146f02..168e6b11532 100644 --- a/build/pkgs/gap_packages/spkg-install.in +++ b/build/pkgs/gap_packages/spkg-install.in @@ -23,6 +23,7 @@ sdh_install \ hap-* \ hapcryst-* \ hecke-* \ + images-* \ liealgdb-* \ liepring-* \ liering-* \ @@ -64,7 +65,7 @@ install_compiled_pkg() # # These packages have an old ./configure that take the GAP_ROOT as a positional # argument -for pkg in cohomolo-* crypting-* grape-* guava-* orb-* +for pkg in cohomolo-* crypting-* grape-* guava-* orb-* datastructures-* do echo "Building GAP package $pkg" CFLAGS="$CFLAGS -Wno-implicit-function-declaration" diff --git a/build/pkgs/giac/spkg-configure.m4 b/build/pkgs/giac/spkg-configure.m4 index 83974a56e83..2776032a4f5 100644 --- a/build/pkgs/giac/spkg-configure.m4 +++ b/build/pkgs/giac/spkg-configure.m4 @@ -2,7 +2,7 @@ SAGE_SPKG_CONFIGURE([giac], [ SAGE_SPKG_DEPCHECK([pari], [ dnl giac does not seem to reveal its patchlevel m4_pushdef([GIAC_MIN_VERSION], [1.5.0]) - m4_pushdef([GIAC_MAX_VERSION], [1.6.999]) + m4_pushdef([GIAC_MAX_VERSION], [1.7.999]) AC_CACHE_CHECK([for giac >= ]GIAC_MIN_VERSION[, <= ]GIAC_MAX_VERSION, [ac_cv_path_GIAC], [ AC_PATH_PROGS_FEATURE_CHECK([GIAC], [giac], [ giac_version=$($ac_path_GIAC --version 2> /dev/null | tail -1) diff --git a/build/pkgs/importlib_resources/SPKG.rst b/build/pkgs/importlib_resources/SPKG.rst new file mode 100644 index 00000000000..ea519afbe99 --- /dev/null +++ b/build/pkgs/importlib_resources/SPKG.rst @@ -0,0 +1,18 @@ +importlib_resources: Read resources from Python packages +======================================================== + +Description +----------- + +Read resources from Python packages + +License +------- + +Apache2 + +Upstream Contact +---------------- + +https://pypi.org/project/importlib-resources/ + diff --git a/build/pkgs/importlib_resources/checksums.ini b/build/pkgs/importlib_resources/checksums.ini new file mode 100644 index 00000000000..7b2b8a13aee --- /dev/null +++ b/build/pkgs/importlib_resources/checksums.ini @@ -0,0 +1,5 @@ +tarball=importlib_resources-VERSION.tar.gz +sha1=d4b853132e9b9a0c58610e23df380c84be428c08 +md5=a4586b3cbb3d39c7a5e7ffc49d9ceb53 +cksum=1484460315 +upstream_url=https://pypi.io/packages/source/i/importlib_resources/importlib_resources-VERSION.tar.gz diff --git a/build/pkgs/importlib_resources/dependencies b/build/pkgs/importlib_resources/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/importlib_resources/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/importlib_resources/install-requires.txt b/build/pkgs/importlib_resources/install-requires.txt new file mode 100644 index 00000000000..2b0146fc669 --- /dev/null +++ b/build/pkgs/importlib_resources/install-requires.txt @@ -0,0 +1 @@ +importlib-resources diff --git a/build/pkgs/importlib_resources/package-version.txt b/build/pkgs/importlib_resources/package-version.txt new file mode 100644 index 00000000000..76e9e619d63 --- /dev/null +++ b/build/pkgs/importlib_resources/package-version.txt @@ -0,0 +1 @@ +5.1.4 diff --git a/build/pkgs/importlib_resources/spkg-install.in b/build/pkgs/importlib_resources/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/importlib_resources/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/importlib_resources/type b/build/pkgs/importlib_resources/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/importlib_resources/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/kenzo/SPKG.rst b/build/pkgs/kenzo/SPKG.rst index 7567b7c4a63..5d139df545e 100644 --- a/build/pkgs/kenzo/SPKG.rst +++ b/build/pkgs/kenzo/SPKG.rst @@ -19,6 +19,8 @@ Upstream Contact - https://github.com/gheber/kenzo +- https://github.com/miguelmarco/kenzo/ + Dependencies ------------ diff --git a/build/pkgs/kenzo/checksums.ini b/build/pkgs/kenzo/checksums.ini index 22ef2a25127..ad2a7c4cc60 100644 --- a/build/pkgs/kenzo/checksums.ini +++ b/build/pkgs/kenzo/checksums.ini @@ -1,5 +1,5 @@ -tarball=kenzo-1.1.9.tar.gz -upstream_url=https://github.com/miguelmarco/kenzo/releases/download/1.1.9/kenzo-1.1.9.tar.gz -sha1=f153b0c172b6c11851a5f248947b61b0bf5be527 -md5=98adc197c6f23716a6ddd115115ab4f6 -cksum=880933361 +tarball=kenzo-1.1.10.tar.gz +upstream_url=https://github.com/miguelmarco/kenzo/releases/download/1.1.10/kenzo-1.1.10.tar.gz +sha1=76115aae9972090d5d51fee18592fc7a79461474 +md5=3a3d5350fb17304f03e614713e585ed4 +cksum=2981306888 diff --git a/build/pkgs/kenzo/package-version.txt b/build/pkgs/kenzo/package-version.txt index 512a1faa680..5ed5faa5f16 100644 --- a/build/pkgs/kenzo/package-version.txt +++ b/build/pkgs/kenzo/package-version.txt @@ -1 +1 @@ -1.1.9 +1.1.10 diff --git a/build/pkgs/kenzo/spkg-install.in b/build/pkgs/kenzo/spkg-install.in index 3c8e5946547..7bf5982be3a 100644 --- a/build/pkgs/kenzo/spkg-install.in +++ b/build/pkgs/kenzo/spkg-install.in @@ -5,9 +5,11 @@ cd src ecl < compile.lisp -# Install Kenzo into ECL's library directory (installation procedure -# copied from Maxima's spkg-install.in file): -ECLLIB=`ecl -eval "(princ (SI:GET-LIBRARY-PATHNAME))" -eval "(quit)"` +# Install Kenzo into ECL's library directory. +# Ensure that the $ECLLIB directory exists in +# case we're using ECL from the system. +ECLLIB="${SAGE_LOCAL}/lib/ecl" +mkdir -p "${ECLLIB}" echo echo "Now installing Kenzo as '$ECLLIB/kenzo.fas'..." cp -f kenzo--all-systems.fasb "$ECLLIB/kenzo.fas" \ diff --git a/build/pkgs/lcalc/spkg-configure.m4 b/build/pkgs/lcalc/spkg-configure.m4 index 19a87c8d304..4225eef4e7a 100644 --- a/build/pkgs/lcalc/spkg-configure.m4 +++ b/build/pkgs/lcalc/spkg-configure.m4 @@ -24,10 +24,22 @@ SAGE_SPKG_CONFIGURE([lcalc], [ AC_MSG_RESULT([no; install lcalc]) sage_spkg_install_lcalc=yes LIBS=$LCALC_SAVED_LIBS + ], [ + AC_MSG_RESULT([cross compiling; check linking only]) + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([[#include ]], + [[initialize_globals(); + Complex x; + x = Pi*I; + L_function L4; + return 0;]] + )], [AC_MSG_RESULT([yes; use lcalc from the system])], [ + AC_MSG_RESULT([no; install lcalc]) + sage_spkg_install_lcalc=yes + LIBS=$LCALC_SAVED_LIBS + ]) + ]) ]) - ], [ - AC_MSG_RESULT([no. Install lcalc]) - sage_spkg_install_lcalc=yes]) ]) ]) m4_popdef([SAGE_LCALC_MINVER]) diff --git a/build/pkgs/libatomic_ops/spkg-install.in b/build/pkgs/libatomic_ops/spkg-install.in index 8bcf5eec8d7..4953f4b0c1a 100644 --- a/build/pkgs/libatomic_ops/spkg-install.in +++ b/build/pkgs/libatomic_ops/spkg-install.in @@ -9,6 +9,6 @@ if [ "$UNAME" = "CYGWIN" ]; then LIBATOMIC_OPS_CONFIGURE="$LIBATOMIC_OPS_CONFIGURE --enable-shared --disable-static" fi -sdh_configure $LIBATOMIC_OPS_CONFIGURE +sdh_configure --enable-static $LIBATOMIC_OPS_CONFIGURE sdh_make sdh_make_install diff --git a/build/pkgs/libbraiding/spkg-configure.m4 b/build/pkgs/libbraiding/spkg-configure.m4 index 81f4d57f225..8fd86aa20ea 100644 --- a/build/pkgs/libbraiding/spkg-configure.m4 +++ b/build/pkgs/libbraiding/spkg-configure.m4 @@ -24,6 +24,26 @@ SAGE_SPKG_CONFIGURE([libbraiding], [ [ AC_MSG_RESULT([no]) sage_spkg_install_libbraiding=yes + ],[ + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([ + #include + #include + using namespace Braiding; + ],[ + // Mimic BraidGroup(2)([1,1]).thurston_type() in SageMath. + // thurstontype == 1 corresponds to "periodic" + if (thurstontype(2, {1,1}) == 1) { return 0; } else { return 1; } + ]) + ], + [ + AC_MSG_RESULT([yes]) + sage_spkg_install_libbraiding=no + ], + [ + AC_MSG_RESULT([no]) + sage_spkg_install_libbraiding=yes + ]) ]) LIBS=$SAVED_LIBS AC_LANG_POP diff --git a/build/pkgs/libgd/spkg-install.in b/build/pkgs/libgd/spkg-install.in index 03b23ab710e..dc73d868fa5 100644 --- a/build/pkgs/libgd/spkg-install.in +++ b/build/pkgs/libgd/spkg-install.in @@ -17,7 +17,10 @@ fi # We explicitly disable X and fontconfig support, since (1) X is not a SAGE dependency, # and (2) the gd build fails on a lot of OS X PPC machines when X is enabled. +# Also, libgd will try to link against system libavif/libvmaf and fail +# on Fedora 34 sdh_configure --without-jpeg --without-xpm --without-x --without-fontconfig \ + --without-avif \ --with-zlib="$SAGE_LOCAL" $LIBGD_CONFIGURE sdh_make sdh_make_install diff --git a/build/pkgs/libsemigroups/checksums.ini b/build/pkgs/libsemigroups/checksums.ini index a64622471b6..62c4268515f 100644 --- a/build/pkgs/libsemigroups/checksums.ini +++ b/build/pkgs/libsemigroups/checksums.ini @@ -1,5 +1,5 @@ tarball=libsemigroups-VERSION.tar.gz -sha1=084ca07ea1101f668274a26ac61d13eab0508f71 -md5=60069d1f82d2285867fd829624f9e60d -cksum=2338845637 -upstream_url=https://github.com/libsemigroups/libsemigroups/releases/download/v1.0.9/libsemigroups-1.0.9.tar.gz +sha1=2b16c095cc5ffd3f77a71dfbf48cce188e054c03 +md5=7082cadcf7a195ccb93175cd72b6db95 +cksum=1501022358 +upstream_url=https://github.com/libsemigroups/libsemigroups/releases/download/vVERSION/libsemigroups-VERSION.tar.gz diff --git a/build/pkgs/libsemigroups/package-version.txt b/build/pkgs/libsemigroups/package-version.txt index 66c4c2263e5..9084fa2f716 100644 --- a/build/pkgs/libsemigroups/package-version.txt +++ b/build/pkgs/libsemigroups/package-version.txt @@ -1 +1 @@ -1.0.9 +1.1.0 diff --git a/build/pkgs/maxima/checksums.ini b/build/pkgs/maxima/checksums.ini index 065f7d02e61..a804c7b831f 100644 --- a/build/pkgs/maxima/checksums.ini +++ b/build/pkgs/maxima/checksums.ini @@ -1,5 +1,5 @@ tarball=maxima-VERSION.tar.gz -sha1=5f1fce915675f46823c33638480dcc1fcaf447a1 -md5=75e040745161901968d9c99c7a258e5c -cksum=2316004676 +sha1=ed15d5285794413ba94412079eca3d0fa55a47bf +md5=9b9ae1dace55b1386739dabaa9122e60 +cksum=1765409766 upstream_url=https://sourceforge.net/projects/maxima/files/Maxima-source/VERSION-source/maxima-VERSION.tar.gz/download diff --git a/build/pkgs/maxima/package-version.txt b/build/pkgs/maxima/package-version.txt index d532a08f3cd..5a053acd6b8 100644 --- a/build/pkgs/maxima/package-version.txt +++ b/build/pkgs/maxima/package-version.txt @@ -1 +1 @@ -5.44.0 +5.45.0 diff --git a/build/pkgs/maxima/spkg-install.in b/build/pkgs/maxima/spkg-install.in index 41da6c16692..d72b0c37da3 100644 --- a/build/pkgs/maxima/spkg-install.in +++ b/build/pkgs/maxima/spkg-install.in @@ -57,10 +57,12 @@ sdh_make_install # Install Maxima into ECL's library directory: -ecl -eval '(WITH-OPEN-FILE (f "ecllib" :DIRECTION :OUTPUT :IF-EXISTS :SUPERSEDE) (FORMAT f "~A~%" (SI:GET-LIBRARY-PATHNAME)))' -eval '(QUIT)' -ECLLIB=$(cat ecllib) +# Ensure that the $ECLLIB directory exists in +# case we're using ECL from the system. +ECLLIB="${SAGE_LOCAL}/lib/ecl" echo echo "Now installing the Maxima library as '$ECLLIB/maxima.fas'..." +mkdir -p "${ECLLIB}" cp -f "src/binary-ecl/maxima.fas" "$ECLLIB/maxima.fas" \ || sdh_die "Failed to install 'src/binary-ecl/maxima.fas' as '$ECLLIB/maxima.fas'." diff --git a/build/pkgs/memory_allocator/SPKG.rst b/build/pkgs/memory_allocator/SPKG.rst new file mode 100644 index 00000000000..42975c02f55 --- /dev/null +++ b/build/pkgs/memory_allocator/SPKG.rst @@ -0,0 +1,26 @@ +MemoryAllocator: An extension class to allocate memory easily with cython. +========================================================================== + +This extension class started as part of the Sage software. + +Description +----------- + +development website: https://github.com/sagemath/memory_allocator + +PyPI page: https://pypi.org/project/memory_allocator + +License +------- + +GPL-3.0 + +Upstream Contact +---------------- + +https://github.com/sagemath/memory_allocator + +Dependencies +------------ + +- Cython diff --git a/build/pkgs/memory_allocator/checksums.ini b/build/pkgs/memory_allocator/checksums.ini new file mode 100644 index 00000000000..f7efcd9b150 --- /dev/null +++ b/build/pkgs/memory_allocator/checksums.ini @@ -0,0 +1,5 @@ +tarball=memory_allocator-VERSION.tar.gz +sha1=0e9256d307957e84eaba408f190cafc04d5c246a +md5=1ce1fb6dc5972017a26ee82c4371b180 +cksum=2493862442 +upstream_url=https://pypi.io/packages/source/m/memory_allocator/memory_allocator-VERSION.tar.gz diff --git a/build/pkgs/memory_allocator/dependencies b/build/pkgs/memory_allocator/dependencies new file mode 100644 index 00000000000..d3dac75e5f2 --- /dev/null +++ b/build/pkgs/memory_allocator/dependencies @@ -0,0 +1,5 @@ +$(PYTHON) cython | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. +It is copied by SAGE_ROOT/build/make/install into SAGE_ROOT/build/make/Makefile. diff --git a/build/pkgs/memory_allocator/install_requires.txt b/build/pkgs/memory_allocator/install_requires.txt new file mode 100644 index 00000000000..fad07b22ebe --- /dev/null +++ b/build/pkgs/memory_allocator/install_requires.txt @@ -0,0 +1 @@ +memory_allocator diff --git a/build/pkgs/memory_allocator/package-version.txt b/build/pkgs/memory_allocator/package-version.txt new file mode 100644 index 00000000000..6e8bf73aa55 --- /dev/null +++ b/build/pkgs/memory_allocator/package-version.txt @@ -0,0 +1 @@ +0.1.0 diff --git a/build/pkgs/memory_allocator/spkg-check.in b/build/pkgs/memory_allocator/spkg-check.in new file mode 100644 index 00000000000..c9f080a7d0d --- /dev/null +++ b/build/pkgs/memory_allocator/spkg-check.in @@ -0,0 +1 @@ +python3 -c "import memory_allocator.test; import os; os.chdir('src'); exec(open('test.py').read())" diff --git a/build/pkgs/memory_allocator/spkg-install.in b/build/pkgs/memory_allocator/spkg-install.in new file mode 100644 index 00000000000..058b1344dc2 --- /dev/null +++ b/build/pkgs/memory_allocator/spkg-install.in @@ -0,0 +1,3 @@ +cd src + +sdh_pip_install . diff --git a/build/pkgs/memory_allocator/type b/build/pkgs/memory_allocator/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/memory_allocator/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/mpfi/spkg-configure.m4 b/build/pkgs/mpfi/spkg-configure.m4 index 7805ef4a2dd..0cdb10a920e 100644 --- a/build/pkgs/mpfi/spkg-configure.m4 +++ b/build/pkgs/mpfi/spkg-configure.m4 @@ -28,7 +28,8 @@ SAGE_SPKG_CONFIGURE([mpfi], [ else return 1; ]])], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) - sage_spkg_install_mpfi=yes]) + sage_spkg_install_mpfi=yes], + [AC_MSG_RESULT([cross compiling. assume yes])]) AC_LANG_POP(C)], [sage_spkg_install_mpfi=yes]) fi diff --git a/build/pkgs/mpmath/checksums.ini b/build/pkgs/mpmath/checksums.ini index 6c532f911b9..1313275f20e 100644 --- a/build/pkgs/mpmath/checksums.ini +++ b/build/pkgs/mpmath/checksums.ini @@ -1,4 +1,5 @@ tarball=mpmath-VERSION.tar.gz -sha1=3f479408ea65b08bc23eeebe5dac2f2293dfec9d -md5=acb1cdddf38e16084628065b174ddbfe -cksum=3387899135 +sha1=ce8bd24606eeb02218b26304e6d99228919021f8 +md5=ef8a6449851755319673b06f71731d52 +cksum=3549837503 +upstream_url=https://files.pythonhosted.org/packages/source/m/mpmath/mpmath-VERSION.tar.gz diff --git a/build/pkgs/mpmath/package-version.txt b/build/pkgs/mpmath/package-version.txt index 9084fa2f716..6085e946503 100644 --- a/build/pkgs/mpmath/package-version.txt +++ b/build/pkgs/mpmath/package-version.txt @@ -1 +1 @@ -1.1.0 +1.2.1 diff --git a/build/pkgs/networkx/checksums.ini b/build/pkgs/networkx/checksums.ini index 6df69c9aca9..100611f9fa2 100644 --- a/build/pkgs/networkx/checksums.ini +++ b/build/pkgs/networkx/checksums.ini @@ -1,5 +1,5 @@ tarball=networkx-VERSION.tar.gz -sha1=439af030cbfdc2c4cbdef5dc423d5ea8e527d02a -md5=21f25be1f4373e19153a9beca63346e7 -cksum=1531245694 +sha1=67bf3fb6634d20d56afff123a0a3f03f0567316c +md5=bded9f095b2757f6ce11531a6f874d9d +cksum=3353048305 upstream_url=https://pypi.io/packages/source/n/networkx/networkx-VERSION.tar.gz diff --git a/build/pkgs/networkx/package-version.txt b/build/pkgs/networkx/package-version.txt index 95e3ba81920..73462a5a134 100644 --- a/build/pkgs/networkx/package-version.txt +++ b/build/pkgs/networkx/package-version.txt @@ -1 +1 @@ -2.5 +2.5.1 diff --git a/build/pkgs/normaliz/checksums.ini b/build/pkgs/normaliz/checksums.ini index 708adca47bf..c3afbfb2656 100644 --- a/build/pkgs/normaliz/checksums.ini +++ b/build/pkgs/normaliz/checksums.ini @@ -1,5 +1,5 @@ tarball=normaliz-VERSION.tar.gz -sha1=068103675d605b6a905b809611a4d2f506adf505 -md5=5d843dd1af6802bff412865dfe7b2322 -cksum=3186058075 +sha1=7486d046c5e8e352d6d7c3544a0e6a1164e9b1fd +md5=136edc12b5c027bb1a019e06fb8d9113 +cksum=1640404889 upstream_url=https://github.com/Normaliz/Normaliz/releases/download/vVERSION/normaliz-VERSION.tar.gz diff --git a/build/pkgs/normaliz/package-version.txt b/build/pkgs/normaliz/package-version.txt index 203e6d5c9a6..d20cc2bf020 100644 --- a/build/pkgs/normaliz/package-version.txt +++ b/build/pkgs/normaliz/package-version.txt @@ -1 +1 @@ -3.8.9 +3.8.10 diff --git a/build/pkgs/ntl/spkg-configure.m4 b/build/pkgs/ntl/spkg-configure.m4 index 48e41de6ea7..228c672cc4e 100644 --- a/build/pkgs/ntl/spkg-configure.m4 +++ b/build/pkgs/ntl/spkg-configure.m4 @@ -29,6 +29,10 @@ SAGE_SPKG_CONFIGURE([ntl], [ ], [ AC_MSG_RESULT([no]) sage_spkg_install_ntl=yes + ], [ + dnl assume that the person running cross-compiling + dnl knows what they are doing + AC_MSG_RESULT([yes]) ]) ]) diff --git a/build/pkgs/numpy/checksums.ini b/build/pkgs/numpy/checksums.ini index 4906cab96a2..74d283e01f0 100644 --- a/build/pkgs/numpy/checksums.ini +++ b/build/pkgs/numpy/checksums.ini @@ -1,5 +1,5 @@ tarball=numpy-VERSION.zip -sha1=61f0b3dad58ce97b14da9dccbee0722d36f26937 -md5=f6a1b48717c552bbc18f1adc3cc1fe0e -cksum=1120756655 +sha1=56731f9eedf3bd42de2940515cfe3fadea7dfcca +md5=949d9114af9accc25ede1daa359c4227 +cksum=2320980313 upstream_url=https://pypi.io/packages/source/n/numpy/numpy-VERSION.zip diff --git a/build/pkgs/numpy/package-version.txt b/build/pkgs/numpy/package-version.txt index 83d5e73f00e..f5b00dc262b 100644 --- a/build/pkgs/numpy/package-version.txt +++ b/build/pkgs/numpy/package-version.txt @@ -1 +1 @@ -1.19.5 +1.20.3 diff --git a/build/pkgs/numpy/patches/0001-ENH-Use-AVX-512-for-np.isnan-np.infinite-np.isinf-an.patch b/build/pkgs/numpy/patches/0001-ENH-Use-AVX-512-for-np.isnan-np.infinite-np.isinf-an.patch deleted file mode 100644 index 791be1864ec..00000000000 --- a/build/pkgs/numpy/patches/0001-ENH-Use-AVX-512-for-np.isnan-np.infinite-np.isinf-an.patch +++ /dev/null @@ -1,356 +0,0 @@ -From 7bde5a589359a096be253738ed7c53330f7ff4ad Mon Sep 17 00:00:00 2001 -From: Raghuveer Devulapalli -Date: Sun, 31 May 2020 09:16:33 -0700 -Subject: [PATCH 1/2] ENH: Use AVX-512 for np.isnan, np.infinite, np.isinf and - np.signbit (#16334) - -* ENH: Use AVX-512 for np.isnan, np.infinite, np.isinf and np.signbit - -* TST: Add tests to validate isnan, isfinite, signbit and isinf ufuncs - -* BENCH: Adding benchmarks for isnan, isinf, isfinite and signbit ---- - benchmarks/benchmarks/bench_avx.py | 6 +- - numpy/core/code_generators/generate_umath.py | 8 +- - numpy/core/include/numpy/npy_common.h | 7 ++ - numpy/core/setup_common.py | 11 ++ - numpy/core/src/umath/loops.c.src | 10 +- - numpy/core/src/umath/loops.h.src | 7 +- - numpy/core/src/umath/simd.inc.src | 116 ++++++++++++++++++- - numpy/core/tests/test_umath.py | 18 +++ - 8 files changed, 173 insertions(+), 10 deletions(-) - -diff --git a/benchmarks/benchmarks/bench_avx.py b/benchmarks/benchmarks/bench_avx.py -index 2a128b3ff..4f915f82a 100644 ---- a/benchmarks/benchmarks/bench_avx.py -+++ b/benchmarks/benchmarks/bench_avx.py -@@ -13,7 +13,11 @@ avx_ufuncs = ['sin', - 'rint', - 'floor', - 'ceil' , -- 'trunc'] -+ 'trunc', -+ 'isnan', -+ 'isfinite', -+ 'isinf', -+ 'signbit'] - stride = [1, 2, 4] - dtype = ['f', 'd'] - -diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py -index 202912c5f..992bdc862 100644 ---- a/numpy/core/code_generators/generate_umath.py -+++ b/numpy/core/code_generators/generate_umath.py -@@ -843,7 +843,7 @@ defdict = { - Ufunc(1, 1, None, - docstrings.get('numpy.core.umath.isnan'), - 'PyUFunc_IsFiniteTypeResolver', -- TD(noobj, out='?'), -+ TD(noobj, simd=[('avx512_skx', 'fd')], out='?'), - ), - 'isnat': - Ufunc(1, 1, None, -@@ -855,19 +855,19 @@ defdict = { - Ufunc(1, 1, None, - docstrings.get('numpy.core.umath.isinf'), - 'PyUFunc_IsFiniteTypeResolver', -- TD(noobj, out='?'), -+ TD(noobj, simd=[('avx512_skx', 'fd')], out='?'), - ), - 'isfinite': - Ufunc(1, 1, None, - docstrings.get('numpy.core.umath.isfinite'), - 'PyUFunc_IsFiniteTypeResolver', -- TD(noobj, out='?'), -+ TD(noobj, simd=[('avx512_skx', 'fd')], out='?'), - ), - 'signbit': - Ufunc(1, 1, None, - docstrings.get('numpy.core.umath.signbit'), - None, -- TD(flts, out='?'), -+ TD(flts, simd=[('avx512_skx', 'fd')], out='?'), - ), - 'copysign': - Ufunc(2, 1, None, -diff --git a/numpy/core/include/numpy/npy_common.h b/numpy/core/include/numpy/npy_common.h -index c2e755958..3cec0c6ff 100644 ---- a/numpy/core/include/numpy/npy_common.h -+++ b/numpy/core/include/numpy/npy_common.h -@@ -64,6 +64,13 @@ - #define NPY_GCC_TARGET_AVX512F - #endif - -+#if defined HAVE_ATTRIBUTE_TARGET_AVX512_SKX && defined HAVE_LINK_AVX512_SKX -+#define NPY_GCC_TARGET_AVX512_SKX __attribute__((target("avx512f,avx512dq,avx512vl,avx512bw,avx512cd"))) -+#elif defined HAVE_ATTRIBUTE_TARGET_AVX512_SKX_WITH_INTRINSICS -+#define NPY_GCC_TARGET_AVX512_SKX __attribute__((target("avx512f,avx512dq,avx512vl,avx512bw,avx512cd"))) -+#else -+#define NPY_GCC_TARGET_AVX512_SKX -+#endif - /* - * mark an argument (starting from 1) that must not be NULL and is not checked - * DO NOT USE IF FUNCTION CHECKS FOR NULL!! the compiler will remove the check -diff --git a/numpy/core/setup_common.py b/numpy/core/setup_common.py -index 63c4a76a9..a37514eec 100644 ---- a/numpy/core/setup_common.py -+++ b/numpy/core/setup_common.py -@@ -146,6 +146,10 @@ OPTIONAL_INTRINSICS = [("__builtin_isnan", '5.'), - "stdio.h", "LINK_AVX2"), - ("__asm__ volatile", '"vpaddd %zmm1, %zmm2, %zmm3"', - "stdio.h", "LINK_AVX512F"), -+ ("__asm__ volatile", '"vfpclasspd $0x40, %zmm15, %k6\\n"\ -+ "vmovdqu8 %xmm0, %xmm1\\n"\ -+ "vpbroadcastmb2q %k0, %xmm0\\n"', -+ "stdio.h", "LINK_AVX512_SKX"), - ("__asm__ volatile", '"xgetbv"', "stdio.h", "XGETBV"), - ] - -@@ -164,6 +168,8 @@ OPTIONAL_FUNCTION_ATTRIBUTES = [('__attribute__((optimize("unroll-loops")))', - 'attribute_target_avx2'), - ('__attribute__((target ("avx512f")))', - 'attribute_target_avx512f'), -+ ('__attribute__((target ("avx512f,avx512dq,avx512bw,avx512vl,avx512cd")))', -+ 'attribute_target_avx512_skx'), - ] - - # function attributes with intrinsics -@@ -180,6 +186,11 @@ OPTIONAL_FUNCTION_ATTRIBUTES_WITH_INTRINSICS = [('__attribute__((target("avx2,fm - 'attribute_target_avx512f_with_intrinsics', - '__m512 temp = _mm512_set1_ps(1.0)', - 'immintrin.h'), -+ ('__attribute__((target ("avx512f,avx512dq,avx512bw,avx512vl,avx512cd")))', -+ 'attribute_target_avx512_skx_with_intrinsics', -+ '__mmask8 temp = _mm512_fpclass_pd_mask(_mm512_set1_pd(1.0), 0x01);\ -+ _mm_mask_storeu_epi8(NULL, 0xFF, _mm_broadcastmb_epi64(temp))', -+ 'immintrin.h'), - ] - - # variable attributes tested via "int %s a" % attribute -diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src -index a59a9acf5..dbb8dd48e 100644 ---- a/numpy/core/src/umath/loops.c.src -+++ b/numpy/core/src/umath/loops.c.src -@@ -1863,10 +1863,15 @@ NPY_NO_EXPORT void - * #kind = isnan, isinf, isfinite, signbit# - * #func = npy_isnan, npy_isinf, npy_isfinite, npy_signbit# - **/ -+ -+/**begin repeat2 -+ * #ISA = , _avx512_skx# -+ * #isa = simd, avx512_skx# -+ **/ - NPY_NO_EXPORT void --@TYPE@_@kind@(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)) -+@TYPE@_@kind@@ISA@(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)) - { -- if (!run_@kind@_simd_@TYPE@(args, dimensions, steps)) { -+ if (!run_@kind@_@isa@_@TYPE@(args, dimensions, steps)) { - UNARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - *((npy_bool *)op1) = @func@(in1) != 0; -@@ -1874,6 +1879,7 @@ NPY_NO_EXPORT void - } - npy_clear_floatstatus_barrier((char*)dimensions); - } -+/**end repeat2**/ - /**end repeat1**/ - - NPY_NO_EXPORT void -diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src -index 50a7ccfee..b63d442ef 100644 ---- a/numpy/core/src/umath/loops.h.src -+++ b/numpy/core/src/umath/loops.h.src -@@ -274,8 +274,13 @@ NPY_NO_EXPORT void - * #kind = isnan, isinf, isfinite, signbit, copysign, nextafter, spacing# - * #func = npy_isnan, npy_isinf, npy_isfinite, npy_signbit, npy_copysign, nextafter, spacing# - **/ -+ -+/**begin repeat2 -+ * #ISA = , _avx512_skx# -+ **/ - NPY_NO_EXPORT void --@TYPE@_@kind@(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)); -+@TYPE@_@kind@@ISA@(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)); -+/**end repeat2**/ - /**end repeat1**/ - - /**begin repeat1 -diff --git a/numpy/core/src/umath/simd.inc.src b/numpy/core/src/umath/simd.inc.src -index b28c63930..a5308d83a 100644 ---- a/numpy/core/src/umath/simd.inc.src -+++ b/numpy/core/src/umath/simd.inc.src -@@ -1,4 +1,4 @@ --/* -*- c -*- */ -+ - - /* - * This file is for the definitions of simd vectorized operations. -@@ -295,6 +295,40 @@ run_binary_avx512f_@func@_@TYPE@(char **args, npy_intp const *dimensions, npy_in - } - - -+/**end repeat1**/ -+/**end repeat**/ -+ -+/**begin repeat -+ * #type = npy_float, npy_double, npy_longdouble# -+ * #TYPE = FLOAT, DOUBLE, LONGDOUBLE# -+ * #EXISTS = 1, 1, 0# -+ */ -+ -+/**begin repeat1 -+ * #func = isnan, isfinite, isinf, signbit# -+ */ -+ -+#if defined HAVE_ATTRIBUTE_TARGET_AVX512_SKX_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS && @EXISTS@ -+static NPY_INLINE NPY_GCC_TARGET_AVX512_SKX void -+AVX512_SKX_@func@_@TYPE@(npy_bool*, @type@*, const npy_intp n, const npy_intp stride); -+#endif -+ -+static NPY_INLINE int -+run_@func@_avx512_skx_@TYPE@(char **args, npy_intp const *dimensions, npy_intp const *steps) -+{ -+#if defined HAVE_ATTRIBUTE_TARGET_AVX512_SKX_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS && @EXISTS@ -+ if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(npy_bool), 64)) { -+ AVX512_SKX_@func@_@TYPE@((npy_bool*)args[1], (@type@*)args[0], dimensions[0], steps[0]); -+ return 1; -+ } -+ else { -+ return 0; -+ } -+#endif -+ return 0; -+} -+ -+ - /**end repeat1**/ - /**end repeat**/ - -@@ -1973,6 +2007,84 @@ static NPY_INLINE NPY_GCC_OPT_3 NPY_GCC_TARGET_@ISA@ @vtype@d - #endif - /**end repeat**/ - -+/**begin repeat -+ * #type = npy_float, npy_double# -+ * #TYPE = FLOAT, DOUBLE# -+ * #num_lanes = 16, 8# -+ * #vsuffix = ps, pd# -+ * #mask = __mmask16, __mmask8# -+ * #vtype = __m512, __m512d# -+ * #scale = 4, 8# -+ * #vindextype = __m512i, __m256i# -+ * #vindexload = _mm512_loadu_si512, _mm256_loadu_si256# -+ * #episize = epi32, epi64# -+ */ -+ -+/**begin repeat1 -+ * #func = isnan, isfinite, isinf, signbit# -+ * #IMM8 = 0x81, 0x99, 0x18, 0x04# -+ * #is_finite = 0, 1, 0, 0# -+ * #is_signbit = 0, 0, 0, 1# -+ */ -+#if defined HAVE_ATTRIBUTE_TARGET_AVX512_SKX_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS -+static NPY_INLINE NPY_GCC_TARGET_AVX512_SKX void -+AVX512_SKX_@func@_@TYPE@(npy_bool* op, @type@* ip, const npy_intp array_size, const npy_intp steps) -+{ -+ const npy_intp stride_ip = steps/(npy_intp)sizeof(@type@); -+ npy_intp num_remaining_elements = array_size; -+ -+ @mask@ load_mask = avx512_get_full_load_mask_@vsuffix@(); -+#if @is_signbit@ -+ @vtype@ signbit = _mm512_set1_@vsuffix@(-0.0); -+#endif -+ -+ /* -+ * Note: while generally indices are npy_intp, we ensure that our maximum -+ * index will fit in an int32 as a precondition for this function via -+ * IS_OUTPUT_BLOCKABLE_UNARY -+ */ -+ -+ npy_int32 index_ip[@num_lanes@]; -+ for (npy_int32 ii = 0; ii < @num_lanes@; ii++) { -+ index_ip[ii] = ii*stride_ip; -+ } -+ @vindextype@ vindex_ip = @vindexload@((@vindextype@*)&index_ip[0]); -+ @vtype@ zeros_f = _mm512_setzero_@vsuffix@(); -+ __m512i ones = _mm512_set1_@episize@(1); -+ -+ while (num_remaining_elements > 0) { -+ if (num_remaining_elements < @num_lanes@) { -+ load_mask = avx512_get_partial_load_mask_@vsuffix@( -+ num_remaining_elements, @num_lanes@); -+ } -+ @vtype@ x1; -+ if (stride_ip == 1) { -+ x1 = avx512_masked_load_@vsuffix@(load_mask, ip); -+ } -+ else { -+ x1 = avx512_masked_gather_@vsuffix@(zeros_f, ip, vindex_ip, load_mask); -+ } -+#if @is_signbit@ -+ x1 = _mm512_and_@vsuffix@(x1,signbit); -+#endif -+ -+ @mask@ fpclassmask = _mm512_fpclass_@vsuffix@_mask(x1, @IMM8@); -+#if @is_finite@ -+ fpclassmask = _mm512_knot(fpclassmask); -+#endif -+ -+ __m128i out =_mm512_maskz_cvts@episize@_epi8(fpclassmask, ones); -+ _mm_mask_storeu_epi8(op, load_mask, out); -+ -+ ip += @num_lanes@*stride_ip; -+ op += @num_lanes@; -+ num_remaining_elements -= @num_lanes@; -+ } -+} -+#endif -+/**end repeat1**/ -+/**end repeat**/ -+ - /**begin repeat - * #type = npy_float, npy_double# - * #TYPE = FLOAT, DOUBLE# -@@ -2066,8 +2178,8 @@ AVX512F_@func@_@TYPE@(char **args, npy_intp const *dimensions, npy_intp const *s - } - } - #endif --/**end repeat**/ - /**end repeat1**/ -+/**end repeat**/ - - /**begin repeat - * #ISA = FMA, AVX512F# -diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py -index 7caa4c7f9..f208d375e 100644 ---- a/numpy/core/tests/test_umath.py -+++ b/numpy/core/tests/test_umath.py -@@ -771,6 +771,24 @@ class TestSpecialFloats: - for dt in ['f', 'd', 'g']: - assert_raises(FloatingPointError, np.reciprocal, np.array(-0.0, dtype=dt)) - -+class TestFPClass: -+ @pytest.mark.parametrize("stride", [-4,-2,-1,1,2,4]) -+ def test_fpclass(self, stride): -+ arr_f64 = np.array([np.nan, -np.nan, np.inf, -np.inf, -1.0, 1.0, -0.0, 0.0, 2.2251e-308, -2.2251e-308], dtype='d') -+ arr_f32 = np.array([np.nan, -np.nan, np.inf, -np.inf, -1.0, 1.0, -0.0, 0.0, 1.4013e-045, -1.4013e-045], dtype='f') -+ nan = np.array([True, True, False, False, False, False, False, False, False, False]) -+ inf = np.array([False, False, True, True, False, False, False, False, False, False]) -+ sign = np.array([False, True, False, True, True, False, True, False, False, True]) -+ finite = np.array([False, False, False, False, True, True, True, True, True, True]) -+ assert_equal(np.isnan(arr_f32[::stride]), nan[::stride]) -+ assert_equal(np.isnan(arr_f64[::stride]), nan[::stride]) -+ assert_equal(np.isinf(arr_f32[::stride]), inf[::stride]) -+ assert_equal(np.isinf(arr_f64[::stride]), inf[::stride]) -+ assert_equal(np.signbit(arr_f32[::stride]), sign[::stride]) -+ assert_equal(np.signbit(arr_f64[::stride]), sign[::stride]) -+ assert_equal(np.isfinite(arr_f32[::stride]), finite[::stride]) -+ assert_equal(np.isfinite(arr_f64[::stride]), finite[::stride]) -+ - # func : [maxulperror, low, high] - avx_ufuncs = {'sqrt' :[1, 0., 100.], - 'absolute' :[0, -100., 100.], --- -2.26.2 - diff --git a/build/pkgs/numpy/patches/0002-BUG-Update-compiler-check-for-AVX-512F.patch b/build/pkgs/numpy/patches/0002-BUG-Update-compiler-check-for-AVX-512F.patch deleted file mode 100644 index 08dd874fe47..00000000000 --- a/build/pkgs/numpy/patches/0002-BUG-Update-compiler-check-for-AVX-512F.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 27d2da99c2390fec567feba3cdb0afa20c810863 Mon Sep 17 00:00:00 2001 -From: Raghuveer Devulapalli -Date: Tue, 14 Jul 2020 13:34:55 -0700 -Subject: [PATCH 2/2] BUG: Update compiler check for AVX-512F - -gcc-4.9 is missing a few AVX-512F intrisics, see -https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61878. We use some of these -missing intrinsics to check for compiler support of AVX-512F. ---- - numpy/core/setup_common.py | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/numpy/core/setup_common.py b/numpy/core/setup_common.py -index a37514eec..7901770e4 100644 ---- a/numpy/core/setup_common.py -+++ b/numpy/core/setup_common.py -@@ -177,6 +177,9 @@ OPTIONAL_FUNCTION_ATTRIBUTES = [('__attribute__((optimize("unroll-loops")))', - # gcc 4.8.4 support attributes but not with intrisics - # tested via "#include<%s> int %s %s(void *){code; return 0;};" % (header, attribute, name, code) - # function name will be converted to HAVE_ preprocessor macro -+# The _mm512_castps_si512 instruction is specific check for AVX-512F support -+# in gcc-4.9 which is missing a subset of intrinsics. See -+# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61878 - OPTIONAL_FUNCTION_ATTRIBUTES_WITH_INTRINSICS = [('__attribute__((target("avx2,fma")))', - 'attribute_target_avx2_with_intrinsics', - '__m256 temp = _mm256_set1_ps(1.0); temp = \ -@@ -184,11 +187,12 @@ OPTIONAL_FUNCTION_ATTRIBUTES_WITH_INTRINSICS = [('__attribute__((target("avx2,fm - 'immintrin.h'), - ('__attribute__((target("avx512f")))', - 'attribute_target_avx512f_with_intrinsics', -- '__m512 temp = _mm512_set1_ps(1.0)', -+ '__m512i temp = _mm512_castps_si512(_mm512_set1_ps(1.0))', - 'immintrin.h'), - ('__attribute__((target ("avx512f,avx512dq,avx512bw,avx512vl,avx512cd")))', - 'attribute_target_avx512_skx_with_intrinsics', - '__mmask8 temp = _mm512_fpclass_pd_mask(_mm512_set1_pd(1.0), 0x01);\ -+ __m512i temp = _mm512_castps_si512(_mm512_set1_ps(1.0));\ - _mm_mask_storeu_epi8(NULL, 0xFF, _mm_broadcastmb_epi64(temp))', - 'immintrin.h'), - ] --- -2.26.2 - diff --git a/build/pkgs/numpy/patches/gh-18016.patch b/build/pkgs/numpy/patches/gh-18016.patch deleted file mode 100644 index 1a4637d17fe..00000000000 --- a/build/pkgs/numpy/patches/gh-18016.patch +++ /dev/null @@ -1,22 +0,0 @@ -From a91f53119c53d99a3f832dc41e96ccd712d705a3 Mon Sep 17 00:00:00 2001 -From: FX Coudert -Date: Thu, 3 Dec 2020 16:44:16 +0100 -Subject: [PATCH] Update gnu.py - ---- - numpy/distutils/fcompiler/gnu.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/numpy/distutils/fcompiler/gnu.py b/numpy/distutils/fcompiler/gnu.py -index 0d9d769c2a8..68d1501eee6 100644 ---- a/numpy/distutils/fcompiler/gnu.py -+++ b/numpy/distutils/fcompiler/gnu.py -@@ -126,7 +126,7 @@ def get_flags_linker_so(self): - target = '10.9' - s = f'Env. variable MACOSX_DEPLOYMENT_TARGET set to {target}' - warnings.warn(s, stacklevel=2) -- os.environ['MACOSX_DEPLOYMENT_TARGET'] = target -+ os.environ['MACOSX_DEPLOYMENT_TARGET'] = str(target) - opt.extend(['-undefined', 'dynamic_lookup', '-bundle']) - else: - opt.append("-shared") diff --git a/build/pkgs/openblas/spkg-check.in b/build/pkgs/openblas/spkg-check.in index 3032da767f5..de401dfc0af 100644 --- a/build/pkgs/openblas/spkg-check.in +++ b/build/pkgs/openblas/spkg-check.in @@ -1,33 +1,10 @@ -cd src - -# OpenBlas has no proper configure script +# OpenBLAS has no proper configure script # Options are directly passed to make # And the name static library archive produced depends on them # And the tests directly link to that archive rather than through a symlink -# Therefore the following is copied from spkg-install -# We could also patch the Makefile to use a generic symlink pointing -# to the archive with a specific name +# So we use the saved configuration from spkg-install.in +. ./set_openblas_configure || sdh_die "Saved configuration not found" -if [ `sage-bootstrap-python -c "from __future__ import print_function; import platform; print(platform.architecture()[0])"` = "32bit" ]; then - OPENBLAS_CONFIGURE="$OPENBLAS_CONFIGURE BINARY=32" -fi - -if [ "x$SAGE_FAT_BINARY" = "xyes" ]; then - # See https://github.com/xianyi/OpenBLAS/issues/510 - OPENBLAS_CONFIGURE="$OPENBLAS_CONFIGURE TARGET=PRESCOTT" -fi +cd src -${MAKE:-make} tests $OPENBLAS_CONFIGURE -if [ $? -ne 0 ]; then - # First make sure we already didn't set a target - if [[ $OPENBLAS_CONFIGURE == *"TARGET"* ]]; then - sdh_die "Failures while running the OpenBLAS testsuite ... exiting" - else - # The recommended TARGET is ATOM if CPU fails - # See https://github.com/xianyi/OpenBLAS/issues/1204 - OPENBLAS_CONFIGURE="$OPENBLAS_CONFIGURE TARGET=ATOM" - echo "Failures while testing the OpenBLAS testsuite" - echo "Retrying the OpenBLAS testsuite with TARGET=ATOM" - ${MAKE:-make} tests $OPENBLAS_CONFIGURE - fi -fi +${MAKE:-make} tests $OPENBLAS_CONFIGURE || sdh_die "'make tests' failed" diff --git a/build/pkgs/openblas/spkg-configure.m4 b/build/pkgs/openblas/spkg-configure.m4 index 912248c474c..177bbb1d4ff 100644 --- a/build/pkgs/openblas/spkg-configure.m4 +++ b/build/pkgs/openblas/spkg-configure.m4 @@ -75,7 +75,8 @@ SAGE_SPKG_CONFIGURE([openblas], [ + 100 * ]]SAGE_OPENBLAS_MIN_VERSION_MINOR[[ + ]]SAGE_OPENBLAS_MIN_VERSION_MICRO[[) return 1;]]) - ], [AS_VAR_SET([HAVE_OPENBLAS], [yes])], [AS_VAR_SET([HAVE_OPENBLAS], [no])]) + ], [AS_VAR_SET([HAVE_OPENBLAS], [yes])], [AS_VAR_SET([HAVE_OPENBLAS], [no])], + [AS_VAR_SET([HAVE_OPENBLAS], [yes])]) AC_LANG_POP([C]) AC_MSG_RESULT([$HAVE_OPENBLAS]) ]) diff --git a/build/pkgs/openblas/spkg-install.in b/build/pkgs/openblas/spkg-install.in index 70ad7b5d189..b06a55d3130 100644 --- a/build/pkgs/openblas/spkg-install.in +++ b/build/pkgs/openblas/spkg-install.in @@ -45,6 +45,10 @@ sdh_make_install PREFIX="$SAGE_LOCAL" NO_STATIC=1 $OPENBLAS_CONFIGURE cd .. ./write_pc_file.py +# Save configuration for spkg-check +echo >&2 "Writing configuration to $(pwd)/set_openblas_configure" +echo OPENBLAS_CONFIGURE=\'"$OPENBLAS_CONFIGURE"\' > set_openblas_configure + # OpenBLAS's Makefile has a bug w.r.t. calling install_name_tool when # DESTDIR is set. It should *not* include the DESTDIR in the library's # install_name; we set the correct install_name here diff --git a/build/pkgs/pari/checksums.ini b/build/pkgs/pari/checksums.ini index f10b432dbb7..ec00516a1d6 100644 --- a/build/pkgs/pari/checksums.ini +++ b/build/pkgs/pari/checksums.ini @@ -1,5 +1,5 @@ tarball=pari-VERSION.tar.gz -sha1=2b9ff51feb388664b834dc346a44867546c78618 -md5=fb2968d7805424518fe44a59a2024afd -cksum=1247903778 +sha1=40731c850cc50fb4994148fac49fda4f68ce6106 +md5=826064cf75af268be8a482ade6e27501 +cksum=550849474 upstream_url=https://pari.math.u-bordeaux.fr/pub/pari/unix/pari-VERSION.tar.gz diff --git a/build/pkgs/pari/package-version.txt b/build/pkgs/pari/package-version.txt index 25cce6b3556..94f15e9cc30 100644 --- a/build/pkgs/pari/package-version.txt +++ b/build/pkgs/pari/package-version.txt @@ -1 +1 @@ -2.11.4.p1 +2.13.1 diff --git a/build/pkgs/pari/patches/pari-rnfdisc.patch b/build/pkgs/pari/patches/pari-rnfdisc.patch new file mode 100644 index 00000000000..39d325911e8 --- /dev/null +++ b/build/pkgs/pari/patches/pari-rnfdisc.patch @@ -0,0 +1,35 @@ +From 3edb98db78dd49bb8b4137b46781a7cd570c2556 Mon Sep 17 00:00:00 2001 +From: Bill Allombert +Date: Sun, 28 Mar 2021 13:27:24 +0200 +Subject: [PATCH] rnfdisc_factored: remove spurious Q_primpart [#2284] + +diff --git a/src/basemath/base2.c b/src/basemath/base2.c +index b2b63ada5..531f5c558 100644 +--- a/src/basemath/base2.c ++++ b/src/basemath/base2.c +@@ -3582,7 +3582,7 @@ rnfdisc_factored(GEN nf, GEN pol, GEN *pd) + + nf = checknf(nf); + pol = rnfdisc_get_T(nf, pol, &lim); +- disc = nf_to_scalar_or_basis(nf, nfX_disc(nf, Q_primpart(pol))); ++ disc = nf_to_scalar_or_basis(nf, nfX_disc(nf, pol)); + pol = nfX_to_monic(nf, pol, NULL); + fa = idealfactor_partial(nf, disc, lim); + P = gel(fa,1); l = lg(P); +diff --git a/src/test/32/rnf b/src/test/32/rnf +index 6bd4585..d24e1ce 100644 (file) +--- a/src/test/32/rnf ++++ b/src/test/32/rnf +@@ -832,9 +832,9 @@ error("inconsistent dimensions in idealtwoelt.") + 0 + 0 + 1 +-[[7361, 3786, 318, 5823; 0, 1, 0, 0; 0, 0, 1, 0; 0, 0, 0, 1], [-3, 6, -2, 0] +-~] +-[2, -1] ++[[433, 322, 318, 1318/17; 0, 1, 0, 12/17; 0, 0, 1, 5/17; 0, 0, 0, 1/17], [25 ++/17, -12/17, 12/17, 16/17]~] ++[1, -1] + *** at top-level: rnfdedekind(nf,P,pr2,1) + *** ^----------------------- + *** rnfdedekind: sorry, Dedekind in the difficult case is not yet implemented. diff --git a/build/pkgs/pari/patches/prot_none_cygwin.patch b/build/pkgs/pari/patches/prot_none_cygwin.patch index 5cb7438b145..16340dd492b 100644 --- a/build/pkgs/pari/patches/prot_none_cygwin.patch +++ b/build/pkgs/pari/patches/prot_none_cygwin.patch @@ -3,10 +3,10 @@ Fix pari_mainstack_mreset() on Cygwin Rejected upstream because Cygwin is considered a dead platform diff --git a/src/language/init.c b/src/language/init.c -index 2d13684..6ea2888 100644 +index 8473a3b..d9993cc 100644 --- a/src/language/init.c +++ b/src/language/init.c -@@ -653,8 +653,8 @@ pari_mainstack_mfree(void *s, size_t size) +@@ -884,8 +884,8 @@ pari_mainstack_mfree(void *s, size_t size) /* Completely discard the memory mapped between the addresses "from" * and "to" (which must be page-aligned). * @@ -17,7 +17,7 @@ index 2d13684..6ea2888 100644 * still keep the mapping such that we can change the flags to * PROT_READ|PROT_WRITE later. * -@@ -662,7 +662,12 @@ pari_mainstack_mfree(void *s, size_t size) +@@ -893,7 +893,12 @@ pari_mainstack_mfree(void *s, size_t size) * calling mprotect(..., PROT_NONE) because the latter will keep the * memory committed (this is in particular relevant on Linux with * vm.overcommit = 2). This remains true even when calling @@ -31,15 +31,17 @@ index 2d13684..6ea2888 100644 static void pari_mainstack_mreset(pari_sp from, pari_sp to) { -@@ -671,9 +676,13 @@ pari_mainstack_mreset(pari_sp from, pari_sp to) +@@ -902,11 +907,15 @@ pari_mainstack_mreset(pari_sp from, pari_sp to) if (!s) return; addr = (void*)from; +#ifdef __CYGWIN__ + if (mprotect(addr, s, PROT_NONE)) pari_err(e_MEM); +#else + BLOCK_SIGINT_START; res = mmap(addr, s, PROT_NONE, MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0); + BLOCK_SIGINT_END; if (res != addr) pari_err(e_MEM); +#endif } diff --git a/build/pkgs/pari/spkg-configure.m4 b/build/pkgs/pari/spkg-configure.m4 index 9a1b0c7db8c..02d472c0c8f 100644 --- a/build/pkgs/pari/spkg-configure.m4 +++ b/build/pkgs/pari/spkg-configure.m4 @@ -1,6 +1,6 @@ SAGE_SPKG_CONFIGURE([pari], [ dnl See gp_version below on how the version is computed from MAJV.MINV.PATCHV - m4_pushdef([SAGE_PARI_MINVER],["133889"]) + m4_pushdef([SAGE_PARI_MINVER],["134401"]) SAGE_SPKG_DEPCHECK([gmp mpir readline], [ AC_PATH_PROG([GP], [gp]) if test x$GP = x; then dnl GP test @@ -66,33 +66,11 @@ SAGE_SPKG_CONFIGURE([pari], [ AC_MSG_NOTICE([Otherwise Sage will build its own pari/GP.]) sage_spkg_install_pari=yes fi - AC_MSG_CHECKING([whether hyperellcharpoly bug is fixed]) - bug_check=`echo "hyperellcharpoly(Mod(1,3)*(x^10 + x^9 + x^8 + x))" | $GP -qf 2>> config.log` - expected="x^8 + 2*x^7 + 6*x^6 + 9*x^5 + 18*x^4 + 27*x^3 + 54*x^2 + 54*x + 81" + AC_MSG_CHECKING([whether rnfdisc bug of pari 2.13.1 is fixed]) + bug_check=`echo "K = nfinit(y^4-10*y^2+1); disc = rnfdisc(K,x^2-(y^3/2+y^2-5*y/2+1)); idealnorm(K,disc)" | $GP -qf 2>> config.log` + expected="2304" if test x"$bug_check" = x"$expected"; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no; cannot use system pari/GP with known bug]) - AC_MSG_NOTICE([Upgrade your system package and reconfigure.]) - AC_MSG_NOTICE([Otherwise Sage will build its own pari/GP.]) - sage_spkg_install_pari=yes - fi - AC_MSG_CHECKING([whether bnfisunit bug of pari 2.11.3 is fixed]) - bug_check=`echo "bnf = bnfinit(y^4-y-1); bnfisunit(bnf,-y^3+2*y^2-1)" | $GP -qf 2>> config.log` - expected="[[0, 2, Mod(0, 2)]]~" - if test x"$bug_check" = x"$expected"; then - AC_MSG_RESULT([yes]) - else - AC_MSG_RESULT([no; cannot use system pari/GP with known bug]) - AC_MSG_NOTICE([Upgrade your system package and reconfigure.]) - AC_MSG_NOTICE([Otherwise Sage will build its own pari/GP.]) - sage_spkg_install_pari=yes - fi - AC_MSG_CHECKING([whether qfisom bug of pari 2.11.2 is fixed]) - bug_check=`echo "qfisom([[16,6;6,10]],[[4,3;3,10]])" | $GP -qf 2>> config.log` - expected="0" - if test x"$bug_check" = x"$expected"; then - AC_MSG_RESULT([yes]) + AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no; cannot use system pari/GP with known bug]) AC_MSG_NOTICE([Upgrade your system package and reconfigure.]) @@ -119,7 +97,8 @@ SAGE_SPKG_CONFIGURE([pari], [ [return vers!=$gp_version;]])], [AC_MSG_RESULT([libpari's and GP's versions match. Good])], [AC_MSG_RESULT([libpari's version does not match GP's version. Not good]) - sage_spkg_install_pari=yes]) + sage_spkg_install_pari=yes], + [AC_MSG_RESULT([cross compiling. Assume they match])]) AC_MSG_CHECKING([is GP's version good enough? ]) AX_COMPARE_VERSION([$gp_version], [ge], [$SAGE_PARI_MINVER], [ AC_MSG_RESULT([yes]) @@ -142,13 +121,15 @@ SAGE_SPKG_CONFIGURE([pari], [ [[return strcmp(PARI_MT_ENGINE, "pthread") != 0]])], [AC_MSG_RESULT([yes. Good])], [AC_MSG_RESULT([no. Not good]) - sage_spkg_install_pari=yes]) + sage_spkg_install_pari=yes], + [AC_MSG_RESULT([cross compiling. Assume yes])]) ], [AC_MSG_RESULT([libpari's datadir does not match GP's datadir. Not good]) - sage_spkg_install_pari=yes]) + sage_spkg_install_pari=yes], + [AC_MSG_RESULT([cross compiling. Assume yes])]) ], [ AC_MSG_RESULT([no]) - sage_spkg_install_pari=yes]) + sage_spkg_install_pari=yes], [AC_MSG_RESULT([cross compiling. Assume yes])]) AC_LANG_POP() ], [sage_spkg_install_pari=yes]) fi dnl end main PARI test diff --git a/build/pkgs/pari/spkg-install.in b/build/pkgs/pari/spkg-install.in index 1b0fec6bfd7..eb8a8483cdc 100644 --- a/build/pkgs/pari/spkg-install.in +++ b/build/pkgs/pari/spkg-install.in @@ -135,4 +135,4 @@ sdh_make $PARI_MAKEFLAGS gp # install non-parallel (-j1) because of race conditions -sdh_make_install -j1 install-lib-sta +sdh_make_install -j1 diff --git a/build/pkgs/perl_cpan_polymake_prereq/distros/cpan.txt b/build/pkgs/perl_cpan_polymake_prereq/distros/cpan.txt index 13e1dc9fc9e..fbd93fc89a2 100644 --- a/build/pkgs/perl_cpan_polymake_prereq/distros/cpan.txt +++ b/build/pkgs/perl_cpan_polymake_prereq/distros/cpan.txt @@ -1 +1,6 @@ -XML::Writer XML::LibXML XML::LibXSLT File::Slurp JSON SVG MongoDB +XML::Writer +XML::LibXML +XML::LibXSLT +File::Slurp +JSON +SVG diff --git a/build/pkgs/perl_cpan_polymake_prereq/distros/debian.txt b/build/pkgs/perl_cpan_polymake_prereq/distros/debian.txt index 34c5682b266..216a40ab2c9 100644 --- a/build/pkgs/perl_cpan_polymake_prereq/distros/debian.txt +++ b/build/pkgs/perl_cpan_polymake_prereq/distros/debian.txt @@ -7,4 +7,3 @@ libjson-perl libsvg-perl libterm-readkey-perl libterm-readline-gnu-perl -libmongodb-perl diff --git a/build/pkgs/perl_cpan_polymake_prereq/distros/fedora.txt b/build/pkgs/perl_cpan_polymake_prereq/distros/fedora.txt index 1ea99958152..89a8a7622bf 100644 --- a/build/pkgs/perl_cpan_polymake_prereq/distros/fedora.txt +++ b/build/pkgs/perl_cpan_polymake_prereq/distros/fedora.txt @@ -1,2 +1,7 @@ -perl-ExtUtils-Embed perl-File-Slurp perl-JSON perl-MongoDB perl-Term-ReadLine-Gnu -perl-XML-Writer perl-XML-LibXML perl-XML-LibXSLT +perl-ExtUtils-Embed +perl-File-Slurp +perl-JSON +perl-Term-ReadLine-Gnu +perl-XML-Writer +perl-XML-LibXML +perl-XML-LibXSLT diff --git a/build/pkgs/perl_cpan_polymake_prereq/distros/freebsd.txt b/build/pkgs/perl_cpan_polymake_prereq/distros/freebsd.txt index ad6913a88d7..9b2b62bccd9 100644 --- a/build/pkgs/perl_cpan_polymake_prereq/distros/freebsd.txt +++ b/build/pkgs/perl_cpan_polymake_prereq/distros/freebsd.txt @@ -1 +1,6 @@ -textproc/p5-XML-Writer textproc/p5-XML-LibXML textproc/p5-XML-LibXSLT devel/p5-File-Slurp converters/p5-JSON textproc/p5-SVG databases/p5-MongoDB +textproc/p5-XML-Writer +textproc/p5-XML-LibXML +textproc/p5-XML-LibXSLT +devel/p5-File-Slurp +converters/p5-JSON +textproc/p5-SVG diff --git a/build/pkgs/perl_cpan_polymake_prereq/distros/gentoo.txt b/build/pkgs/perl_cpan_polymake_prereq/distros/gentoo.txt index ecfce6042c2..67835e72530 100644 --- a/build/pkgs/perl_cpan_polymake_prereq/distros/gentoo.txt +++ b/build/pkgs/perl_cpan_polymake_prereq/distros/gentoo.txt @@ -1 +1,7 @@ -XML-Writer XML-LibXML XML-LibXSLT File-Slurp dev-perl/Term-ReadLine-Gnu JSON SVG dev-perl/MongoDB +XML-Writer +XML-LibXML +XML-LibXSLT +File-Slurp +dev-perl/Term-ReadLine-Gnu +JSON +SVG diff --git a/build/pkgs/perl_mongodb/SPKG.rst b/build/pkgs/perl_mongodb/SPKG.rst new file mode 100644 index 00000000000..89384fad4b6 --- /dev/null +++ b/build/pkgs/perl_mongodb/SPKG.rst @@ -0,0 +1,13 @@ +perl_mongodb: A prerequisite for polymake's PolyDB feature +========================================================== + +Description +----------- + +This script package represents the Perl package MongoDB, which is needed for +the PolyDB feature of polymake. + +License +------- + +Various free software licenses diff --git a/build/pkgs/perl_mongodb/distros/cpan.txt b/build/pkgs/perl_mongodb/distros/cpan.txt new file mode 100644 index 00000000000..df37c12a455 --- /dev/null +++ b/build/pkgs/perl_mongodb/distros/cpan.txt @@ -0,0 +1 @@ +MongoDB diff --git a/build/pkgs/perl_mongodb/distros/debian.txt b/build/pkgs/perl_mongodb/distros/debian.txt new file mode 100644 index 00000000000..1c063ceb440 --- /dev/null +++ b/build/pkgs/perl_mongodb/distros/debian.txt @@ -0,0 +1 @@ +libmongodb-perl diff --git a/build/pkgs/perl_mongodb/distros/fedora.txt b/build/pkgs/perl_mongodb/distros/fedora.txt new file mode 100644 index 00000000000..c6b5b90f9f0 --- /dev/null +++ b/build/pkgs/perl_mongodb/distros/fedora.txt @@ -0,0 +1 @@ +perl-MongoDB diff --git a/build/pkgs/perl_mongodb/distros/freebsd.txt b/build/pkgs/perl_mongodb/distros/freebsd.txt new file mode 100644 index 00000000000..ba89f051ecb --- /dev/null +++ b/build/pkgs/perl_mongodb/distros/freebsd.txt @@ -0,0 +1 @@ +databases/p5-MongoDB diff --git a/build/pkgs/perl_mongodb/distros/gentoo.txt b/build/pkgs/perl_mongodb/distros/gentoo.txt new file mode 100644 index 00000000000..c174107689f --- /dev/null +++ b/build/pkgs/perl_mongodb/distros/gentoo.txt @@ -0,0 +1 @@ +dev-perl/MongoDB diff --git a/build/pkgs/perl_mongodb/spkg-configure.m4 b/build/pkgs/perl_mongodb/spkg-configure.m4 new file mode 100644 index 00000000000..e2b737203a2 --- /dev/null +++ b/build/pkgs/perl_mongodb/spkg-configure.m4 @@ -0,0 +1,9 @@ +SAGE_SPKG_CONFIGURE( + [perl_mongodb], [ + m4_pushdef([MODULES], m4_include(build/pkgs/perl_mongodb/distros/cpan.txt)) + AX_PROG_PERL_MODULES(MODULES, + [], + [sage_spkg_install_perl_mongodb=yes + ] + ) +]) diff --git a/build/pkgs/perl_mongodb/spkg-install b/build/pkgs/perl_mongodb/spkg-install new file mode 100755 index 00000000000..7f087781bb9 --- /dev/null +++ b/build/pkgs/perl_mongodb/spkg-install @@ -0,0 +1,6 @@ +#! /usr/bin/env bash +echo Error: The optional prerequisite perl_mongodb of the package polymake are not installed. +echo Please install CPAN package $(cat distros/cpan.txt) +echo manually, either using the system package recommended by ./configure +echo or directly from CPAN. +exit 1 diff --git a/build/pkgs/perl_mongodb/type b/build/pkgs/perl_mongodb/type new file mode 100644 index 00000000000..134d9bc32d5 --- /dev/null +++ b/build/pkgs/perl_mongodb/type @@ -0,0 +1 @@ +optional diff --git a/build/pkgs/pip/checksums.ini b/build/pkgs/pip/checksums.ini index fabc83e4fa0..858cd7a1023 100644 --- a/build/pkgs/pip/checksums.ini +++ b/build/pkgs/pip/checksums.ini @@ -1,5 +1,5 @@ tarball=pip-VERSION.tar.gz -sha1=85a0cca517b854373dfcf43c3cac4ff726120c7f -md5=7fe80e6b3f94b5350284ed536d5b3a23 -cksum=3136634190 +sha1=b26e4afd5524f5613edf5b1b94dd1b6e9a2e1f87 +md5=a867fd51eacfd5293f5b7e0c2e7867a7 +cksum=2004742197 upstream_url=https://pypi.io/packages/source/p/pip/pip-VERSION.tar.gz diff --git a/build/pkgs/pip/package-version.txt b/build/pkgs/pip/package-version.txt index 2d978e312b5..9fcf356b68f 100644 --- a/build/pkgs/pip/package-version.txt +++ b/build/pkgs/pip/package-version.txt @@ -1 +1 @@ -21.1.1 +21.1.2 diff --git a/build/pkgs/pip/patches/10029.patch b/build/pkgs/pip/patches/10029.patch new file mode 100644 index 00000000000..cfd93403298 --- /dev/null +++ b/build/pkgs/pip/patches/10029.patch @@ -0,0 +1,71 @@ +From b8f1fcf863081fde0b9d558759c0e3c46ce09a12 Mon Sep 17 00:00:00 2001 +From: Ben Darnell <> +Date: Fri, 28 May 2021 16:01:41 +0000 +Subject: [PATCH] Avoid importing a non-vendored version of Tornado + +Code depending on this conditional import could break if an old +version of Tornado is present in the environment, rendering pip +unusable. +--- + news/10020.bugfix.rst | 1 + + src/pip/_vendor/tenacity/__init__.py | 10 ++++++---- + tools/vendoring/patches/tenacity.patch | 21 +++++++++++++++++++++ + 3 files changed, 28 insertions(+), 4 deletions(-) + create mode 100644 news/10020.bugfix.rst + create mode 100644 tools/vendoring/patches/tenacity.patch + +diff --git a/news/10020.bugfix.rst b/news/10020.bugfix.rst +new file mode 100644 +index 00000000000..9425626fb07 +--- /dev/null ++++ b/news/10020.bugfix.rst +@@ -0,0 +1 @@ ++Remove unused optional ``tornado`` import in vendored ``tenacity`` to prevent old versions of Tornado from breaking pip. +diff --git a/src/pip/_vendor/tenacity/__init__.py b/src/pip/_vendor/tenacity/__init__.py +index 5f8cb505896..42e9d8940b1 100644 +--- a/src/pip/_vendor/tenacity/__init__.py ++++ b/src/pip/_vendor/tenacity/__init__.py +@@ -22,10 +22,12 @@ + except ImportError: + iscoroutinefunction = None + +-try: +- import tornado +-except ImportError: +- tornado = None ++# Replace a conditional import with a hard-coded None so that pip does ++# not attempt to use tornado even if it is present in the environment. ++# If tornado is non-None, tenacity will attempt to execute some code ++# that is sensitive to the version of tornado, which could break pip ++# if an old version is found. ++tornado = None + + import sys + import threading +diff --git a/tools/vendoring/patches/tenacity.patch b/tools/vendoring/patches/tenacity.patch +new file mode 100644 +index 00000000000..006588b3653 +--- /dev/null ++++ b/tools/vendoring/patches/tenacity.patch +@@ -0,0 +1,21 @@ ++diff --git a/src/pip/_vendor/tenacity/__init__.py b/src/pip/_vendor/tenacity/__init__.py ++index 5f8cb5058..42e9d8940 100644 ++--- a/src/pip/_vendor/tenacity/__init__.py +++++ b/src/pip/_vendor/tenacity/__init__.py ++@@ -22,10 +22,12 @@ try: ++ except ImportError: ++ iscoroutinefunction = None ++ ++-try: ++- import tornado ++-except ImportError: ++- tornado = None +++# Replace a conditional import with a hard-coded None so that pip does +++# not attempt to use tornado even if it is present in the environment. +++# If tornado is non-None, tenacity will attempt to execute some code +++# that is sensitive to the version of tornado, which could break pip +++# if an old version is found. +++tornado = None ++ ++ import sys ++ import threading diff --git a/build/pkgs/pluggy/SPKG.rst b/build/pkgs/pluggy/SPKG.rst new file mode 100644 index 00000000000..b15967007ed --- /dev/null +++ b/build/pkgs/pluggy/SPKG.rst @@ -0,0 +1,18 @@ +pluggy: plugin and hook calling mechanisms for python +===================================================== + +Description +----------- + +plugin and hook calling mechanisms for python + +License +------- + +MIT license + +Upstream Contact +---------------- + +https://pypi.org/project/pluggy/ + diff --git a/build/pkgs/pluggy/checksums.ini b/build/pkgs/pluggy/checksums.ini new file mode 100644 index 00000000000..9a6e585fcc9 --- /dev/null +++ b/build/pkgs/pluggy/checksums.ini @@ -0,0 +1,5 @@ +tarball=pluggy-VERSION.tar.gz +sha1=828b2c10996d902b8c47f2fded0e101c636b9ff9 +md5=7f610e28b8b34487336b585a3dfb803d +cksum=3074963981 +upstream_url=https://pypi.io/packages/source/p/pluggy/pluggy-VERSION.tar.gz diff --git a/build/pkgs/pluggy/dependencies b/build/pkgs/pluggy/dependencies new file mode 100644 index 00000000000..0738c2d7777 --- /dev/null +++ b/build/pkgs/pluggy/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/pluggy/install-requires.txt b/build/pkgs/pluggy/install-requires.txt new file mode 100644 index 00000000000..11bdb5c1f5f --- /dev/null +++ b/build/pkgs/pluggy/install-requires.txt @@ -0,0 +1 @@ +pluggy diff --git a/build/pkgs/pluggy/package-version.txt b/build/pkgs/pluggy/package-version.txt new file mode 100644 index 00000000000..c317a91891f --- /dev/null +++ b/build/pkgs/pluggy/package-version.txt @@ -0,0 +1 @@ +0.13.1 diff --git a/build/pkgs/pluggy/spkg-install.in b/build/pkgs/pluggy/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/pluggy/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/pluggy/type b/build/pkgs/pluggy/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/pluggy/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/polymake/SPKG.rst b/build/pkgs/polymake/SPKG.rst index c8ab646b67a..20bb07d0ec6 100644 --- a/build/pkgs/polymake/SPKG.rst +++ b/build/pkgs/polymake/SPKG.rst @@ -28,11 +28,11 @@ Polymake needs a working installation of Perl, including its shared library and some modules (XML::Writer XML::LibXML XML::LibXSLT Term::ReadLine::Gnu JSON SVG). The Polymake interface in Sage additionally needs File::Slurp. For full functionality including -polymake's polyDB, also the Perl module MongoDB is required. +polymake's polyDB, also the Perl module MongoDB is needed. These are not provided by a Sage package. The script package -perl_cpan_polymake_prereq will signal an error at build time if these -prerequisites are not met. +perl_cpan_polymake_prereq will signal an error at build time if the +required prerequisites are not met. The configure script will inform you about the equivalent system packages that you should install. Otherwise, you can use CPAN (see diff --git a/build/pkgs/py/SPKG.rst b/build/pkgs/py/SPKG.rst new file mode 100644 index 00000000000..bf0409acda7 --- /dev/null +++ b/build/pkgs/py/SPKG.rst @@ -0,0 +1,18 @@ +py: library with cross-python path, ini-parsing, io, code, log facilities +========================================================================= + +Description +----------- + +library with cross-python path, ini-parsing, io, code, log facilities + +License +------- + +MIT license + +Upstream Contact +---------------- + +https://pypi.org/project/py/ + diff --git a/build/pkgs/py/checksums.ini b/build/pkgs/py/checksums.ini new file mode 100644 index 00000000000..ea29f7298f5 --- /dev/null +++ b/build/pkgs/py/checksums.ini @@ -0,0 +1,5 @@ +tarball=py-VERSION.tar.gz +sha1=690e4e3dcaeafe02ad4af36233148e7e10032d1a +md5=5f108bfe00d5468cbdb8071051f86a55 +cksum=220330409 +upstream_url=https://pypi.io/packages/source/p/py/py-VERSION.tar.gz diff --git a/build/pkgs/py/dependencies b/build/pkgs/py/dependencies new file mode 100644 index 00000000000..14a312e5dee --- /dev/null +++ b/build/pkgs/py/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) | $(PYTHON_TOOLCHAIN) setuptools_scm + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/py/install-requires.txt b/build/pkgs/py/install-requires.txt new file mode 100644 index 00000000000..edfce786a4d --- /dev/null +++ b/build/pkgs/py/install-requires.txt @@ -0,0 +1 @@ +py diff --git a/build/pkgs/py/package-version.txt b/build/pkgs/py/package-version.txt new file mode 100644 index 00000000000..81c871de46b --- /dev/null +++ b/build/pkgs/py/package-version.txt @@ -0,0 +1 @@ +1.10.0 diff --git a/build/pkgs/py/spkg-install.in b/build/pkgs/py/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/py/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/py/type b/build/pkgs/py/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/py/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/pynac/package-version.txt b/build/pkgs/pynac/package-version.txt index 4373c7f55cb..94a2c2bf655 100644 --- a/build/pkgs/pynac/package-version.txt +++ b/build/pkgs/pynac/package-version.txt @@ -1 +1 @@ -0.7.27.p7 +0.7.27.p8 diff --git a/build/pkgs/pynac/patches/integer_content.patch b/build/pkgs/pynac/patches/integer_content.patch new file mode 100644 index 00000000000..f93be4b6d45 --- /dev/null +++ b/build/pkgs/pynac/patches/integer_content.patch @@ -0,0 +1,44 @@ +diff --git a/ginac/normal.cpp b/ginac/normal.cpp +index 461bb44..2a52c9e 100644 +--- a/ginac/normal.cpp ++++ b/ginac/normal.cpp +@@ -87,7 +87,12 @@ numeric basic::integer_content() const + + numeric numeric::integer_content() const + { +- return abs(); ++ if (is_real()) { ++ return abs(); ++ } ++ else { ++ return real().numer().gcd(imag().numer()) / real().denom().lcm(imag().denom()); ++ } + } + + numeric add::integer_content() const +@@ -98,12 +103,12 @@ numeric add::integer_content() const + while (it != itend) { + GINAC_ASSERT(!is_exactly_a(it->rest)); + GINAC_ASSERT(is_exactly_a(it->coeff)); +- c = gcd(ex_to(it->coeff).numer(), c); +- l = lcm(ex_to(it->coeff).denom(), l); ++ c = gcd(ex_to(it->coeff).integer_content().numer(), c); ++ l = lcm(ex_to(it->coeff).integer_content().denom(), l); + it++; + } +- c = gcd(overall_coeff.numer(), c); +- l = lcm(overall_coeff.denom(), l); ++ c = gcd(overall_coeff.integer_content().numer(), c); ++ l = lcm(overall_coeff.integer_content().denom(), l); + return (c/l).abs(); + } + +@@ -117,7 +122,7 @@ numeric mul::integer_content() const + ++it; + } + #endif // def DO_GINAC_ASSERT +- return overall_coeff.abs(); ++ return overall_coeff.integer_content(); + } + + diff --git a/build/pkgs/pynormaliz/checksums.ini b/build/pkgs/pynormaliz/checksums.ini index f777a64c2d3..a7e0febfe80 100644 --- a/build/pkgs/pynormaliz/checksums.ini +++ b/build/pkgs/pynormaliz/checksums.ini @@ -1,5 +1,5 @@ tarball=PyNormaliz-VERSION.tar.gz -sha1=c01c7a734deeb09e1dd236fb53575b152b99657b -md5=6ff9ccc61592190fdb88fe08a50e062c -cksum=3111232777 +sha1=f24f5c4a1b9b7a084ad1f2a0b95374d1a83e31b2 +md5=51e67733702d6cea3cd81144888d9dc7 +cksum=3201946747 upstream_url=https://pypi.io/packages/source/p/pynormaliz/PyNormaliz-VERSION.tar.gz diff --git a/build/pkgs/pynormaliz/install-requires.txt b/build/pkgs/pynormaliz/install-requires.txt index b1e222ae76c..ecc1ec0712b 100644 --- a/build/pkgs/pynormaliz/install-requires.txt +++ b/build/pkgs/pynormaliz/install-requires.txt @@ -1 +1 @@ -pynormaliz ==2.12 +pynormaliz ==2.14 diff --git a/build/pkgs/pynormaliz/package-version.txt b/build/pkgs/pynormaliz/package-version.txt index ae656d47364..123a39a8e91 100644 --- a/build/pkgs/pynormaliz/package-version.txt +++ b/build/pkgs/pynormaliz/package-version.txt @@ -1 +1 @@ -2.13 +2.14 diff --git a/build/pkgs/python3/checksums.ini b/build/pkgs/python3/checksums.ini index 825afb53913..f012638d349 100644 --- a/build/pkgs/python3/checksums.ini +++ b/build/pkgs/python3/checksums.ini @@ -1,5 +1,5 @@ tarball=Python-VERSION.tar.xz -sha1=110ca5bca7989f9558a54ee6762e6774a4b9644a -md5=f0dc9000312abeb16de4eccce9a870ab -cksum=1344509533 +sha1=edc80e5e33fc3d3fae53e6b95ae4ca9277809b9b +md5=71f7ada6bec9cdbf4538adc326120cfd +cksum=1916830623 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 2009c7dfad9..11aaa06863c 100644 --- a/build/pkgs/python3/package-version.txt +++ b/build/pkgs/python3/package-version.txt @@ -1 +1 @@ -3.9.2 +3.9.5 diff --git a/build/pkgs/python3/spkg-build.in b/build/pkgs/python3/spkg-build.in index 8c7aee8ae3f..bcdc5f6898d 100644 --- a/build/pkgs/python3/spkg-build.in +++ b/build/pkgs/python3/spkg-build.in @@ -72,15 +72,11 @@ rm -f "$SAGE_LOCAL/lib/python" rm -f "$SAGE_LOCAL"/lib/lib"$PKG_BASE"* -if [ "$PKG_BASE" = "python2" ]; then - PYTHON_CONFIGURE="$PYTHON_CONFIGURE --enable-unicode=ucs4" -else # Note: --without-ensurepip ensures that setuptools+pip are *not* installed # automatically when installing python3. They will be installed instead by # the separate setuptools and pip packages; see # https://trac.sagemath.org/ticket/23398 PYTHON_CONFIGURE="$PYTHON_CONFIGURE --without-ensurepip" -fi sdh_configure --enable-shared $PYTHON_CONFIGURE @@ -110,7 +106,8 @@ fi echo "Testing importing of various modules..." import_errors=false -test_modules="ctypes math hashlib crypt readline socket zlib sqlite3" +# Trac #31160: We no longer check for readline here. +test_modules="ctypes math hashlib crypt socket zlib sqlite3" if [ "$UNAME" = "Darwin" ]; then test_modules="$test_modules _scproxy" fi diff --git a/build/pkgs/python3/spkg-configure.m4 b/build/pkgs/python3/spkg-configure.m4 index a76f27e3cb9..1a835b37a0a 100644 --- a/build/pkgs/python3/spkg-configure.m4 +++ b/build/pkgs/python3/spkg-configure.m4 @@ -22,7 +22,8 @@ SAGE_SPKG_CONFIGURE([python3], [ SAGE_SPKG_DEPCHECK([bzip2 xz libffi], [ dnl Check if we can do venv with a system python3 dnl instead of building our own copy. - check_modules="sqlite3, ctypes, math, hashlib, crypt, readline, socket, zlib, distutils.core" + dnl Trac #31160: We no longer check for readline here. + check_modules="sqlite3, ctypes, math, hashlib, crypt, socket, zlib, distutils.core" AC_CACHE_CHECK([for python3 >= ]MIN_VERSION[, < ]LT_VERSION[ with modules $check_modules], [ac_cv_path_PYTHON3], [ AS_IF([test x"$ac_path_PYTHON3" != x], [dnl checking explicitly specified $with_python AC_MSG_RESULT([]) diff --git a/build/pkgs/sage_conf/src/sage_conf.py.in b/build/pkgs/sage_conf/src/sage_conf.py.in index e0075b1677d..d156a274250 100644 --- a/build/pkgs/sage_conf/src/sage_conf.py.in +++ b/build/pkgs/sage_conf/src/sage_conf.py.in @@ -2,16 +2,28 @@ VERSION = "@PACKAGE_VERSION@" +# The following must not be used during build to determine source or installation +# location of sagelib. See comments in SAGE_ROOT/src/Makefile.in +# These variables come first so that other substituted variable values can refer +# to it. +SAGE_LOCAL = "@prefix@" +SAGE_ROOT = "@SAGE_ROOT@" + MAXIMA = "@prefix@/bin/maxima" +# Delete this line if your ECL can load maxima without further prodding. +MAXIMA_FAS = "@SAGE_MAXIMA_FAS@".replace('${prefix}', SAGE_LOCAL) + +# Delete this line if your ECL can load Kenzo without further prodding. +KENZO_FAS = "@SAGE_KENZO_FAS@".replace('${prefix}', SAGE_LOCAL) + ARB_LIBRARY = "@SAGE_ARB_LIBRARY@" NTL_INCDIR = "@NTL_INCDIR@" NTL_LIBDIR = "@NTL_LIBDIR@" # Path to the ecl-config script -# TODO: At the moment this is hard-coded, needs to be set during the configure phase if we want to support system-installed ecl. -ECL_CONFIG = "@prefix@/bin/ecl-config" +ECL_CONFIG = "@SAGE_ECL_CONFIG@".replace('${prefix}', SAGE_LOCAL) SAGE_NAUTY_BINS_PREFIX = "@SAGE_NAUTY_BINS_PREFIX@" @@ -20,19 +32,18 @@ SAGE_NAUTY_BINS_PREFIX = "@SAGE_NAUTY_BINS_PREFIX@" # always provides cblas.pc, if necessary by creating a facade pc file for a system BLAS. CBLAS_PC_MODULES = "cblas" +# for sage_setup.setenv +SAGE_ARCHFLAGS = "@SAGE_ARCHFLAGS@" +SAGE_PKG_CONFIG_PATH = "@SAGE_PKG_CONFIG_PATH@".replace('$SAGE_LOCAL', SAGE_LOCAL) + # Used in sage.repl.ipython_kernel.install -MATHJAX_DIR = "@prefix@/share/mathjax" -THREEJS_DIR = "@prefix@/share/threejs" +MATHJAX_DIR = SAGE_LOCAL + "/share/mathjax" +THREEJS_DIR = SAGE_LOCAL + "/share/threejs-sage" # OpenMP flags, if available. OPENMP_CFLAGS = "@OPENMP_CFLAGS@" OPENMP_CXXFLAGS = "@OPENMP_CXXFLAGS@" -# The following must not be used during build to determine source or installation -# location of sagelib. See comments in SAGE_ROOT/src/Makefile.in -SAGE_LOCAL = "@prefix@" -SAGE_ROOT = "@SAGE_ROOT@" - # Entry point 'sage-config'. It does not depend on any packages. def _main(): diff --git a/build/pkgs/sagelib/dependencies b/build/pkgs/sagelib/dependencies index 770025fb5b2..62aa32fca69 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 mpc mpfi mpfr $(MP_LIBRARY) ntl numpy pari pip pkgconfig planarity ppl pplpy pycygwin pynac $(PYTHON) ratpoints readline rw sage_conf singular symmetrica zn_poly $(PCFILES) +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 pycygwin pynac $(PYTHON) ratpoints readline rw sage_conf singular symmetrica zn_poly $(PCFILES) ---------- 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 9f62018ee76..0c2ffe14533 100644 --- a/build/pkgs/sagelib/package-version.txt +++ b/build/pkgs/sagelib/package-version.txt @@ -1 +1 @@ -9.4.beta0 +9.4.beta4 diff --git a/build/pkgs/sagelib/src/setup.py b/build/pkgs/sagelib/src/setup.py index f4550f05f01..2abfa3318ae 100755 --- a/build/pkgs/sagelib/src/setup.py +++ b/build/pkgs/sagelib/src/setup.py @@ -31,6 +31,9 @@ from sage_setup.excepthook import excepthook sys.excepthook = excepthook +from sage_setup.setenv import setenv +setenv() + ######################################################### ### Configuration ######################################################### diff --git a/build/pkgs/scipy/checksums.ini b/build/pkgs/scipy/checksums.ini index 54a5f9f216e..55b9191bcdd 100644 --- a/build/pkgs/scipy/checksums.ini +++ b/build/pkgs/scipy/checksums.ini @@ -1,5 +1,5 @@ tarball=scipy-VERSION.tar.gz -sha1=ffbc97517d08d8a5b290c7a5dd6cda0c730ed531 -md5=293401ee7ac354a2f2313373b497f40e -cksum=3851762650 +sha1=7de2a7d7bfce73111c1d8aa3248bec089127e48c +md5=05b4ca400aa1157290abff69016f1cab +cksum=484570496 upstream_url=https://pypi.io/packages/source/s/scipy/scipy-VERSION.tar.gz diff --git a/build/pkgs/scipy/install-requires.txt b/build/pkgs/scipy/install-requires.txt index 2e7f4c30acc..375bf360da9 100644 --- a/build/pkgs/scipy/install-requires.txt +++ b/build/pkgs/scipy/install-requires.txt @@ -1 +1 @@ -scipy >=1.5, <1.6 +scipy >=1.5, <1.8 diff --git a/build/pkgs/scipy/package-version.txt b/build/pkgs/scipy/package-version.txt index 94fe62c2740..266146b87cb 100644 --- a/build/pkgs/scipy/package-version.txt +++ b/build/pkgs/scipy/package-version.txt @@ -1 +1 @@ -1.5.4 +1.6.3 diff --git a/build/pkgs/setuptools/spkg-install.in b/build/pkgs/setuptools/spkg-install.in index efc63964f5c..09a8b8f65ce 100644 --- a/build/pkgs/setuptools/spkg-install.in +++ b/build/pkgs/setuptools/spkg-install.in @@ -1,19 +1,5 @@ -# distribute doesn't allow itself to be replaced by setuptools -# so we manually have to delete it -# (pip actually can uninstall setuptools but we may not have pip -# install yet) -rm -rf "$SAGE_LOCAL"/lib/python*/site-packages/setuptools* -rm -rf "$SAGE_LOCAL"/lib/python*/site-packages/distribute* - -export PYTHON_EGG_CACHE="$DOT_SAGE/.python-eggs" - cd src - -versions=3 - -# Prevent setuptools from installing itself with easy_install -for vers in $versions; do - python${vers} setup.py --no-user-cfg install \ +# Use --single-version-externally-managed to prevent setuptools from installing itself with easy_install +python3 setup.py --no-user-cfg install \ --single-version-externally-managed --root="$SAGE_DESTDIR" || \ sdh_die "Error building / installing setuptools for Python ${vers}" -done diff --git a/build/pkgs/singular/SPKG.rst b/build/pkgs/singular/SPKG.rst index b754451aac7..31c09493083 100644 --- a/build/pkgs/singular/SPKG.rst +++ b/build/pkgs/singular/SPKG.rst @@ -35,9 +35,6 @@ Dependencies Special Update/Build Instructions --------------------------------- -The current upstream tarball is made from the branch at -https://github.com/mkoeppe/Singular/tree/Release-4-2-0-p1%2Bsage - Other notes: - If the environment variable SAGE_DEBUG is set to "yes", then diff --git a/build/pkgs/singular/checksums.ini b/build/pkgs/singular/checksums.ini index 01b340193bf..549f039ff70 100644 --- a/build/pkgs/singular/checksums.ini +++ b/build/pkgs/singular/checksums.ini @@ -1,5 +1,5 @@ tarball=singular-VERSION.tar.gz -sha1=dc0f618a669fb8c5e56166d82edc494a4e5504be -md5=2354d9456c9fad0353b3157aff82796f -cksum=2872654555 -upstream_url=https://github.com/mkoeppe/Singular/releases/download/singular-VERSION/singular-VERSION.tar.gz +sha1=3b3287b54ba42b884a2be12c0f1b9b5d812636bd +md5=39d9d121ab8a6d2571a4f34e4d66b235 +cksum=987736501 +upstream_url=ftp://jim.mathematik.uni-kl.de/pub/Math/Singular/SOURCES/4-2-0/singular-VERSION.tar.gz diff --git a/build/pkgs/singular/package-version.txt b/build/pkgs/singular/package-version.txt index 59d3d633161..8a498833f9c 100644 --- a/build/pkgs/singular/package-version.txt +++ b/build/pkgs/singular/package-version.txt @@ -1 +1 @@ -4.2.0p1+2021-04-06+sage +4.2.0p3 diff --git a/build/pkgs/singular/spkg-install.in b/build/pkgs/singular/spkg-install.in index baec6f12db6..172bade36bd 100644 --- a/build/pkgs/singular/spkg-install.in +++ b/build/pkgs/singular/spkg-install.in @@ -86,6 +86,7 @@ config() --disable-python_module \ --disable-python-module \ --disable-static \ + --with-libparse \ $SINGULAR_CONFIGURE } @@ -99,7 +100,7 @@ build_singular() sdh_make -j1 sdh_make_install - # Singular tarballs made using "make dist" do not contain built documentation. + # Singular tarballs made using "make dist" (without --enable-doc-build) do not contain built documentation. if [ ! -e doc/doc.tbz2 ]; then (cd doc && make singular.hlp && sdh_install singular.hlp "$SAGE_SHARE/info/") || sdh_die "Building documentation failed" fi diff --git a/build/pkgs/sphinx/checksums.ini b/build/pkgs/sphinx/checksums.ini index ce072d1bb1e..b42e84540d3 100644 --- a/build/pkgs/sphinx/checksums.ini +++ b/build/pkgs/sphinx/checksums.ini @@ -1,5 +1,5 @@ tarball=Sphinx-VERSION.tar.gz -sha1=9043e0f324d62a5c47a0773f562d423e66163f63 -md5=6e01aa4ab0f1dcbc8d65cc25c736e3ea -cksum=4106849897 +sha1=ac493c525509f639a4eeb784a85648b3066f1f40 +md5=4714d870be4e599280e2c06561b44430 +cksum=2654318749 upstream_url=https://pypi.io/packages/source/s/sphinx/Sphinx-VERSION.tar.gz diff --git a/build/pkgs/sphinx/install-requires.txt b/build/pkgs/sphinx/install-requires.txt index d7680fc6a37..2d146f2fe48 100644 --- a/build/pkgs/sphinx/install-requires.txt +++ b/build/pkgs/sphinx/install-requires.txt @@ -1,2 +1 @@ -# gentoo uses 3.2.1 -sphinx >=3, <3.3 +sphinx >=4, <4.2 diff --git a/build/pkgs/sphinx/package-version.txt b/build/pkgs/sphinx/package-version.txt index 0be0b439969..f727dc16efb 100644 --- a/build/pkgs/sphinx/package-version.txt +++ b/build/pkgs/sphinx/package-version.txt @@ -1 +1 @@ -3.1.2.p0 +4.0.1.p0 diff --git a/build/pkgs/sphinx/patches/environment.patch b/build/pkgs/sphinx/patches/environment.patch index 4fbdac0cfe4..12627ec70f3 100644 --- a/build/pkgs/sphinx/patches/environment.patch +++ b/build/pkgs/sphinx/patches/environment.patch @@ -13,4 +13,4 @@ diff -ru a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py + continue if self.config[item.name] != item.value: self.config_status = CONFIG_CHANGED - break + self.config_status_extra = ' (%r)' % (item.name,) diff --git a/build/pkgs/sqlite/spkg-configure.m4 b/build/pkgs/sqlite/spkg-configure.m4 index c7fd821f593..901e27f19e3 100644 --- a/build/pkgs/sqlite/spkg-configure.m4 +++ b/build/pkgs/sqlite/spkg-configure.m4 @@ -26,7 +26,8 @@ SAGE_SPKG_CONFIGURE([sqlite], [ [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) LIBS="$SQLITE_SAVED_LIBS" - sage_spkg_install_sqlite=yes]) + sage_spkg_install_sqlite=yes], + [AC_MSG_RESULT([cross compiling. assume yes])]) m4_popdef([SAGE_SQLITE3_MIN_VERSION_MAJOR]) m4_popdef([SAGE_SQLITE3_MIN_VERSION_MINOR]) m4_popdef([SAGE_SQLITE3_MIN_VERSION_MICRO]) diff --git a/build/pkgs/symmetrica/dependencies b/build/pkgs/symmetrica/dependencies index 7a7b9cf8a80..0fef19aa992 100644 --- a/build/pkgs/symmetrica/dependencies +++ b/build/pkgs/symmetrica/dependencies @@ -1,4 +1,4 @@ -| xz +xz xz is needed for unpacking the tarball when sage-bootstrap-python is ancient diff --git a/build/pkgs/symmetrica/spkg-configure.m4 b/build/pkgs/symmetrica/spkg-configure.m4 index 7dc77df703e..0a9b0d5c375 100644 --- a/build/pkgs/symmetrica/spkg-configure.m4 +++ b/build/pkgs/symmetrica/spkg-configure.m4 @@ -21,7 +21,8 @@ dnl check for one of its many functions [ende();]])], [AC_MSG_RESULT([appears to be a well-patched version.])], [AC_MSG_RESULT([buggy version. Sage will build its own.]) - sage_spkg_install_symmetrica=yes]) + sage_spkg_install_symmetrica=yes], + [AC_MSG_RESULT([cross compiling. Assume not buggy.])]) ], [sage_spkg_install_symmetrica=yes]) ], [sage_spkg_install_symmetrica=yes]) AC_LANG_POP(C) diff --git a/build/pkgs/sympy/checksums.ini b/build/pkgs/sympy/checksums.ini index 06a62f358ac..1cde11210f8 100644 --- a/build/pkgs/sympy/checksums.ini +++ b/build/pkgs/sympy/checksums.ini @@ -1,5 +1,5 @@ tarball=sympy-VERSION.tar.gz -sha1=e9a321af141998638cc822505f07107736776c39 -md5=f5973bcbe33fdc86203ca397cc901994 -cksum=1357232010 +sha1=c52dd135f675cee79e46984b8454d9bb6b127edd +md5=37af34367e3f05692e6ddede95eccddb +cksum=4053721036 upstream_url=https://github.com/sympy/sympy/releases/download/sympy-VERSION/sympy-VERSION.tar.gz diff --git a/build/pkgs/sympy/install-requires.txt b/build/pkgs/sympy/install-requires.txt index 9c1c610c7c0..207de33a893 100644 --- a/build/pkgs/sympy/install-requires.txt +++ b/build/pkgs/sympy/install-requires.txt @@ -1 +1 @@ -sympy >=1.6, <1.8 +sympy >=1.6, <2.0 diff --git a/build/pkgs/sympy/package-version.txt b/build/pkgs/sympy/package-version.txt index 943f9cbc4ec..6259340971b 100644 --- a/build/pkgs/sympy/package-version.txt +++ b/build/pkgs/sympy/package-version.txt @@ -1 +1 @@ -1.7.1 +1.8 diff --git a/build/pkgs/threejs/package-version.txt b/build/pkgs/threejs/package-version.txt index 98ffb8e64ea..f149e1131d2 100644 --- a/build/pkgs/threejs/package-version.txt +++ b/build/pkgs/threejs/package-version.txt @@ -1 +1 @@ -r122 +r122.p0 diff --git a/build/pkgs/threejs/spkg-install.in b/build/pkgs/threejs/spkg-install.in index 579161d67d9..0ae18ac7347 100644 --- a/build/pkgs/threejs/spkg-install.in +++ b/build/pkgs/threejs/spkg-install.in @@ -1 +1,2 @@ -sdh_install src/* "${SAGE_SHARE}/threejs" +sdh_install src/version "${SAGE_SHARE}/threejs-sage/" +sdh_install -T src/build "${SAGE_SHARE}/threejs-sage/$(cat src/version)" diff --git a/build/pkgs/tox/SPKG.rst b/build/pkgs/tox/SPKG.rst index 007e28f02a3..442f4e85756 100644 --- a/build/pkgs/tox/SPKG.rst +++ b/build/pkgs/tox/SPKG.rst @@ -1,21 +1,18 @@ -tox: A command line driven CI frontend and development task automation tool -=========================================================================== +tox: tox is a generic virtualenv management and test command line tool +====================================================================== Description ----------- -Command line driven CI frontend and development task automation tool. - -The Sage library uses tox as an entry point for testing and linting. See ``src/tox.ini`` and ``sage --advanced``. - -Sage-the-distribution uses tox for portability testing. See ``SAGE_ROOT/tox.ini``. +tox is a generic virtualenv management and test command line tool License ------- -- MIT License +MIT Upstream Contact ---------------- https://pypi.org/project/tox/ + diff --git a/build/pkgs/tox/checksums.ini b/build/pkgs/tox/checksums.ini new file mode 100644 index 00000000000..c202fbcb281 --- /dev/null +++ b/build/pkgs/tox/checksums.ini @@ -0,0 +1,5 @@ +tarball=tox-VERSION.tar.gz +sha1=e98277cf47d1e4ca9dbaeeaf7e0e301700c9694b +md5=9fe85f2cab666ad764f5a076c4544956 +cksum=2996434561 +upstream_url=https://pypi.io/packages/source/t/tox/tox-VERSION.tar.gz diff --git a/build/pkgs/tox/dependencies b/build/pkgs/tox/dependencies index 7b139dc904c..a8747364ef2 100644 --- a/build/pkgs/tox/dependencies +++ b/build/pkgs/tox/dependencies @@ -1,4 +1,4 @@ -$(PYTHON) packaging six | $(PYTHON_TOOLCHAIN) +$(PYTHON) packaging six filelock pluggy py toml virtualenv importlib_metadata | $(PYTHON_TOOLCHAIN) ---------- All lines of this file are ignored except the first. diff --git a/build/pkgs/tox/install-requires.txt b/build/pkgs/tox/install-requires.txt index eb752fd0762..053148f8486 100644 --- a/build/pkgs/tox/install-requires.txt +++ b/build/pkgs/tox/install-requires.txt @@ -1,2 +1 @@ -# Matches version checked in spkg-configure.m4 -tox >=2.5.0 +tox diff --git a/build/pkgs/tox/package-version.txt b/build/pkgs/tox/package-version.txt new file mode 100644 index 00000000000..9b2f2a16885 --- /dev/null +++ b/build/pkgs/tox/package-version.txt @@ -0,0 +1 @@ +3.23.1 diff --git a/build/pkgs/tox/requirements.txt b/build/pkgs/tox/requirements.txt deleted file mode 100644 index 053148f8486..00000000000 --- a/build/pkgs/tox/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -tox diff --git a/build/pkgs/tox/spkg-install.in b/build/pkgs/tox/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/tox/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/tox/type b/build/pkgs/tox/type index 134d9bc32d5..a6a7b9cd726 100644 --- a/build/pkgs/tox/type +++ b/build/pkgs/tox/type @@ -1 +1 @@ -optional +standard diff --git a/build/pkgs/virtualenv/SPKG.rst b/build/pkgs/virtualenv/SPKG.rst new file mode 100644 index 00000000000..9b869626ce4 --- /dev/null +++ b/build/pkgs/virtualenv/SPKG.rst @@ -0,0 +1,18 @@ +virtualenv: Virtual Python Environment builder +============================================== + +Description +----------- + +Virtual Python Environment builder + +License +------- + +MIT + +Upstream Contact +---------------- + +https://pypi.org/project/virtualenv/ + diff --git a/build/pkgs/virtualenv/checksums.ini b/build/pkgs/virtualenv/checksums.ini new file mode 100644 index 00000000000..177479bc005 --- /dev/null +++ b/build/pkgs/virtualenv/checksums.ini @@ -0,0 +1,5 @@ +tarball=virtualenv-VERSION.tar.gz +sha1=42c609ae86f5b6b3d491e4a865235e71ed35bfd3 +md5=6693920d5de24bd3e6ec8a6749c22b0d +cksum=2813528266 +upstream_url=https://pypi.io/packages/source/v/virtualenv/virtualenv-VERSION.tar.gz diff --git a/build/pkgs/virtualenv/dependencies b/build/pkgs/virtualenv/dependencies new file mode 100644 index 00000000000..fe7890ee1d0 --- /dev/null +++ b/build/pkgs/virtualenv/dependencies @@ -0,0 +1,4 @@ +$(PYTHON) appdirs distlib filelock six importlib_metadata importlib_resources | $(PYTHON_TOOLCHAIN) + +---------- +All lines of this file are ignored except the first. diff --git a/build/pkgs/virtualenv/install-requires.txt b/build/pkgs/virtualenv/install-requires.txt new file mode 100644 index 00000000000..66072c76450 --- /dev/null +++ b/build/pkgs/virtualenv/install-requires.txt @@ -0,0 +1 @@ +virtualenv diff --git a/build/pkgs/virtualenv/package-version.txt b/build/pkgs/virtualenv/package-version.txt new file mode 100644 index 00000000000..f065d76cc07 --- /dev/null +++ b/build/pkgs/virtualenv/package-version.txt @@ -0,0 +1 @@ +20.4.7 diff --git a/build/pkgs/virtualenv/spkg-configure.m4 b/build/pkgs/virtualenv/spkg-configure.m4 new file mode 100644 index 00000000000..9fd26115afb --- /dev/null +++ b/build/pkgs/virtualenv/spkg-configure.m4 @@ -0,0 +1,7 @@ +SAGE_SPKG_CONFIGURE([virtualenv], [ + sage_spkg_install_virtualenv=yes + ], [dnl REQUIRED-CHECK + AC_REQUIRE([SAGE_SPKG_CONFIGURE_TOX]) + dnl virtualenv is only needed when we cannot use system tox. + AS_VAR_SET([SPKG_REQUIRE], [$sage_spkg_install_tox]) + ]) diff --git a/build/pkgs/virtualenv/spkg-install.in b/build/pkgs/virtualenv/spkg-install.in new file mode 100644 index 00000000000..37ac1a53437 --- /dev/null +++ b/build/pkgs/virtualenv/spkg-install.in @@ -0,0 +1,2 @@ +cd src +sdh_pip_install . diff --git a/build/pkgs/virtualenv/type b/build/pkgs/virtualenv/type new file mode 100644 index 00000000000..a6a7b9cd726 --- /dev/null +++ b/build/pkgs/virtualenv/type @@ -0,0 +1 @@ +standard diff --git a/build/pkgs/zlib/spkg-install.in b/build/pkgs/zlib/spkg-install.in index c78a77614b6..6b21192934b 100644 --- a/build/pkgs/zlib/spkg-install.in +++ b/build/pkgs/zlib/spkg-install.in @@ -12,7 +12,10 @@ if [ "$UNAME" = CYGWIN ]; then # We want to install shared objects sed -i 's/SHARED_MODE=0/SHARED_MODE=1/' Makefile else - sdh_configure --shared + # Trac #28890: zlib does not use a standard autoconf-generated configure + # script, so don't use the sdh_configure helper as it may have minor + # incompatibilities + ./configure --shared --prefix="$SAGE_LOCAL" --libdir="$SAGE_LOCAL/lib" || sdh_die "Error configuring $PKG_NAME" fi sdh_make diff --git a/src/.relint.yml b/src/.relint.yml index cfa6592a651..e174f30d6dc 100644 --- a/src/.relint.yml +++ b/src/.relint.yml @@ -7,10 +7,9 @@ hint: | # ifilter, imap, izip # __metaclass__ Hint: # update raise statements # except Exception, var - Hint: # cmp # apply - Hint: # <> # sagenb + Hint: # cmp # sagenb Hint: # next # six is no longer allowed - pattern: '(import.*ifilter|import.*imap|import.*izip|^\s*raise\s*[A-Za-z]*Error\s*,|[\s,\(]cmp\s*=|[^_a-z]cmp\(|<>|\.next\(\)|__metaclass__|except\s*[A-Za-z]\s*,|[^_a-z]apply\(|sagenb|import six|from six import)' + pattern: '(import.*ifilter|import.*imap|import.*izip|^\s*raise\s*[A-Za-z]*Error\s*,|[\s,\(]cmp\s*=|[^_a-z]cmp\(|\.next\(\)|__metaclass__|except\s*[A-Za-z]\s*,|sagenb|import six|from six import)' filePattern: .*[.](py|pyx|rst) - name: 'foreign_latex: foreign commands in LaTeX' @@ -18,39 +17,24 @@ use equivalent LaTeX commands instead of plain TeX commands such as \over, \choose, etc. pattern: '(\\choose|\\over[^l]|\\atop|\\above|\\overwithdelims|\\atopwithdelims|\\abovewithdelims)' -- name: 'doctest_continuation: old-style doctest continuation (...)' - hint: | - the correct syntax is ....: - pattern: '^\s*\.\.\.[ ][ ]*\S' - - name: 'blocks: wrong syntax for blocks (INPUT, OUTPUT, EXAMPLES, NOTE, etc.)' hint: | # the correct syntax is .. SEEALSO:: Hint: # TESTS and EXAMPLES should be plural, NOTE singular Hint: # no :: after INPUT, OUTPUT, REFERENCE blocks Hint: # no " :" at the end of lines - Hint: # no "Returns" at the start of lines - pattern: '(\.\.SEE|SEE ALSO|SEEALSO:($|[^:])|^\s*TEST:|^\s*EXAMPLE:|^\s*NOTES:|^\s*[A-Z]*PUT::|^\s*REFERENCES?::|\s:$)' + pattern: '(\.\.SEE|SEE ALSO|SEEALSO:($|[^:])|^\s*TEST:|^\s*EXAMPLE:|^\s*NOTES:|^\s*[A-Z]*PUT::|^\s*REFERENCES?::$)' - name: 'trac_links: bad trac link' hint: | the correct syntax for trac roles is :trac:`NUMBER`, note the initial colon pattern: '[^:]trac:`[0-9]' -- name: 'trailing_whitespace: trailing whitespace' - pattern: '[ ]$' - - name: 'triple_colon: triple colon (::: or : ::)' - pattern: ':[ ]*::' + pattern: ':[ ]*::$' # From various typo tickets -# https://trac.sagemath.org/ticket/30472 -- name: 'capitalization_sage: all-caps "SAGE" detected' - hint: | - Use "Sage" or "SageMath" instead - pattern: '\bSAGE\b' - # https://trac.sagemath.org/ticket/30585 - name: 'typo "homogenous" detected' hint: | diff --git a/src/VERSION.txt b/src/VERSION.txt index 9f62018ee76..0c2ffe14533 100644 --- a/src/VERSION.txt +++ b/src/VERSION.txt @@ -1 +1 @@ -9.4.beta0 +9.4.beta4 diff --git a/src/bin/sage-env b/src/bin/sage-env index e099f2c311e..c0848199623 100644 --- a/src/bin/sage-env +++ b/src/bin/sage-env @@ -586,12 +586,6 @@ if [ "$UNAME" = "CYGWIN" -a -n "$SAGE_LOCAL" ]; then LD_LIBRARY_PATH="$SAGE_LOCAL/bin:$LD_LIBRARY_PATH" && export LD_LIBRARY_PATH fi -# See trac 7186 -- this is needed if ecl is moved -if [ -n "$SAGE_LOCAL" ]; then - ECLDIR="$SAGE_LOCAL/lib/ecl/" && export ECLDIR -fi - - # Handle parallel building/testing/... case "$SAGE_NUM_THREADS,$SAGE_NUM_THREADS_PARALLEL" in [1-9][0-9]*,[1-9][0-9]*) diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index c1b4451e263..f9935624875 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.4.beta0' -SAGE_RELEASE_DATE='2021-05-25' -SAGE_VERSION_BANNER='SageMath version 9.4.beta0, Release Date: 2021-05-25' +SAGE_VERSION='9.4.beta4' +SAGE_RELEASE_DATE='2021-07-01' +SAGE_VERSION_BANNER='SageMath version 9.4.beta4, Release Date: 2021-07-01' diff --git a/src/doc/Makefile b/src/doc/Makefile index de8689b56ea..b10ae80a147 100644 --- a/src/doc/Makefile +++ b/src/doc/Makefile @@ -1,7 +1,71 @@ -all: - @echo "Please build the doc using either 'make doc' from SAGE_ROOT, or" - @echo "'sage -docbuild all html'. See 'sage -docbuild help' for more informations." +######################################################## +# +# 'make doc-html' (synonym for 'make' and 'make all') builds the html documentation. +# 'make doc-pdf' builds the PDF documentation. +# +# SAGE_ROOT must be defined for these to work, so these commands +# should be called by build/make/Makefile. +# +# 'make clean' removes build artifacts; SAGE_ROOT need not be defined for this to work. + +all: doc-html + clean: rm -rf en/reference/*/sage rm -rf en/reference/sage rm -f common/*.pyc + +# Matches doc-inventory--reference-manifolds etc. +doc-inventory--%: + cd $(SAGE_ROOT) && ./sage --docbuild --no-pdf-links $(subst -,/,$(subst doc-inventory--,,$@)) inventory $(SAGE_DOCBUILD_OPTS) + +# Matches doc-html--developer, doc-html--reference-manifolds etc. +doc-html--%: + cd $(SAGE_ROOT) && ./sage --docbuild --no-pdf-links $(subst -,/,$(subst doc-html--,,$@)) html $(SAGE_DOCBUILD_OPTS) + +# reference manual, inventory +ifndef SAGE_ROOT +doc-inventory-reference: + $(error SAGE_ROOT undefined. This Makefile needs to be invoked by build/make/install) +else +doc-inventory-reference: + $(eval DOCS = $(shell cd $(SAGE_ROOT) && ./sage --docbuild --all-documents reference)) + $(eval BIBLIO = $(firstword $(DOCS))) + $(eval OTHER_DOCS = $(wordlist 2, 100, $(DOCS))) + $(MAKE) doc-inventory--$(subst /,-,$(BIBLIO)) + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" $(foreach doc, $(OTHER_DOCS), doc-inventory--$(subst /,-,$(doc))) + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" doc-inventory--reference_top +endif + +# reference manual, html +doc-html-reference: doc-inventory-reference + $(eval DOCS = $(shell cd $(SAGE_ROOT) && ./sage --docbuild --all-documents reference)) + $(eval BIBLIO = $(firstword $(DOCS))) + $(eval OTHER_DOCS = $(wordlist 2, 100, $(DOCS))) + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" doc-html--$(subst /,-,$(BIBLIO)) + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" $(foreach doc, $(OTHER_DOCS), doc-html--$(subst /,-,$(doc))) + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" doc-html--reference_top + +# other documentation, html +doc-html-other: doc-html-reference + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" $(foreach doc, $(wordlist 2, 100, $(shell cd $(SAGE_ROOT) && ./sage --docbuild --all-documents all)), doc-html--$(subst /,-,$(doc))) + +doc-html: doc-html-reference doc-html-other + +# Matches doc-pdf--developer, doc-pdf--reference-manifolds etc. +doc-pdf--%: + cd $(SAGE_ROOT) && ./sage --docbuild $(subst -,/,$(subst doc-pdf--,,$@)) pdf $(SAGE_DOCBUILD_OPTS) + +doc-pdf: doc-inventory-reference + $(eval DOCS = $(shell cd $(SAGE_ROOT) && ./sage --docbuild --all-documents reference)) + $(eval BIBLIO = $(firstword $(DOCS))) + $(eval OTHER_DOCS = $(wordlist 2, 100, $(DOCS))) + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" doc-pdf--$(subst /,-,$(BIBLIO)) + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" $(foreach doc, $(OTHER_DOCS), doc-pdf--$(subst /,-,$(doc))) + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" doc-pdf--reference_top + $(MAKE) SAGE_DOCBUILD_OPTS="$(SAGE_DOCBUILD_OPTS) --no-prune-empty-dirs" $(foreach doc, $(wordlist 2, 100, $(shell cd $(SAGE_ROOT) && ./sage --docbuild --all-documents all)), doc-pdf--$(subst /,-,$(doc))) + +.PHONY: all clean \ + doc-html doc-pdf \ + doc-inventory-reference doc-html-reference \ + doc-html-other diff --git a/src/doc/en/constructions/groups.rst b/src/doc/en/constructions/groups.rst index b2ff0e16a4c..042d37088b1 100644 --- a/src/doc/en/constructions/groups.rst +++ b/src/doc/en/constructions/groups.rst @@ -185,7 +185,7 @@ Here's another way, working more directly with GAP:: sage: print(gap.eval("G := SymmetricGroup( 4 )")) Sym( [ 1 .. 4 ] ) sage: print(gap.eval("normal := NormalSubgroups( G );")) - [ Sym( [ 1 .. 4 ] ), Alt( [ 1 .. 4 ] ), Group([ (1,4)(2,3), (1,2)(3,4) ]), + [ Sym( [ 1 .. 4 ] ), Alt( [ 1 .. 4 ] ), Group([ (1,4)(2,3), ... ]), Group(()) ] .. index:: @@ -252,7 +252,7 @@ Another example of using the small groups database: ``group_id`` gap> G:=Group((4,6,5)(7,8,9),(1,7,2,4,6,9,5,3)); Group([ (4,6,5)(7,8,9), (1,7,2,4,6,9,5,3) ]) gap> StructureDescription(G); - "(C3 x C3) : GL(2,3)" + "(C3 x C3) : GL(2,3)" Construction instructions for every group of order less than 32 =============================================================== diff --git a/src/doc/en/developer/packaging.rst b/src/doc/en/developer/packaging.rst index 0fa04587099..5032d95744c 100644 --- a/src/doc/en/developer/packaging.rst +++ b/src/doc/en/developer/packaging.rst @@ -385,9 +385,9 @@ begin with ``sdh_``, which stands for "Sage-distribution helper". - ``sdh_configure [...]``: Runs ``./configure`` with arguments ``--prefix="$SAGE_LOCAL"``, ``--libdir="$SAGE_LOCAL/lib"``, - ``--disable-maintainer-mode``, and - ``--disable-dependency-tracking``. Additional arguments to - ``./configure`` may be given as arguments. + ``--disable-static``, ``--disable-maintainer-mode``, and + ``--disable-dependency-tracking``. Additional arguments to ``./configure`` + may be given as arguments. - ``sdh_make [...]``: Runs ``$MAKE`` with the default target. Additional arguments to ``$MAKE`` may be given as arguments. diff --git a/src/doc/en/prep/Quickstarts/Linear-Algebra.rst b/src/doc/en/prep/Quickstarts/Linear-Algebra.rst index 3f6b6feb3c0..666e4b48765 100644 --- a/src/doc/en/prep/Quickstarts/Linear-Algebra.rst +++ b/src/doc/en/prep/Quickstarts/Linear-Algebra.rst @@ -330,17 +330,17 @@ We can easily solve linear equations using the backslash, like in Matlab. :: - sage: A=random_matrix(QQ,3) # random - sage: v=vector([2,3,1]) - sage: A,v # random + sage: A = random_matrix(QQ, 3, algorithm='unimodular') + sage: v = vector([2,3,1]) + sage: A,v # random ( [ 0 -1 1] [-1 -1 -1] [ 0 2 2], (2, 3, 1) ) - sage: x=A\v; x # random + sage: x=A\v; x # random (-7/2, -3/4, 5/4) - sage: A*x # random + sage: A*x # random (2, 3, 1) For *lots* more (concise) information, see the Sage `Linear Algebra diff --git a/src/doc/en/reference/combinat/module_list.rst b/src/doc/en/reference/combinat/module_list.rst index efb6e1fba22..529ff3f41a7 100644 --- a/src/doc/en/reference/combinat/module_list.rst +++ b/src/doc/en/reference/combinat/module_list.rst @@ -145,6 +145,7 @@ Comprehensive Module list sage/combinat/integer_vector_weighted sage/combinat/integer_vectors_mod_permgroup sage/combinat/interval_posets + sage/combinat/k_regular_sequence sage/combinat/k_tableau sage/combinat/kazhdan_lusztig sage/combinat/knutson_tao_puzzles @@ -365,7 +366,6 @@ Comprehensive Module list sage/combinat/superpartition sage/combinat/symmetric_group_algebra sage/combinat/symmetric_group_representations - sage/combinat/super_tableau sage/combinat/tableau sage/combinat/tableau_residues sage/combinat/tableau_tuple diff --git a/src/doc/en/reference/databases/index.rst b/src/doc/en/reference/databases/index.rst index 5c198d9dcdf..34ae3ba3f1f 100644 --- a/src/doc/en/reference/databases/index.rst +++ b/src/doc/en/reference/databases/index.rst @@ -62,5 +62,6 @@ database engine. sage/databases/cunningham_tables sage/databases/db_class_polynomials sage/databases/db_modular_polynomials + sage/databases/knotinfo_db .. include:: ../footer.txt diff --git a/src/doc/en/reference/discrete_geometry/index.rst b/src/doc/en/reference/discrete_geometry/index.rst index 3d027326933..6975fa144a7 100644 --- a/src/doc/en/reference/discrete_geometry/index.rst +++ b/src/doc/en/reference/discrete_geometry/index.rst @@ -105,8 +105,10 @@ Miscellaneous .. toctree:: :maxdepth: 1 + sage/geometry/convex_set sage/geometry/linear_expression sage/geometry/newton_polygon + sage/geometry/relative_interior sage/geometry/ribbon_graph sage/geometry/pseudolines sage/geometry/voronoi_diagram diff --git a/src/doc/en/reference/function_fields/index.rst b/src/doc/en/reference/function_fields/index.rst index 4f581f569df..205398197b8 100644 --- a/src/doc/en/reference/function_fields/index.rst +++ b/src/doc/en/reference/function_fields/index.rst @@ -4,9 +4,8 @@ Algebraic Function Fields Sage allows basic computations with elements and ideals in orders of algebraic function fields over arbitrary constant fields. Advanced computations, like computing the genus or a basis of the Riemann-Roch space of a divisor, are -available for function fields over finite fields, number fields, and `\QQbar`. - -A reference for the basic theory of algebraic function fields is [Stich2009]_. +available for function fields over finite fields, number fields, and the +algebraic closure of `\QQ`. .. toctree:: :maxdepth: 1 @@ -22,4 +21,14 @@ A reference for the basic theory of algebraic function fields is [Stich2009]_. sage/rings/function_field/maps sage/rings/function_field/constructor +A basic reference for the theory of algebraic function fields is [Stich2009]_. + +A Support Module +---------------- + +.. toctree:: + :maxdepth: 1 + + sage/rings/function_field/hermite_form_polynomial + .. include:: ../footer.txt diff --git a/src/doc/en/reference/homology/index.rst b/src/doc/en/reference/homology/index.rst index bf98e0841fd..32424286980 100644 --- a/src/doc/en/reference/homology/index.rst +++ b/src/doc/en/reference/homology/index.rst @@ -1,12 +1,8 @@ -Cell complexes and their homology -================================= +Chain complexes and homology +============================ -Sage includes some tools for algebraic topology: from the algebraic -side, chain complexes and their homology, and from the topological -side, simplicial complexes, `\Delta`-complexes, cubical complexes, and -simplicial sets. A class of generic cell complexes is also available, -mainly for developers who want to use it as a base for other types of -cell complexes. +Sage includes some tools for algebraic topology, and in particular +computing homology groups. .. toctree:: :maxdepth: 2 @@ -16,18 +12,6 @@ cell complexes. sage/homology/chain_complex_morphism sage/homology/chain_homotopy sage/homology/chain_complex_homspace - sage/homology/simplicial_complex - sage/homology/simplicial_complex_morphism - sage/homology/simplicial_complex_homset - sage/homology/examples - sage/homology/delta_complex - sage/homology/cubical_complex - sage/homology/simplicial_set - sage/homology/simplicial_set_constructions - sage/homology/simplicial_set_examples - sage/homology/simplicial_set_catalog - sage/homology/simplicial_set_morphism - sage/homology/cell_complex sage/homology/koszul_complex sage/homology/hochschild_complex sage/homology/homology_group diff --git a/src/doc/en/reference/knots/index.rst b/src/doc/en/reference/knots/index.rst index ecaa3832288..8fc794b4705 100644 --- a/src/doc/en/reference/knots/index.rst +++ b/src/doc/en/reference/knots/index.rst @@ -6,5 +6,6 @@ Knot Theory sage/knots/knot sage/knots/link + sage/knots/knotinfo .. include:: ../footer.txt diff --git a/src/doc/en/reference/manifolds/continuous_map.rst b/src/doc/en/reference/manifolds/continuous_map.rst index 9c4afeb8f82..bd17639a2f4 100644 --- a/src/doc/en/reference/manifolds/continuous_map.rst +++ b/src/doc/en/reference/manifolds/continuous_map.rst @@ -7,3 +7,5 @@ Continuous Maps sage/manifolds/manifold_homset sage/manifolds/continuous_map + + sage/manifolds/continuous_map_image diff --git a/src/doc/en/reference/manifolds/diff_manifold.rst b/src/doc/en/reference/manifolds/diff_manifold.rst index 0d061007fe2..31e86015d97 100644 --- a/src/doc/en/reference/manifolds/diff_manifold.rst +++ b/src/doc/en/reference/manifolds/diff_manifold.rst @@ -24,6 +24,8 @@ Differentiable Manifolds mixed_form + sage/manifolds/differentiable/de_rham_cohomology + multivector sage/manifolds/differentiable/affine_connection diff --git a/src/doc/en/reference/manifolds/manifold.rst b/src/doc/en/reference/manifolds/manifold.rst index f11d8135500..c5dc02c0098 100644 --- a/src/doc/en/reference/manifolds/manifold.rst +++ b/src/doc/en/reference/manifolds/manifold.rst @@ -21,3 +21,7 @@ Topological Manifolds sage/manifolds/topological_submanifold vector_bundle + + sage/manifolds/family + + sage/manifolds/subsets/closure diff --git a/src/doc/en/reference/modfrm/index.rst b/src/doc/en/reference/modfrm/index.rst index e1f8a483a3f..3d5e55dfd63 100644 --- a/src/doc/en/reference/modfrm/index.rst +++ b/src/doc/en/reference/modfrm/index.rst @@ -23,7 +23,6 @@ Module List sage/modular/modform/hecke_operator_on_qexp sage/modular/modform/numerical sage/modular/modform/vm_basis - sage/modular/modform/ambient sage/modular/modform/half_integral sage/modular/modform/find_generators sage/modular/modform/j_invariant diff --git a/src/doc/en/reference/numerical/index.rst b/src/doc/en/reference/numerical/index.rst index 254faadec8f..6a17afe2dfb 100644 --- a/src/doc/en/reference/numerical/index.rst +++ b/src/doc/en/reference/numerical/index.rst @@ -13,6 +13,7 @@ Numerical Optimization sage/numerical/linear_tensor_constraints sage/numerical/optimize sage/numerical/interactive_simplex_method + sage/numerical/gauss_legendre Linear Optimization (LP) and Mixed Integer Linear Optimization (MIP) Solver backends ------------------------------------------------------------------------------------ diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index a5b4d54eeed..cd605c25be0 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -304,6 +304,10 @@ REFERENCES: of crystals for the quantum queer superalgebra*. Preprint (2018). :arxiv:`1803.06317` +.. [AS2003] Jean-Paul Allouche, Jeffrey Shallit, + *Automatic Sequences: Theory, Applications, Generalizations*, + Cambridge University Press, 2003. + .. [As2008b] Sami Assaf. *Dual equivalence graphs and a combinatorial proof of LLT and Macdonald positivity*. (2008). :arxiv:`1005.3759v5`. @@ -3461,6 +3465,11 @@ REFERENCES: .. [Knu2005] Lars R. Knudsen, *SMASH - A Cryptographic Hash Function*; in FSE'05, (2005), pp. 228-242. +.. [KO2000] Yuji Kobayashi and Friedrich Otto, + *Repetitiveness of languages generated by morphisms*. + Theoret. Comp. Sci. 240 (2000) 337--378. + :doi:`10.1016/S0304-3975(99)00238-8` + .. [Kob1993] Neal Koblitz, *Introduction to Elliptic Curves and Modular Forms*. Springer GTM 97, 1993. @@ -3582,6 +3591,11 @@ REFERENCES: algebra of type* `A`. Proc. Amer. Math. Soc. **138** (2010), no. 11, 3877--3889. +.. [KS2015] Karel Klouda and Štěpán Starosta, + *An Algorithm Enumerating All Infinite Repetitions in a D0L System*. + Journal of Discrete Algorithms, 33 (2015), 130-138. + :doi:`10.1016/j.jda.2015.03.006` + .. [KS2019] \J. Kliem and C. Stump. *A face iterator for polyhedra and more general finite locally branched lattices*. @@ -4373,6 +4387,11 @@ REFERENCES: Proceedings of the National Academy of Sciences 36.1 (1950): 48-49. +.. [Neu2018] Christian Neurohr, *Efficient Integration on Riemann Surfaces & + Applications*, + PhD Thesis, Carl von Ossietzky Universität Oldenburg + http://oops.uni-oldenburg.de/3607. + .. [New2003] Newman, M.E.J. *The Structure and function of complex networks*, SIAM Review vol. 45, no. 2 (2003), pp. 167-256. :doi:`10.1137/S003614450342480`. @@ -4766,6 +4785,9 @@ REFERENCES: .. [Rea2009] Nathan Reading, *Noncrossing partitions and the shard intersection order*, DMTCS Proceedings of FPSAC 2009, 745--756 +.. [ReSt2020] Nathan Reading and Salvatore Stella, *An affine almost positive + roots model*, J. Comb. Algebra Volume 4, Issue 1, 2020, pp. 1--59 + .. [Red2001] Maria Julia Redondo. *Hochschild cohomology: some methods for computations*. Resenhas IME-USP 5 (2), 113-137 (2001). http://inmabb.criba.edu.ar/gente/mredondo/crasp.pdfc diff --git a/src/doc/en/reference/sat/index.rst b/src/doc/en/reference/sat/index.rst index 03f26030a0c..c7a3ba619dd 100644 --- a/src/doc/en/reference/sat/index.rst +++ b/src/doc/en/reference/sat/index.rst @@ -124,7 +124,12 @@ Sage provides various highlevel functions which make working with Boolean polyno construct a very small-scale AES system of equations and pass it to a SAT solver:: sage: sr = mq.SR(1,1,1,4,gf2=True,polybori=True) - sage: F,s = sr.polynomial_system() + sage: while True: + ....: try: + ....: F,s = sr.polynomial_system() + ....: break + ....: except ZeroDivisionError: + ....: pass sage: from sage.sat.boolean_polynomials import solve as solve_sat # optional - cryptominisat sage: s = solve_sat(F) # optional - cryptominisat sage: F.subs(s[0]) # optional - cryptominisat diff --git a/src/doc/en/reference/topology/conf.py b/src/doc/en/reference/topology/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/topology/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/topology/index.rst b/src/doc/en/reference/topology/index.rst new file mode 100644 index 00000000000..bf3eff5149b --- /dev/null +++ b/src/doc/en/reference/topology/index.rst @@ -0,0 +1,26 @@ +Topology +======== + +Sage includes some tools for topology, and in particular cell +complexes: simplicial complexes, `\Delta`-complexes, cubical +complexes, and simplicial sets. A class of generic cell complexes is +also available, mainly for developers who want to use it as a base for +other types of cell complexes. + +.. toctree:: + :maxdepth: 2 + + sage/topology/simplicial_complex + sage/topology/simplicial_complex_morphism + sage/topology/simplicial_complex_homset + sage/topology/simplicial_complex_examples + sage/topology/delta_complex + sage/topology/cubical_complex + sage/topology/simplicial_set + sage/topology/simplicial_set_constructions + sage/topology/simplicial_set_examples + sage/topology/simplicial_set_catalog + sage/topology/simplicial_set_morphism + sage/topology/cell_complex + +.. include:: ../footer.txt diff --git a/src/doc/en/reference/homology/media/klein.png b/src/doc/en/reference/topology/media/klein.png similarity index 100% rename from src/doc/en/reference/homology/media/klein.png rename to src/doc/en/reference/topology/media/klein.png diff --git a/src/doc/en/reference/homology/media/rp2.png b/src/doc/en/reference/topology/media/rp2.png similarity index 100% rename from src/doc/en/reference/homology/media/rp2.png rename to src/doc/en/reference/topology/media/rp2.png diff --git a/src/doc/en/reference/homology/media/simplices.png b/src/doc/en/reference/topology/media/simplices.png similarity index 100% rename from src/doc/en/reference/homology/media/simplices.png rename to src/doc/en/reference/topology/media/simplices.png diff --git a/src/doc/en/reference/homology/media/torus.png b/src/doc/en/reference/topology/media/torus.png similarity index 100% rename from src/doc/en/reference/homology/media/torus.png rename to src/doc/en/reference/topology/media/torus.png diff --git a/src/doc/en/reference/homology/media/torus_labelled.png b/src/doc/en/reference/topology/media/torus_labelled.png similarity index 100% rename from src/doc/en/reference/homology/media/torus_labelled.png rename to src/doc/en/reference/topology/media/torus_labelled.png diff --git a/src/doc/en/thematic_tutorials/coercion_and_categories.rst b/src/doc/en/thematic_tutorials/coercion_and_categories.rst index 24bd3abafd3..4a8d86c0ae9 100644 --- a/src/doc/en/thematic_tutorials/coercion_and_categories.rst +++ b/src/doc/en/thematic_tutorials/coercion_and_categories.rst @@ -108,8 +108,6 @@ This base class provides a lot more methods than a general parent:: '__ideal_monoid', '__iter__', '__len__', - '__pow__', - '__rpow__', '__rtruediv__', '__rxor__', '__truediv__', diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/elliptic_curves.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/elliptic_curves.rst index 034e52699de..d1a2bda863b 100644 --- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/elliptic_curves.rst +++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/elliptic_curves.rst @@ -108,7 +108,7 @@ compute its cardinality, which behind the scenes uses SEA. :: sage: E = EllipticCurve_from_j(k.random_element()) - sage: E.cardinality() # less than a second + sage: E.cardinality() # random, less than a second 99999999999371984255 To see how Sage chooses when to use SEA versus other methods, type diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst index 07e84de55ea..4832272f1cb 100644 --- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst +++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst @@ -40,7 +40,7 @@ Some more advanced number-theoretical tools are available via G: sage: P = K.primes_above(2)[0] sage: G.inertia_group(P) - Subgroup [(), (1,4,6)(2,5,3), (1,6,4)(2,3,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 + 40*x^3 + 1372 + Subgroup generated by [(1,4,6)(2,5,3)] of (Galois group 6T2 ([3]2) with order 6 of x^6 + 40*x^3 + 1372) sage: sorted([G.artin_symbol(Q) for Q in K.primes_above(5)]) # random order, see Trac #18308 [(1,3)(2,6)(4,5), (1,2)(3,4)(5,6), (1,5)(2,4)(3,6)] @@ -305,7 +305,7 @@ ideal classes containing :math:`(5,\sqrt{-30})` and sage: category(C) Category of finite enumerated commutative groups sage: C.gens() - (Fractional ideal class (2, a), Fractional ideal class (3, a)) + (Fractional ideal class (5, a), Fractional ideal class (3, a)) Arithmetic in the class group @@ -322,17 +322,17 @@ means "the product of the 0th and 1st generators of the class group sage: K. = QuadraticField(-30) sage: C = K.class_group() sage: C.0 - Fractional ideal class (2, a) + Fractional ideal class (5, a) sage: C.0.ideal() - Fractional ideal (2, a) + Fractional ideal (5, a) sage: I = C.0 * C.1 sage: I - Fractional ideal class (5, a) + Fractional ideal class (2, a) Next we find that the class of the fractional ideal :math:`(2,\sqrt{-30}+4/3)` is equal to the ideal class -:math:`C.0`. +:math:`C.0*C.1`. .. link @@ -342,7 +342,7 @@ Next we find that the class of the fractional ideal sage: J = C(A) sage: J Fractional ideal class (2/3, 1/3*a) - sage: J == C.0 + sage: J == C.0*C.1 True diff --git a/src/doc/en/thematic_tutorials/geometry/polyhedra_quickref.rst b/src/doc/en/thematic_tutorials/geometry/polyhedra_quickref.rst index d684c689c3e..c8f6b40b171 100644 --- a/src/doc/en/thematic_tutorials/geometry/polyhedra_quickref.rst +++ b/src/doc/en/thematic_tutorials/geometry/polyhedra_quickref.rst @@ -172,6 +172,8 @@ List of Polyhedron methods :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.face_generator` | a generator over the faces :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.faces` | the list of faces :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.facets` | the list of facets + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.join_of_Vrep`, :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.least_common_superface_of_Vrep` | smallest face containing specified Vrepresentatives + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.meet_of_Hrep`, :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.greatest_common_subface_of_Hrep` | largest face contained in specified Hrepresentatives :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.normal_fan` | returns the fan spanned by the normals of the supporting hyperplanes of the polyhedron :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.gale_transform` | returns the (affine) Gale transform of the vertices of the polyhedron :meth:`~sage.geometry.polyhedron.base.Polyhedron_base.hyperplane_arrangement` | returns the hyperplane arrangement given by the defining facets of the polyhedron diff --git a/src/doc/en/thematic_tutorials/sandpile.rst b/src/doc/en/thematic_tutorials/sandpile.rst index 75ac904ec58..7dd7091415a 100644 --- a/src/doc/en/thematic_tutorials/sandpile.rst +++ b/src/doc/en/thematic_tutorials/sandpile.rst @@ -324,9 +324,9 @@ sink. [1, 1, 3] sage: S.reduced_laplacian().dense_matrix().smith_form() ( - [1 0 0] [ 0 0 1] [3 1 4] - [0 1 0] [ 1 0 0] [4 1 6] - [0 0 3], [ 0 1 -1], [4 1 5] + [1 0 0] [ 1 0 0] [1 3 5] + [0 1 0] [ 0 1 0] [1 4 6] + [0 0 3], [ 0 -1 1], [1 4 7] ) Adding the identity to any recurrent configuration and stabilizing yields @@ -687,7 +687,7 @@ Approximation to the zero set (setting ``x_0 = 1``):: The zeros are generated as a group by a single vector:: sage: S.points() - [[(1/2*I + 1/2)*sqrt(2), -(1/2*I + 1/2)*sqrt(2)]] + [[-(1/2*I + 1/2)*sqrt(2), (1/2*I + 1/2)*sqrt(2)]] Resolutions @@ -1384,12 +1384,12 @@ EXAMPLES:: sage: s = sandpiles.Cycle(5) sage: s.group_gens() - [{1: 1, 2: 1, 3: 1, 4: 0}] + [{1: 0, 2: 1, 3: 1, 4: 1}] sage: s.group_gens()[0].order() 5 sage: s = sandpiles.Complete(5) sage: s.group_gens(False) - [[2, 2, 3, 2], [2, 3, 2, 2], [3, 2, 2, 2]] + [[2, 3, 2, 2], [2, 2, 3, 2], [2, 2, 2, 3]] sage: [i.order() for i in s.group_gens()] [5, 5, 5] sage: s.invariant_factors() @@ -2058,7 +2058,7 @@ single generator for the group of solutions. sage: S = sandpiles.Complete(4) sage: S.points() - [[1, I, -I], [I, 1, -I]] + [[-I, I, 1], [-I, 1, I]] --- @@ -4257,7 +4257,7 @@ EXAMPLES:: sage: D.is_linearly_equivalent([0,1,1]) True sage: D.is_linearly_equivalent([0,1,1],True) - (1, 0, 0) + (0, -1, -1) sage: v = vector(D.is_linearly_equivalent([0,1,1],True)) sage: vector(D.values()) - s.laplacian()*v (0, 1, 1) @@ -4983,8 +4983,8 @@ Other sage: P = matrix([[2,3,-7,-3],[5,2,-5,5],[8,2,5,4],[-5,-9,6,6]]) sage: wilmes_algorithm(P) - [ 1642 -13 -1627 -1] - [ -1 1980 -1582 -397] + [ 3279 -79 -1599 -1600] + [ -1 1539 -136 -1402] [ 0 -1 1650 -1649] [ 0 0 -1658 1658] diff --git a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_advanced.rst b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_advanced.rst index ee4e6e95d12..f828b94d602 100644 --- a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_advanced.rst +++ b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_advanced.rst @@ -311,7 +311,7 @@ The set `C^\infty(\mathbb{E}^3)` of all smooth scalar fields on sage: CE Algebra of differentiable scalar fields on the Euclidean space E^3 sage: CE.category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces sage: f in CE True diff --git a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst index 19ee71e386e..fc62be2c863 100644 --- a/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst +++ b/src/doc/en/thematic_tutorials/vector_calculus/vector_calc_plane.rst @@ -612,7 +612,7 @@ on `\mathbb{E}^2`, `C^\infty(\mathbb{E}^2)`:: sage: CE is XE.base_ring() True sage: CE.category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces sage: rank(XE) 2 @@ -647,8 +647,7 @@ A vector field evaluated at a point $p$ is a vector in the tangent space sage: isinstance(Tp, FiniteRankFreeModule) True sage: sorted(Tp.bases(), key=str) - [Basis (d/dr,d/dph) on the Tangent space at Point p on the Euclidean plane E^2, - Basis (e_r,e_ph) on the Tangent space at Point p on the Euclidean plane E^2, + [Basis (e_r,e_ph) on the Tangent space at Point p on the Euclidean plane E^2, Basis (e_x,e_y) on the Tangent space at Point p on the Euclidean plane E^2] diff --git a/src/doc/ja/a_tour_of_sage/conf.py b/src/doc/ja/a_tour_of_sage/conf.py index edcad0cf018..ab57a150cbb 100644 --- a/src/doc/ja/a_tour_of_sage/conf.py +++ b/src/doc/ja/a_tour_of_sage/conf.py @@ -37,13 +37,3 @@ ('index', name + '.tex', project, 'The Sage Group', 'manual'), ] - -# LaTeX の docclass 設定 -latex_docclass = {'manual': 'jsbook'} - -# Additional LaTeX stuff for the French version -#latex_elements['preamble'] += '\\DeclareUnicodeCharacter{00A0}{\\nobreakspace}\n' - -# the definition of \\at in the standard preamble of the sphinx doc -# conflicts with that in babel/french[b] -latex_elements['preamble'] += '\\let\\at\\undefined' diff --git a/src/doc/ja/tutorial/conf.py b/src/doc/ja/tutorial/conf.py index 141629139f0..520f4cc46f3 100644 --- a/src/doc/ja/tutorial/conf.py +++ b/src/doc/ja/tutorial/conf.py @@ -37,16 +37,3 @@ ('index', name + '.tex', project, 'The Sage Group', 'manual'), ] - -# LaTeX の docclass 設定 -latex_docclass = {'manual': 'jsbook'} - -# Additional LaTeX stuff for the French version -#latex_elements['preamble'] += '\\DeclareUnicodeCharacter{00A0}{\\nobreakspace}\n' - -# the definition of \\at in the standard preamble of the sphinx doc -# conflicts with that in babel/french[b] -latex_elements['preamble'] += '\\let\\at\\undefined' - -# -# html_use_smartypants = False diff --git a/src/doc/ja/tutorial/tour_groups.rst b/src/doc/ja/tutorial/tour_groups.rst index a2518600d34..1b067df06c0 100644 --- a/src/doc/ja/tutorial/tour_groups.rst +++ b/src/doc/ja/tutorial/tour_groups.rst @@ -21,7 +21,7 @@ Sageでは,置換群,有限古典群(例えば :math:`SU(n,q)`),有限行 False sage: G.derived_series() # 結果は変化しがち [Subgroup generated by [(3,4), (1,2,3)(4,5)] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]), - Subgroup generated by [(1,3,5), (1,5)(3,4), (1,5)(2,4)] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)])] + Subgroup generated by [...] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)])] sage: G.center() Subgroup generated by [()] of (Permutation Group with generators [(3,4), (1,2,3)(4,5)]) sage: G.random_element() # random 出力は変化する diff --git a/src/sage/algebras/cluster_algebra.py b/src/sage/algebras/cluster_algebra.py index dae5f0650f7..0b53cac387d 100644 --- a/src/sage/algebras/cluster_algebra.py +++ b/src/sage/algebras/cluster_algebra.py @@ -363,6 +363,7 @@ from sage.functions.other import binomial from sage.geometry.cone import Cone from sage.geometry.fan import Fan +from sage.graphs.digraph import DiGraph from sage.matrix.constructor import identity_matrix, matrix from sage.matrix.special import block_matrix from sage.misc.cachefunc import cached_method @@ -492,13 +493,11 @@ def g_vector(self): sage: sum(A.initial_cluster_variables()).g_vector() Traceback (most recent call last): ... - ValueError: this element is not homogeneous + ValueError: this element does not have a well defined g-vector """ - components = self.homogeneous_components() - if len(components) != 1: - raise ValueError("this element is not homogeneous") - k, = components - return k + if not self.is_homogeneous(): + raise ValueError("this element does not have a well defined g-vector") + return self._g_vector def F_polynomial(self): r""" @@ -514,17 +513,17 @@ def F_polynomial(self): sage: sum(A.initial_cluster_variables()).F_polynomial() Traceback (most recent call last): ... - ValueError: this element is not homogeneous + ValueError: this element does not have a well defined g-vector """ if not self.is_homogeneous(): - raise ValueError("this element is not homogeneous") + raise ValueError("this element does not have a well defined g-vector") subs_dict = {} A = self.parent() for x in A.initial_cluster_variables(): subs_dict[x.lift()] = A._U(1) for i in range(A.rank()): subs_dict[A.coefficient(i).lift()] = A._U.gen(i) - return self.lift().substitute(subs_dict) + return A._U(self.lift().substitute(subs_dict)) def is_homogeneous(self): r""" @@ -540,7 +539,7 @@ def is_homogeneous(self): sage: x.is_homogeneous() False """ - return len(self.homogeneous_components()) == 1 + return getattr(self, '_is_homogeneous', len(self.homogeneous_components()) == 1) def homogeneous_components(self): r""" @@ -569,8 +568,56 @@ def homogeneous_components(self): components[g_vect] += self.parent().retract(x.monomial_coefficient(m) * m) else: components[g_vect] = self.parent().retract(x.monomial_coefficient(m) * m) + for g_vect in components: + components[g_vect]._is_homogeneous = True + components[g_vect]._g_vector = g_vect + self._is_homogeneous = (len(components) == 1) + if self._is_homogeneous: + self._g_vector = list(components.keys())[0] return components + def theta_basis_decomposition(self): + r""" + Return the decomposition of ``self`` in the theta basis. + + OUTPUT: + + A dictionary whose keys are the g-vectors and whose values are the coefficients + in the decomposition of ``self`` in the theta basis. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,-2],[3,0]]), principal_coefficients=True) + sage: f = (A.theta_basis_element((1,0)) + A.theta_basis_element((0,1)))**2 + A.coefficient(1)* A.theta_basis_element((1,1)) + sage: decomposition = f.theta_basis_decomposition() + sage: sum(decomposition[g] * A.theta_basis_element(g) for g in decomposition) == f + True + sage: f = A.theta_basis_element((4,-4))*A.theta_basis_element((1,-1)) + sage: decomposition = f.theta_basis_decomposition() + sage: sum(decomposition[g] * A.theta_basis_element(g) for g in decomposition) == f + True + """ + A = self.parent() + B = A.b_matrix() + U = A._U + out = dict() + zero_A = A(0) + zero_U = U(0) + zero_t = (0,)*A.rank() + + components = self.homogeneous_components() + + for g_vect in components: + f_poly = components[g_vect].F_polynomial() + g_vect = vector(g_vect) + while f_poly != zero_U: + y_exp = min(f_poly.dict()) + coeff = f_poly.dict()[y_exp] + g_theta = tuple(g_vect + B*vector(y_exp)) + out[g_theta] = out.get(g_theta, zero_A) + A({zero_t + tuple(y_exp):coeff}) + f_poly -= U({y_exp:coeff}) * A.theta_basis_F_polynomial(g_theta) + + return out ############################################################################## # Seeds @@ -1477,6 +1524,48 @@ def _coerce_map_from_(self, other): # everything that is in the base can be coerced to self return self.base().has_coerce_map_from(other) + @cached_method + def coxeter_element(self): + r""" + Return the Coxeter element associated to the initial exchange matrix, if acyclic. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.coxeter_element() + [0, 1, 2] + + Raise an error if the initial exchange matrix is not acyclic:: + + sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]])) + sage: A.coxeter_element() + Traceback (most recent call last): + ... + ValueError: the initial exchange matrix is not acyclic. + """ + dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ(0) if x <= 0 else ZZ(1))) + acyclic, coxeter = dg.is_directed_acyclic(certificate=True) + if not acyclic: + raise ValueError("the initial exchange matrix is not acyclic.") + return coxeter + + @cached_method + def is_acyclic(self): + r""" + Return ``True`` if the exchange matrix in the initial seed is acyclic, ``False`` otherwise. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.is_acyclic() + True + sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]])) + sage: A.is_acyclic() + False + """ + dg = DiGraph(self.b_matrix().apply_map(lambda x: ZZ(0) if x <= 0 else ZZ(1))) + return dg.is_directed_acyclic() + def rank(self): r""" Return the rank of ``self``, i.e. the number of cluster variables @@ -1530,12 +1619,12 @@ def set_current_seed(self, seed): sage: A.set_current_seed(A1.initial_seed()) Traceback (most recent call last): ... - ValueError: This is not a seed in this cluster algebra + ValueError: this is not a seed in this cluster algebra """ if self.contains_seed(seed): self._seed = seed else: - raise ValueError("This is not a seed in this cluster algebra") + raise ValueError("this is not a seed in this cluster algebra") def reset_current_seed(self): r""" @@ -1627,6 +1716,92 @@ def b_matrix(self): n = self.rank() return copy(self._B0[:n, :]) + def euler_matrix(self): + r""" + Return the Euler matrix associated to ``self``. + + ALGORITHM: + + This method returns the matrix of the bilinear form defined in Equation (2.1) of [ReSt2020]_ . + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.euler_matrix() + [ 1 0 0] + [-1 1 0] + [-1 -1 1] + + Raise an error if the initial exchange matrix is not acyclic:: + + sage: A = ClusterAlgebra(matrix([[0,1,-1],[-1,0,1],[1,-1,0]])) + sage: A.euler_matrix() + Traceback (most recent call last): + ... + ValueError: the initial exchange matrix is not acyclic. + """ + + if not self.is_acyclic(): + raise ValueError("the initial exchange matrix is not acyclic.") + return 1 + self.b_matrix().apply_map(lambda x: min(ZZ(0), x)) + + def d_vector_to_g_vector(self, d): + r""" + Return the g-vector of an element of ``self`` having d-vector ``d`` + + INPUT: + + - ``d`` -- the d-vector + + ALGORITHM: + + This method implements the piecewise-linear map `\\nu_c` introduced in Section 9.1 of [ReSt2020]_. + + .. WARNING: + + This implementation works only when the initial exchange matrix is acyclic. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.d_vector_to_g_vector((1,0,-1)) + (-1, 1, 2) + """ + dm = vector(( x if x < 0 else 0 for x in d)) + dp = vector(d) - dm + return tuple(- dm - self.euler_matrix()*dp) + + def g_vector_to_d_vector(self, g): + r""" + Return the d-vector of an element of ``self`` having g-vector ``g`` + + INPUT: + + - ``g`` -- the g-vector + + ALGORITHM: + + This method implements the inverse of the piecewise-linear map `\\nu_c` introduced in Section 9.1 of [ReSt2020]_. + + .. WARNING: + + This implementation works only when the initial exchange matrix is acyclic. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,1,1],[-1,0,1],[-1,-1,0]])) + sage: A.g_vector_to_d_vector((-1,1,2)) + (1, 0, -1) + """ + E = -self.euler_matrix() + c = self.coxeter_element() + dp = vector(ZZ, self.rank()) + g = vector(g) + for i in c: + dp[i] = -min(g[i], 0) + g += min(g[i],0)*E.column(i) + return tuple(-g+dp) + def g_vectors(self, mutating_F=True): r""" Return an iterator producing all the g-vectors of ``self``. @@ -2394,28 +2569,112 @@ def greedy_element(self, d_vector): if self.rank() != 2: raise ValueError('greedy elements are only defined in rank 2') - if len(self.coefficients()) != 0: - raise NotImplementedError('can only compute greedy elements in the coefficient-free case') + return self.theta_basis_element(self.d_vector_to_g_vector(d_vector)) - b = abs(self.b_matrix()[0, 1]) - c = abs(self.b_matrix()[1, 0]) - a1, a2 = d_vector - # Here we use the generators of self.ambient() because cluster variables - # do not have an inverse. - x1, x2 = self.ambient().gens() - if a1 < 0: - if a2 < 0: - return self.retract(x1 ** (-a1) * x2 ** (-a2)) - else: - return self.retract(x1 ** (-a1) * ((1 + x2 ** c) / x1) ** a2) - elif a2 < 0: - return self.retract(((1 + x1 ** b) / x2) ** a1 * x2 ** (-a2)) - output = 0 - for p in range(a2 + 1): - for q in range(a1 + 1): - output += self._greedy_coefficient(d_vector, p, q) * x1 ** (b * p) * x2 ** (c * q) - return self.retract(x1 ** (-a1) * x2 ** (-a2) * output) + @cached_method(key=lambda a, b: tuple(b)) + def theta_basis_element(self, g_vector): + r""" + Return the element of the theta basis of ``self`` with g-vector ``g_vector``. + + INPUT: + + - ``g_vector`` -- tuple; the g-vector of the element to compute + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,-3],[2,0]]), principal_coefficients=True) + sage: A.theta_basis_element((-1,-1)) + (x1^8*y0^4*y1 + 4*x1^6*y0^3*y1 + 6*x1^4*y0^2*y1 + x0^3*x1^2*y0 + 4*x1^2*y0*y1 + x0^3 + y1)/(x0^4*x1) + + sage: A = ClusterAlgebra(['F', 4]) + sage: A.theta_basis_element((1, 0, 0, 0)) + Traceback (most recent call last): + ... + NotImplementedError: Currently only implemented for cluster algebras of rank 2. + + .. NOTE:: + + Elements of the theta basis correspond with the associated cluster + monomial only for appropriate coefficient choices. For example:: + + sage: A = ClusterAlgebra(matrix([[0,-1],[1,0],[-1,0]])) + sage: A.theta_basis_element((-1,0)) + (x1 + y0)/(x0*y0) + + while:: + + sage: _ = A.find_g_vector((-1,0)); + sage: A.cluster_variable((-1,0)) + (x1 + y0)/x0 + + In particular theta basis elements do not satisfy a separation of additions formula. + + .. WARNING:: + + Currently only cluster algebras of rank 2 are supported + + .. SEEALSO:: + + :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` + """ + g_vector = tuple(g_vector) + F = self.theta_basis_F_polynomial(g_vector).subs(self._yhat) + g_mon = prod(self.ambient().gen(i) ** g_vector[i] for i in range(self.rank())) + # we only return the monomal g_mon times the evaluated F-polynomial because this is how + # theta basis elements behave. + return self.retract(g_mon * F) + + @cached_method(key=lambda a, b: tuple(b)) + def theta_basis_F_polynomial(self, g_vector): + r""" + Return the F-polynomial of the element of the theta basis of ``self`` with g-vector ``g_vector``. + + INPUT: + + - ``g_vector`` -- tuple; the g-vector of the F-polynomial to compute + + .. WARNING:: + + Elements of the theta basis do not satisfy a separation of additions formula. + See the implementation of :meth:`sage.algebras.cluster_algebra.theta_basis_F_polynomial` + for further details. + + ALGORITHM: + This method uses the fact that the greedy basis and the theta basis + coincide in rank 2 and uses the former defining recursion (Equation + (1.5) from [LLZ2014]_) to compute. + + EXAMPLES:: + + sage: A = ClusterAlgebra(matrix([[0,-3],[2,0]]), principal_coefficients=True) + sage: A.theta_basis_F_polynomial((-1,-1)) + u0^4*u1 + 4*u0^3*u1 + 6*u0^2*u1 + 4*u0*u1 + u0 + u1 + 1 + + sage: A = ClusterAlgebra(['F', 4]) + sage: A.theta_basis_F_polynomial((1, 0, 0, 0)) + Traceback (most recent call last): + ... + NotImplementedError: Currently only implemented for cluster algebras of rank 2. + """ + if self.rank() != 2: + raise NotImplementedError("Currently only implemented for cluster algebras of rank 2.") + + # extract the part of g_vector not coming from the initial cluster + d = tuple( max(x, 0) for x in self.g_vector_to_d_vector(g_vector) ) + g = self.d_vector_to_g_vector(d) + + shifts = ((d[0]+g[0])/self._B0[0, 1], (d[1]+g[1])/self._B0[1, 0] ) + signs = ( sign(self._B0[0, 1]), sign(self._B0[1, 0]) ) + + u = list(self._U.gens()) + output = self._U.zero() + for p in range(0, d[1] + 1): + for q in range(0, d[0] + 1): + output += self._greedy_coefficient(d, p, q) * u[1] ** (signs[0]*p - shifts[0]) * u[0] ** (signs[1]*q - shifts[1]) + return output + + @cached_method def _greedy_coefficient(self, d_vector, p, q): r""" Return the coefficient of the monomial ``x1 ** (b * p) * x2 ** (c * q)`` @@ -2431,8 +2690,8 @@ def _greedy_coefficient(self, d_vector, p, q): sage: A._greedy_coefficient((1, 1), 1, 0) 1 """ - b = abs(self.b_matrix()[0, 1]) - c = abs(self.b_matrix()[1, 0]) + b = abs(self._B0[0, 1]) + c = abs(self._B0[1, 0]) a1, a2 = d_vector p = Integer(p) q = Integer(q) @@ -2495,17 +2754,3 @@ def lower_bound(self): NotImplementedError: not implemented yet """ raise NotImplementedError("not implemented yet") - - def theta_basis_element(self, g_vector): - r""" - Return the element of the theta basis with g-vector ``g_vector``. - - EXAMPLES:: - - sage: A = ClusterAlgebra(['F', 4]) - sage: A.theta_basis_element((1, 0, 0, 0)) - Traceback (most recent call last): - ... - NotImplementedError: not implemented yet - """ - raise NotImplementedError("not implemented yet") diff --git a/src/sage/algebras/commutative_dga.py b/src/sage/algebras/commutative_dga.py index d430f3d88df..acf83f4d8ba 100644 --- a/src/sage/algebras/commutative_dga.py +++ b/src/sage/algebras/commutative_dga.py @@ -78,6 +78,7 @@ from sage.misc.inherit_comparison import InheritComparisonClasscallMetaclass from sage.misc.functional import is_odd, is_even from sage.misc.misc_c import prod +from sage.categories.chain_complexes import ChainComplexes from sage.categories.algebras import Algebras from sage.categories.morphism import Morphism from sage.categories.modules import Modules @@ -545,6 +546,8 @@ def cohomology(self, n): sorting_key=sorting_keys, monomial_reverse=True) + homology = cohomology + def _is_nonzero(self): """ Return ``True`` iff this morphism is nonzero. @@ -837,6 +840,8 @@ def cohomology(self, n, total=False): sorting_key=sorting_keys, monomial_reverse=True) + homology = cohomology + ########################################################### # Commutative graded algebras @@ -896,7 +901,7 @@ class GCAlgebra(UniqueRepresentation, QuotientRing_nc): """ # TODO: This should be a __classcall_private__? @staticmethod - def __classcall__(cls, base, names=None, degrees=None, R=None, I=None): + def __classcall__(cls, base, names=None, degrees=None, R=None, I=None, category=None): r""" Normalize the input for the :meth:`__init__` method and the unique representation. @@ -995,9 +1000,10 @@ def __classcall__(cls, base, names=None, degrees=None, R=None, I=None): side='twosided') return super(GCAlgebra, cls).__classcall__(cls, base=base, names=names, - degrees=degrees, R=R, I=I) + degrees=degrees, R=R, I=I, + category=category) - def __init__(self, base, R=None, I=None, names=None, degrees=None): + def __init__(self, base, R=None, I=None, names=None, degrees=None, category=None): """ Initialize ``self``. @@ -1027,8 +1033,8 @@ def __init__(self, base, R=None, I=None, names=None, degrees=None): sage: TestSuite(A).run() """ self._degrees = tuple(degrees) - cat = Algebras(R.base_ring()).Graded() - QuotientRing_nc.__init__(self, R, I, names, category=cat) + category = Algebras(R.base_ring()).Graded().or_subcategory(category) + QuotientRing_nc.__init__(self, R, I, names, category=category) def _repr_(self): """ @@ -1660,7 +1666,7 @@ class GCAlgebra_multigraded(GCAlgebra): sage: c.degree(total=True) 2 """ - def __init__(self, base, degrees, names=None, R=None, I=None): + def __init__(self, base, degrees, names=None, R=None, I=None, category=None): """ Initialize ``self``. @@ -1674,7 +1680,8 @@ def __init__(self, base, degrees, names=None, R=None, I=None): sage: TestSuite(C).run() """ total_degs = [total_degree(d) for d in degrees] - GCAlgebra.__init__(self, base, R=R, I=I, names=names, degrees=total_degs) + GCAlgebra.__init__(self, base, R=R, I=I, names=names, + degrees=total_degs, category=category) self._degrees_multi = degrees self._grading_rank = len(list(degrees[0])) @@ -2001,10 +2008,10 @@ def __init__(self, A, differential): ... ValueError: The given dictionary does not determine a valid differential """ + cat = Algebras(A.base()).Graded() & ChainComplexes(A.base()) GCAlgebra.__init__(self, A.base(), names=A._names, - degrees=A._degrees, - R=A.cover_ring(), - I=A.defining_ideal()) + degrees=A._degrees, R=A.cover_ring(), + I=A.defining_ideal(), category=cat) self._differential = Differential(self, differential._dic_) self._minimalmodels = {} self._numerical_invariants = {} @@ -2255,6 +2262,8 @@ def cohomology(self, n): """ return self._differential.cohomology(n) + homology = cohomology + def cohomology_generators(self, max_degree): """ Return lifts of algebra generators for cohomology in degrees at @@ -3069,10 +3078,11 @@ def __init__(self, A, differential): ... ValueError: The differential does not have a well-defined degree """ + cat = Algebras(A.base()).Graded() & ChainComplexes(A.base()) GCAlgebra_multigraded.__init__(self, A.base(), names=A._names, degrees=A._degrees_multi, - R=A.cover_ring(), - I=A.defining_ideal()) + R=A.cover_ring(), I=A.defining_ideal(), + category=cat) self._differential = Differential_multigraded(self, differential._dic_) def _base_repr(self): @@ -3228,6 +3238,8 @@ def cohomology(self, n, total=False): """ return self._differential.cohomology(n, total) + homology = cohomology + class Element(GCAlgebra_multigraded.Element, DifferentialGCAlgebra.Element): """ Element class of a commutative differential multi-graded algebra. diff --git a/src/sage/algebras/free_algebra_quotient_element.py b/src/sage/algebras/free_algebra_quotient_element.py index 9e291010fa2..0b364492741 100644 --- a/src/sage/algebras/free_algebra_quotient_element.py +++ b/src/sage/algebras/free_algebra_quotient_element.py @@ -244,7 +244,8 @@ def monomial_product(X,w,m): mats = X._FreeAlgebraQuotient__matrix_action for (j,k) in m._element_list: M = mats[int(j)] - for l in range(k): w *= M + for l in range(k): + w *= M return w u = self.__vector.__copy__() v = y.__vector @@ -252,7 +253,8 @@ def monomial_product(X,w,m): B = A.monomial_basis() for i in range(A.dimension()): c = v[i] - if c != 0: z.__vector += monomial_product(A,c*u,B[i]) + if c != 0: + z.__vector += monomial_product(A,c*u,B[i]) return z def _rmul_(self, c): diff --git a/src/sage/algebras/lie_algebras/nilpotent_lie_algebra.py b/src/sage/algebras/lie_algebras/nilpotent_lie_algebra.py index d2919917d33..54a0fb97d2a 100644 --- a/src/sage/algebras/lie_algebras/nilpotent_lie_algebra.py +++ b/src/sage/algebras/lie_algebras/nilpotent_lie_algebra.py @@ -113,10 +113,13 @@ def __classcall_private__(cls, R, s_coeff, names=None, index_set=None, # extract names from structural coefficients names = [] for (X, Y), d in s_coeff.items(): - if X not in names: names.append(X) - if Y not in names: names.append(Y) + if X not in names: + names.append(X) + if Y not in names: + names.append(Y) for k in d: - if k not in names: names.append(k) + if k not in names: + names.append(k) from sage.structure.indexed_generators import standardize_names_index_set names, index_set = standardize_names_index_set(names, index_set) diff --git a/src/sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py b/src/sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py index 45849316534..2e37b63ef9a 100644 --- a/src/sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py +++ b/src/sage/algebras/lie_conformal_algebras/free_bosons_lie_conformal_algebra.py @@ -121,8 +121,8 @@ def __init__(self, R, ngens=None, gram_matrix=None, names=None, "{0} x {0} matrix, got {1}".format(ngens,gram_matrix)) else: if ngens is None: - ngens = 1; - gram_matrix = identity_matrix(R,ngens,ngens) + ngens = 1 + gram_matrix = identity_matrix(R, ngens, ngens) latex_names = None if (names is None) and (index_set is None): diff --git a/src/sage/all.py b/src/sage/all.py index 52d59fadf2e..e3813c2adab 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -183,6 +183,7 @@ from sage.dynamics.all import * from sage.homology.all import * +from sage.topology.all import * from sage.quadratic_forms.all import * diff --git a/src/sage/arith/misc.py b/src/sage/arith/misc.py index 173baa3b793..76a3f2f7f8d 100644 --- a/src/sage/arith/misc.py +++ b/src/sage/arith/misc.py @@ -1337,21 +1337,30 @@ def random_prime(n, proof=None, lbound=2): EXAMPLES:: - sage: random_prime(100000) - 30029 + sage: p = random_prime(100000) + sage: p.is_prime() + True + sage: p <= 100000 + True sage: random_prime(2) 2 Here we generate a random prime between 100 and 200:: - sage: random_prime(200, lbound=100) - 167 + sage: p = random_prime(200, lbound=100) + sage: p.is_prime() + True + sage: 100 <= p <= 200 + True If all we care about is finding a pseudo prime, then we can pass in ``proof=False`` :: - sage: random_prime(200, proof=False, lbound=100) - 197 + sage: p = random_prime(200, proof=False, lbound=100) + sage: p.is_pseudoprime() + True + sage: 100 <= p <= 200 + True TESTS:: @@ -1458,13 +1467,13 @@ def divisors(n): sage: K. = QuadraticField(7) sage: divisors(K.ideal(7)) - [Fractional ideal (1), Fractional ideal (a), Fractional ideal (7)] + [Fractional ideal (1), Fractional ideal (-a), Fractional ideal (7)] sage: divisors(K.ideal(3)) [Fractional ideal (1), Fractional ideal (3), Fractional ideal (-a + 2), Fractional ideal (-a - 2)] sage: divisors(K.ideal(35)) - [Fractional ideal (1), Fractional ideal (5), Fractional ideal (a), - Fractional ideal (7), Fractional ideal (5*a), Fractional ideal (35)] + [Fractional ideal (1), Fractional ideal (5), Fractional ideal (-a), + Fractional ideal (7), Fractional ideal (-5*a), Fractional ideal (35)] TESTS:: @@ -2810,7 +2819,8 @@ def is_square(n, root=False): sage: is_square((x-1)^2) Traceback (most recent call last): ... - NotImplementedError: is_square() not implemented for non numeric elements of Symbolic Ring + NotImplementedError: is_square() not implemented for + non-constant or relational elements of Symbolic Ring :: @@ -5613,11 +5623,12 @@ def sort_complex_numbers_for_display(nums): r""" Given a list of complex numbers (or a list of tuples, where the first element of each tuple is a complex number), we sort the list - in a "pretty" order. First come the real numbers (with zero - imaginary part), then the complex numbers sorted according to - their real part. If two complex numbers have the same real part, - then they are sorted according to their - imaginary part. + in a "pretty" order. + + Real numbers (with a zero imaginary part) come before complex numbers, + and are sorted. Complex numbers are sorted by their real part + unless their real parts are quite close, in which case they are + sorted by their imaginary part. This is not a useful function mathematically (not least because there is no principled way to determine whether the real components @@ -5639,8 +5650,13 @@ def sort_complex_numbers_for_display(nums): ....: nums.append(CDF(i + RDF.random_element(-3e-11, 3e-11), ....: RDF.random_element())) sage: shuffle(nums) - sage: sort_c(nums) - [0.0, 1.0, 2.0, -2.862406201002009e-11 - 0.7088740263015161*I, 2.2108362706985576e-11 - 0.43681052967509904*I, 1.0000000000138833 - 0.7587654737635712*I, 0.9999999999760288 - 0.7238965893336062*I, 1.9999999999874383 - 0.4560801012073723*I, 1.9999999999869107 + 0.6090836283134269*I] + sage: nums = sort_c(nums) + sage: nums[:3] + [0.0, 1.0, 2.0] + sage: for i in range(3, len(nums)-1): + ....: assert nums[i].real() <= nums[i+1].real() + 1e-10 + ....: if abs(nums[i].real() - nums[i+1].real()) < 1e-10: + ....: assert nums[i].imag() <= nums[i+1].imag() + 1e-10 """ if not nums: return nums diff --git a/src/sage/arith/multi_modular.pyx b/src/sage/arith/multi_modular.pyx index e84720a7891..cdde67af2d0 100644 --- a/src/sage/arith/multi_modular.pyx +++ b/src/sage/arith/multi_modular.pyx @@ -55,14 +55,9 @@ cdef class MultiModularBasis_base(object): sage: height = 52348798724 sage: mm = MultiModularBasis_base(height); mm - MultiModularBasis with moduli [31051, 16981, 6007] - sage: mm = MultiModularBasis_base(height); mm - MultiModularBasis with moduli [21419, 13751, 15901] - sage: mm = MultiModularBasis_base(height); mm - MultiModularBasis with moduli [14369, 31379, 10067] - - sage: mm.prod()//height - 86 + MultiModularBasis with moduli [...] + sage: mm.prod() >= 2*height + True TESTS:: @@ -116,7 +111,7 @@ cdef class MultiModularBasis_base(object): sage: from sage.arith.multi_modular import MultiModularBasis_base sage: mm = MultiModularBasis_base(1099511627791); mm - MultiModularBasis with moduli [31051, 16981, 6007] + MultiModularBasis with moduli [...] sage: del mm """ sig_free(self.moduli) @@ -164,7 +159,7 @@ cdef class MultiModularBasis_base(object): OverflowError: given modulus 1000000000000000000000000000057 is larger than 3037000498 sage: mm = MultiModularBasis_base(0); mm - MultiModularBasis with moduli [6007] + MultiModularBasis with moduli [...] sage: mm = MultiModularBasis_base([6, 10]) Traceback (most recent call last): @@ -312,17 +307,24 @@ cdef class MultiModularBasis_base(object): sage: from sage.arith.multi_modular import MultiModularBasis_base sage: mm = MultiModularBasis_base(0); mm - MultiModularBasis with moduli [31051] + MultiModularBasis with moduli [...] + sage: p = mm[0] sage: mm._extend_moduli_to_height(70000) sage: mm - MultiModularBasis with moduli [31051, 16981] + MultiModularBasis with moduli [...] + sage: p == mm[0] + True + sage: mm.prod() >= 2*70000 + True sage: mm = MultiModularBasis_base([46307]); mm MultiModularBasis with moduli [46307] sage: mm._extend_moduli_to_height(10^30); mm - MultiModularBasis with moduli [46307, 6007, 21419, 13751, 15901, 14369, 31379, 10067] + MultiModularBasis with moduli [...] + sage: mm.prod() >= 2*10^30 + True TESTS: @@ -408,7 +410,9 @@ cdef class MultiModularBasis_base(object): sage: mm._extend_moduli_to_count(3) 3 sage: mm - MultiModularBasis with moduli [46307, 31051, 16981] + MultiModularBasis with moduli [...] + sage: len(mm) + 3 """ if count <= self.n: return self.n @@ -433,8 +437,12 @@ cdef class MultiModularBasis_base(object): sage: from sage.arith.multi_modular import MultiModularBasis_base sage: mm = MultiModularBasis_base([46307]); mm MultiModularBasis with moduli [46307] - sage: mm._extend_moduli(2); mm + sage: mm._extend_moduli(2); mm # random MultiModularBasis with moduli [46307, 31051, 16981] + sage: mm[0] + 46307 + sage: all(p.is_prime() for p in mm) + True """ self._extend_moduli_to_count(self.n + count) @@ -930,11 +938,11 @@ cdef class MutableMultiModularBasis(MultiModularBasis): sage: from sage.arith.multi_modular import MutableMultiModularBasis sage: mm = MutableMultiModularBasis([10007]) - sage: mm.next_prime() - 31051 # 64-bit - 31051L # 32-bit - sage: mm - MultiModularBasis with moduli [10007, 31051] + sage: p = mm.next_prime() + sage: p > 10007 + True + sage: mm.list() == [10007, p] + True """ self._extend_moduli(1) return self.moduli[self.n-1] @@ -956,23 +964,24 @@ cdef class MutableMultiModularBasis(MultiModularBasis): sage: mm = MutableMultiModularBasis([10007, 10009, 10037, 10039]) sage: mm MultiModularBasis with moduli [10007, 10009, 10037, 10039] - sage: mm.prod() + sage: prev_prod = mm.prod(); prev_prod 10092272478850909 sage: mm.precomputation_list() [1, 5004, 6536, 6060] sage: mm.partial_product(2) 1005306552331 - sage: mm.replace_prime(1) - 31051 # 64-bit - 31051L # 32-bit - sage: mm - MultiModularBasis with moduli [10007, 31051, 10037, 10039] - sage: mm.prod() - 31309336870896151 - sage: mm.precomputation_list() - [1, 17274, 1770, 2170] - sage: mm.partial_product(2) - 3118770482209 + sage: p = mm.replace_prime(1) + sage: mm.list() == [10007, p, 10037, 10039] + True + sage: mm.prod()*10009 == prev_prod*p + True + sage: precomputed = mm.precomputation_list() + sage: precomputed == [prod(Integers(mm[i])(1 / mm[j]) + ....: for j in range(i)) + ....: for i in range(4)] + True + sage: mm.partial_product(2) == prod(mm.list()[:3]) + True """ cdef mod_int new_p diff --git a/src/sage/calculus/calculus.py b/src/sage/calculus/calculus.py index 6109e18c45f..58376f6d530 100644 --- a/src/sage/calculus/calculus.py +++ b/src/sage/calculus/calculus.py @@ -545,7 +545,7 @@ def symbolic_sum(expression, v, a, b, algorithm='maxima', hold=False): sage: assumptions() # check the assumptions were really forgotten [] - This summation only Mathematica can perform:: + A summation performed by Mathematica:: sage: symbolic_sum(1/(1+k^2), k, -oo, oo, algorithm = 'mathematica') # optional - mathematica pi*coth(pi) @@ -555,13 +555,10 @@ def symbolic_sum(expression, v, a, b, algorithm='maxima', hold=False): sage: symbolic_sum(1/(1+k^2), k, -oo, oo, algorithm = 'giac') (pi*e^(2*pi) - pi*e^(-2*pi))/(e^(2*pi) + e^(-2*pi) - 2) - SymPy can't solve that summation:: + The same summation is solved by SymPy:: sage: symbolic_sum(1/(1+k^2), k, -oo, oo, algorithm = 'sympy') - Traceback (most recent call last): - ... - AttributeError: Unable to convert SymPy result (=Sum(1/(k**2 + 1), - (k, -oo, oo))) into Sage + pi/tanh(pi) SymPy and Maxima 5.39.0 can do the following (see :trac:`22005`):: @@ -1563,12 +1560,12 @@ def laplace(ex, t, s, algorithm='maxima'): sage: inverse_laplace(L, s, t) t*e^(a + 2*t)*sin(t) - Unable to compute solution with Maxima:: + Heaviside step function can be handled with different interfaces. + Try with Maxima:: sage: laplace(heaviside(t-1), t, s) - laplace(heaviside(t - 1), t, s) + e^(-s)/s - Heaviside step function can be handled with different interfaces. Try with giac:: sage: laplace(heaviside(t-1), t, s, algorithm='giac') @@ -2267,8 +2264,8 @@ def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima): # use a global flag so all expressions obtained via # evaluation of maxima code are assumed pre-simplified is_simplified = True - parser_make_Mvar.set_names(var_syms) - parser_make_function.set_names(function_syms) + SRM_parser._variable_constructor().set_names(var_syms) + SRM_parser._callable_constructor().set_names(function_syms) return SRM_parser.parse_sequence(s) except SyntaxError: raise TypeError("unable to make sense of Maxima expression '%s' in Sage" % s) @@ -2329,7 +2326,7 @@ def maxima_options(**kwds): -def _find_var(name): +def _find_var(name, interface=None): """ Function to pass to Parser for constructing variables from strings. For internal use. @@ -2341,10 +2338,21 @@ def _find_var(name): y sage: sage.calculus.calculus._find_var('I') I + sage: sage.calculus.calculus._find_var(repr(maxima(y)), interface='maxima') + y + sage: sage.calculus.calculus._find_var(repr(giac(y)), interface='giac') + y """ - v = SR.symbols.get(name) - if v is not None: - return v + if interface == 'maxima': + if name.startswith("_SAGE_VAR_"): + return var(name[10:]) + elif interface == 'giac': + if name.startswith('sageVAR'): + return var(name[7:]) + else: + v = SR.symbols.get(name) + if v is not None: + return v # try to find the name in the global namespace # needed for identifiers like 'e', etc. @@ -2397,7 +2405,8 @@ def _find_func(name, create_when_missing=True): make_var = parser_make_var, make_function = parser_make_function) -def symbolic_expression_from_string(s, syms={}, accept_sequence=False): + +def symbolic_expression_from_string(s, syms=None, accept_sequence=False, *, parser=None): """ Given a string, (attempt to) parse it and return the corresponding Sage symbolic expression. Normally used @@ -2414,6 +2423,8 @@ def symbolic_expression_from_string(s, syms={}, accept_sequence=False): to allow a (possibly nested) set of lists and tuples as input + - ``parser`` -- (default: ``SR_parser``) parser for internal use + EXAMPLES:: sage: y = var('y') @@ -2428,42 +2439,34 @@ def symbolic_expression_from_string(s, syms={}, accept_sequence=False): 0.3333333333333333333333333333 sage: sage.calculus.calculus.symbolic_expression_from_string(str(RealField(100)(10^-500/3))) 3.333333333333333333333333333e-501 - """ - parse_func = SR_parser.parse_sequence if accept_sequence else SR_parser.parse_expression - parser_make_var.set_names({k: v for k, v in syms.items() - if not _is_function(v)}) - parser_make_function.set_names({k: v for k, v in syms.items() - if _is_function(v)}) - return parse_func(s) - - -def _find_Mvar(name): - """ - Function to pass to Parser for constructing - variables from strings. For internal use. - EXAMPLES:: + The Giac interface uses a different parser (:trac:`30133`):: - sage: y = var('y') - sage: sage.calculus.calculus._find_var('y') - y - sage: sage.calculus.calculus._find_var('I') - I + sage: from sage.calculus.calculus import SR_parser_giac + sage: sage.calculus.calculus.symbolic_expression_from_string(repr(giac(SR.var('e'))), parser=SR_parser_giac) + e """ - if name[:10] == "_SAGE_VAR_": - return var(name[10:]) + if syms is None: + syms = {} + if parser is None: + parser = SR_parser + parse_func = parser.parse_sequence if accept_sequence else parser.parse_expression + # this assumes that the parser has constructors of type `LookupNameMaker` + parser._variable_constructor().set_names({k: v for k, v in syms.items() + if not _is_function(v)}) + parser._callable_constructor().set_names({k: v for k, v in syms.items() + if _is_function(v)}) + return parse_func(s) - # try to find the name in the global namespace - # needed for identifiers like 'e', etc. - import sage.all - try: - return SR(sage.all.__dict__[name]) - except (KeyError, TypeError): - return var(name) -parser_make_Mvar = LookupNameMaker({}, fallback=_find_Mvar) +parser_make_Mvar = LookupNameMaker({}, fallback=lambda x: _find_var(x, interface='maxima')) SRM_parser = Parser(make_int = lambda x: SR(Integer(x)), make_float = lambda x: SR(RealDoubleElement(x)), make_var = parser_make_Mvar, make_function = parser_make_function) + +SR_parser_giac = Parser(make_int = lambda x: SR(Integer(x)), + make_float = lambda x: SR(create_RealNumber(x)), + make_var = LookupNameMaker({}, fallback=lambda x: _find_var(x, interface='giac')), + make_function = parser_make_function) diff --git a/src/sage/calculus/desolvers.py b/src/sage/calculus/desolvers.py index ea0688c80c0..b180e504016 100644 --- a/src/sage/calculus/desolvers.py +++ b/src/sage/calculus/desolvers.py @@ -78,7 +78,7 @@ from sage.interfaces.maxima import Maxima from sage.plot.all import line from sage.symbolic.expression import is_SymbolicEquation -from sage.symbolic.ring import is_SymbolicVariable +from sage.symbolic.ring import SR, is_SymbolicVariable from sage.calculus.functional import diff from sage.misc.functional import N from sage.rings.real_mpfr import RealField @@ -1351,6 +1351,9 @@ def desolve_rk4(de, dvar, ics=None, ivar=None, end_points=None, step=0.1, output if ics is None: raise ValueError("No initial conditions, specify with ics=[x0,y0].") + if output not in ['list', 'plot', 'slope_field']: + raise ValueError("Option output should be 'list', 'plot' or 'slope_field'.") + if ivar is None: ivars = de.variables() ivars = [t for t in ivars if t != dvar] @@ -1358,66 +1361,65 @@ def desolve_rk4(de, dvar, ics=None, ivar=None, end_points=None, step=0.1, output raise ValueError("Unable to determine independent variable, please specify.") ivar = ivars[0] + step=abs(step) + + def desolve_rk4_inner(de, dvar): + de0=de._maxima_() + maxima("load('dynamics)") + lower_bound,upper_bound=desolve_rk4_determine_bounds(ics,end_points) + sol_1, sol_2 = [],[] + if lower_boundics[0]: + cmd="rk(%s,%s,%s,[%s,%s,%s,%s])\ + "%(de0.str(),'_SAGE_VAR_'+str(dvar),str(ics[1]),'_SAGE_VAR_'+str(ivar),str(ics[0]),upper_bound,step) + sol_2=maxima(cmd).sage() + sol_2.pop(0) + sol=sol_1 + sol.extend([[ics[0],ics[1]]]) + sol.extend(sol_2) + + if output == 'list': + return sol + from sage.plot.plot import list_plot + from sage.plot.plot_field import plot_slope_field + R = list_plot(sol, plotjoined=True, **kwds) + if output == 'plot': + return R + if output == 'slope_field': + XMIN = sol[0][0] + YMIN = sol[0][1] + XMAX = XMIN + YMAX = YMIN + for s, t in sol: + if s > XMAX: + XMAX = s + if s < XMIN: + XMIN = s + if t > YMAX: + YMAX = t + if t < YMIN: + YMIN = t + return plot_slope_field(de, (ivar,XMIN,XMAX), (dvar,YMIN,YMAX))+R + if not is_SymbolicVariable(dvar): from sage.symbolic.ring import SR from sage.calculus.all import diff from sage.symbolic.relation import solve if is_SymbolicEquation(de): de = de.lhs() - de.rhs() - dummy_dvar = SR.var('dummy_dvar') # consider to add warning if the solution is not unique de=solve(de,diff(dvar,ivar),solution_dict=True) if len(de) != 1: raise NotImplementedError("Sorry, cannot find explicit formula for right-hand side of the ODE.") - de=de[0][diff(dvar,ivar)].subs(dvar==dummy_dvar) + with SR.temp_var() as dummy_dvar: + return desolve_rk4_inner(de[0][diff(dvar,ivar)].subs({dvar:dummy_dvar}), dummy_dvar) else: - dummy_dvar=dvar - - step=abs(step) - de0=de._maxima_() - maxima("load('dynamics)") - lower_bound,upper_bound=desolve_rk4_determine_bounds(ics,end_points) - sol_1, sol_2 = [],[] - if lower_boundics[0]: - cmd="rk(%s,%s,%s,[%s,%s,%s,%s])\ - "%(de0.str(),'_SAGE_VAR_'+str(dummy_dvar),str(ics[1]),'_SAGE_VAR_'+str(ivar),str(ics[0]),upper_bound,step) - sol_2=maxima(cmd).sage() - sol_2.pop(0) - sol=sol_1 - sol.extend([[ics[0],ics[1]]]) - sol.extend(sol_2) - - if output == 'list': - return sol - from sage.plot.plot import list_plot - from sage.plot.plot_field import plot_slope_field - R = list_plot(sol, plotjoined=True, **kwds) - if output == 'plot': - return R - if output == 'slope_field': - XMIN = sol[0][0] - YMIN = sol[0][1] - XMAX = XMIN - YMAX = YMIN - for s, t in sol: - if s > XMAX: - XMAX = s - if s < XMIN: - XMIN = s - if t > YMAX: - YMAX = t - if t < YMIN: - YMIN = t - return plot_slope_field(de, (ivar,XMIN,XMAX), (dummy_dvar,YMIN,YMAX))+R - - raise ValueError("Option output should be 'list', 'plot' or 'slope_field'.") - + return desolve_rk4_inner(de, dvar) def desolve_system_rk4(des, vars, ics=None, ivar=None, end_points=None, step=0.1): r""" @@ -1655,6 +1657,49 @@ def desolve_odeint(des, ics, times, dvars, ivar=None, compute_jac=False, args=() from sage.ext.fast_eval import fast_float from sage.calculus.functions import jacobian + def desolve_odeint_inner(ivar): + # one-dimensional systems: + if is_SymbolicVariable(dvars): + func = fast_float(des, dvars, ivar) + if not compute_jac: + Dfun = None + else: + J = diff(des, dvars) + J = fast_float(J, dvars, ivar) + + def Dfun(y, t): + return [J(y, t)] + + # n-dimensional systems: + else: + desc = [] + variabs = dvars[:] + variabs.append(ivar) + for de in des: + desc.append(fast_float(de,*variabs)) + + def func(y,t): + v = list(y[:]) + v.append(t) + return [dec(*v) for dec in desc] + + if not compute_jac: + Dfun=None + else: + J = jacobian(des,dvars) + J = [list(v) for v in J] + J = fast_float(J,*variabs) + def Dfun(y,t): + v = list(y[:]) + v.append(t) + return [[element(*v) for element in row] for row in J] + + + sol=odeint(func, ics, times, args=args, Dfun=Dfun, rtol=rtol, atol=atol, + tcrit=tcrit, h0=h0, hmax=hmax, hmin=hmin, ixpr=ixpr, mxstep=mxstep, + mxhnil=mxhnil, mxordn=mxordn, mxords=mxords, printmessg=printmessg) + return sol + if ivar is None: if len(dvars)==0 or len(dvars)==1: if len(dvars)==1: @@ -1671,59 +1716,17 @@ def desolve_odeint(des, ics, times, dvars, ivar=None, compute_jac=False, args=() ivars = all_vars - set(dvars) if len(ivars)==1: - ivar = ivars.pop() + return desolve_odeint_inner(ivars[0]) elif not ivars: - try: - safe_names = [ 't_' + str(dvar) for dvar in dvars ] - except TypeError: # not iterable - safe_names = [ 't_' + str(dvars) ] - from sage.symbolic.ring import SR - ivar = [SR.var(name) for name in safe_names] + if is_SymbolicVariable(dvars): + with SR.temp_var() as ivar: + return desolve_odeint_inner(ivar) + else: + with SR.temp_var(n=len(dvars)) as ivar: + return desolve_odeint_inner(ivar) else: raise ValueError("Unable to determine independent variable, please specify.") - - # one-dimensional systems: - if is_SymbolicVariable(dvars): - func = fast_float(des, dvars, ivar) - if not compute_jac: - Dfun = None - else: - J = diff(des, dvars) - J = fast_float(J, dvars, ivar) - - def Dfun(y, t): - return [J(y, t)] - - # n-dimensional systems: - else: - desc = [] - variabs = dvars[:] - variabs.append(ivar) - for de in des: - desc.append(fast_float(de,*variabs)) - - def func(y,t): - v = list(y[:]) - v.append(t) - return [dec(*v) for dec in desc] - - if not compute_jac: - Dfun=None - else: - J = jacobian(des,dvars) - J = [list(v) for v in J] - J = fast_float(J,*variabs) - def Dfun(y,t): - v = list(y[:]) - v.append(t) - return [[element(*v) for element in row] for row in J] - - - sol=odeint(func, ics, times, args=args, Dfun=Dfun, rtol=rtol, atol=atol, - tcrit=tcrit, h0=h0, hmax=hmax, hmin=hmin, ixpr=ixpr, mxstep=mxstep, - mxhnil=mxhnil, mxordn=mxordn, mxords=mxords, printmessg=printmessg) - - return sol + return desolve_odeint_inner(ivar) def desolve_mintides(f, ics, initial, final, delta, tolrel=1e-16, tolabs=1e-16): r""" diff --git a/src/sage/calculus/functional.py b/src/sage/calculus/functional.py index cb11e909f3d..4410875cad2 100644 --- a/src/sage/calculus/functional.py +++ b/src/sage/calculus/functional.py @@ -27,7 +27,7 @@ sage: laplace( e^(x+a), x, a) e^a/(a - 1) sage: inverse_laplace( e^a/(a-1), x, a) - ilt(e^a/(a - 1), x, a) + dirac_delta(a)*e^a/(a - 1) """ from .calculus import SR diff --git a/src/sage/calculus/integration.pyx b/src/sage/calculus/integration.pyx index f76a6eeab81..93a3681854d 100644 --- a/src/sage/calculus/integration.pyx +++ b/src/sage/calculus/integration.pyx @@ -27,15 +27,15 @@ AUTHORS: # **************************************************************************** from cysignals.signals cimport sig_on, sig_off -from sage.rings.real_double import RDF +from memory_allocator cimport MemoryAllocator +import inspect +from sage.rings.real_double import RDF from sage.libs.gsl.all cimport * from sage.misc.sageinspect import sage_getargspec from sage.ext.fast_eval cimport FastDoubleFunc from sage.ext.interpreters.wrapper_rdf cimport Wrapper_rdf from sage.ext.fast_callable import fast_callable -from sage.ext.memory_allocator cimport MemoryAllocator -import inspect cdef class PyFunctionWrapper: diff --git a/src/sage/categories/all.py b/src/sage/categories/all.py index 1d85664c5fe..053fd577d24 100644 --- a/src/sage/categories/all.py +++ b/src/sage/categories/all.py @@ -4,7 +4,7 @@ from .category_types import Elements -from .chain_complexes import ChainComplexes +from .chain_complexes import ChainComplexes, HomologyFunctor from .simplicial_complexes import SimplicialComplexes diff --git a/src/sage/categories/chain_complexes.py b/src/sage/categories/chain_complexes.py index 72a4763b3b8..3b7d35ceef3 100644 --- a/src/sage/categories/chain_complexes.py +++ b/src/sage/categories/chain_complexes.py @@ -7,14 +7,17 @@ # 2009 Mike Hansen # 2013 Volker Braun # 2013, 2015 Travis Scrimshaw +# 2021 Michael Jung # # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.categories.category_types import Category_module +from .category_types import Category_module +from .commutative_additive_groups import CommutativeAdditiveGroups +from .functor import Functor +from sage.misc.abstract_method import abstract_method -# TODO: make this into a better category ############################################################# # ChainComplex ############################################################# @@ -26,13 +29,13 @@ class ChainComplexes(Category_module): sage: ChainComplexes(RationalField()) Category of chain complexes over Rational Field - sage: ChainComplexes(Integers(9)) Category of chain complexes over Ring of integers modulo 9 TESTS:: sage: TestSuite(ChainComplexes(RationalField())).run() + """ def super_categories(self): @@ -41,9 +44,222 @@ def super_categories(self): sage: ChainComplexes(Integers(9)).super_categories() [Category of modules over Ring of integers modulo 9] + """ from sage.categories.all import Fields, Modules, VectorSpaces base_ring = self.base_ring() if base_ring in Fields(): return [VectorSpaces(base_ring)] return [Modules(base_ring)] + + class ParentMethods: + @abstract_method + def homology(self, n=None): + r""" + Return the homology of the chain complex. + + INPUT: + + - ``n`` -- (default: ``None``) degree of the homology; if none is + provided, the direct sum homology will be used + + EXAMPLES:: + + sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}) + sage: C.homology(0) + Z x Z + sage: C.homology(1) + Z x C3 + sage: C.homology(2) + 0 + + :: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(2, 2, 3)) + sage: C = A.cdg_algebra({z: x*y}) + sage: C.homology(0) + Free module generated by {[1]} over Rational Field + sage: C.homology(1) + Free module generated by {} over Rational Field + sage: C.homology(2) + Free module generated by {[x], [y]} over Rational Field + sage: C.homology(3) + Free module generated by {} over Rational Field + sage: C.homology(4) + Free module generated by {[x^2], [y^2]} over Rational Field + + """ + + @abstract_method + def differential(self, *args, **kwargs): + r""" + Return the differentials (or boundary maps) of the chain complex. + + EXAMPLES:: + + sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}) + sage: C.differential(0) + [3 0 0] + [0 0 0] + + :: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(2, 2, 3)) + sage: C = A.cdg_algebra({z: x*y}) + sage: C.differential() + Differential of Commutative Differential Graded Algebra with generators ('x', 'y', 'z') in degrees (2, 2, 3) over Rational Field + Defn: x --> 0 + y --> 0 + z --> x*y + + """ + + @abstract_method(optional=True) + def lift_from_homology(self, x): + r""" + Lift the homology element ``x`` to the corresponding module. + + EXAMPLES:: + + sage: E3 = EuclideanSpace(3) + sage: C = E3.de_rham_complex() + sage: one = C.homology().one() + sage: C.lift_from_homology(one) + Mixed differential form one on the Euclidean space E^3 + + """ + + def reduce_to_homology(self, x, n=None): + r""" + Reduce a cycle to the corresponding quotient in homology. + + INPUT: + + - ``x`` -- a cycle + - ``n`` -- (default: ``None``) degree of the homology; if none is + provided, the direct sum homology will be used + + + EXAMPLES:: + + sage: E3 = EuclideanSpace(3) + sage: C = E3.de_rham_complex() + sage: one = C.one() + sage: C.reduce_to_homology(one) + [one] + + """ + try: + # try coercion + return self.homology(n)(x) + except TypeError: + # if not, this methods needs to be overwritten by parent + raise NotImplementedError + +class HomologyFunctor(Functor): + r""" + Homology functor. + + INPUT: + + - ``domain`` -- must be a category of chain complexes + - ``n`` -- (default: ``None``) degree of the homology; if none is provided, + the direct sum homology will be used + + EXAMPLES:: + + sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}) + sage: H = HomologyFunctor(ChainComplexes(ZZ), 1) + sage: H(C) + Z x C3 + + :: + + sage: A. = GradedCommutativeAlgebra(QQ, degrees=(2, 2, 3)) + sage: C = A.cdg_algebra({z: x*y}) + sage: H = HomologyFunctor(ChainComplexes(QQ), 2) + sage: H(C) + Free module generated by {[x], [y]} over Rational Field + + Applying to a chain map:: + + sage: S = simplicial_complexes.Sphere(1); S + Minimal triangulation of the 1-sphere + sage: C = S.chain_complex() + sage: C.differential() + {0: [], 1: [-1 -1 0] + [ 1 0 -1] + [ 0 1 1], 2: []} + sage: f = {0:zero_matrix(ZZ,3,3),1:zero_matrix(ZZ,3,3)} + sage: G = Hom(C,C) + sage: x = G(f) + sage: H = HomologyFunctor(ChainComplexes(ZZ), 1) + sage: H(C) + Z + sage: H(x) + Generic morphism: + From: Z + To: Z + + """ + def __init__(self, domain, n=None): + r""" + Construct the homology functor. + + TESTS:: + + sage: H = HomologyFunctor(ChainComplexes(QQ), 1); H + Functor from Category of chain complexes over Rational Field to + Category of commutative additive groups + + """ + if not isinstance(domain, ChainComplexes): + raise TypeError(f'{domain} must be a category of chain complexes') + codomain = CommutativeAdditiveGroups() + super().__init__(domain, codomain) + self._n = n + + def _apply_functor(self, x): + r""" + Apply ``self`` to a chain complex. + + TESTS:: + + sage: C = ChainComplex({0: matrix(ZZ, 2, 3, [3, 0, 0, 0, 0, 0])}) + sage: H = HomologyFunctor(ChainComplexes(ZZ), 1) + sage: H._apply_functor(C) + Z x C3 + + """ + return x.homology(self._n) + + def _apply_functor_to_morphism(self, f): + r""" + Apply ``self`` to a chain map. + + TESTS: + + sage: E3 = EuclideanSpace(3) + sage: C = E3.de_rham_complex() + sage: id = Hom(C, C).identity() + sage: H = HomologyFunctor(ChainComplexes(SR)) + sage: id_star = H(id); id_star + Generic endomorphism of De Rham cohomology ring on the + Euclidean space E^3 + sage: one = H(C).one() + sage: id_star(one) + [one] + + """ + from .morphism import SetMorphism + from .homset import Hom + + domain = f.domain() + codomain = f.codomain() + lift = domain.lift_from_homology + reduce = codomain.reduce_to_homology + apply_f_star = lambda x: reduce(f(lift(x)), self._n) + return SetMorphism(Hom(domain.homology(self._n), + codomain.homology(self._n), + CommutativeAdditiveGroups()), + apply_f_star) diff --git a/src/sage/categories/fields.py b/src/sage/categories/fields.py index d88743cc17f..17324a08b2e 100644 --- a/src/sage/categories/fields.py +++ b/src/sage/categories/fields.py @@ -500,18 +500,6 @@ def _squarefree_decomposition_univariate_polynomial(self, f): return Factorization(factors, unit=unit, sort=False) - def _pow_int(self, n): - r""" - Returns the vector space of dimension `n` over ``self``. - - EXAMPLES:: - - sage: QQ^4 - Vector space of dimension 4 over Rational Field - """ - from sage.modules.all import FreeModule - return FreeModule(self, n) - def vector_space(self, *args, **kwds): r""" Gives an isomorphism of this field with a vector space over a subfield. diff --git a/src/sage/categories/finite_monoids.py b/src/sage/categories/finite_monoids.py index ffc959d3b6b..69e4a6bc83e 100644 --- a/src/sage/categories/finite_monoids.py +++ b/src/sage/categories/finite_monoids.py @@ -157,7 +157,7 @@ def nerve(self): 3: Vector space of dimension 1 over Finite Field of size 5, 4: Vector space of dimension 1 over Finite Field of size 5} """ - from sage.homology.simplicial_set_examples import Nerve + from sage.topology.simplicial_set_examples import Nerve return Nerve(self) def rhodes_radical_congruence(self, base_ring=None): diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index 214b9d22733..6ce57f2217e 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -148,7 +148,7 @@ def highest_weight_vector(self): sage: C.highest_weight_vector() 1 """ - hw = self.highest_weight_vectors(); + hw = self.highest_weight_vectors() if len(hw) == 1: return hw[0] else: diff --git a/src/sage/categories/loop_crystals.py b/src/sage/categories/loop_crystals.py index 784ea2b3b43..06bd9f55a17 100644 --- a/src/sage/categories/loop_crystals.py +++ b/src/sage/categories/loop_crystals.py @@ -119,8 +119,11 @@ def digraph(self, subset=None, index_set=None): """ G = Crystals().parent_class.digraph(self, subset, index_set) if have_dot2tex(): - f = lambda u_v_label: ({"backward": u_v_label[2] == 0}) - G.set_latex_options(edge_options=f) + def eopt(u_v_label): + if u_v_label[2] == 0: + return {"dir": "back"} + return {} + G.set_latex_options(edge_options=eopt) return G # TODO: Should we make "regular" an axiom? diff --git a/src/sage/categories/pushout.py b/src/sage/categories/pushout.py index 7120901bfd2..7a15885b609 100644 --- a/src/sage/categories/pushout.py +++ b/src/sage/categories/pushout.py @@ -3055,7 +3055,7 @@ def __init__(self, polys, names, embeddings=None, structures=None, sage: F2(GF(5)) Traceback (most recent call last): ... - NotImplementedError: ring extension with prescripted embedding is not implemented + NotImplementedError: ring extension with prescribed embedding is not implemented When applying a number field constructor to the ring of integers, an order (not necessarily maximal) of that field is diff --git a/src/sage/categories/rings.py b/src/sage/categories/rings.py index ea02c088a8b..cd3ff689314 100644 --- a/src/sage/categories/rings.py +++ b/src/sage/categories/rings.py @@ -510,6 +510,29 @@ def _mul_(self, x, switch_sides=False): # duck typing failed raise TypeError("Don't know how to transform %s into an ideal of %s"%(x,self)) + def __pow__(self, n): + """ + Return the free module of rank `n` over this ring. If n is a tuple of + two elements, creates a matrix space. + + EXAMPLES:: + + sage: QQ^5 + Vector space of dimension 5 over Rational Field + sage: Integers(20)^1000 + Ambient free module of rank 1000 over Ring of integers modulo 20 + + sage: QQ^(2,3) + Full MatrixSpace of 2 by 3 dense matrices over Rational Field + """ + if isinstance(n, tuple): + m, n = n + from sage.matrix.matrix_space import MatrixSpace + return MatrixSpace(self, m, n) + else: + from sage.modules.free_module import FreeModule + return FreeModule(self, n) + @cached_method def ideal_monoid(self): """ diff --git a/src/sage/categories/sets_cat.py b/src/sage/categories/sets_cat.py index 68ba29bfa7c..3c5979a1ecb 100644 --- a/src/sage/categories/sets_cat.py +++ b/src/sage/categories/sets_cat.py @@ -1458,7 +1458,7 @@ def _test_construction(self, **options): and if it returns the latter, then it is supposed that ``F(O)==self`. The test verifies this assumption. - EXAMPLE: + EXAMPLES: We create a parent that returns a wrong construction (its construction returns the rational field rather than the parent itself):: @@ -1686,6 +1686,47 @@ def algebra(self, base_ring, category=None, **kwds): result.__doc__ = Sets.ParentMethods.algebra.__doc__ return result + def _sympy_(self): + """ + Return an instance of a subclass of SymPy ``Set`` corresponding to ``self``. + + The default implementation creates an instance of + :class:`~sage.interfaces.sympy_wrapper`. + + EXAMPLES:: + + sage: F = FiniteEnumeratedSets().example(); F + An example of a finite enumerated set: {1,2,3} + sage: sF = F._sympy_(); sF + SageSet(An example of a finite enumerated set: {1,2,3}) + sage: sF.is_finite_set + True + sage: bool(sF) + True + sage: len(sF) + 3 + sage: list(sF) + [1, 2, 3] + sage: from sympy import FiniteSet + sage: FiniteSet.fromiter(sF) + FiniteSet(1, 2, 3) + + sage: RR._sympy_().is_finite_set + False + + sage: F = Set([1, 2]) + sage: F is Set([1, 2]) + False + sage: sF = F._sympy_(); sF + SageSet({1, 2}) + sage: sF._sage_() is F + True + """ + from sage.interfaces.sympy_wrapper import SageSet + from sage.interfaces.sympy import sympy_init + sympy_init() + return SageSet(self) + class ElementMethods: ## Should eventually contain the basic operations which are no math ## latex, hash, ... @@ -2438,6 +2479,23 @@ def _cartesian_product_of_elements(self, elements): (42, 47, 42) """ + def _sympy_(self): + """ + Return a SymPy ``ProductSet`` corresponding to ``self``. + + EXAMPLES:: + + sage: ZZ3 = cartesian_product([ZZ, ZZ, ZZ]) + sage: sZZ3 = ZZ3._sympy_(); sZZ3 + ProductSet(Integers, Integers, Integers) + sage: (1, 2, 3) in sZZ3 + True + """ + from sympy import ProductSet + from sage.interfaces.sympy import sympy_init + sympy_init() + return ProductSet(*self.cartesian_factors()) + class ElementMethods: def cartesian_projection(self, i): @@ -2601,7 +2659,6 @@ def example(self, base_ring = None, set = None): from sage.categories.examples.with_realizations import SubsetAlgebra return SubsetAlgebra(base_ring, set) - class ParentMethods: def _test_with_realizations(self, **options): diff --git a/src/sage/categories/simplicial_sets.py b/src/sage/categories/simplicial_sets.py index 7f40f791097..56ca3e7b76d 100644 --- a/src/sage/categories/simplicial_sets.py +++ b/src/sage/categories/simplicial_sets.py @@ -85,7 +85,7 @@ def is_pointed(self): EXAMPLES:: - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet sage: v = AbstractSimplex(0) sage: w = AbstractSimplex(0) sage: e = AbstractSimplex(1) @@ -109,7 +109,7 @@ def set_base_point(self, point): EXAMPLES:: - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet sage: v = AbstractSimplex(0, name='v_0') sage: w = AbstractSimplex(0, name='w_0') sage: e = AbstractSimplex(1) @@ -136,7 +136,7 @@ def set_base_point(self, point): ... ValueError: the point is not a simplex in this simplicial set """ - from sage.homology.simplicial_set import SimplicialSet + from sage.topology.simplicial_set import SimplicialSet if point.dimension() != 0: raise ValueError('the "point" is not a zero-simplex') if point not in self._simplices: @@ -158,7 +158,7 @@ def one(self): Simplicial set endomorphism of Torus Defn: Identity map """ - from sage.homology.simplicial_set_morphism import SimplicialSetMorphism + from sage.topology.simplicial_set_morphism import SimplicialSetMorphism return SimplicialSetMorphism(domain=self.domain(), codomain=self.codomain(), identity=True) @@ -196,7 +196,7 @@ def base_point(self): EXAMPLES:: - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet sage: v = AbstractSimplex(0, name='*') sage: e = AbstractSimplex(1) sage: S1 = SimplicialSet({e: (v, v)}, base_point=v) @@ -220,7 +220,7 @@ def base_point_map(self, domain=None): - ``domain`` -- optional, default ``None``. Use this to specify a particular one-point space as the domain. The default behavior is to use the - :func:`sage.homology.simplicial_set.Point` + :func:`sage.topology.simplicial_set.Point` function to use a standard one-point space. EXAMPLES:: @@ -250,7 +250,7 @@ def base_point_map(self, domain=None): To: Classifying space of Multiplicative Abelian group isomorphic to C5 Defn: Constant map at 1 """ - from sage.homology.simplicial_set_examples import Point + from sage.topology.simplicial_set_examples import Point if domain is None: domain = Point() else: @@ -491,7 +491,7 @@ def unset_base_point(self): EXAMPLES:: - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet sage: v = AbstractSimplex(0, name='v_0') sage: w = AbstractSimplex(0, name='w_0') sage: e = AbstractSimplex(1) @@ -504,7 +504,7 @@ def unset_base_point(self): sage: Z.is_pointed() False """ - from sage.homology.simplicial_set import SimplicialSet + from sage.topology.simplicial_set import SimplicialSet return SimplicialSet(self.face_data()) def fat_wedge(self, n): @@ -530,7 +530,7 @@ def fat_wedge(self, n): sage: S1.fat_wedge(4).homology() {0: 0, 1: Z x Z x Z x Z, 2: Z^6, 3: Z x Z x Z x Z} """ - from sage.homology.simplicial_set_examples import Point + from sage.topology.simplicial_set_examples import Point if n == 0: return Point() if n == 1: @@ -561,6 +561,6 @@ def smash_product(self, *others): sage: X.homology(reduced=False) {0: Z, 1: 0, 2: Z x Z, 3: Z} """ - from sage.homology.simplicial_set_constructions import SmashProductOfSimplicialSets_finite + from sage.topology.simplicial_set_constructions import SmashProductOfSimplicialSets_finite return SmashProductOfSimplicialSets_finite((self,) + others) diff --git a/src/sage/coding/all.py b/src/sage/coding/all.py index b918bfda74a..89778395ff3 100644 --- a/src/sage/coding/all.py +++ b/src/sage/coding/all.py @@ -4,10 +4,7 @@ _lazy_import("sage.coding.code_constructions", ["permutation_action", "walsh_matrix"]) -_lazy_import("sage.coding.linear_code", [ - "LinearCode", - "LinearCodeFromVectorSpace", - "self_orthogonal_binary_codes"]) +_lazy_import("sage.coding.linear_code", "LinearCode") # Functions removed from the global namespace diff --git a/src/sage/coding/code_bounds.py b/src/sage/coding/code_bounds.py index 6fb82fd4315..6c60c42d2a9 100644 --- a/src/sage/coding/code_bounds.py +++ b/src/sage/coding/code_bounds.py @@ -472,7 +472,8 @@ def elias_upper_bound(n,q,d,algorithm=None): return QQ(ans) else: def ff(n,d,w,q): - return r*n*d*q**n/((w**2-2*r*n*w+r*n*d)*volume_hamming(n,q,w)); + return r*n*d*q**n/((w**2-2*r*n*w+r*n*d)*volume_hamming(n,q,w)) + def get_list(n,d,q): I = [] for i in range(1,int(r*n)+1): @@ -502,7 +503,7 @@ def hamming_upper_bound(n,q,d): .. MATH:: - M \leq {q^n \over V(n,e)}, + M \leq \frac{q^n}{V(n,e)}, diff --git a/src/sage/coding/code_constructions.py b/src/sage/coding/code_constructions.py index 44ecc8a33d7..e5b48049d11 100644 --- a/src/sage/coding/code_constructions.py +++ b/src/sage/coding/code_constructions.py @@ -596,12 +596,14 @@ def QuadraticResidueCodeEvenPair(n,F): n = Integer(n) if n <= 2 or not n.is_prime(): raise ValueError("the argument n must be an odd prime") - Q = quadratic_residues(n); Q.remove(0) # non-zero quad residues - N = [x for x in srange(1,n) if x not in Q] # non-zero quad non-residues + Q = quadratic_residues(n) + Q.remove(0) # non-zero quad residues + N = [x for x in srange(1, n) if x not in Q] # non-zero quad non-residues if q not in Q: raise ValueError("the order of the finite field must be a quadratic residue modulo n") return DuadicCodeEvenPair(F,Q,N) + def QuadraticResidueCodeOddPair(n,F): """ Quadratic residue codes of a given odd prime length and base ring @@ -654,12 +656,14 @@ def QuadraticResidueCodeOddPair(n,F): n = Integer(n) if n <= 2 or not n.is_prime(): raise ValueError("the argument n must be an odd prime") - Q = quadratic_residues(n); Q.remove(0) # non-zero quad residues - N = [x for x in srange(1,n) if x not in Q] # non-zero quad non-residues + Q = quadratic_residues(n) + Q.remove(0) # non-zero quad residues + N = [x for x in srange(1, n) if x not in Q] # non-zero quad non-residues if q not in Q: raise ValueError("the order of the finite field must be a quadratic residue modulo n") return DuadicCodeOddPair(F,Q,N) + def random_linear_code(F, length, dimension): r""" Generate a random linear code of length ``length``, dimension ``dimension`` diff --git a/src/sage/coding/databases.py b/src/sage/coding/databases.py index e0bce13871c..c2b1a2a9e23 100644 --- a/src/sage/coding/databases.py +++ b/src/sage/coding/databases.py @@ -297,17 +297,20 @@ def self_orthogonal_binary_codes(n, k, b=2, parent=None, BC=None, equal=False, M = Matrix(FiniteField(2), [[1]*j]) if in_test(M): for N in self_orthogonal_binary_codes(n, k, d, M, BC, in_test=in_test): - if out_test(N): yield N + if out_test(N): + yield N else: C = LinearCode(parent) - if out_test(C): yield C + if out_test(C): + yield C if k == parent.nrows(): return for nn in range(parent.ncols()+1, n+1): if in_test(parent): for child in BC.generate_children(BinaryCode(parent), nn, d): for N in self_orthogonal_binary_codes(n, k, d, child, BC, in_test=in_test): - if out_test(N): yield N + if out_test(N): + yield N # Import the following function so that it is available as # sage.codes.databases.self_dual_binary_codes sage.codes.databases functions diff --git a/src/sage/coding/grs_code.py b/src/sage/coding/grs_code.py index 9bc6248c4a9..2aa9cfc5c8a 100644 --- a/src/sage/coding/grs_code.py +++ b/src/sage/coding/grs_code.py @@ -65,7 +65,7 @@ from copy import copy from sage.functions.other import binomial, floor -from sage.calculus.var import var +from sage.symbolic.ring import SR from .linear_code import AbstractLinearCode from .encoder import Encoder @@ -215,7 +215,7 @@ def __init__(self, evaluation_points, dimension, column_multipliers=None): """ if column_multipliers: if len(evaluation_points) != len(column_multipliers): - raise ValueError("There must be the same number of evaluation points as column multipliers"); + raise ValueError("There must be the same number of evaluation points as column multipliers") try: common_points = vector(list(evaluation_points) + list(column_multipliers)) F = common_points.base_ring() @@ -534,7 +534,7 @@ def weight_distribution(self): d = self.minimum_distance() n = self.length() q = self.base_ring().order() - s = var('s') + s = SR.var('s') wd = [1] + [0] * (d - 1) for i in range(d, n+1): tmp = binomial(n, i) * (q - 1) diff --git a/src/sage/coding/self_dual_codes.py b/src/sage/coding/self_dual_codes.py index 2b12d5d90ed..132eb9b10cc 100644 --- a/src/sage/coding/self_dual_codes.py +++ b/src/sage/coding/self_dual_codes.py @@ -112,7 +112,9 @@ def _MS(n): sage: self_dual_codes._MS(8) Full MatrixSpace of 4 by 8 dense matrices over Finite Field of size 2 """ - n2 = ZZ(n)/2; return MatrixSpace(_F, n2, n) + n2 = ZZ(n)/2 + return MatrixSpace(_F, n2, n) + def _matA(n): r""" diff --git a/src/sage/combinat/affine_permutation.py b/src/sage/combinat/affine_permutation.py index 114895b417c..8caffd1a81e 100644 --- a/src/sage/combinat/affine_permutation.py +++ b/src/sage/combinat/affine_permutation.py @@ -187,7 +187,7 @@ def apply_simple_reflection(self, i, side='right'): EXAMPLES:: - sage: p=AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) + sage: p = AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.apply_simple_reflection(3) Type A affine permutation with window [3, -1, 6, 0, 5, 4, 10, 9] sage: p.apply_simple_reflection(11) @@ -208,7 +208,7 @@ def __call__(self, i): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.value(1) #indirect doctest 3 @@ -229,7 +229,7 @@ def is_i_grassmannian(self, i=0, side="right"): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.is_i_grassmannian() False @@ -250,7 +250,7 @@ def index_set(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: A.index_set() (0, 1, 2, 3, 4, 5, 6, 7) """ @@ -266,7 +266,7 @@ def lower_covers(self,side="right"): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.lower_covers() [Type A affine permutation with window [-1, 3, 0, 6, 5, 4, 10, 9], @@ -283,7 +283,7 @@ def is_one(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.is_one() False @@ -299,7 +299,7 @@ def reduced_word(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.reduced_word() [0, 7, 4, 1, 0, 7, 5, 4, 2, 1] @@ -323,7 +323,7 @@ def signature(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.signature() 1 @@ -337,7 +337,7 @@ def to_weyl_group_element(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.to_weyl_group_element() [ 0 -1 0 1 0 0 1 0] @@ -367,7 +367,7 @@ def grassmannian_quotient(self, i=0, side='right'): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: gq=p.grassmannian_quotient() sage: gq @@ -504,7 +504,7 @@ def apply_simple_reflection_right(self, i): EXAMPLES:: - sage: p=AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) + sage: p = AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.apply_simple_reflection_right(3) Type A affine permutation with window [3, -1, 6, 0, 5, 4, 10, 9] sage: p.apply_simple_reflection_right(11) @@ -534,7 +534,7 @@ def apply_simple_reflection_left(self, i): EXAMPLES:: - sage: p=AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) + sage: p = AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.apply_simple_reflection_left(3) Type A affine permutation with window [4, -1, 0, 6, 5, 3, 10, 9] sage: p.apply_simple_reflection_left(11) @@ -573,7 +573,7 @@ def apply_simple_reflection_left(self, i): l.append(self[m]) return type(self)(self.parent(), l, check=False) - def has_right_descent(self, i): + def has_right_descent(self, i) -> bool: r""" Determine whether there is a descent at ``i``. @@ -583,7 +583,7 @@ def has_right_descent(self, i): EXAMPLES:: - sage: p=AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) + sage: p = AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.has_right_descent(1) True sage: p.has_right_descent(9) @@ -593,7 +593,7 @@ def has_right_descent(self, i): """ return self.value(i)>self.value(i+1) - def has_left_descent(self, i): + def has_left_descent(self, i) -> bool: r""" Determine whether there is a descent at ``i``. @@ -603,7 +603,7 @@ def has_left_descent(self, i): EXAMPLES:: - sage: p=AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) + sage: p = AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.has_left_descent(1) True sage: p.has_left_descent(9) @@ -640,7 +640,7 @@ def flip_automorphism(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.flip_automorphism() Type A affine permutation with window [0, -1, 5, 4, 3, 9, 10, 6] @@ -655,7 +655,7 @@ def promotion(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.promotion() Type A affine permutation with window [2, 4, 0, 1, 7, 6, 5, 11] @@ -685,7 +685,7 @@ def maximal_cyclic_factor(self, typ='decreasing', side='right', verbose=False): EXAMPLES:: - sage: p=AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) + sage: p = AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.maximal_cyclic_factor() [7, 5, 4, 2, 1] sage: p.maximal_cyclic_factor(side='left') @@ -751,7 +751,7 @@ def maximal_cyclic_decomposition(self, typ='decreasing', side='right', verbose=F EXAMPLES:: - sage: p=AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) + sage: p = AffinePermutationGroup(['A',7,1])([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.maximal_cyclic_decomposition() [[0, 7], [4, 1, 0], [7, 5, 4, 2, 1]] sage: p.maximal_cyclic_decomposition(side='left') @@ -763,7 +763,7 @@ def maximal_cyclic_decomposition(self, typ='decreasing', side='right', verbose=F TESTS:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: S=p.maximal_cyclic_decomposition() sage: p==prod(A.from_word(l) for l in S) @@ -819,7 +819,7 @@ def to_lehmer_code(self, typ='decreasing', side='right'): EXAMPLES:: sage: import itertools - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: orders = ('increasing','decreasing') sage: sides = ('left','right') @@ -886,7 +886,7 @@ def is_fully_commutative(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.is_fully_commutative() False @@ -927,7 +927,7 @@ def to_bounded_partition(self, typ='decreasing', side='right'): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',2,1]) + sage: A = AffinePermutationGroup(['A',2,1]) sage: p=A.from_lehmer_code([4,1,0]) sage: p.to_bounded_partition() [2, 1, 1, 1] @@ -950,7 +950,7 @@ def to_core(self, typ='decreasing', side='right'): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',2,1]) + sage: A = AffinePermutationGroup(['A',2,1]) sage: p=A.from_lehmer_code([4,1,0]) sage: p.to_bounded_partition() [2, 1, 1, 1] @@ -975,7 +975,7 @@ def to_dominant(self, typ='decreasing', side='right'): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.to_dominant() Type A affine permutation with window [-2, -1, 1, 3, 4, 8, 10, 13] @@ -1006,11 +1006,11 @@ def tableau_of_word(self, w, typ='decreasing', side='right', alpha=None): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: p.tableau_of_word(p.reduced_word()) [[], [1, 6, 9], [2, 7, 10], [], [3], [4, 8], [], [5]] - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: w=p.reduced_word() sage: w @@ -1149,7 +1149,7 @@ def apply_simple_reflection_right(self, i): EXAMPLES:: - sage: C=AffinePermutationGroup(['C',4,1]) + sage: C = AffinePermutationGroup(['C',4,1]) sage: x=C([-1,5,3,7]) sage: for i in C.index_set(): x.apply_simple_reflection_right(i) Type C affine permutation with window [1, 5, 3, 7] @@ -1225,7 +1225,7 @@ def apply_simple_reflection_left(self, i): l.append(self[m]) return type(self)(self.parent(), l, check=False) - def has_right_descent(self, i): + def has_right_descent(self, i) -> bool: r""" Determine whether there is a descent at index ``i``. @@ -1246,7 +1246,7 @@ def has_right_descent(self, i): """ return self.value(i) > self.value(i+1) - def has_left_descent(self, i): + def has_left_descent(self, i) -> bool: r""" Determine whether there is a descent at ``i``. @@ -1329,7 +1329,7 @@ def apply_simple_reflection_right(self, i): EXAMPLES:: - sage: B=AffinePermutationGroup(['B',4,1]) + sage: B = AffinePermutationGroup(['B',4,1]) sage: p=B([-5,1,6,-2]) sage: p.apply_simple_reflection_right(1) Type B affine permutation with window [1, -5, 6, -2] @@ -1359,7 +1359,7 @@ def apply_simple_reflection_left(self, i): EXAMPLES:: - sage: B=AffinePermutationGroup(['B',4,1]) + sage: B = AffinePermutationGroup(['B',4,1]) sage: p=B([-5,1,6,-2]) sage: p.apply_simple_reflection_left(0) Type B affine permutation with window [-5, -2, 6, 1] @@ -1409,7 +1409,7 @@ def apply_simple_reflection_left(self, i): l.append(self[m]) return type(self)(self.parent(), l, check=False) - def has_right_descent(self, i): + def has_right_descent(self, i) -> bool: r""" Determines whether there is a descent at index ``i``. @@ -1428,7 +1428,7 @@ def has_right_descent(self, i): return self.value(-2) > self.value(1) return self.value(i) > self.value(i+1) - def has_left_descent(self, i): + def has_left_descent(self, i) -> bool: r""" Determines whether there is a descent at ``i``. @@ -1438,7 +1438,7 @@ def has_left_descent(self, i): EXAMPLES:: - sage: B=AffinePermutationGroup(['B',4,1]) + sage: B = AffinePermutationGroup(['B',4,1]) sage: p=B([-5,1,6,-2]) sage: [p.has_left_descent(i) for i in B.index_set()] [True, True, False, False, True] @@ -1497,7 +1497,7 @@ def apply_simple_reflection_right(self, i): EXAMPLES:: - sage: D=AffinePermutationGroup(['D',4,1]) + sage: D = AffinePermutationGroup(['D',4,1]) sage: p=D([1,-6,5,-2]) sage: p.apply_simple_reflection_right(0) Type D affine permutation with window [6, -1, 5, -2] @@ -1530,7 +1530,7 @@ def apply_simple_reflection_left(self, i): EXAMPLES:: - sage: D=AffinePermutationGroup(['D',4,1]) + sage: D = AffinePermutationGroup(['D',4,1]) sage: p=D([1,-6,5,-2]) sage: p.apply_simple_reflection_left(0) Type D affine permutation with window [-2, -6, 5, 1] @@ -1584,7 +1584,7 @@ def apply_simple_reflection_left(self, i): l.append(self[m]) return type(self)(self.parent(), l, check=False) - def has_right_descent(self, i): + def has_right_descent(self, i) -> bool: r""" Determine whether there is a descent at index ``i``. @@ -1594,7 +1594,7 @@ def has_right_descent(self, i): EXAMPLES:: - sage: D=AffinePermutationGroup(['D',4,1]) + sage: D = AffinePermutationGroup(['D',4,1]) sage: p=D([1,-6,5,-2]) sage: [p.has_right_descent(i) for i in D.index_set()] [True, True, False, True, False] @@ -1605,7 +1605,7 @@ def has_right_descent(self, i): return self.value(i) > self.value(i+2) return self.value(i) > self.value(i+1) - def has_left_descent(self, i): + def has_left_descent(self, i) -> bool: r""" Determine whether there is a descent at ``i``. @@ -1615,7 +1615,7 @@ def has_left_descent(self, i): EXAMPLES:: - sage: D=AffinePermutationGroup(['D',4,1]) + sage: D = AffinePermutationGroup(['D',4,1]) sage: p=D([1,-6,5,-2]) sage: [p.has_left_descent(i) for i in D.index_set()] [True, True, False, True, True] @@ -1670,7 +1670,7 @@ def value(self, i, base_window=False): EXAMPLES:: - sage: G=AffinePermutationGroup(['G',2,1]) + sage: G = AffinePermutationGroup(['G',2,1]) sage: p=G([2, 10, -5, 12, -3, 5]) sage: [p.value(i) for i in [1..12]] [2, 10, -5, 12, -3, 5, 8, 16, 1, 18, 3, 11] @@ -1706,7 +1706,7 @@ def apply_simple_reflection_right(self, i): EXAMPLES:: - sage: G=AffinePermutationGroup(['G',2,1]) + sage: G = AffinePermutationGroup(['G',2,1]) sage: p=G([2, 10, -5, 12, -3, 5]) sage: p.apply_simple_reflection_right(0) Type G affine permutation with window [-9, -1, -5, 12, 8, 16] @@ -1745,7 +1745,7 @@ def apply_simple_reflection_left(self, i): EXAMPLES:: - sage: G=AffinePermutationGroup(['G',2,1]) + sage: G = AffinePermutationGroup(['G',2,1]) sage: p=G([2, 10, -5, 12, -3, 5]) sage: p.apply_simple_reflection_left(0) Type G affine permutation with window [0, 10, -7, 14, -3, 7] @@ -1786,7 +1786,7 @@ def apply_simple_reflection_left(self, i): l.append(self[m]) return type(self)(self.parent(), l, check=False) - def has_right_descent(self, i): + def has_right_descent(self, i) -> bool: r""" Determines whether there is a descent at index `i`. @@ -1807,7 +1807,7 @@ def has_right_descent(self, i): return self.value(0) > self.value(2) return self.value(i) > self.value(i+1) - def has_left_descent(self, i): + def has_left_descent(self, i) -> bool: r""" Determines whether there is a descent at ``i``. @@ -1969,19 +1969,19 @@ def AffinePermutationGroup(cartan_type): We can also form affine permutation groups in types `B`, `C`, `D`, and `G`:: - sage: B=AffinePermutationGroup(['B',4,1]) + sage: B = AffinePermutationGroup(['B',4,1]) sage: B.an_element() Type B affine permutation with window [-1, 3, 4, 11] - sage: C=AffinePermutationGroup(['C',4,1]) + sage: C = AffinePermutationGroup(['C',4,1]) sage: C.an_element() Type C affine permutation with window [2, 3, 4, 10] - sage: D=AffinePermutationGroup(['D',4,1]) + sage: D = AffinePermutationGroup(['D',4,1]) sage: D.an_element() Type D affine permutation with window [-1, 3, 11, 5] - sage: G=AffinePermutationGroup(['G',2,1]) + sage: G = AffinePermutationGroup(['G',2,1]) sage: G.an_element() Type G affine permutation with window [0, 4, -1, 8, 3, 7] """ @@ -2054,7 +2054,7 @@ def _test_coxeter_relations(self, **options): TESTS:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: A._test_coxeter_relations() """ tester = self._tester(**options) @@ -2094,7 +2094,7 @@ def weyl_group(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: A.weyl_group() Weyl Group of type ['A', 7, 1] (as a matrix group acting on the root space) """ @@ -2106,7 +2106,7 @@ def classical(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: A.classical() Symmetric group of order 8! as a permutation group """ @@ -2221,7 +2221,7 @@ def from_word(self, w): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: A.from_word([0, 7, 4, 1, 0, 7, 5, 4, 2, 1]) Type A affine permutation with window [3, -1, 0, 6, 5, 4, 10, 9] @@ -2235,7 +2235,7 @@ def _an_element_(self): EXAMPLES:: - sage: A=AffinePermutationGroup(['A',7,1]) + sage: A = AffinePermutationGroup(['A',7,1]) sage: p=A([3, -1, 0, 6, 5, 4, 10, 9]) sage: A.from_word([0, 7, 4, 1, 0, 7, 5, 4, 2, 1]) Type A affine permutation with window [3, -1, 0, 6, 5, 4, 10, 9] @@ -2261,7 +2261,7 @@ def one(self): TESTS:: - sage: A=AffinePermutationGroup(['A',5,1]) + sage: A = AffinePermutationGroup(['A',5,1]) sage: A==loads(dumps(A)) True sage: TestSuite(A).run() @@ -2363,7 +2363,7 @@ def one(self): EXAMPLES:: sage: ct=CartanType(['C',4,1]) - sage: C=AffinePermutationGroup(ct) + sage: C = AffinePermutationGroup(ct) sage: C.one() Type C affine permutation with window [1, 2, 3, 4] sage: C.one()*C.one()==C.one() @@ -2371,7 +2371,7 @@ def one(self): TESTS:: - sage: C=AffinePermutationGroup(['C',4,1]) + sage: C = AffinePermutationGroup(['C',4,1]) sage: C==loads(dumps(C)) True sage: TestSuite(C).run() @@ -2388,12 +2388,6 @@ class AffinePermutationGroupTypeB(AffinePermutationGroupTypeC): #------------------------ Element = AffinePermutationTypeB -class AffinePermutationGroupTypeC(AffinePermutationGroupTypeC): - #------------------------ - #Type-specific methods. - #(Methods in all types, but with specific definition.) - #------------------------ - Element = AffinePermutationTypeC class AffinePermutationGroupTypeD(AffinePermutationGroupTypeC): #------------------------ @@ -2402,6 +2396,7 @@ class AffinePermutationGroupTypeD(AffinePermutationGroupTypeC): #------------------------ Element = AffinePermutationTypeD + class AffinePermutationGroupTypeG(AffinePermutationGroupGeneric): #------------------------ #Type-specific methods. @@ -2419,7 +2414,7 @@ def one(self): TESTS:: - sage: G=AffinePermutationGroup(['G',2,1]) + sage: G = AffinePermutationGroup(['G',2,1]) sage: G==loads(dumps(G)) True sage: TestSuite(G).run() diff --git a/src/sage/combinat/all.py b/src/sage/combinat/all.py index 4e4e002ce6a..cc23f903efd 100644 --- a/src/sage/combinat/all.py +++ b/src/sage/combinat/all.py @@ -217,6 +217,7 @@ lazy_import('sage.combinat.binary_recurrence_sequences', 'BinaryRecurrenceSequence') lazy_import('sage.combinat.recognizable_series', 'RecognizableSeriesSpace') +lazy_import('sage.combinat.k_regular_sequence', 'kRegularSequenceSpace') # Six Vertex Model lazy_import('sage.combinat.six_vertex_model', 'SixVertexModel') diff --git a/src/sage/combinat/cluster_algebra_quiver/quiver.py b/src/sage/combinat/cluster_algebra_quiver/quiver.py index 850f5531c66..dedef28db11 100644 --- a/src/sage/combinat/cluster_algebra_quiver/quiver.py +++ b/src/sage/combinat/cluster_algebra_quiver/quiver.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Quiver @@ -41,6 +42,13 @@ from sage.rings.all import ZZ, CC, infinity from sage.graphs.all import Graph, DiGraph from sage.graphs.views import EdgesView +from sage.arith.misc import gcd +from sage.modules.free_module_element import vector +from sage.matrix.constructor import matrix +from sage.categories.cartesian_product import cartesian_product +from sage.misc.misc_c import prod +from sage.rings.rational_field import QQ +from sage.rings.polynomial.polynomial_ring import polygen from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import QuiverMutationType, QuiverMutationType_Irreducible, QuiverMutationType_Reducible, _edge_list_to_matrix from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part, _digraph_mutate, _matrix_to_digraph, _dg_canonical_form, _mutation_class_iter, _digraph_to_dig6, _dig6_to_matrix from sage.combinat.cluster_algebra_quiver.mutation_type import _connected_mutation_type, _mutation_type_from_data, is_mutation_finite @@ -1919,7 +1927,7 @@ def is_mutation_finite( self, nr_of_checks=None, return_path=False ): # and getting the corresponding matrix M = _dig6_to_matrix(dig6) - is_finite, path = is_mutation_finite(M,nr_of_checks=nr_of_checks) + is_finite, path = is_mutation_finite(M, nr_of_checks=nr_of_checks) if return_path: return is_finite, path else: @@ -1992,7 +2000,8 @@ def relabel(self, relabelling, inplace=True): # If the key is in the old vertices, use that mapping digraph_labels[key] = val # And place it in the right order for our dictionary - loc = [i for i,x in enumerate(old_vertices) if x == key][0] + loc = [i for i, x in enumerate(old_vertices) + if x == key][0] dict_labels[loc] = val elif isinstance(key, int) and len(old_vertices) > key: # If the key is an integer, grab that particular vertex @@ -2004,6 +2013,113 @@ def relabel(self, relabelling, inplace=True): quiver._vertex_dictionary = dict_labels return quiver + def poincare_semistable(self, theta, d): + r""" + Return the Poincaré polynomial of the moduli space of semi-stable + representations of dimension vector `d`. + + INPUT: + + - ``theta`` -- stability weight, as list or vector of rationals + - ``d`` -- dimension vector, as list or vector of coprime integers + + The semi-stability is taken with respect to the slope function + + .. MATH:: + + \mu(d) = \theta(d) / \operatorname{dim}(d) + + where `d` is a dimension vector. + + This uses the matrix-inversion algorithm from [Rei2002]_. + + EXAMPLES:: + + sage: Q = ClusterQuiver(['A',2]) + sage: Q.poincare_semistable([1,0],[1,0]) + 1 + sage: Q.poincare_semistable([1,0],[1,1]) + 1 + + sage: K2 = ClusterQuiver(matrix([[0,2],[-2,0]])) + sage: theta = (1, 0) + sage: K2.poincare_semistable(theta, [1,0]) + 1 + sage: K2.poincare_semistable(theta, [1,1]) + v^2 + 1 + sage: K2.poincare_semistable(theta, [1,2]) + 1 + sage: K2.poincare_semistable(theta, [1,3]) + 0 + + sage: K3 = ClusterQuiver(matrix([[0,3],[-3,0]])) + sage: theta = (1, 0) + sage: K3.poincare_semistable(theta, (2,3)) + v^12 + v^10 + 3*v^8 + 3*v^6 + 3*v^4 + v^2 + 1 + sage: K3.poincare_semistable(theta, (3,4))(1) + 68 + + TESTS:: + + sage: Q = ClusterQuiver(['A',2]) + sage: Q.poincare_semistable([1,0],[2,2]) + Traceback (most recent call last): + ... + ValueError: dimension vector d is not coprime + + sage: Q = ClusterQuiver(['A',3]) + sage: Q.poincare_semistable([1,1,0],[2,3,4]) + 0 + + REFERENCES: + + .. [Rei2002] Markus Reineke, *The Harder-Narasimhan system in quantum + groups and cohomology of quiver moduli*, :arxiv:`math/0204059` + """ + if gcd([x for x in d if x]) != 1: + raise ValueError("dimension vector d is not coprime") + d = vector(ZZ, d) + theta = vector(theta) + + n = self.n() + b_mat = self.b_matrix() + Eu = matrix(ZZ, n, n, + lambda i, j: -b_mat[i, j] if b_mat[i, j] > 0 else 0) + Eu = 1 + Eu + edges = list(self.digraph().edges(labels=False)) + + mu_d = theta.dot_product(d) / sum(d) + + Li = [0 * d] + it = (vector(e) for e in cartesian_product([range(d_i + 1) + for d_i in d])) + Li += [e for e in it if e.dot_product(theta) > mu_d * sum(e)] + Li.append(d) + N = len(Li) - 1 + + q = polygen(QQ, 'v') # q stands for v**2 until the last line + + def cardinal_RG(d): + cardinal_G = prod(q**d_i - q**k for d_i in d for k in range(d_i)) + cardinal_R = prod(q**(b_mat[i, j] * d[i] * d[j]) + for i, j in edges) + return cardinal_R / cardinal_G + + Reineke_submat = matrix(q.parent().fraction_field(), N, N) + + for i, e in enumerate(Li[:-1]): + for j, f in enumerate(Li[1:]): + if e == f: + Reineke_submat[i, j] = 1 + continue + f_e = f - e + if all(x >= 0 for x in f_e): + power = (-f_e) * Eu * e + Reineke_submat[i, j] = q**power * cardinal_RG(f_e) + + poly = (-1)**N * ((1 - q) * Reineke_submat.det()).numerator() + return poly(q**2) # replacing q by v**2 + def d_vector_fan(self): r""" Return the d-vector fan associated with the quiver. diff --git a/src/sage/combinat/composition.py b/src/sage/combinat/composition.py index 05833955854..a1b2f97d396 100644 --- a/src/sage/combinat/composition.py +++ b/src/sage/combinat/composition.py @@ -251,7 +251,7 @@ def conjugate(self): cocjg = [] for i in range(n-1): - cocjg += [i+1 for _ in range(0, (coofcp[n-i-1]-coofcp[n-i-2]))] + cocjg += [i + 1 for _ in range(coofcp[n-i-1]-coofcp[n-i-2])] cocjg += [n for j in range(coofcp[0])] return self.parent()([cocjg[0]] + [cocjg[i]-cocjg[i-1]+1 for i in range(1,len(cocjg))]) @@ -854,11 +854,11 @@ def fatten(self, grouping): sage: c.fatten(Composition([3,1,1])).__class__ == c.__class__ True """ - result = [None] * len(grouping) + result = [] j = 0 - for i in range(len(grouping)): - result[i] = sum(self[j:j+grouping[i]]) - j += grouping[i] + for gi in grouping: + result.append(sum(self[j:j + gi])) + j += gi return Compositions()(result) def fatter(self): diff --git a/src/sage/combinat/crystals/alcove_path.py b/src/sage/combinat/crystals/alcove_path.py index 1a9c5f0a127..6ef9d20720d 100644 --- a/src/sage/combinat/crystals/alcove_path.py +++ b/src/sage/combinat/crystals/alcove_path.py @@ -250,9 +250,8 @@ def __classcall_private__(cls, starting_weight, cartan_type=None, """ if isinstance(cartan_type, bool): # new style signature, optional arguments leak over highest_weight_crystal = cartan_type - - elif isinstance(cartan_type, list) or isinstance(cartan_type, tuple): #old style signature - #switch positional arguments + elif isinstance(cartan_type, (list, tuple)): # old style signature + # switch positional arguments cartan_type, starting_weight = CartanType(starting_weight), cartan_type if highest_weight_crystal is False: diff --git a/src/sage/combinat/crystals/letters.pyx b/src/sage/combinat/crystals/letters.pyx index 3e899d3fc93..d9469333238 100644 --- a/src/sage/combinat/crystals/letters.pyx +++ b/src/sage/combinat/crystals/letters.pyx @@ -1963,173 +1963,173 @@ cdef class Crystal_of_letters_type_E7_element(LetterTuple): sage: C((-7,6)).e(7) (7,) """ - if self.value == (-7, 6) and i == 7 : + if self.value == (-7, 6) and i == 7: return self._parent._element_constructor_( (7,) ) - if self.value == (-6, 5) and i == 6 : + if self.value == (-6, 5) and i == 6: return self._parent._element_constructor_( (-7, 6) ) - if self.value == (-5, 4) and i == 5 : + if self.value == (-5, 4) and i == 5: return self._parent._element_constructor_( (-6, 5) ) - if self.value == (-4, 2, 3) and i == 4 : + if self.value == (-4, 2, 3) and i == 4: return self._parent._element_constructor_( (-5, 4) ) - if self.value == (-2, 3) and i == 2 : + if self.value == (-2, 3) and i == 2: return self._parent._element_constructor_( (-4, 2, 3) ) - if self.value == (-3, 1, 2) and i == 3 : + if self.value == (-3, 1, 2) and i == 3: return self._parent._element_constructor_( (-4, 2, 3) ) - if self.value == (-3, -2, 1, 4) and i == 3 : + if self.value == (-3, -2, 1, 4) and i == 3: return self._parent._element_constructor_( (-2, 3) ) - if self.value == (-1, 2) and i == 1 : + if self.value == (-1, 2) and i == 1: return self._parent._element_constructor_( (-3, 1, 2) ) - if self.value == (-3, -2, 1, 4) and i == 2 : + if self.value == (-3, -2, 1, 4) and i == 2: return self._parent._element_constructor_( (-3, 1, 2) ) - if self.value == (-1, -2, 4) and i == 1 : + if self.value == (-1, -2, 4) and i == 1: return self._parent._element_constructor_( (-3, -2, 1, 4) ) - if self.value == (-4, 1, 5) and i == 4 : + if self.value == (-4, 1, 5) and i == 4: return self._parent._element_constructor_( (-3, -2, 1, 4) ) - if self.value == (-7, 1) and i == 7 : + if self.value == (-7, 1) and i == 7: return self._parent._element_constructor_( (-6, 7, 1) ) - if self.value == (-1, -6, 3, 7) and i == 1 : + if self.value == (-1, -6, 3, 7) and i == 1: return self._parent._element_constructor_( (-6, 7, 1) ) - if self.value == (-1, -2, 4) and i == 2 : + if self.value == (-1, -2, 4) and i == 2: return self._parent._element_constructor_( (-1, 2) ) - if self.value == (-4, -1, 3, 5) and i == 4 : + if self.value == (-4, -1, 3, 5) and i == 4: return self._parent._element_constructor_( (-1, -2, 4) ) - if self.value == (-4, -1, 3, 5) and i == 1 : + if self.value == (-4, -1, 3, 5) and i == 1: return self._parent._element_constructor_( (-4, 1, 5) ) - if self.value == (-5, 6, 1) and i == 5 : + if self.value == (-5, 6, 1) and i == 5: return self._parent._element_constructor_( (-4, 1, 5) ) - if self.value == (-3, 5) and i == 3 : + if self.value == (-3, 5) and i == 3: return self._parent._element_constructor_( (-4, -1, 3, 5) ) - if self.value == (-5, -1, 3, 6) and i == 5 : + if self.value == (-5, -1, 3, 6) and i == 5: return self._parent._element_constructor_( (-4, -1, 3, 5) ) - if self.value == (-5, -3, 4, 6) and i == 5 : + if self.value == (-5, -3, 4, 6) and i == 5: return self._parent._element_constructor_( (-3, 5) ) - if self.value == (-6, 7, 1) and i == 6 : + if self.value == (-6, 7, 1) and i == 6: return self._parent._element_constructor_( (-5, 6, 1) ) - if self.value == (-5, -1, 3, 6) and i == 1 : + if self.value == (-5, -1, 3, 6) and i == 1: return self._parent._element_constructor_( (-5, 6, 1) ) - if self.value == (-5, -3, 4, 6) and i == 3 : + if self.value == (-5, -3, 4, 6) and i == 3: return self._parent._element_constructor_( (-5, -1, 3, 6) ) - if self.value == (-1, -6, 3, 7) and i == 6 : + if self.value == (-1, -6, 3, 7) and i == 6: return self._parent._element_constructor_( (-5, -1, 3, 6) ) - if self.value == (-4, 2, 6) and i == 4 : + if self.value == (-4, 2, 6) and i == 4: return self._parent._element_constructor_( (-5, -3, 4, 6) ) - if self.value == (-6, -3, 7, 4) and i == 6 : + if self.value == (-6, -3, 7, 4) and i == 6: return self._parent._element_constructor_( (-5, -3, 4, 6) ) - if self.value == (-6, -2, 7, 5) and i == 6 : + if self.value == (-6, -2, 7, 5) and i == 6: return self._parent._element_constructor_( (-2, 6) ) - if self.value == (-6, -3, 7, 4) and i == 3 : + if self.value == (-6, -3, 7, 4) and i == 3: return self._parent._element_constructor_( (-1, -6, 3, 7) ) - if self.value == (-1, -7, 3) and i == 7 : + if self.value == (-1, -7, 3) and i == 7: return self._parent._element_constructor_( (-1, -6, 3, 7) ) - if self.value == (-7, -3, 4) and i == 7 : + if self.value == (-7, -3, 4) and i == 7: return self._parent._element_constructor_( (-6, -3, 7, 4) ) - if self.value == (-6, -4, 2, 7, 5) and i == 4 : + if self.value == (-6, -4, 2, 7, 5) and i == 4: return self._parent._element_constructor_( (-6, -3, 7, 4) ) - if self.value == (-2, 6) and i == 2 : + if self.value == (-2, 6) and i == 2: return self._parent._element_constructor_( (-4, 2, 6) ) - if self.value == (-6, -4, 2, 7, 5) and i == 6 : + if self.value == (-6, -4, 2, 7, 5) and i == 6: return self._parent._element_constructor_( (-4, 2, 6) ) - if self.value == (-6, -2, 7, 5) and i == 2 : + if self.value == (-6, -2, 7, 5) and i == 2: return self._parent._element_constructor_( (-6, -4, 2, 7, 5) ) - if self.value == (-4, -7, 2, 5) and i == 7 : + if self.value == (-4, -7, 2, 5) and i == 7: return self._parent._element_constructor_( (-6, -4, 2, 7, 5) ) - if self.value == (-7, -4, 6, 3) and i == 7 : + if self.value == (-7, -4, 6, 3) and i == 7: return self._parent._element_constructor_( (-4, 7, 3) ) - if self.value == (-3, 1, 7) and i == 3 : + if self.value == (-3, 1, 7) and i == 3: return self._parent._element_constructor_( (-4, 7, 3) ) - if self.value == (-1, 7) and i == 1 : + if self.value == (-1, 7) and i == 1: return self._parent._element_constructor_( (-3, 1, 7) ) - if self.value == (-3, -7, 1, 6) and i == 7 : + if self.value == (-3, -7, 1, 6) and i == 7: return self._parent._element_constructor_( (-3, 1, 7) ) - if self.value == (-1, -7, 3) and i == 1 : + if self.value == (-1, -7, 3) and i == 1: return self._parent._element_constructor_( (-7, 1) ) - if self.value == (-7, -2, 5) and i == 2 : + if self.value == (-7, -2, 5) and i == 2: return self._parent._element_constructor_( (-4, -7, 2, 5) ) - if self.value == (-5, -7, 6, 2) and i == 5 : + if self.value == (-5, -7, 6, 2) and i == 5: return self._parent._element_constructor_( (-4, -7, 2, 5) ) - if self.value == (-5, -2, -7, 4, 6) and i == 5 : + if self.value == (-5, -2, -7, 4, 6) and i == 5: return self._parent._element_constructor_( (-7, -2, 5) ) - if self.value == (-5, -7, 6, 2) and i == 7 : + if self.value == (-5, -7, 6, 2) and i == 7: return self._parent._element_constructor_( (-5, 7, 2) ) - if self.value == (-5, -2, 4, 7) and i == 2 : + if self.value == (-5, -2, 4, 7) and i == 2: return self._parent._element_constructor_( (-5, 7, 2) ) - if self.value == (-7, -3, 4) and i == 3 : + if self.value == (-7, -3, 4) and i == 3: return self._parent._element_constructor_( (-1, -7, 3) ) - if self.value == (-5, 7, 2) and i == 5 : + if self.value == (-5, 7, 2) and i == 5: return self._parent._element_constructor_( (-6, -4, 2, 7, 5) ) - if self.value == (-6, 2) and i == 6 : + if self.value == (-6, 2) and i == 6: return self._parent._element_constructor_( (-5, -7, 6, 2) ) - if self.value == (-5, -2, -7, 4, 6) and i == 2 : + if self.value == (-5, -2, -7, 4, 6) and i == 2: return self._parent._element_constructor_( (-5, -7, 6, 2) ) - if self.value == (-7, -2, 5) and i == 7 : + if self.value == (-7, -2, 5) and i == 7: return self._parent._element_constructor_( (-6, -2, 7, 5) ) - if self.value == (-5, -2, 4, 7) and i == 5 : + if self.value == (-5, -2, 4, 7) and i == 5: return self._parent._element_constructor_( (-6, -2, 7, 5) ) - if self.value == (-4, 7, 3) and i == 4 : + if self.value == (-4, 7, 3) and i == 4: return self._parent._element_constructor_( (-5, -2, 4, 7) ) - if self.value == (-5, -2, -7, 4, 6) and i == 7 : + if self.value == (-5, -2, -7, 4, 6) and i == 7: return self._parent._element_constructor_( (-5, -2, 4, 7) ) - if self.value == (-4, -7, 2, 5) and i == 4 : + if self.value == (-4, -7, 2, 5) and i == 4: return self._parent._element_constructor_( (-7, -3, 4) ) - if self.value == (-7, -4, 6, 3) and i == 4 : + if self.value == (-7, -4, 6, 3) and i == 4: return self._parent._element_constructor_( (-5, -2, -7, 4, 6) ) - if self.value == (-2, -6, 4) and i == 6 : + if self.value == (-2, -6, 4) and i == 6: return self._parent._element_constructor_( (-5, -2, -7, 4, 6) ) - if self.value == (-6, -4, 5, 3) and i == 6 : + if self.value == (-6, -4, 5, 3) and i == 6: return self._parent._element_constructor_( (-7, -4, 6, 3) ) - if self.value == (-3, -7, 1, 6) and i == 3 : + if self.value == (-3, -7, 1, 6) and i == 3: return self._parent._element_constructor_( (-7, -4, 6, 3) ) - if self.value == (-3, -6, 1, 5) and i == 6 : + if self.value == (-3, -6, 1, 5) and i == 6: return self._parent._element_constructor_( (-3, -7, 1, 6) ) - if self.value == (-6, -1, 5) and i == 6 : + if self.value == (-6, -1, 5) and i == 6: return self._parent._element_constructor_( (-7, -1, 6) ) - if self.value == (-2, -6, 4) and i == 2 : + if self.value == (-2, -6, 4) and i == 2: return self._parent._element_constructor_( (-6, 2) ) - if self.value == (-6, -4, 5, 3) and i == 4 : + if self.value == (-6, -4, 5, 3) and i == 4: return self._parent._element_constructor_( (-2, -6, 4) ) - if self.value == (-7, -1, 6) and i == 1 : + if self.value == (-7, -1, 6) and i == 1: return self._parent._element_constructor_( (-3, -7, 1, 6) ) - if self.value == (-5, 3) and i == 5 : + if self.value == (-5, 3) and i == 5: return self._parent._element_constructor_( (-6, -4, 5, 3) ) - if self.value == (-3, -6, 1, 5) and i == 3 : + if self.value == (-3, -6, 1, 5) and i == 3: return self._parent._element_constructor_( (-6, -4, 5, 3) ) - if self.value == (-6, -1, 5) and i == 1 : + if self.value == (-6, -1, 5) and i == 1: return self._parent._element_constructor_( (-3, -6, 1, 5) ) - if self.value == (-3, -5, 4, 1) and i == 5 : + if self.value == (-3, -5, 4, 1) and i == 5: return self._parent._element_constructor_( (-3, -6, 1, 5) ) - if self.value == (-5, -1, 4) and i == 5 : + if self.value == (-5, -1, 4) and i == 5: return self._parent._element_constructor_( (-6, -1, 5) ) - if self.value == (-3, -5, 4, 1) and i == 3 : + if self.value == (-3, -5, 4, 1) and i == 3: return self._parent._element_constructor_( (-5, 3) ) - if self.value == (-4, 1, 2) and i == 4 : + if self.value == (-4, 1, 2) and i == 4: return self._parent._element_constructor_( (-3, -5, 4, 1) ) - if self.value == (-5, -1, 4) and i == 1 : + if self.value == (-5, -1, 4) and i == 1: return self._parent._element_constructor_( (-3, -5, 4, 1) ) - if self.value == (-1, -4, 3, 2) and i == 4 : + if self.value == (-1, -4, 3, 2) and i == 4: return self._parent._element_constructor_( (-5, -1, 4) ) - if self.value == (-1, -4, 3, 2) and i == 1 : + if self.value == (-1, -4, 3, 2) and i == 1: return self._parent._element_constructor_( (-4, 1, 2) ) - if self.value == (-2, 1) and i == 2 : + if self.value == (-2, 1) and i == 2: return self._parent._element_constructor_( (-4, 1, 2) ) - if self.value == (-3, 2) and i == 3 : + if self.value == (-3, 2) and i == 3: return self._parent._element_constructor_( (-1, -4, 3, 2) ) - if self.value == (-2, -1, 3) and i == 2 : + if self.value == (-2, -1, 3) and i == 2: return self._parent._element_constructor_( (-1, -4, 3, 2) ) - if self.value == (-2, -1, 3) and i == 1 : + if self.value == (-2, -1, 3) and i == 1: return self._parent._element_constructor_( (-2, 1) ) - if self.value == (-7, -1, 6) and i == 7 : + if self.value == (-7, -1, 6) and i == 7: return self._parent._element_constructor_( (-1, 7) ) - if self.value == (-2, -3, 4) and i == 3 : + if self.value == (-2, -3, 4) and i == 3: return self._parent._element_constructor_( (-2, -1, 3) ) - if self.value == (-2, -3, 4) and i == 2 : + if self.value == (-2, -3, 4) and i == 2: return self._parent._element_constructor_( (-3, 2) ) - if self.value == (-4, 5) and i == 4 : + if self.value == (-4, 5) and i == 4: return self._parent._element_constructor_( (-2, -3, 4) ) - if self.value == (-5, 6) and i == 5 : + if self.value == (-5, 6) and i == 5: return self._parent._element_constructor_( (-4, 5) ) - if self.value == (-6, 7) and i == 6 : + if self.value == (-6, 7) and i == 6: return self._parent._element_constructor_( (-5, 6) ) - if self.value == (-7,) and i == 7 : + if self.value == (-7,) and i == 7: return self._parent._element_constructor_( (-6, 7) ) else: return None @@ -2145,173 +2145,173 @@ cdef class Crystal_of_letters_type_E7_element(LetterTuple): sage: C((7,)).f(7) (-7, 6) """ - if self.value == (7,) and i == 7 : + if self.value == (7,) and i == 7: return self._parent._element_constructor_( (-7, 6) ) - if self.value == (-7, 6) and i == 6 : + if self.value == (-7, 6) and i == 6: return self._parent._element_constructor_( (-6, 5) ) - if self.value == (-6, 5) and i == 5 : + if self.value == (-6, 5) and i == 5: return self._parent._element_constructor_( (-5, 4) ) - if self.value == (-5, 4) and i == 4 : + if self.value == (-5, 4) and i == 4: return self._parent._element_constructor_( (-4, 2, 3) ) - if self.value == (-4, 2, 3) and i == 2 : + if self.value == (-4, 2, 3) and i == 2: return self._parent._element_constructor_( (-2, 3) ) - if self.value == (-4, 2, 3) and i == 3 : + if self.value == (-4, 2, 3) and i == 3: return self._parent._element_constructor_( (-3, 1, 2) ) - if self.value == (-2, 3) and i == 3 : + if self.value == (-2, 3) and i == 3: return self._parent._element_constructor_( (-3, -2, 1, 4) ) - if self.value == (-3, 1, 2) and i == 1 : + if self.value == (-3, 1, 2) and i == 1: return self._parent._element_constructor_( (-1, 2) ) - if self.value == (-3, 1, 2) and i == 2 : + if self.value == (-3, 1, 2) and i == 2: return self._parent._element_constructor_( (-3, -2, 1, 4) ) - if self.value == (-3, -2, 1, 4) and i == 1 : + if self.value == (-3, -2, 1, 4) and i == 1: return self._parent._element_constructor_( (-1, -2, 4) ) - if self.value == (-3, -2, 1, 4) and i == 4 : + if self.value == (-3, -2, 1, 4) and i == 4: return self._parent._element_constructor_( (-4, 1, 5) ) - if self.value == (-6, 7, 1) and i == 7 : + if self.value == (-6, 7, 1) and i == 7: return self._parent._element_constructor_( (-7, 1) ) - if self.value == (-6, 7, 1) and i == 1 : + if self.value == (-6, 7, 1) and i == 1: return self._parent._element_constructor_( (-1, -6, 3, 7) ) - if self.value == (-1, 2) and i == 2 : + if self.value == (-1, 2) and i == 2: return self._parent._element_constructor_( (-1, -2, 4) ) - if self.value == (-1, -2, 4) and i == 4 : + if self.value == (-1, -2, 4) and i == 4: return self._parent._element_constructor_( (-4, -1, 3, 5) ) - if self.value == (-4, 1, 5) and i == 1 : + if self.value == (-4, 1, 5) and i == 1: return self._parent._element_constructor_( (-4, -1, 3, 5) ) - if self.value == (-4, 1, 5) and i == 5 : + if self.value == (-4, 1, 5) and i == 5: return self._parent._element_constructor_( (-5, 6, 1) ) - if self.value == (-4, -1, 3, 5) and i == 3 : + if self.value == (-4, -1, 3, 5) and i == 3: return self._parent._element_constructor_( (-3, 5) ) - if self.value == (-4, -1, 3, 5) and i == 5 : + if self.value == (-4, -1, 3, 5) and i == 5: return self._parent._element_constructor_( (-5, -1, 3, 6) ) - if self.value == (-3, 5) and i == 5 : + if self.value == (-3, 5) and i == 5: return self._parent._element_constructor_( (-5, -3, 4, 6) ) - if self.value == (-5, 6, 1) and i == 6 : + if self.value == (-5, 6, 1) and i == 6: return self._parent._element_constructor_( (-6, 7, 1) ) - if self.value == (-5, 6, 1) and i == 1 : + if self.value == (-5, 6, 1) and i == 1: return self._parent._element_constructor_( (-5, -1, 3, 6) ) - if self.value == (-5, -1, 3, 6) and i == 3 : + if self.value == (-5, -1, 3, 6) and i == 3: return self._parent._element_constructor_( (-5, -3, 4, 6) ) - if self.value == (-5, -1, 3, 6) and i == 6 : + if self.value == (-5, -1, 3, 6) and i == 6: return self._parent._element_constructor_( (-1, -6, 3, 7) ) - if self.value == (-5, -3, 4, 6) and i == 4 : + if self.value == (-5, -3, 4, 6) and i == 4: return self._parent._element_constructor_( (-4, 2, 6) ) - if self.value == (-5, -3, 4, 6) and i == 6 : + if self.value == (-5, -3, 4, 6) and i == 6: return self._parent._element_constructor_( (-6, -3, 7, 4) ) - if self.value == (-2, 6) and i == 6 : + if self.value == (-2, 6) and i == 6: return self._parent._element_constructor_( (-6, -2, 7, 5) ) - if self.value == (-1, -6, 3, 7) and i == 3 : + if self.value == (-1, -6, 3, 7) and i == 3: return self._parent._element_constructor_( (-6, -3, 7, 4) ) - if self.value == (-1, -6, 3, 7) and i == 7 : + if self.value == (-1, -6, 3, 7) and i == 7: return self._parent._element_constructor_( (-1, -7, 3) ) - if self.value == (-6, -3, 7, 4) and i == 7 : + if self.value == (-6, -3, 7, 4) and i == 7: return self._parent._element_constructor_( (-7, -3, 4) ) - if self.value == (-6, -3, 7, 4) and i == 4 : + if self.value == (-6, -3, 7, 4) and i == 4: return self._parent._element_constructor_( (-6, -4, 2, 7, 5) ) - if self.value == (-4, 2, 6) and i == 2 : + if self.value == (-4, 2, 6) and i == 2: return self._parent._element_constructor_( (-2, 6) ) - if self.value == (-4, 2, 6) and i == 6 : + if self.value == (-4, 2, 6) and i == 6: return self._parent._element_constructor_( (-6, -4, 2, 7, 5) ) - if self.value == (-6, -4, 2, 7, 5) and i == 2 : + if self.value == (-6, -4, 2, 7, 5) and i == 2: return self._parent._element_constructor_( (-6, -2, 7, 5) ) - if self.value == (-6, -4, 2, 7, 5) and i == 7 : + if self.value == (-6, -4, 2, 7, 5) and i == 7: return self._parent._element_constructor_( (-4, -7, 2, 5) ) - if self.value == (-4, 7, 3) and i == 7 : + if self.value == (-4, 7, 3) and i == 7: return self._parent._element_constructor_( (-7, -4, 6, 3) ) - if self.value == (-4, 7, 3) and i == 3 : + if self.value == (-4, 7, 3) and i == 3: return self._parent._element_constructor_( (-3, 1, 7) ) - if self.value == (-3, 1, 7) and i == 1 : + if self.value == (-3, 1, 7) and i == 1: return self._parent._element_constructor_( (-1, 7) ) - if self.value == (-3, 1, 7) and i == 7 : + if self.value == (-3, 1, 7) and i == 7: return self._parent._element_constructor_( (-3, -7, 1, 6) ) - if self.value == (-7, 1) and i == 1 : + if self.value == (-7, 1) and i == 1: return self._parent._element_constructor_( (-1, -7, 3) ) - if self.value == (-4, -7, 2, 5) and i == 2 : + if self.value == (-4, -7, 2, 5) and i == 2: return self._parent._element_constructor_( (-7, -2, 5) ) - if self.value == (-4, -7, 2, 5) and i == 5 : + if self.value == (-4, -7, 2, 5) and i == 5: return self._parent._element_constructor_( (-5, -7, 6, 2) ) - if self.value == (-7, -2, 5) and i == 5 : + if self.value == (-7, -2, 5) and i == 5: return self._parent._element_constructor_( (-5, -2, -7, 4, 6) ) - if self.value == (-5, 7, 2) and i == 7 : + if self.value == (-5, 7, 2) and i == 7: return self._parent._element_constructor_( (-5, -7, 6, 2) ) - if self.value == (-5, 7, 2) and i == 2 : + if self.value == (-5, 7, 2) and i == 2: return self._parent._element_constructor_( (-5, -2, 4, 7) ) - if self.value == (-1, -7, 3) and i == 3 : + if self.value == (-1, -7, 3) and i == 3: return self._parent._element_constructor_( (-7, -3, 4) ) - if self.value == (-6, -4, 2, 7, 5) and i == 5 : + if self.value == (-6, -4, 2, 7, 5) and i == 5: return self._parent._element_constructor_( (-5, 7, 2) ) - if self.value == (-5, -7, 6, 2) and i == 6 : + if self.value == (-5, -7, 6, 2) and i == 6: return self._parent._element_constructor_( (-6, 2) ) - if self.value == (-5, -7, 6, 2) and i == 2 : + if self.value == (-5, -7, 6, 2) and i == 2: return self._parent._element_constructor_( (-5, -2, -7, 4, 6) ) - if self.value == (-6, -2, 7, 5) and i == 7 : + if self.value == (-6, -2, 7, 5) and i == 7: return self._parent._element_constructor_( (-7, -2, 5) ) - if self.value == (-6, -2, 7, 5) and i == 5 : + if self.value == (-6, -2, 7, 5) and i == 5: return self._parent._element_constructor_( (-5, -2, 4, 7) ) - if self.value == (-5, -2, 4, 7) and i == 4 : + if self.value == (-5, -2, 4, 7) and i == 4: return self._parent._element_constructor_( (-4, 7, 3) ) - if self.value == (-5, -2, 4, 7) and i == 7 : + if self.value == (-5, -2, 4, 7) and i == 7: return self._parent._element_constructor_( (-5, -2, -7, 4, 6) ) - if self.value == (-7, -3, 4) and i == 4 : + if self.value == (-7, -3, 4) and i == 4: return self._parent._element_constructor_( (-4, -7, 2, 5) ) - if self.value == (-5, -2, -7, 4, 6) and i == 4 : + if self.value == (-5, -2, -7, 4, 6) and i == 4: return self._parent._element_constructor_( (-7, -4, 6, 3) ) - if self.value == (-5, -2, -7, 4, 6) and i == 6 : + if self.value == (-5, -2, -7, 4, 6) and i == 6: return self._parent._element_constructor_( (-2, -6, 4) ) - if self.value == (-7, -4, 6, 3) and i == 6 : + if self.value == (-7, -4, 6, 3) and i == 6: return self._parent._element_constructor_( (-6, -4, 5, 3) ) - if self.value == (-7, -4, 6, 3) and i == 3 : + if self.value == (-7, -4, 6, 3) and i == 3: return self._parent._element_constructor_( (-3, -7, 1, 6) ) - if self.value == (-3, -7, 1, 6) and i == 6 : + if self.value == (-3, -7, 1, 6) and i == 6: return self._parent._element_constructor_( (-3, -6, 1, 5) ) - if self.value == (-7, -1, 6) and i == 6 : + if self.value == (-7, -1, 6) and i == 6: return self._parent._element_constructor_( (-6, -1, 5) ) - if self.value == (-6, 2) and i == 2 : + if self.value == (-6, 2) and i == 2: return self._parent._element_constructor_( (-2, -6, 4) ) - if self.value == (-2, -6, 4) and i == 4 : + if self.value == (-2, -6, 4) and i == 4: return self._parent._element_constructor_( (-6, -4, 5, 3) ) - if self.value == (-3, -7, 1, 6) and i == 1 : + if self.value == (-3, -7, 1, 6) and i == 1: return self._parent._element_constructor_( (-7, -1, 6) ) - if self.value == (-6, -4, 5, 3) and i == 5 : + if self.value == (-6, -4, 5, 3) and i == 5: return self._parent._element_constructor_( (-5, 3) ) - if self.value == (-6, -4, 5, 3) and i == 3 : + if self.value == (-6, -4, 5, 3) and i == 3: return self._parent._element_constructor_( (-3, -6, 1, 5) ) - if self.value == (-3, -6, 1, 5) and i == 1 : + if self.value == (-3, -6, 1, 5) and i == 1: return self._parent._element_constructor_( (-6, -1, 5) ) - if self.value == (-3, -6, 1, 5) and i == 5 : + if self.value == (-3, -6, 1, 5) and i == 5: return self._parent._element_constructor_( (-3, -5, 4, 1) ) - if self.value == (-6, -1, 5) and i == 5 : + if self.value == (-6, -1, 5) and i == 5: return self._parent._element_constructor_( (-5, -1, 4) ) - if self.value == (-5, 3) and i == 3 : + if self.value == (-5, 3) and i == 3: return self._parent._element_constructor_( (-3, -5, 4, 1) ) - if self.value == (-3, -5, 4, 1) and i == 4 : + if self.value == (-3, -5, 4, 1) and i == 4: return self._parent._element_constructor_( (-4, 1, 2) ) - if self.value == (-3, -5, 4, 1) and i == 1 : + if self.value == (-3, -5, 4, 1) and i == 1: return self._parent._element_constructor_( (-5, -1, 4) ) - if self.value == (-5, -1, 4) and i == 4 : + if self.value == (-5, -1, 4) and i == 4: return self._parent._element_constructor_( (-1, -4, 3, 2) ) - if self.value == (-4, 1, 2) and i == 1 : + if self.value == (-4, 1, 2) and i == 1: return self._parent._element_constructor_( (-1, -4, 3, 2) ) - if self.value == (-4, 1, 2) and i == 2 : + if self.value == (-4, 1, 2) and i == 2: return self._parent._element_constructor_( (-2, 1) ) - if self.value == (-1, -4, 3, 2) and i == 3 : + if self.value == (-1, -4, 3, 2) and i == 3: return self._parent._element_constructor_( (-3, 2) ) - if self.value == (-1, -4, 3, 2) and i == 2 : + if self.value == (-1, -4, 3, 2) and i == 2: return self._parent._element_constructor_( (-2, -1, 3) ) - if self.value == (-2, 1) and i == 1 : + if self.value == (-2, 1) and i == 1: return self._parent._element_constructor_( (-2, -1, 3) ) - if self.value == (-1, 7) and i == 7 : + if self.value == (-1, 7) and i == 7: return self._parent._element_constructor_( (-7, -1, 6) ) - if self.value == (-2, -1, 3) and i == 3 : + if self.value == (-2, -1, 3) and i == 3: return self._parent._element_constructor_( (-2, -3, 4) ) - if self.value == (-3, 2) and i == 2 : + if self.value == (-3, 2) and i == 2: return self._parent._element_constructor_( (-2, -3, 4) ) - if self.value == (-2, -3, 4) and i == 4 : + if self.value == (-2, -3, 4) and i == 4: return self._parent._element_constructor_( (-4, 5) ) - if self.value == (-4, 5) and i == 5 : + if self.value == (-4, 5) and i == 5: return self._parent._element_constructor_( (-5, 6) ) - if self.value == (-5, 6) and i == 6 : + if self.value == (-5, 6) and i == 6: return self._parent._element_constructor_( (-6, 7) ) - if self.value == (-6, 7) and i == 7 : + if self.value == (-6, 7) and i == 7: return self._parent._element_constructor_( (-7,) ) else: return None diff --git a/src/sage/combinat/designs/all.py b/src/sage/combinat/designs/all.py index 7ec907e7100..81210cf3b7f 100644 --- a/src/sage/combinat/designs/all.py +++ b/src/sage/combinat/designs/all.py @@ -3,9 +3,9 @@ """ from sage.misc.lazy_import import lazy_import -lazy_import('sage.combinat.designs.block_design', 'BlockDesign') lazy_import('sage.combinat.designs.incidence_structures', 'IncidenceStructure') +lazy_import('sage.combinat.designs.incidence_structures', 'IncidenceStructure', 'BlockDesign') lazy_import('sage.combinat.designs.incidence_structures', 'IncidenceStructure', as_='Hypergraph') diff --git a/src/sage/combinat/designs/covering_design.py b/src/sage/combinat/designs/covering_design.py index f6b69cc8180..eeaa04bf1a3 100644 --- a/src/sage/combinat/designs/covering_design.py +++ b/src/sage/combinat/designs/covering_design.py @@ -128,18 +128,18 @@ def trivial_covering_design(v, k, t): 1 3 4 2 3 4 - NOTES: + .. NOTE:: - Cases are: + Cases are: - * `t=0`: This could be empty, but it's a useful convention to have - one block (which is empty if $k=0$). + * `t=0`: This could be empty, but it's a useful convention to have + one block (which is empty if $k=0$). - * `t=1` : This contains `\lceil v/k \rceil` blocks: - `[0, ..., k-1], [k, ..., 2k-1], ...`. The last block wraps around if - `k` does not divide `v`. + * `t=1` : This contains `\lceil v/k \rceil` blocks: + `[0, ..., k-1], [k, ..., 2k-1], ...`. The last block wraps around if + `k` does not divide `v`. - * anything else: Just use every `k`-subset of `[0, 1,..., v-1]`. + * anything else: Just use every `k`-subset of `[0, 1,..., v-1]`. """ if t == 0: # single block [0, ..., k-1] diff --git a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py index 64bb91d61ca..7a63bac3c11 100644 --- a/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py +++ b/src/sage/combinat/designs/orthogonal_arrays_build_recursive.py @@ -783,7 +783,6 @@ def thwart_lemma_4_1(k,n,m,explain_construction=False): T. G. Ostrom and F. A. Sherk. Canad. Math. Bull vol7 num.4 (1964) """ - from sage.combinat.designs.designs_pyx import is_orthogonal_array from sage.rings.finite_rings.finite_field_constructor import FiniteField from sage.arith.all import is_prime_power from .block_design import DesarguesianProjectivePlaneDesign @@ -1075,12 +1074,10 @@ def product_with_parallel_classes(OA1,k,g1,g2,g1_parall,parall,check=True): # Check our stuff before we return it if check: - profile = [i for i in range(g2*g1) for _ in range(g1)] for classs in new_g1_parallel_classes: - assert_c_partition(classs,k,g2*g1,g1) - profile = list(range(g2*g1)) + assert_c_partition(classs, k, g2 * g1, g1) for classs in new_parallel_classes: - assert_c_partition(classs,k,g2*g1,1) + assert_c_partition(classs, k, g2 * g1, 1) return new_g1_parallel_classes, new_parallel_classes diff --git a/src/sage/combinat/designs/steiner_quadruple_systems.py b/src/sage/combinat/designs/steiner_quadruple_systems.py index 35834d93e50..5f49cdd3daf 100644 --- a/src/sage/combinat/designs/steiner_quadruple_systems.py +++ b/src/sage/combinat/designs/steiner_quadruple_systems.py @@ -637,7 +637,7 @@ def barP_system(m): # pairs. Those are added to 'last', a new list of pairs last = [] - for n in range(0, (m-3)//2+1): + for n in range((m - 3) // 2 + 1): pairs.append([p for p in P(2*n,m) if not isequal(p,(2*n,(4*n+1)%(2*m)))]) last.append((2*n,(4*n+1)%(2*m))) pairs.append([p for p in P(2*n+1,m) if not isequal(p,(2*m-2-2*n,2*m-3-4*n))]) @@ -659,7 +659,7 @@ def barP_system(m): # Now the points must be relabeled relabel = {} - for n in range(0, (m-3)//2+1): + for n in range((m - 3) // 2 + 1): relabel[2*n] = (4*n)%(2*m) relabel[4*n+1] = (4*n+1)%(2*m) relabel[2*m-2-2*n] = (4*n+2)%(2*m) diff --git a/src/sage/combinat/free_module.py b/src/sage/combinat/free_module.py index eb3305f3af1..1dfe7d6cc33 100644 --- a/src/sage/combinat/free_module.py +++ b/src/sage/combinat/free_module.py @@ -29,7 +29,6 @@ import sage.data_structures.blas_dict as blas from sage.typeset.ascii_art import AsciiArt, ascii_art from sage.typeset.unicode_art import UnicodeArt, unicode_art -from sage.misc.superseded import deprecation class CombinatorialFreeModule(UniqueRepresentation, Module, IndexedGenerators): @@ -413,7 +412,7 @@ def __init__(self, R, basis_keys=None, element_class=None, category=None, ... ValueError: keyy is not a valid print option. """ - #Make sure R is a ring with unit element + # Make sure R is a ring with unit element from sage.categories.all import Rings if R not in Rings(): raise TypeError("Argument R must be a ring.") @@ -529,7 +528,7 @@ def _an_element_(self): pass try: g = iter(self.basis().keys()) - for c in range(1,4): + for c in range(1, 4): x = x + self.term(next(g), R(c)) except Exception: pass @@ -556,7 +555,7 @@ def __contains__(self, x): sage: 5/3 in F False """ - return parent(x) == self # is self? + return parent(x) == self # is self? def _element_constructor_(self, x): """ @@ -661,7 +660,7 @@ def _element_constructor_(self, x): """ R = self.base_ring() - #Coerce ints to Integers + # Coerce ints to Integers if isinstance(x, int): x = Integer(x) @@ -670,12 +669,12 @@ def _element_constructor_(self, x): return self.zero() else: raise TypeError("do not know how to make x (= %s) an element of %s" % (x, self)) - #x is an element of the basis enumerated set; + # x is an element of the basis enumerated set; # This is a very ugly way of testing this elif ((hasattr(self._indices, 'element_class') and isinstance(self._indices.element_class, type) and - isinstance(x, self._indices.element_class)) - or (parent(x) == self._indices)): + isinstance(x, self._indices.element_class)) or + parent(x) == self._indices): return self.monomial(x) elif x in self._indices: return self.monomial(self._indices(x)) @@ -710,9 +709,10 @@ def _convert_map_from_(self, S): if isinstance(S, FormalSums) and K.has_coerce_map_from(S.base_ring()): G = self.basis().keys() return SetMorphism(S.Hom(self, category=self.category() | S.category()), - lambda x: self.sum_of_terms( (G(g), K(c)) for c,g in x )) + lambda x: self.sum_of_terms((G(g), K(c)) + for c, g in x)) - def _an_element_impl(self): # TODO: REMOVE? + def _an_element_impl(self): # TODO: REMOVE? """ Return an element of ``self``, namely the zero element. @@ -848,71 +848,6 @@ def get_order(self): self.set_order(self.basis().keys().list()) return self._order - def get_order_cmp(self): - """ - Return a comparison function on the basis indices that is - compatible with the current term order. - - DEPRECATED by :trac:`24548`. - - EXAMPLES:: - - sage: A = FiniteDimensionalAlgebrasWithBasis(QQ).example() - sage: Acmp = A.get_order_cmp() - doctest:warning...: - DeprecationWarning: comparison should use keys - See http://trac.sagemath.org/24548 for details. - - sage: sorted(A.basis().keys(), Acmp) # py2 - ['x', 'y', 'a', 'b'] - sage: A.set_order(list(reversed(A.basis().keys()))) - sage: Acmp = A.get_order_cmp() - sage: sorted(A.basis().keys(), Acmp) # py2 - ['b', 'a', 'y', 'x'] - """ - deprecation(24548, 'comparison should use keys') - self.get_order() - return self._order_cmp - - def _order_cmp(self, x, y): - """ - Compare `x` and `y` w.r.t. the term order. - - DEPRECATED by :trac:`24548`. - - INPUT: - - - ``x``, ``y`` -- indices of the basis of ``self`` - - OUTPUT: - - `-1`, `0`, or `1` depending on whether `xy` - w.r.t. the term order. - - EXAMPLES:: - - sage: A = CombinatorialFreeModule(QQ, ['x','y','a','b']) - sage: A.set_order(['x', 'y', 'a', 'b']) - sage: A._order_cmp('x', 'y') - doctest:warning...: - DeprecationWarning: comparison should use keys - See http://trac.sagemath.org/24548 for details. - -1 - sage: A._order_cmp('y', 'y') - 0 - sage: A._order_cmp('a', 'y') - 1 - """ - deprecation(24548, 'comparison should use keys') - ix = self._rank_basis(x) - iy = self._rank_basis(y) - if ix < iy: - return -1 - elif ix > iy: - return 1 - else: - return 0 - def get_order_key(self): """ Return a comparison key on the basis indices that is @@ -971,7 +906,8 @@ def from_vector(self, vector, order=None): """ if order is None: order = self.get_order() - return self._from_dict({order[index]: coeff for (index,coeff) in vector.items()}) + return self._from_dict({order[index]: coeff + for (index, coeff) in vector.items()}) def sum(self, iter_of_elements): """ @@ -1019,9 +955,9 @@ def linear_combination(self, iter_of_elements_coeff, factor_on_left=True): sage: F.linear_combination( (f,i) for i in range(5) ) 20*B[1] + 20*B[2] """ - return self._from_dict(blas.linear_combination( ((element._monomial_coefficients, coeff) + return self._from_dict(blas.linear_combination(((element._monomial_coefficients, coeff) for element, coeff in iter_of_elements_coeff), - factor_on_left=factor_on_left ), + factor_on_left=factor_on_left), remove_zeros=False) def term(self, index, coeff=None): @@ -1577,19 +1513,18 @@ def _coerce_map_from_(self, R): sage: T(tensor((p,p))) 4*B[2] # B[2] + 4*B[2] # B[4] + 4*B[4] # B[2] + 4*B[4] # B[4] """ - if ((R in ModulesWithBasis(self.base_ring()).TensorProducts() - or R in GradedAlgebrasWithBasis(self.base_ring()).SignedTensorProducts()) + if ((R in ModulesWithBasis(self.base_ring()).TensorProducts() or + R in GradedAlgebrasWithBasis(self.base_ring()).SignedTensorProducts()) and isinstance(R, CombinatorialFreeModule_Tensor) and len(R._sets) == len(self._sets) and all(self._sets[i].has_coerce_map_from(M) - for i,M in enumerate(R._sets))): + for i, M in enumerate(R._sets))): modules = R._sets vector_map = [self._sets[i]._internal_coerce_map_from(M) - for i,M in enumerate(modules)] + for i, M in enumerate(modules)] return R.module_morphism(lambda x: self._tensor_of_elements( - [vector_map[i](M.monomial(x[i])) - for i,M in enumerate(modules)]), - codomain=self) + [vector_map[i](M.monomial(x[i])) + for i, M in enumerate(modules)]), codomain=self) return super(CombinatorialFreeModule_Tensor, self)._coerce_map_from_(R) @@ -1628,7 +1563,8 @@ def __call__(self, *indices): (1, 2, 3, 4, 5, 6, 7, 8) """ - return sum( (i if flatten else (i,) for (i,flatten) in zip(indices, self._flatten) ), ()) + return sum((i if flatten else (i,) + for (i, flatten) in zip(indices, self._flatten)), ()) # TODO: find a way to avoid this hack to allow for cross references @@ -1686,7 +1622,7 @@ def __init__(self, modules, **options): Free module generated by {2, 4, 5} over Integer Ring (+) Free module generated by {2, 4, 7} over Integer Ring sage: TestSuite(C).run() """ - assert(len(modules) > 0) # TODO: generalize to a family or tuple + assert(len(modules)) # TODO: generalize to a family or tuple R = modules[0].base_ring() assert(all(module in ModulesWithBasis(R)) for module in modules) # should check the base ring @@ -1834,9 +1770,9 @@ def _cartesian_product_of_elements(self, elements): B[(0, 0)] + B[(1, 0)] """ - return self.sum( self.summand_embedding(i)(element_i) - for (i, element_i) in zip(self._sets_keys(), - elements) ) + return self.sum(self.summand_embedding(i)(element_i) + for (i, element_i) in zip(self._sets_keys(), + elements)) def cartesian_factors(self): """ @@ -1853,7 +1789,7 @@ def cartesian_factors(self): """ return self._sets - class Element(CombinatorialFreeModule.Element): # TODO: get rid of this inheritance + class Element(CombinatorialFreeModule.Element): # TODO: get rid of this inheritance pass diff --git a/src/sage/combinat/integer_list_old.py b/src/sage/combinat/integer_list_old.py index 99c6dcfa3e7..cdd420bb79e 100644 --- a/src/sage/combinat/integer_list_old.py +++ b/src/sage/combinat/integer_list_old.py @@ -1209,6 +1209,6 @@ def __contains__(self, v): sage: all(v in C for v in C) True """ - if isinstance(v, self.element_class) or isinstance(v, list): + if isinstance(v, (self.element_class, list)): return is_a(v, *(self.build_args())) and sum(v) in self.n_range return False diff --git a/src/sage/combinat/k_regular_sequence.py b/src/sage/combinat/k_regular_sequence.py new file mode 100644 index 00000000000..ee7cefaa6ce --- /dev/null +++ b/src/sage/combinat/k_regular_sequence.py @@ -0,0 +1,365 @@ +r""" +`k`-regular Sequences + +An introduction and formal definition of `k`-regular sequences can be +found, for example, on the :wikipedia:`k-regular_sequence` or in +[AS2003]_. + + +.. WARNING:: + + As this code is experimental, warnings are thrown when a + `k`-regular sequence space is created for the first time in a + session (see :class:`sage.misc.superseded.experimental`). + + TESTS:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + doctest:...: FutureWarning: This class/method/function is + marked as experimental. It, its functionality or its interface + might change without a formal deprecation. + See http://trac.sagemath.org/21202 for details. + + +Examples +======== + +Binary sum of digits +-------------------- + +The binary sum of digits `S(n)` of a nonnegative integer `n` satisfies +`S(2n) = S(n)` and `S(2n+1) = S(n) + 1`. We model this by the following:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[1, 0], [1, 1]])), + ....: left=vector([0, 1]), right=vector([1, 0])) + sage: S + 2-regular sequence 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, ... + sage: all(S[n] == sum(n.digits(2)) for n in srange(10)) + True + +Number of odd entries in Pascal's triangle +------------------------------------------ + +Let us consider the number of odd entries in the first `n` rows +of Pascals's triangle:: + + sage: @cached_function + ....: def u(n): + ....: if n <= 1: + ....: return n + ....: return 2*u(floor(n/2)) + u(ceil(n/2)) + sage: tuple(u(n) for n in srange(10)) + (0, 1, 3, 5, 9, 11, 15, 19, 27, 29) + +There is a `2`-recursive sequence describing the numbers above as well:: + + sage: U = Seq2((Matrix([[3, 2], [0, 1]]), Matrix([[2, 0], [1, 3]])), + ....: left=vector([0, 1]), right=vector([1, 0])).transposed() + sage: all(U[n] == u(n) for n in srange(30)) + True + + +Various +======= + +.. SEEALSO:: + + :mod:`recognizable series `, + :mod:`sage.rings.cfinite_sequence`, + :mod:`sage.combinat.binary_recurrence_sequences`. + + +AUTHORS: + +- Daniel Krenn (2016, 2021) + + +ACKNOWLEDGEMENT: + +- Daniel Krenn is supported by the + Austrian Science Fund (FWF): P 24644-N26. + + +Classes and Methods +=================== +""" +#***************************************************************************** +# Copyright (C) 2016 Daniel Krenn +# +# 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 3 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from .recognizable_series import RecognizableSeries +from .recognizable_series import RecognizableSeriesSpace +from sage.misc.cachefunc import cached_method + + +class kRegularSequence(RecognizableSeries): + def __init__(self, parent, mu, left=None, right=None): + r""" + A `k`-regular sequence. + + INPUT: + + - ``parent`` -- an instance of :class:`kRegularSequenceSpace` + + - ``mu`` -- a family of square matrices, all of which have the + same dimension. The indices of this family are `0,...,k-1`. + ``mu`` may be a list or tuple of cardinality `k` + as well. See also + :meth:`~sage.combinat.recognizable_series.RecognizableSeries.mu`. + + - ``left`` -- (default: ``None``) a vector. + When evaluating the sequence, this vector is multiplied + from the left to the matrix product. If ``None``, then this + multiplication is skipped. + + - ``right`` -- (default: ``None``) a vector. + When evaluating the sequence, this vector is multiplied + from the right to the matrix product. If ``None``, then this + multiplication is skipped. + + EXAMPLES:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: S = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed(); S + 2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ... + + We can access the coefficients of a sequence by + :: + + sage: S[5] + 11 + + or iterating over the first, say `10`, by + :: + + sage: from itertools import islice + sage: list(islice(S, 10)) + [0, 1, 3, 5, 9, 11, 15, 19, 27, 29] + + .. SEEALSO:: + + :doc:`k-regular sequence `, + :class:`kRegularSequenceSpace`. + """ + super(kRegularSequence, self).__init__( + parent=parent, mu=mu, left=left, right=right) + + def _repr_(self): + r""" + Return a representation string of this `k`-regular sequence. + + OUTPUT: + + A string + + TESTS:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: s = Seq2((Matrix([[3, 6], [0, 1]]), Matrix([[0, -6], [1, 5]])), + ....: vector([0, 1]), vector([1, 0])).transposed() + sage: repr(s) # indirect doctest + '2-regular sequence 0, 1, 3, 5, 9, 11, 15, 19, 27, 29, ...' + """ + from sage.misc.lazy_list import lazy_list_formatter + return lazy_list_formatter( + self, + name='{}-regular sequence'.format(self.parent().k), + opening_delimiter='', closing_delimiter='', + preview=10) + + @cached_method + def __getitem__(self, n, **kwds): + r""" + Return the `n`-th entry of this sequence. + + INPUT: + + - ``n`` -- a nonnegative integer + + OUTPUT: + + An element of the universe of the sequence + + EXAMPLES:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + ....: left=vector([0, 1]), right=vector([1, 0])) + sage: S[7] + 3 + + TESTS:: + + sage: S[-1] + Traceback (most recent call last): + ... + ValueError: value -1 of index is negative + + :: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: W = Seq2.indices() + sage: M0 = Matrix([[1, 0], [0, 1]]) + sage: M1 = Matrix([[0, -1], [1, 2]]) + sage: S = Seq2((M0, M1), [0, 1], [1, 1]) + sage: S._mu_of_word_(W(0.digits(2))) == M0 + True + sage: S._mu_of_word_(W(1.digits(2))) == M1 + True + sage: S._mu_of_word_(W(3.digits(2))) == M1^2 + True + """ + return self.coefficient_of_word(self.parent()._n_to_index_(n), **kwds) + + def __iter__(self): + r""" + Return an iterator over the coefficients of this sequence. + + EXAMPLES:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: S = Seq2((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), + ....: left=vector([0, 1]), right=vector([1, 0])) + sage: from itertools import islice + sage: tuple(islice(S, 10)) + (0, 1, 1, 2, 1, 2, 2, 3, 1, 2) + + TESTS:: + + sage: it = iter(S) + sage: iter(it) is it + True + sage: iter(S) is not it + True + """ + from itertools import count + return iter(self[n] for n in count()) + + +class kRegularSequenceSpace(RecognizableSeriesSpace): + r""" + The space of `k`-regular Sequences over the given ``coefficients``. + + INPUT: + + - ``k`` -- an integer at least `2` specifying the base + + - ``coefficient_ring`` -- a (semi-)ring. + + - ``category`` -- (default: ``None``) the category of this + space + + EXAMPLES:: + + sage: kRegularSequenceSpace(2, ZZ) + Space of 2-regular sequences over Integer Ring + sage: kRegularSequenceSpace(3, ZZ) + Space of 3-regular sequences over Integer Ring + + .. SEEALSO:: + + :doc:`k-regular sequence `, + :class:`kRegularSequence`. + """ + Element = kRegularSequence + + @classmethod + def __normalize__(cls, k, coefficient_ring, **kwds): + r""" + Normalizes the input in order to ensure a unique + representation. + + For more information see :class:`kRegularSequenceSpace`. + + TESTS:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: Seq2.category() + Category of sets + sage: Seq2.alphabet() + {0, 1} + """ + from sage.arith.srange import srange + nargs = super(kRegularSequenceSpace, cls).__normalize__( + coefficient_ring, alphabet=srange(k), **kwds) + return (k,) + nargs + + def __init__(self, k, *args, **kwds): + r""" + See :class:`kRegularSequenceSpace` for details. + + INPUT: + + - ``k`` -- an integer at least `2` specifying the base + + Other input arguments are passed on to + :meth:`~sage.combinat.recognizable_series.RecognizableSeriesSpace.__init__`. + + TESTS:: + + sage: kRegularSequenceSpace(2, ZZ) + Space of 2-regular sequences over Integer Ring + sage: kRegularSequenceSpace(3, ZZ) + Space of 3-regular sequences over Integer Ring + + .. SEEALSO:: + + :doc:`k-regular sequence `, + :class:`kRegularSequence`. + """ + self.k = k + super(kRegularSequenceSpace, self).__init__(*args, **kwds) + + def _repr_(self): + r""" + Return a representation string of this `k`-regular sequence space. + + OUTPUT: + + A string + + TESTS:: + + sage: repr(kRegularSequenceSpace(2, ZZ)) # indirect doctest + 'Space of 2-regular sequences over Integer Ring' + """ + return 'Space of {}-regular sequences over {}'.format(self.k, self.base()) + + def _n_to_index_(self, n): + r""" + Convert `n` to an index usable by the underlying + recognizable series. + + INPUT: + + - ``n`` -- a nonnegative integer + + OUTPUT: + + A word + + TESTS:: + + sage: Seq2 = kRegularSequenceSpace(2, ZZ) + sage: Seq2._n_to_index_(6) + word: 011 + sage: Seq2._n_to_index_(-1) + Traceback (most recent call last): + ... + ValueError: value -1 of index is negative + """ + from sage.rings.integer_ring import ZZ + n = ZZ(n) + W = self.indices() + try: + return W(n.digits(self.k)) + except OverflowError: + raise ValueError('value {} of index is negative'.format(n)) from None diff --git a/src/sage/combinat/k_tableau.py b/src/sage/combinat/k_tableau.py index 490f212d415..005f3504456 100644 --- a/src/sage/combinat/k_tableau.py +++ b/src/sage/combinat/k_tableau.py @@ -1818,7 +1818,7 @@ def straighten_input(t, k): """ W = WeylGroup(['A', k, 1], prefix='s') if len(t) > 0: - if isinstance(t[0], list) or isinstance(t[0], tuple): + if isinstance(t[0], (list, tuple)): w_tuple = tuple(W.from_reduced_word(p) for p in t) else: w_tuple = tuple(W(r) for r in t) diff --git a/src/sage/combinat/matrices/dancing_links.pyx b/src/sage/combinat/matrices/dancing_links.pyx index c62780c5d74..fb34665a7e7 100644 --- a/src/sage/combinat/matrices/dancing_links.pyx +++ b/src/sage/combinat/matrices/dancing_links.pyx @@ -1018,7 +1018,7 @@ cdef class dancing_linksWrapper: OUTPUT: - MixedIntegerLinearProgram instance - - MIPVariable of dimension 1 + - MIPVariable with binary components EXAMPLES:: @@ -1029,7 +1029,7 @@ cdef class dancing_linksWrapper: sage: p Boolean Program (no objective, 4 variables, 4 constraints) sage: x - MIPVariable of dimension 1 + MIPVariable with 4 binary components In the reduction, the boolean variable x_i is True if and only if the i-th row is in the solution:: @@ -1053,7 +1053,7 @@ cdef class dancing_linksWrapper: sage: d.to_milp('gurobi') # optional - gurobi sage_numerical_backends_gurobi (Boolean Program (no objective, 4 variables, 4 constraints), - MIPVariable of dimension 1) + MIPVariable with 4 binary components) """ from sage.numerical.mip import MixedIntegerLinearProgram diff --git a/src/sage/combinat/matrices/latin.py b/src/sage/combinat/matrices/latin.py index 4183a520b26..765b2065cae 100644 --- a/src/sage/combinat/matrices/latin.py +++ b/src/sage/combinat/matrices/latin.py @@ -177,11 +177,11 @@ def __init__(self, *args): [0 1] [2 3] """ - - if len(args) == 1 and (isinstance(args[0], Integer) or isinstance(args[0], int)): + if len(args) == 1 and isinstance(args[0], (Integer, int)): self.square = matrix(ZZ, args[0], args[0]) self.clear_cells() - elif len(args) == 2 and (isinstance(args[0], Integer) or isinstance(args[0], int)) and (isinstance(args[1], Integer) or isinstance(args[1], int)): + elif len(args) == 2 and all(isinstance(a, (Integer, int)) + for a in args): self.square = matrix(ZZ, args[0], args[1]) self.clear_cells() elif len(args) == 1 and isinstance(args[0], Matrix_integer_dense): @@ -1513,8 +1513,8 @@ def isotopism(p): """ # Identity isotopism on p points: - if isinstance(p, Integer) or isinstance(p, int): - return Permutation(range(1, p+1)) + if isinstance(p, (Integer, int)): + return Permutation(range(1, p + 1)) if isinstance(p, PermutationGroupElement): # fixme Ask the Sage mailing list about the tuple/list issue! diff --git a/src/sage/combinat/multiset_partition_into_sets_ordered.py b/src/sage/combinat/multiset_partition_into_sets_ordered.py index 42aeb20da66..1d6eb8b37f6 100755 --- a/src/sage/combinat/multiset_partition_into_sets_ordered.py +++ b/src/sage/combinat/multiset_partition_into_sets_ordered.py @@ -81,7 +81,7 @@ from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ from sage.functions.other import binomial -from sage.calculus.var import var +from sage.symbolic.ring import SR from sage.combinat.subset import Subsets_sk from sage.combinat.composition import Composition, Compositions, composition_iterator_fast @@ -2067,7 +2067,7 @@ def cardinality(self): # # The 2-regular partitions have a nice generating function (see OEIS:A000009). # Below, we take (products of) coefficients of polynomials to compute cardinality. - t = var('t') + t = SR.var('t') partspoly = prod(1+t**k for k in range(1,self._n+1)).coefficients() deg = 0 for alpha in composition_iterator_fast(self._n): diff --git a/src/sage/combinat/ncsf_qsym/qsym.py b/src/sage/combinat/ncsf_qsym/qsym.py index 44aae642397..f76f1891ad3 100644 --- a/src/sage/combinat/ncsf_qsym/qsym.py +++ b/src/sage/combinat/ncsf_qsym/qsym.py @@ -1806,7 +1806,7 @@ def coproduct_on_basis(self, compo): """ return self.tensor_square().sum_of_monomials((self._indices(compo[:i]), self._indices(compo[i:])) - for i in range(0,len(compo)+1)) + for i in range(len(compo)+1)) def lambda_of_monomial(self, I, n): r""" @@ -2758,7 +2758,7 @@ def coproduct_on_basis(self, compo): """ return self.tensor_square().sum_of_monomials((self._indices(compo[:i]), self._indices(compo[i:])) - for i in range(0,len(compo)+1)) + for i in range(len(compo)+1)) def product_on_basis(self, I, J): r""" @@ -3906,7 +3906,9 @@ def _from_Monomial_on_basis(self, I): """ R = self.base_ring() minus_one = -R.one() - def z(J): return R(J.to_partition().centralizer_size()) + + def z(J): + return R(J.to_partition().centralizer_size()) return self._from_dict({J: minus_one**(len(I)-len(J)) / z(J) * coeff_lp(I, J) for J in I.fatter()}) @@ -4046,7 +4048,9 @@ def _from_Monomial_on_basis(self, I): """ R = self.base_ring() minus_one = -R.one() - def z(J): return R(J.to_partition().centralizer_size()) + + def z(J): + return R(J.to_partition().centralizer_size()) return self._from_dict({J: minus_one**(len(I)-len(J)) * R.prod(J) / (coeff_ell(I, J) * z(J)) for J in I.fatter()}) diff --git a/src/sage/combinat/parallelogram_polyomino.py b/src/sage/combinat/parallelogram_polyomino.py index e24ed79070b..f238c132189 100644 --- a/src/sage/combinat/parallelogram_polyomino.py +++ b/src/sage/combinat/parallelogram_polyomino.py @@ -1185,24 +1185,86 @@ def __init__(self, parent, value, check=True): self.check() self._options = None + def reflect(self): + r""" + Return the parallelogram polyomino obtained by switching rows and + columns. + + EXAMPLES:: + + sage: pp = ParallelogramPolyomino([[0,0,0,0,1,1,0,1,0,1], [1,0,1,0,0,1,1,0,0,0]]) + sage: pp.heights(), pp.upper_heights() + ([4, 3, 2, 3], [0, 1, 3, 3]) + sage: pp = pp.reflect() + sage: pp.widths(), pp.lower_widths() + ([4, 3, 2, 3], [0, 1, 3, 3]) + + sage: pp = ParallelogramPolyomino([[0,0,0,1,1], [1,0,0,1,0]]) + sage: ascii_art(pp) + * + * + ** + sage: ascii_art(pp.reflect()) + *** + * + + TESTS:: + + sage: pp = ParallelogramPolyomino([[1], [1]]) + sage: pp.reflect() + [[1], [1]] + """ + if self.size() == 1: + return self + a, b = self + return ParallelogramPolyomino([[1-v for v in b], [1-v for v in a]]) + + def rotate(self): + r""" + Return the parallelogram polyomino obtained by rotation of 180 degrees. + + EXAMPLES:: + + sage: pp = ParallelogramPolyomino([[0,0,0,1,1], [1,0,0,1,0]]) + sage: ascii_art(pp) + * + * + ** + sage: ascii_art(pp.rotate()) + ** + * + * + """ + 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. - This bijection is described page 179 and page 180 Figure 6 in - the article [DeVi1984]_. + This bijection is described on page 179 and page 180 Figure 6 + in the article [DeVi1984]_, where it is called the classical + bijection `\gamma`. EXAMPLES:: - sage: pp = ParallelogramPolyomino( - ....: [[0, 1, 0, 0, 1, 1], [1, 1, 1, 0, 0, 0]] - ....: ) + sage: pp = ParallelogramPolyomino([[0, 1, 0, 0, 1, 1], [1, 1, 1, 0, 0, 0]]) sage: pp._to_dyck_delest_viennot() [1, 1, 0, 1, 1, 0, 1, 0, 0, 0] + + TESTS:: + + sage: pp = ParallelogramPolyomino([[1], [1]]) + sage: pp._to_dyck_delest_viennot() + [] + """ from sage.combinat.dyck_word import DyckWord dyck = [] dick_size = self.size()-1 + if not dick_size: + return DyckWord([]) upper_path = self.upper_path() lower_path = self.lower_path() dyck.append(1 - lower_path[0]) @@ -1212,20 +1274,53 @@ def _to_dyck_delest_viennot(self): dyck.append(upper_path[dick_size]) return DyckWord(dyck) + def _to_dyck_delest_viennot_peaks_valleys(self): + r""" + Convert to a Dyck word using the Delest-Viennot bijection `\beta`. + + This bijection is described on page 182 and Figure 8 in the + article [DeVi1984]_. It returns the unique Dyck path whose + 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]_:: + + sage: pp = ParallelogramPolyomino([[0,0,0,0,1,1,0,1,0,1], [1,0,1,0,0,1,1,0,0,0]]) + sage: pp._to_dyck_delest_viennot_peaks_valleys() + [1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0] + + TESTS:: + + sage: pp = ParallelogramPolyomino([[1], [1]]) + sage: pp._to_dyck_delest_viennot_peaks_valleys() + [] + + """ + from sage.combinat.dyck_word import DyckWord + a = self.heights() + u = self.upper_heights() + 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])) + return DyckWord(dyck) + @combinatorial_map(name="To Dyck word") def to_dyck_word(self, bijection=None): r""" Convert to a Dyck word. - This bijection is described page 179 and page 180 Figure 6 in - the article [DeVi1984]_. - INPUT: - ``bijection`` -- string or ``None`` (default:``None``) The name of the bijection. If it is set to ``None`` then the ``'Delest-Viennot'`` bijection is used. - Expected values are ``None`` or ``'Delest-Viennot'``. + Expected values are ``None``, ``'Delest-Viennot'``, or ``'Delest-Viennot-beta'``. OUTPUT: @@ -1233,24 +1328,30 @@ def to_dyck_word(self, bijection=None): EXAMPLES:: - sage: pp = ParallelogramPolyomino( - ....: [[0, 1, 0, 0, 1, 1], [1, 1, 1, 0, 0, 0]] - ....: ) + sage: pp = ParallelogramPolyomino([[0, 1, 0, 0, 1, 1], [1, 1, 1, 0, 0, 0]]) sage: pp.to_dyck_word() [1, 1, 0, 1, 1, 0, 1, 0, 0, 0] sage: pp.to_dyck_word(bijection='Delest-Viennot') [1, 1, 0, 1, 1, 0, 1, 0, 0, 0] + + sage: pp.to_dyck_word(bijection='Delest-Viennot-beta') + [1, 0, 1, 1, 1, 0, 1, 0, 0, 0] """ if bijection is None or bijection == 'Delest-Viennot': return self._to_dyck_delest_viennot() + if bijection == 'Delest-Viennot-beta': + return self._to_dyck_delest_viennot_peaks_valleys() + raise ValueError("The given bijection is not valid.") @staticmethod def _from_dyck_word_delest_viennot(dyck): r""" - Convert Dyck word to parallelogram polyomino using the Delest Viennot - bijection. + Convert a Dyck word to a parallelogram polyomino using the Delest + Viennot bijection. - This bijection come from the article [DeVi1984]_. + This bijection is described on page 179 and page 180 Figure 6 in + the article [DeVi1984]_, where it is called the classical + bijection `\gamma`. INPUT: @@ -1265,6 +1366,14 @@ def _from_dyck_word_delest_viennot(dyck): sage: dyck = DyckWord([1, 1, 0, 1, 1, 0, 1, 0, 0, 0]) sage: ParallelogramPolyomino._from_dyck_word_delest_viennot(dyck) [[0, 1, 0, 0, 1, 1], [1, 1, 1, 0, 0, 0]] + + TESTS:: + + sage: gamma = ParallelogramPolyomino._to_dyck_delest_viennot + 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 = [] @@ -1274,6 +1383,62 @@ def _from_dyck_word_delest_viennot(dyck): word_down.append(1 - l[i+1]) return ParallelogramPolyomino([word_down, word_up]) + @staticmethod + def _from_dyck_word_delest_viennot_peaks_valleys(dyck): + r""" + Convert a Dyck word to a parallelogram polyomino using the Delest + Viennot bijection `\beta`. + + This bijection is described on page 182 and Figure 8 in + the article [DeVi1984]_. + + INPUT: + + - ``dyck`` -- a Dyck word + + OUTPUT: + + A parallelogram polyomino. + + EXAMPLES:: + + sage: dyck = DyckWord([1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0]) + sage: ParallelogramPolyomino._from_dyck_word_delest_viennot_peaks_valleys(dyck) + [[0, 0, 0, 0, 1, 1, 0, 1, 0, 1], [1, 0, 1, 0, 0, 1, 1, 0, 0, 0]] + + sage: dyck = DyckWord([1,1,0,1,1,1,1,1,0,0,1,0,0,0,0,0,1,1,1,0,0,1,0,0]) + sage: ParallelogramPolyomino._from_dyck_word_delest_viennot_peaks_valleys(dyck) + [[0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1], [1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0]] + + TESTS:: + + sage: beta = ParallelogramPolyomino._to_dyck_delest_viennot_peaks_valleys + sage: beta_inv = ParallelogramPolyomino._from_dyck_word_delest_viennot_peaks_valleys + sage: all(all(D == beta(beta_inv(D)) for D in DyckWords(n)) for n in range(7)) + True + """ + if not dyck: + return ParallelogramPolyomino([[1], [1]]) + a = [] + b = [0] + h = 0 + for i in range(len(dyck)-1): + if dyck[i] == 1: + h += 1 + if dyck[i+1] == 0: + a.append(h) + else: + if dyck[i+1] == 1: + b.append(h) + h -= 1 + b.append(0) + word_down = [] + word_up = [] + for i in range(len(a)): + word_down.extend([0]*(a[i]-b[i]) + [1]) + word_up.extend([1]+[0]*(a[i]-b[i+1])) + return ParallelogramPolyomino([word_down, word_up]) + @staticmethod def from_dyck_word(dyck, bijection=None): r""" @@ -1293,17 +1458,18 @@ def from_dyck_word(dyck, bijection=None): EXAMPLES:: sage: dyck = DyckWord([1, 1, 0, 1, 1, 0, 1, 0, 0, 0]) - sage: pp = ParallelogramPolyomino.from_dyck_word(dyck) - sage: pp + sage: ParallelogramPolyomino.from_dyck_word(dyck) [[0, 1, 0, 0, 1, 1], [1, 1, 1, 0, 0, 0]] - sage: pp = ParallelogramPolyomino.from_dyck_word( - ....: dyck, bijection='Delest-Viennot' - ....: ) - sage: pp + sage: ParallelogramPolyomino.from_dyck_word(dyck, bijection='Delest-Viennot') [[0, 1, 0, 0, 1, 1], [1, 1, 1, 0, 0, 0]] + sage: ParallelogramPolyomino.from_dyck_word(dyck, bijection='Delest-Viennot-beta') + [[0, 0, 1, 0, 1, 1], [1, 1, 1, 0, 0, 0]] """ if bijection is None or bijection == 'Delest-Viennot': return ParallelogramPolyomino._from_dyck_word_delest_viennot(dyck) + if bijection == 'Delest-Viennot-beta': + return ParallelogramPolyomino._from_dyck_word_delest_viennot_peaks_valleys(dyck) + raise ValueError("The given bijection is not valid.") def _to_binary_tree_Aval_Boussicault(self, position=[0, 0]): r""" diff --git a/src/sage/combinat/partition.py b/src/sage/combinat/partition.py index 09e87b2e92f..6991d69379b 100644 --- a/src/sage/combinat/partition.py +++ b/src/sage/combinat/partition.py @@ -281,6 +281,7 @@ # **************************************************************************** from copy import copy +from itertools import accumulate from sage.libs.all import pari from sage.libs.flint.arith import number_of_partitions as flint_number_of_partitions @@ -1032,7 +1033,7 @@ def pp(self): def __truediv__(self, p): """ - Returns the skew partition ``self / p``. + Return the skew partition ``self / p``. EXAMPLES:: @@ -1704,6 +1705,7 @@ def next_within_bounds(self, min=[], max=None, partition_type=None): min = min + [0] * (len(max) - len(min)) # finally, run the algo to find next_p next_p = copy(p) + def condition(a, b): if partition_type in ('strict', 'strictly decreasing'): return a < b - 1 @@ -2645,10 +2647,9 @@ def initial_tableau(self): sage: Partition([3,2,2]).initial_tableau() [[1, 2, 3], [4, 5], [6, 7]] """ - mu = self._list - # In Python 3, improve this using itertools.accumulate - tab = [list(range(1+sum(mu[:i]), 1+sum(mu[:(i+1)]))) - for i in range(len(mu))] + sigma = list(accumulate([1] + self._list)) + tab = [list(range(sigma[i], sigma[i + 1])) + for i in range(len(sigma) - 1)] return tableau.StandardTableau(tab) def initial_column_tableau(self): @@ -5281,6 +5282,7 @@ def dual_equivalence_graph(self, directed=False, coloring=None): if coloring is None: d = {2: 'red', 3: 'blue', 4: 'green', 5: 'purple', 6: 'brown', 7: 'orange', 8: 'yellow'} + def coloring(i): if i in d: return d[i] @@ -5999,7 +6001,7 @@ def __init__(self): def subset(self, size=None, **kwargs): """ - Returns the subset of partitions of a given size and additional + Return the subset of partitions of a given size and additional keyword arguments. EXAMPLES:: @@ -6060,10 +6062,9 @@ def __reversed__(self): yield self.element_class(self, p) n += 1 - def from_frobenius_coordinates(self, frobenius_coordinates): """ - Returns a partition from a pair of sequences of Frobenius coordinates. + Return a partition from a pair of sequences of Frobenius coordinates. EXAMPLES:: @@ -6131,7 +6132,7 @@ def from_beta_numbers(self, beta): def from_exp(self, exp): """ - Returns a partition from its list of multiplicities. + Return a partition from its list of multiplicities. EXAMPLES:: @@ -6189,7 +6190,7 @@ def from_zero_one(self, seq): def from_core_and_quotient(self, core, quotient): """ - Returns a partition from its core and quotient. + Return a partition from its core and quotient. Algorithm from mupad-combinat. @@ -6355,7 +6356,7 @@ def _repr_(self): def _an_element_(self): """ - Returns a partition in ``self``. + Return a partition in ``self``. EXAMPLES:: @@ -6605,7 +6606,7 @@ def random_element_plancherel(self): def first(self): """ - Returns the lexicographically first partition of a positive integer + Return the lexicographically first partition of a positive integer `n`. This is the partition ``[n]``. EXAMPLES:: @@ -6730,7 +6731,7 @@ def _repr_(self): def _an_element_(self): """ - Returns a partition in ``self``. + Return a partition in ``self``. EXAMPLES:: @@ -7520,11 +7521,12 @@ def __setstate__(self, data): """ n = data['n'] self.__class__ = Partitions_with_constraints - constraints = {'max_slope' : 0, - 'min_part' : 1} + constraints = {'max_slope': 0, + 'min_part': 1} constraints.update(data['constraints']) self.__init__(n, **constraints) + class Partitions_with_constraints(IntegerListsLex): """ Partitions which satisfy a set of constraints. @@ -8026,7 +8028,7 @@ def cardinality(self): def _an_element_(self): """ - Returns a partition in ``self``. + Return a partition in ``self``. EXAMPLES:: @@ -8639,7 +8641,7 @@ def _an_element_(self): def number_of_partitions(n, algorithm='default'): r""" - Returns the number of partitions of `n` with, optionally, at most `k` + Return the number of partitions of `n` with, optionally, at most `k` parts. The options of :meth:`number_of_partitions()` are being deprecated diff --git a/src/sage/combinat/partition_tuple.py b/src/sage/combinat/partition_tuple.py index 54d5784eb3e..34b5a9e46ff 100644 --- a/src/sage/combinat/partition_tuple.py +++ b/src/sage/combinat/partition_tuple.py @@ -1971,7 +1971,7 @@ def __contains__(self, mu): sage: 1 in PartitionTuples() False """ - if isinstance(mu, PartitionTuple) or isinstance(mu, Partition): + if isinstance(mu, (PartitionTuple, Partition)): return True if isinstance(mu, (tuple, list)): if not mu: diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 42f858f8cb1..6a1e031ba78 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -49,6 +49,7 @@ :meth:`~sage.combinat.permutation.Permutation.rank` | Returns the rank of ``self`` in lexicographic ordering (on the symmetric group containing ``self``). :meth:`~sage.combinat.permutation.Permutation.to_inversion_vector` | Returns the inversion vector of a permutation ``self``. :meth:`~sage.combinat.permutation.Permutation.inversions` | Returns a list of the inversions of permutation ``self``. + :meth:`~sage.combinat.permutation.Permutation.stack_sort` | Returns the permutation obtained by sorting ``self`` through one stack. :meth:`~sage.combinat.permutation.Permutation.to_digraph` | Return a digraph representation of ``self``. :meth:`~sage.combinat.permutation.Permutation.show` | Displays the permutation as a drawing. :meth:`~sage.combinat.permutation.Permutation.number_of_inversions` | Returns the number of inversions in the permutation ``self``. @@ -1597,6 +1598,49 @@ def inversions(self): return [tuple([i+1,j+1]) for i in range(n-1) for j in range(i+1,n) if p[i]>p[j]] + def stack_sort(self) -> "Permutation": + """ + Return the stack sort of a permutation. + + This is another permutation obtained through the + process of sorting using one stack. If the result is the identity + permutation, the original permutation is *stack-sortable*. + + See :wikipedia:`Stack-sortable_permutation` + + EXAMPLES:: + + sage: p = Permutation([2,1,5,3,4,9,7,8,6]) + sage: p.stack_sort() + [1, 2, 3, 4, 5, 7, 6, 8, 9] + + sage: S5 = Permutations(5) + sage: len([1 for s in S5 if s.stack_sort() == S5.one()]) + 42 + + TESTS:: + + sage: p = Permutation([]) + sage: p.stack_sort() + [] + sage: p = Permutation([1]) + sage: p.stack_sort() + [1] + """ + stack = [] + sorted_p = [] + for j in self: + if stack: + for i in reversed(stack): + if i < j: + sorted_p.append(i) + stack.pop() + else: + break + stack.append(j) + sorted_p.extend(reversed(stack)) + return Permutation(sorted_p) + def to_digraph(self): r""" Return a digraph representation of ``self``. diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index abf5d226ef2..4b3b0edbb45 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -617,7 +617,7 @@ def _alternate_interval(self, x, y): sage: P = posets.BooleanLattice(3) sage: P.interval(1, 7) [1, 3, 5, 7] - sage: P._hasse_diagram._precompute_intervals() + sage: P._hasse_diagram._precompute_intervals() # indirect doctest sage: P.interval(1, 7) # Uses this function [1, 3, 5, 7] """ @@ -1420,68 +1420,77 @@ def add_elements(e): @lazy_attribute def _meet(self): r""" - Return the matrix of meets of ``self``. The ``(x,y)``-entry of - this matrix is the meet of ``x`` and ``y`` in ``self``. - - EXAMPLES:: + Return the matrix of meets of ``self``. - sage: from sage.combinat.posets.hasse_diagram import HasseDiagram - sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) - sage: H._meet - [0 0 0 0 0 0 0 0] - [0 1 0 0 1 0 0 1] - [0 0 2 0 2 2 2 2] - [0 0 0 3 0 0 3 3] - [0 1 2 0 4 2 2 4] - [0 0 2 0 2 5 2 5] - [0 0 2 3 2 2 6 6] - [0 1 2 3 4 5 6 7] + The ``(x,y)``-entry of this matrix is the meet of ``x`` and + ``y`` in ``self`` if the meet exists; and `-1` otherwise. - TESTS:: + EXAMPLES:: sage: from sage.combinat.posets.hasse_diagram import HasseDiagram + sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) + sage: H._meet + [0 0 0 0 0 0 0 0] + [0 1 0 0 1 0 0 1] + [0 0 2 0 2 2 2 2] + [0 0 0 3 0 0 3 3] + [0 1 2 0 4 2 2 4] + [0 0 2 0 2 5 2 5] + [0 0 2 3 2 2 6 6] + [0 1 2 3 4 5 6 7] + sage: H = HasseDiagram({0:[2,3],1:[2,3]}) - sage: H.meet_matrix() - Traceback (most recent call last): - ... - ValueError: not a meet-semilattice: no bottom element + sage: H._meet + [ 0 -1 0 0] + [-1 1 1 1] + [ 0 1 2 -1] + [ 0 1 -1 3] sage: H = HasseDiagram({0:[1,2],1:[3,4],2:[3,4]}) - sage: H.meet_matrix() - Traceback (most recent call last): - ... - LatticeError: no meet for ... + sage: H._meet + [ 0 0 0 0 0] + [ 0 1 0 1 1] + [ 0 0 2 2 2] + [ 0 1 2 3 -1] + [ 0 1 2 -1 4] + TESTS:: + + sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: L = LatticePoset({0:[1,2,3],1:[4],2:[4],3:[4]}) sage: P = L.dual() sage: P.meet(2,3) 4 """ + self._meet_semilattice_failure = () n = self.cardinality() if n == 0: return matrix(0) - if not self.has_bottom(): - raise ValueError("not a meet-semilattice: no bottom element") - meet = [[0 for x in range(n)] for x in range(n)] + meet = [[-1 for x in range(n)] for x in range(n)] lc = [self.neighbors_in(x) for x in range(n)] # Lc = lower covers for x in range(n): meet[x][x] = x for y in range(x): - T = [meet[y][z] for z in lc[x]] - - q = max(T) - for z in T: - if meet[z][q] != z: - raise LatticeError('meet', x, y) + T = [meet[y][z] for z in lc[x] if meet[y][z] != -1] + if not T: + q = -1 + else: + q = max(T) + for z in T: + if meet[z][q] != z: + q = -1 + break meet[x][y] = q meet[y][x] = q - + if q == -1: + self._meet_semilattice_failure += ((x, y),) return matrix(ZZ, meet) def meet_matrix(self): r""" - Return the matrix of meets of ``self``. + Return the matrix of meets of ``self``, when ``self`` is a + meet-semilattice; raise an error otherwise. The ``(x,y)``-entry of this matrix is the meet of ``x`` and ``y`` in ``self``. @@ -1491,8 +1500,9 @@ def meet_matrix(self): .. NOTE:: - Once the matrix has been computed, it is stored in - :meth:`_meet_matrix`. Delete this attribute if you want to + If ``self`` is a meet-semilattice, then the return of this method + is the same as :meth:`_meet`. Once the matrix has been computed, + it is stored in :meth:`_meet`. Delete this attribute if you want to recompute the matrix. EXAMPLES:: @@ -1532,7 +1542,15 @@ def meet_matrix(self): ... LatticeError: no meet for ... """ - return self._meet + n = self.cardinality() + if (n != 0) and (not self.has_bottom()): + raise ValueError("not a meet-semilattice: no bottom element") + # call the attribute to build the matrix and _meet_semilattice_failure + mt = self._meet + if self._meet_semilattice_failure: + x, y = self._meet_semilattice_failure[0] + raise LatticeError('meet', x, y) + return mt def is_meet_semilattice(self): r""" @@ -1553,6 +1571,10 @@ def is_meet_semilattice(self): sage: H = HasseDiagram({0:[2,3],1:[2,3]}) sage: H.is_meet_semilattice() False + + sage: H = HasseDiagram({0:[1,2],1:[3,4],2:[3,4]}) + sage: H.is_meet_semilattice() + False """ try: self.meet_matrix() @@ -1565,13 +1587,13 @@ def is_meet_semilattice(self): def _join(self): r""" Computes a matrix whose ``(x,y)``-entry is the join of ``x`` - and ``y`` in ``self`` + and ``y`` in ``self`` if the join exists; and `-1` otherwise. EXAMPLES:: sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) - sage: H.join_matrix() # indirect doctest + sage: H._join [0 1 2 3 4 5 6 7] [1 1 4 7 4 7 7 7] [2 4 2 6 4 5 6 7] @@ -1581,60 +1603,71 @@ def _join(self): [6 7 6 6 7 7 6 7] [7 7 7 7 7 7 7 7] - TESTS:: - - sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2,3],1:[2,3]}) - sage: H.join_matrix() - Traceback (most recent call last): - ... - ValueError: not a join-semilattice: no top element + sage: H._join + [ 0 -1 2 3] + [-1 1 2 3] + [ 2 2 2 -1] + [ 3 3 -1 3] sage: H = HasseDiagram({0:[2,3],1:[2,3],2:[4],3:[4]}) - sage: H.join_matrix() - Traceback (most recent call last): - ... - LatticeError: no join for ... + sage: H._join + [ 0 -1 2 3 4] + [-1 1 2 3 4] + [ 2 2 2 4 4] + [ 3 3 4 3 4] + [ 4 4 4 4 4] + + TESTS:: + sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: L = LatticePoset({0:[1,2,3],1:[4],2:[4],3:[4]}) sage: P = L.dual() sage: P.join(2,3) 0 """ + self._join_semilattice_failure = () n = self.cardinality() if n == 0: return matrix(0) - if not self.has_top(): - raise ValueError("not a join-semilattice: no top element") - join = [[n for x in range(n)] for x in range(n)] + join = [[-1 for x in range(n)] for x in range(n)] uc = [self.neighbors_out(x) for x in range(n)] # uc = upper covers for x in range(n - 1, -1, -1): join[x][x] = x for y in range(n - 1, x, -1): - T = [join[y][z] for z in uc[x]] - - q = min(T) - for z in T: - if join[z][q] != z: - raise LatticeError('join', x, y) + T = [join[y][z] for z in uc[x] if join[y][z] != -1] + if not T: + q = -1 + else: + q = min(T) + for z in T: + if join[z][q] != z: + q = -1 + break join[x][y] = q join[y][x] = q + if q == -1: + self._join_semilattice_failure += ((x, y),) return matrix(ZZ, join) def join_matrix(self): r""" - Return the matrix of joins of ``self``. The ``(x,y)``-entry - of this matrix is the join of ``x`` and ``y`` in ``self``. + Return the matrix of joins of ``self``, when ``self`` is a + join-semilattice; raise an error otherwise. + + The ``(x,y)``-entry of this matrix is the join of ``x`` and + ``y`` in ``self``. This algorithm is modelled after the algorithm of Freese-Jezek-Nation (p217). It can also be found on page 140 of [Gec81]_. - .. note:: + .. NOTE:: - Once the matrix has been computed, it is stored in - :meth:`_join_matrix`. Delete this attribute if you want + If ``self`` is a join-semilattice, then the return of this method + is the same as :meth:`_join`. Once the matrix has been computed, + it is stored in :meth:`_join`. Delete this attribute if you want to recompute the matrix. EXAMPLES:: @@ -1666,7 +1699,15 @@ def join_matrix(self): ... LatticeError: no join for ... """ - return self._join + n = self.cardinality() + if (n != 0) and (not self.has_top()): + raise ValueError("not a join-semilattice: no top element") + # call the attribute to build the matrix and _join_semilattice_failure + jn = self._join + if self._join_semilattice_failure: + x, y = self._join_semilattice_failure[0] + raise LatticeError('join', x, y) + return jn def is_join_semilattice(self): r""" @@ -1723,11 +1764,11 @@ def find_nonsemidistributive_elements(self, meet_or_join): True """ if meet_or_join == 'join': - M1 = self._join - M2 = self._meet + M1 = self.join_matrix() + M2 = self.meet_matrix() elif meet_or_join == 'meet': - M1 = self._meet - M2 = self._join + M1 = self.meet_matrix() + M2 = self.join_matrix() else: raise ValueError("meet_or_join must be 'join' or 'meet'") @@ -1857,11 +1898,12 @@ def pseudocomplement(self, element): True """ e = self.order() - 1 - while self._meet[e, element] != 0: + mt = self.meet_matrix() + while mt[e, element] != 0: e -= 1 e1 = e while e1 > 0: - if self._meet[e1, element] == 0 and not self.is_lequal(e1, e): + if mt[e1, element] == 0 and not self.is_lequal(e1, e): return None e1 -= 1 return e @@ -1986,10 +2028,12 @@ def orthocomplementations_iterator(self): orbit_number[e] = ind comps = [None] * n + mt = self.meet_matrix() + jn = self.join_matrix() for e in range(n): # Fix following after ticket #20727 comps[e] = [x for x in range(n) if - self._meet[e, x] == 0 and self._join[e, x] == n - 1 and + mt[e, x] == 0 and jn[e, x] == n - 1 and x in orbits[orbit_number[dual_isomorphism[e]]]] # Fitting is done by this recursive function: @@ -2360,6 +2404,27 @@ def common_upper_covers(self, vertices): covers = covers.intersection(self.neighbors_out(v)) return list(covers) + def common_lower_covers(self, vertices): + r""" + Return the list of all common lower covers of ``vertices``. + + EXAMPLES:: + + sage: from sage.combinat.posets.hasse_diagram import HasseDiagram + sage: H = HasseDiagram({0: [1,2], 1: [3], 2: [3], 3: []}) + sage: H.common_lower_covers([1, 2]) + [0] + + sage: from sage.combinat.posets.poset_examples import Posets + sage: H = Posets.YoungDiagramPoset(Partition([3, 2, 2]))._hasse_diagram + sage: H.common_lower_covers([4, 5]) + [3] + """ + covers = set(self.neighbors_in(vertices.pop())) + for v in vertices: + covers = covers.intersection(self.neighbors_in(v)) + return list(covers) + def _trivial_nonregular_congruence(self): """ Return a pair of elements giving "trivial" non-regular congruence. @@ -2434,6 +2499,8 @@ def sublattices_iterator(self, elms, min_e): {0} """ yield elms + mt = self.meet_matrix() + jn = self.join_matrix() for e in range(min_e, self.cardinality()): if e in elms: continue @@ -2446,8 +2513,8 @@ def sublattices_iterator(self, elms, min_e): if g in current_set: continue for x in current_set: - gens.add(self._meet[x, g]) - gens.add(self._join[x, g]) + gens.add(mt[x, g]) + gens.add(jn[x, g]) current_set.add(g) else: yield from self.sublattices_iterator(current_set, e + 1) @@ -2657,7 +2724,7 @@ def skeleton(self): raise ValueError("lattice is not pseudocomplemented") p_atoms.append(p_atom) n = len(p_atoms) - mt = self._meet + mt = self.meet_matrix() pos = [0] * n meets = [self.order() - 1] * n result = [self.order() - 1] @@ -2772,8 +2839,8 @@ def neutral_elements(self): notneutrals = set() all_elements = set(range(n)) - mt = self._meet - jn = self._join + mt = self.meet_matrix() + jn = self.join_matrix() def is_neutral(a): noncomp = all_elements.difference(self.depth_first_search(a)) @@ -2996,8 +3063,8 @@ def congruence(self, parts, start=None, stop_pairs=[]): from copy import copy n = self.order() - mt = self._meet - jn = self._join + mt = self.meet_matrix() + jn = self.join_matrix() def fill_to_interval(S): """ @@ -3047,7 +3114,7 @@ def fill_to_interval(S): for a in block: # Quadrilateral up for c in self.neighbor_out_iterator(a): if c not in block: - d = self._join[c, b] + d = jn[c, b] if cong.find(d) != cong.find(c): break else: @@ -3059,7 +3126,7 @@ def fill_to_interval(S): for b in reversed(block): # ...quadrilateral down for d in self.neighbor_in_iterator(b): if d not in block: - c = self._meet[d, a] + c = mt[d, a] if cong.find(c) != cong.find(d): break else: @@ -3088,10 +3155,10 @@ def fill_to_interval(S): if len(mins) > 1 or len(maxs) > 1: c = n - 1 for m in mins: - c = self._meet[c, m] + c = mt[c, m] d = 0 for m in maxs: - d = self._join[d, m] + d = jn[d, m] # This removes duplicates from todo. todo = set(cong.find(x) for x in todo) diff --git a/src/sage/combinat/posets/lattices.py b/src/sage/combinat/posets/lattices.py index 2ef29209a27..024ae3c8644 100644 --- a/src/sage/combinat/posets/lattices.py +++ b/src/sage/combinat/posets/lattices.py @@ -300,12 +300,13 @@ def meet(self, x, y=None): - Dual function: :meth:`~sage.combinat.posets.lattices.FiniteJoinSemilattice.join` """ + mt = self._hasse_diagram.meet_matrix() if y is not None: # Handle basic case fast i, j = map(self._element_to_vertex, (x, y)) - return self._vertex_to_element(self._hasse_diagram._meet[i, j]) + return self._vertex_to_element(mt[i, j]) m = self.cardinality() - 1 # m = top element for i in (self._element_to_vertex(_) for _ in x): - m = self._hasse_diagram._meet[i, m] + m = mt[i, m] return self._vertex_to_element(m) def atoms(self): @@ -625,12 +626,13 @@ def join(self, x, y=None): - Dual function: :meth:`~sage.combinat.posets.lattices.FiniteMeetSemilattice.meet` """ + jn = self._hasse_diagram.join_matrix() if y is not None: # Handle basic case fast i, j = map(self._element_to_vertex, (x, y)) - return self._vertex_to_element(self._hasse_diagram._join[i, j]) + return self._vertex_to_element(jn[i, j]) j = 0 # j = bottom element for i in (self._element_to_vertex(_) for _ in x): - j = self._hasse_diagram._join[i, j] + j = jn[i, j] return self._vertex_to_element(j) def coatoms(self): @@ -1689,7 +1691,7 @@ def is_cosectionally_complemented(self, certificate=False): return False H = self._hasse_diagram - jn = H._join + jn = H.join_matrix() n = H.order() for e in range(n-2, -1, -1): t = 0 @@ -1879,7 +1881,7 @@ def is_sectionally_complemented(self, certificate=False): return False H = self._hasse_diagram - mt = H._meet + mt = H.meet_matrix() n = H.order()-1 for e in range(2, n+1): t = n @@ -1968,7 +1970,7 @@ def breadth(self, certificate=False): H = self._hasse_diagram # Helper function: Join of elements in the list L. - jn = H._join + jn = H.join_matrix() def join(L): j = 0 @@ -2904,6 +2906,8 @@ def is_supersolvable(self, certificate=False): return True H = self._hasse_diagram + mt = H.meet_matrix() + jn = H.join_matrix() height = self.height() n = H.order() cur = H.maximal_elements()[0] @@ -2913,7 +2917,7 @@ def is_supersolvable(self, certificate=False): @cached_function def is_modular_elt(a): return all(H._rank[a] + H._rank[b] == - H._rank[H._meet[a, b]] + H._rank[H._join[a, b]] + H._rank[mt[a, b]] + H._rank[jn[a, b]] for b in range(n)) if not is_modular_elt(cur): @@ -3235,14 +3239,13 @@ def is_sublattice(self, other): sage: P = MeetSemilattice({0: [1]}) sage: E.is_sublattice(P) - Traceback (most recent call last): - ... - TypeError: other is not a lattice + True sage: P = JoinSemilattice({0: [1]}) sage: E.is_sublattice(P) - Traceback (most recent call last): - ... - TypeError: other is not a lattice + True + sage: P = Poset({0: [1]}) + sage: E.is_sublattice(P) + True """ try: o_meet = other.meet @@ -3402,10 +3405,12 @@ def isomorphic_sublattices_iterator(self, other): if not isinstance(other, FiniteLatticePoset): raise TypeError('the input is not a finite lattice') H = self._hasse_diagram + mt = H.meet_matrix() + jn = H.join_matrix() self_closure = H.transitive_closure() other_closure = other._hasse_diagram.transitive_closure() for g in self_closure.subgraph_search_iterator(other_closure, induced=True): - if all(H._meet[a, b] in g and H._join[a, b] in g for a, b in combinations(g, 2)): + if all(mt[a, b] in g and jn[a, b] in g for a, b in combinations(g, 2)): yield self.sublattice([self._vertex_to_element(v) for v in g]) def maximal_sublattices(self): diff --git a/src/sage/combinat/posets/posets.py b/src/sage/combinat/posets/posets.py index 2d385834ff6..1c1059571c0 100644 --- a/src/sage/combinat/posets/posets.py +++ b/src/sage/combinat/posets/posets.py @@ -50,6 +50,10 @@ :meth:`~FinitePoset.lower_covers_iterator` | Return an iterator over elements covered by given element. :meth:`~FinitePoset.upper_covers_iterator` | Return an iterator over elements covering given element. :meth:`~FinitePoset.cover_relations_iterator` | Return an iterator over cover relations of the poset. + :meth:`~FinitePoset.common_upper_covers` | Return the list of all common upper covers of the given elements. + :meth:`~FinitePoset.common_lower_covers` | Return the list of all common lower covers of the given elements. + :meth:`~FinitePoset.meet` | Return the meet of given elements if it exists; ``None`` otherwise. + :meth:`~FinitePoset.join` | Return the join of given elements if it exists; ``None`` otherwise. **Properties of the poset** @@ -2296,6 +2300,87 @@ def common_upper_covers(self, elmts): vertices = list(map(self._element_to_vertex, elmts)) return list(map(self._vertex_to_element, self._hasse_diagram.common_upper_covers(vertices))) + def common_lower_covers(self, elmts): + r""" + Return all of the common lower covers of the elements ``elmts``. + + EXAMPLES:: + + sage: P = Poset({0: [1,2], 1: [3], 2: [3], 3: []}) + sage: P.common_lower_covers([1, 2]) + [0] + """ + vertices = list(map(self._element_to_vertex, elmts)) + return list(map(self._vertex_to_element, self._hasse_diagram.common_lower_covers(vertices))) + + def meet(self, x, y): + r""" + Return the meet of two elements ``x, y`` in the poset if the meet + exists; and ``None`` otherwise. + + EXAMPLES:: + + sage: D = Poset({1:[2,3], 2:[4], 3:[4]}) + sage: D.meet(2, 3) + 1 + sage: P = Poset({'a':['b', 'c'], 'b':['e', 'f'], 'c':['f', 'g'], + ....: 'd':['f', 'g']}) + sage: P.meet('a', 'b') + 'a' + sage: P.meet('e', 'a') + 'a' + sage: P.meet('c', 'b') + 'a' + sage: P.meet('e', 'f') + 'b' + sage: P.meet('e', 'g') + 'a' + sage: P.meet('c', 'd') is None + True + sage: P.meet('g', 'f') is None + True + """ + i, j = map(self._element_to_vertex, (x, y)) + mt = self._hasse_diagram._meet + if mt[i, j] == -1: + return None + else: + return self._vertex_to_element(mt[i, j]) + + def join(self, x, y): + r""" + Return the join of two elements ``x, y`` in the poset if the join + exists; and ``None`` otherwise. + + EXAMPLES:: + + sage: D = Poset({1:[2,3], 2:[4], 3:[4]}) + sage: D.join(2, 3) + 4 + sage: P = Poset({'e':['b'], 'f':['b', 'c', 'd'], 'g':['c', 'd'], + ....: 'b':['a'], 'c':['a']}) + sage: P.join('a', 'b') + 'a' + sage: P.join('e', 'a') + 'a' + sage: P.join('c', 'b') + 'a' + sage: P.join('e', 'f') + 'b' + sage: P.join('e', 'g') + 'a' + sage: P.join('c', 'd') is None + True + sage: P.join('g', 'f') is None + True + """ + i, j = map(self._element_to_vertex, (x, y)) + jn = self._hasse_diagram._join + if jn[i,j] == -1: + return None + else: + return self._vertex_to_element(jn[i,j]) + def is_d_complete(self) -> bool: r""" Return ``True`` if a poset is d-complete and ``False`` otherwise. @@ -4270,7 +4355,7 @@ def is_meet_semilattice(self, certificate=False): """ from sage.combinat.posets.hasse_diagram import LatticeError try: - self._hasse_diagram._meet + self._hasse_diagram.meet_matrix() except LatticeError as error: if not certificate: return False @@ -4341,7 +4426,7 @@ def is_join_semilattice(self, certificate=False): """ from sage.combinat.posets.hasse_diagram import LatticeError try: - self._hasse_diagram._join + self._hasse_diagram.join_matrix() except LatticeError as error: if not certificate: return False @@ -6776,7 +6861,7 @@ def maximal_chains_iterator(self, partial=None): :meth:`antichains_iterator` """ - if partial is None or not partial: + if not partial: start = self.minimal_elements() partial = [] else: @@ -6858,7 +6943,7 @@ def order_complex(self, on_ints=False): sage: P.order_complex(on_ints=True) Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 3), (0, 2, 3)} """ - from sage.homology.simplicial_complex import SimplicialComplex + from sage.topology.simplicial_complex import SimplicialComplex L = self.list() if on_ints: iso = dict([(L[i], i) for i in range(len(L))]) @@ -6874,7 +6959,7 @@ def order_complex(self, on_ints=False): else: facets.append([a.element for a in f]) - return SimplicialComplex(facets) + return SimplicialComplex(facets, maximality_check=False) def order_polytope(self): r""" @@ -7052,7 +7137,7 @@ def f_polynomial(self): .. SEEALSO:: :meth:`is_bounded`, :meth:`h_polynomial`, :meth:`order_complex`, - :meth:`sage.homology.cell_complex.GenericCellComplex.f_vector` + :meth:`sage.topology.cell_complex.GenericCellComplex.f_vector` TESTS:: @@ -7120,7 +7205,7 @@ def h_polynomial(self): .. SEEALSO:: :meth:`is_bounded`, :meth:`f_polynomial`, :meth:`order_complex`, - :meth:`sage.homology.simplicial_complex.SimplicialComplex.h_vector` + :meth:`sage.topology.simplicial_complex.SimplicialComplex.h_vector` """ q = polygen(ZZ, 'q') hasse = self._hasse_diagram diff --git a/src/sage/combinat/recognizable_series.py b/src/sage/combinat/recognizable_series.py index 50219d3abac..0b5d134c6c2 100644 --- a/src/sage/combinat/recognizable_series.py +++ b/src/sage/combinat/recognizable_series.py @@ -44,9 +44,17 @@ Various ======= +.. SEEALSO:: + + :mod:`k-regular sequence `, + :mod:`sage.rings.cfinite_sequence`, + :mod:`sage.combinat.binary_recurrence_sequences`. + + AUTHORS: -- Daniel Krenn (2016) +- Daniel Krenn (2016, 2021) + ACKNOWLEDGEMENT: @@ -66,7 +74,6 @@ # (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** -from __future__ import absolute_import from sage.misc.cachefunc import cached_method from sage.misc.superseded import experimental @@ -529,7 +536,7 @@ def _latex_(self): return self._repr_(latex=True) @cached_method - def __getitem__(self, w): + def coefficient_of_word(self, w, multiply_left=True, multiply_right=True): r""" Return the coefficient to word `w` of this series. @@ -538,6 +545,12 @@ def __getitem__(self, w): - ``w`` -- a word over the parent's :meth:`~RecognizableSeriesSpace.alphabet` + - ``multiply_left`` -- (default: ``True``) a boolean. If ``False``, + then multiplication by :meth:`left ` is skipped. + + - ``multiply_right`` -- (default: ``True``) a boolean. If ``False``, + then multiplication by :meth:`right ` is skipped. + OUTPUT: An element in the parent's @@ -549,10 +562,31 @@ def __getitem__(self, w): sage: W = Rec.indices() sage: S = Rec((Matrix([[1, 0], [0, 1]]), Matrix([[0, -1], [1, 2]])), ....: left=vector([0, 1]), right=vector([1, 0])) - sage: S[W(7.digits(2))] + sage: S[W(7.digits(2))] # indirect doctest 3 + + TESTS:: + + sage: w = W(6.digits(2)) + sage: S.coefficient_of_word(w) + 2 + sage: S.coefficient_of_word(w, multiply_left=False) + (-1, 2) + sage: S.coefficient_of_word(w, multiply_right=False) + (2, 3) + sage: S.coefficient_of_word(w, multiply_left=False, multiply_right=False) + [-1 -2] + [ 2 3] """ - return self.left * self._mu_of_word_(w) * self.right + result = self._mu_of_word_(w) + if multiply_left: + result = self.left * result + if multiply_right: + result = result * self.right + return result + + + __getitem__ = coefficient_of_word @cached_method def _mu_of_empty_word_(self): @@ -940,12 +974,14 @@ def _minimized_left_(self): from sage.rings.integer_ring import ZZ pcs = PrefixClosedSet(self.parent().indices()) - left = self.left * self._mu_of_word_(pcs.elements[0]) + left = self.coefficient_of_word(pcs.elements[0], multiply_right=False) if left.is_zero(): return self.parent().zero() Left = [left] for p in pcs.iterate_possible_additions(): - left = self.left * self._mu_of_word_(p) + left = self.coefficient_of_word(p, + multiply_left=True, + multiply_right=False) try: Matrix(Left).solve_left(left) except ValueError: @@ -958,7 +994,7 @@ def _minimized_left_(self): ML = Matrix(Left) def alpha(c): - return ML.solve_left(self.left * self._mu_of_word_(c)) + return ML.solve_left(self.coefficient_of_word(c, multiply_right=False)) mu_prime = [] for a in self.parent().alphabet(): @@ -968,10 +1004,10 @@ def alpha(c): mu_prime.append(M) left_prime = vector([ZZ(1)] + (len(P)-1)*[ZZ(0)]) - right_prime = vector(self[p] for p in P) + right_prime = vector(self.coefficient_of_word(p) for p in P) - return self.parent().element_class( - self.parent(), mu_prime, left_prime, right_prime) + P = self.parent() + return P.element_class(P, mu_prime, left_prime, right_prime) class RecognizableSeriesSpace(UniqueRepresentation, Parent): diff --git a/src/sage/combinat/ribbon_tableau.py b/src/sage/combinat/ribbon_tableau.py index 0212acd458f..f164a886729 100644 --- a/src/sage/combinat/ribbon_tableau.py +++ b/src/sage/combinat/ribbon_tableau.py @@ -1110,14 +1110,13 @@ def __iter__(self): parts = self._shape mu = self._weight - #Splitting the partition - s = [ p.size() for p in parts ] + # Splitting the partition + s = [p.size() for p in parts] parts = [p.to_list() for p in parts] - #Gluing the partitions + # Gluing the partitions parttmp = parts[0] - i = 1 - for i in range(1,len(parts)): + for i in range(1, len(parts)): trans = parttmp[0][0] current_part = parts[i] current_part[1] += [0]*(len(current_part[0])-len(current_part[1])) @@ -1125,7 +1124,7 @@ def __iter__(self): outer_current = [ trans + j for j in current_part[0] ] parttmp = [ outer_current + parttmp[0], inner_current + parttmp[1] ] - #List the corresponding skew tableaux + # List the corresponding skew tableaux l = [ st.to_word() for st in SemistandardSkewTableaux(parttmp, mu) ] S = SkewTableaux() diff --git a/src/sage/combinat/root_system/ambient_space.py b/src/sage/combinat/root_system/ambient_space.py index 67088b3197b..e1d4adea0ea 100644 --- a/src/sage/combinat/root_system/ambient_space.py +++ b/src/sage/combinat/root_system/ambient_space.py @@ -85,11 +85,11 @@ def __init__(self, root_system, base_ring, index_set=None): """ self.root_system = root_system if index_set is None: - index_set = tuple(range(0, self.dimension())) + index_set = tuple(range(self.dimension())) CombinatorialFreeModule.__init__(self, base_ring, index_set, prefix='e', - category = WeightLatticeRealizations(base_ring)) + category=WeightLatticeRealizations(base_ring)) coroot_lattice = self.root_system.coroot_lattice() coroot_lattice.module_morphism(self.simple_coroot, codomain=self).register_as_coercion() diff --git a/src/sage/combinat/root_system/branching_rules.py b/src/sage/combinat/root_system/branching_rules.py index 1a2d6cbd8b0..ded9b53460b 100644 --- a/src/sage/combinat/root_system/branching_rules.py +++ b/src/sage/combinat/root_system/branching_rules.py @@ -979,7 +979,7 @@ def rule(x): sage: A3(0,1,0).branch(C2,rule=br) C2(0,0) + C2(0,1) """ - if isinstance(rule, str) or isinstance(rule, list): + if isinstance(rule, (str, list)): rule = branching_rule(R._cartan_type, S._cartan_type, rule) if hasattr(rule, "_S"): if rule._S != S.cartan_type(): diff --git a/src/sage/combinat/root_system/root_system.py b/src/sage/combinat/root_system/root_system.py index 83c209d6a09..628afb34833 100644 --- a/src/sage/combinat/root_system/root_system.py +++ b/src/sage/combinat/root_system/root_system.py @@ -329,7 +329,8 @@ def __init__(self, cartan_type, as_dual_of=None): self.dual_side = False # still fails for CartanType G2xA1 try: - self.dual = RootSystem(self._cartan_type.dual(), as_dual_of=self); + self.dual = RootSystem(self._cartan_type.dual(), + as_dual_of=self) except Exception: pass else: diff --git a/src/sage/combinat/root_system/type_super_A.py b/src/sage/combinat/root_system/type_super_A.py index 103c7e15b7c..17cf933e14c 100644 --- a/src/sage/combinat/root_system/type_super_A.py +++ b/src/sage/combinat/root_system/type_super_A.py @@ -585,7 +585,9 @@ def symmetrizer(self): Finite family {-2: 1, -1: 1, 0: 1, 1: -1, 2: -1, 3: -1} """ from sage.sets.family import Family - def ell(i): return ZZ.one() if i <= 0 else -ZZ.one() + + def ell(i): + return ZZ.one() if i <= 0 else -ZZ.one() return Family(self.index_set(), ell) def dynkin_diagram(self): diff --git a/src/sage/combinat/set_partition.py b/src/sage/combinat/set_partition.py index 21900263007..71bac648136 100644 --- a/src/sage/combinat/set_partition.py +++ b/src/sage/combinat/set_partition.py @@ -16,7 +16,7 @@ This module defines a class for immutable partitioning of a set. For mutable version see :func:`DisjointSet`. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 Mike Hansen , # # Distributed under the terms of the GNU General Public License (GPL) @@ -28,8 +28,8 @@ # # The full text of the GPL is available at: # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.sets.set import Set, Set_generic @@ -55,6 +55,7 @@ from sage.sets.disjoint_set import DisjointSet from sage.combinat.posets.hasse_diagram import HasseDiagram + class AbstractSetPartition(ClonableArray, metaclass=InheritComparisonClasscallMetaclass): r""" @@ -444,6 +445,7 @@ def coarsenings(self): [{}] """ SP = SetPartitions(len(self)) + def union(s): # Return the partition obtained by combining, for every # part of s, those parts of self which are indexed by @@ -500,12 +502,14 @@ def conjugate(self): """ def next(a, support): return support[(support.index(a)+1) % len(support)] + def addback(S, terminals, rsupport): out = list(S) for a in terminals*2: if a not in out and next(a, rsupport) in out: out.append(a) return out + def pre_conjugate(sp): if len(sp) <= 1: return SetPartition([[a] for S in sp for a in S]) @@ -526,6 +530,7 @@ def pre_conjugate(sp): return SetPartition([[support[-support.index(a)-1] for a in S] for S in pre_conjugate(self)]) + class SetPartition(AbstractSetPartition, metaclass=InheritComparisonClasscallMetaclass): r""" @@ -1997,6 +2002,7 @@ def plot(self, angle=None, color='black', base_set_dict=None): diag.axes(False) return diag + class SetPartitions(UniqueRepresentation, Parent): r""" An (unordered) partition of a set `S` is a set of pairwise @@ -2188,7 +2194,7 @@ def from_restricted_growth_word_blocks(self, w): integers such that each letter is at most 1 larger than all the letters before to a set partition of `\{1,...,n\}`. - ``w[i]` is the index of the block containing ``i+1`` when + ``w[i]`` is the index of the block containing ``i+1`` when sorting the blocks by their minimal element. INPUT: @@ -2653,6 +2659,7 @@ def is_strict_refinement(self, s, t): return False return True + class SetPartitions_all(SetPartitions): r""" All set partitions. @@ -2696,6 +2703,7 @@ def __iter__(self): yield self.element_class(self, list(x)) n += 1 + class SetPartitions_set(SetPartitions): """ Set partitions of a fixed set `S`. @@ -2865,6 +2873,7 @@ def base_set_cardinality(self): """ return len(self._set) + class SetPartitions_setparts(SetPartitions_set): r""" Set partitions with fixed partition sizes corresponding to an @@ -3024,7 +3033,6 @@ def _set_partition_poset(self): covers[first].append(i) return HasseDiagram(covers) - def __iter__(self): """ An iterator for all the set partitions of the given set with @@ -3083,6 +3091,7 @@ def __contains__(self, x): return False return sorted(map(len, x)) == list(reversed(self._parts)) + class SetPartitions_setn(SetPartitions_set): """ Set partitions with a given number of blocks. @@ -3267,6 +3276,7 @@ def cyclic_permutations_of_set_partition(set_part): """ return list(cyclic_permutations_of_set_partition_iterator(set_part)) + def cyclic_permutations_of_set_partition_iterator(set_part): """ Iterates over all combinations of cyclic permutations of each cell diff --git a/src/sage/combinat/set_partition_ordered.py b/src/sage/combinat/set_partition_ordered.py index 48131aaa65c..4b8512e3274 100644 --- a/src/sage/combinat/set_partition_ordered.py +++ b/src/sage/combinat/set_partition_ordered.py @@ -73,9 +73,9 @@ class OrderedSetPartition(ClonableArray, .. MATH:: - \sum_n {T_n \over n!} x^n = {1 \over 2-e^x}. + \sum_n \frac{T_n}{n!} x^n = \frac{1}{2-e^x}. - (See sequence A000670 in OEIS.) + (See sequence :oeis:`A000670` in OEIS.) INPUT: diff --git a/src/sage/combinat/sf/elementary.py b/src/sage/combinat/sf/elementary.py index 02b4ab90c00..4be0083e782 100644 --- a/src/sage/combinat/sf/elementary.py +++ b/src/sage/combinat/sf/elementary.py @@ -91,7 +91,8 @@ def coproduct_on_generators(self, i): sage: e.coproduct_on_generators(0) e[] # e[] """ - def P(i): return Partition([i]) if i else Partition([]) + def P(i): + return Partition([i]) if i else Partition([]) T = self.tensor_square() return T.sum_of_monomials( (P(j), P(i-j)) for j in range(i+1) ) diff --git a/src/sage/combinat/sf/hecke.py b/src/sage/combinat/sf/hecke.py index 22bd90df969..fa911bbbd0b 100644 --- a/src/sage/combinat/sf/hecke.py +++ b/src/sage/combinat/sf/hecke.py @@ -289,10 +289,10 @@ def coproduct_on_generators(self, r): sage: qbar[2].coproduct() qbar[] # qbar[2] + (q-1)*qbar[1] # qbar[1] + qbar[2] # qbar[] """ - def P(i): return _Partitions([i]) if i else _Partitions([]) + def P(i): + return _Partitions([i]) if i else _Partitions([]) T = self.tensor_square() one = self.base_ring().one() q = self.q return T.sum_of_terms(((P(j), P(r-j)), one if j in [0,r] else q-one) for j in range(r+1)) - diff --git a/src/sage/combinat/sf/homogeneous.py b/src/sage/combinat/sf/homogeneous.py index 585a8fd7ffe..f6729bb9f6a 100644 --- a/src/sage/combinat/sf/homogeneous.py +++ b/src/sage/combinat/sf/homogeneous.py @@ -120,11 +120,11 @@ def coproduct_on_generators(self, i): sage: h.coproduct_on_generators(0) h[] # h[] """ - def P(i): return Partition([i]) if i else Partition([]) + def P(i): + return Partition([i]) if i else Partition([]) T = self.tensor_square() return T.sum_of_monomials( (P(j), P(i-j)) for j in range(i+1) ) - class Element(classical.SymmetricFunctionAlgebra_classical.Element): def omega(self): r""" diff --git a/src/sage/combinat/similarity_class_type.py b/src/sage/combinat/similarity_class_type.py index 857c52c4fe0..e500595082f 100644 --- a/src/sage/combinat/similarity_class_type.py +++ b/src/sage/combinat/similarity_class_type.py @@ -308,7 +308,7 @@ def centralizer_algebra_dim(la): If it is a list, ``la`` is expected to be sorted in decreasing order. """ - return sum([(2*i + 1)*la[i] for i in range(0, len(la))]) + return sum([(2 * i + 1) * la[i] for i in range(len(la))]) @cached_function diff --git a/src/sage/combinat/sine_gordon.py b/src/sage/combinat/sine_gordon.py index 309c07fcd23..a3c63e7b2d8 100644 --- a/src/sage/combinat/sine_gordon.py +++ b/src/sage/combinat/sine_gordon.py @@ -58,7 +58,7 @@ from sage.functions.log import exp from sage.functions.other import ceil from sage.misc.flatten import flatten -from sage.calculus.var import var +from sage.symbolic.ring import SR from sage.functions.other import real_part, imag_part from sage.misc.cachefunc import cached_method @@ -516,7 +516,7 @@ def plot_arc(radius, p, q, **opts): # plot the arc from p to q differently depending on the type of self p = ZZ(p) q = ZZ(q) - t = var('t') + t = SR.var('t') if p - q in [1, -1]: def f(t): return (radius * cos(t), radius * sin(t)) diff --git a/src/sage/combinat/skew_partition.py b/src/sage/combinat/skew_partition.py index 3087118648b..9be0a9b6237 100644 --- a/src/sage/combinat/skew_partition.py +++ b/src/sage/combinat/skew_partition.py @@ -1516,7 +1516,7 @@ def from_row_and_column_length(self, rowL, colL): resIn.append(inP) resOut.append(len(colL_new)) for iCol in range(inP, len(colL_new)): - colL_new[iCol] -= 1; + colL_new[iCol] -= 1 if colL_new[iCol] < 0: raise ValueError("Incompatible row and column length : %s and %s"%(rowL, colL)) while colL_new and colL_new[-1] == 0: diff --git a/src/sage/combinat/sloane_functions.py b/src/sage/combinat/sloane_functions.py index ada6bf23094..bdeb3be26c1 100644 --- a/src/sage/combinat/sloane_functions.py +++ b/src/sage/combinat/sloane_functions.py @@ -84,7 +84,7 @@ # 1. Add a new class to Section II below, which you should # do by copying an existing class and modifying it. # Make sure to at least define _eval and _repr_. -# NOTES: (a) define the _eval method only, which you may +# NOTE: (a) define the _eval method only, which you may # assume has as input a *positive* Sage integer (offset > 0). # Each sequence in the OEIS has an offset >= 0, indicating the # value of the first index. The default offset = 1. diff --git a/src/sage/combinat/subword_complex.py b/src/sage/combinat/subword_complex.py index dd4926c414e..2e447d7076a 100644 --- a/src/sage/combinat/subword_complex.py +++ b/src/sage/combinat/subword_complex.py @@ -116,7 +116,7 @@ from sage.misc.cachefunc import cached_method from sage.structure.element import Element from sage.structure.unique_representation import UniqueRepresentation -from sage.homology.simplicial_complex import SimplicialComplex, Simplex +from sage.topology.simplicial_complex import SimplicialComplex, Simplex from sage.categories.simplicial_complexes import SimplicialComplexes from sage.geometry.polyhedron.constructor import Polyhedron from sage.geometry.cone import Cone diff --git a/src/sage/combinat/tableau_tuple.py b/src/sage/combinat/tableau_tuple.py index f2d5b91a660..9e8e689ac0f 100644 --- a/src/sage/combinat/tableau_tuple.py +++ b/src/sage/combinat/tableau_tuple.py @@ -1080,16 +1080,15 @@ def row_stabilizer(self): sage: rs.one().domain() [1, 2, 3, 4, 5, 6, 7, 8, 9] """ - # Ensure that the permutations involve all elements of the # tableau, by including the identity permutation on the set [1..n]. n = max(self.entries()) gens = [list(range(1, n + 1))] for t in self: for i in range(len(t)): - for j in range(0, len(t[i])-1): - gens.append( (t[i][j], t[i][j+1]) ) - return PermutationGroup( gens ) + for j in range(len(t[i]) - 1): + gens.append((t[i][j], t[i][j + 1])) + return PermutationGroup(gens) def column_stabilizer(self): """ diff --git a/src/sage/combinat/words/alphabet.py b/src/sage/combinat/words/alphabet.py index 88ae2b66ecb..3286eb8b3b4 100644 --- a/src/sage/combinat/words/alphabet.py +++ b/src/sage/combinat/words/alphabet.py @@ -41,8 +41,6 @@ from sage.rings.infinity import Infinity from sage.sets.non_negative_integers import NonNegativeIntegers -from sage.sets.positive_integers import PositiveIntegers -from sage.misc.persist import register_unpickle_override set_of_letters = { @@ -56,8 +54,7 @@ 'octal' : "01234567", 'decimal' : "0123456789", 'hexadecimal' : "0123456789abcdef", - 'radix64' : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", - } + 'radix64' : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"} def build_alphabet(data=None, names=None, name=None): @@ -267,98 +264,6 @@ def build_alphabet(data=None, names=None, name=None): raise ValueError("unable to construct an alphabet from the given parameters") + # TODO: should it be deprecated as it is no more a class ? Alphabet = build_alphabet - -# NOTE: all of the classes below are here for backward compatibility (pickling). -# More precisely, the ticket #8920 suppress several classes. The following code -# just allows to unpickle old style alphabet saved from previous version of -# Sage. - - -class OrderedAlphabet(object): - r""" - .. WARNING:: - - Not to be used! (backward compatibility) - - Returns a finite or infinite ordered alphabet. - - EXAMPLES:: - - sage: from sage.combinat.words.alphabet import OrderedAlphabet - sage: A = OrderedAlphabet('ab'); A - doctest:...: DeprecationWarning: OrderedAlphabet is deprecated; use Alphabet instead. - See http://trac.sagemath.org/8920 for details. - {'a', 'b'} - sage: type(A) - - """ - def __new__(self, alphabet=None, name=None): - """ - EXAMPLES:: - - sage: from sage.combinat.words.alphabet import OrderedAlphabet - sage: A = OrderedAlphabet('ab'); A # indirect doctest - doctest:...: DeprecationWarning: OrderedAlphabet is deprecated; use Alphabet instead. - See http://trac.sagemath.org/8920 for details. - {'a', 'b'} - """ - from sage.misc.superseded import deprecation - deprecation(8920, 'OrderedAlphabet is deprecated; use Alphabet instead.') - - if alphabet is not None or name is not None: - return build_alphabet(data=alphabet, name=name) - from sage.structure.parent import Parent - return Parent.__new__(OrderedAlphabet_backward_compatibility) - -OrderedAlphabet_Finite = OrderedAlphabet - - -class OrderedAlphabet_backward_compatibility(TotallyOrderedFiniteSet): - r""" - .. WARNING:: - - Not to be used! (backward compatibility) - - Version prior to :trac:`8920` uses classes ``Alphabet`` with an argument - ``._alphabet`` instead of ``._elements`` used in - :class:`TotallyOrderedFiniteSet`. This class is dedicated to handle this - problem which occurs when unpickling ``OrderedAlphabet``. - """ - def __getattr__(self, name): - r""" - If the attribute '_elements' is called then it is set to '_alphabet'. - - EXAMPLES:: - - sage: from sage.combinat.words.alphabet import OrderedAlphabet - sage: O = OrderedAlphabet() - doctest:...: DeprecationWarning: OrderedAlphabet is deprecated; use Alphabet instead. - See http://trac.sagemath.org/8920 for details. - sage: O._alphabet = ['a', 'b'] - sage: O._elements - ('a', 'b') - """ - if name == '_elements': - if not hasattr(self, '_alphabet'): - raise AttributeError("no attribute '_elements'") - self._elements = tuple(self._alphabet) - from sage.structure.parent import Parent - from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets - Parent.__init__(self, category=FiniteEnumeratedSets(), facade=True) - return self._elements - raise AttributeError("no attribute %s" % name) - - -register_unpickle_override( - 'sage.combinat.words.alphabet', - 'OrderedAlphabet_NaturalNumbers', - NonNegativeIntegers, - call_name=('sage.sets.non_negative_integers', 'NonNegativeIntegers')) - -register_unpickle_override( - 'sage.combinat.words.alphabet', - 'OrderedAlphabet_PositiveIntegers', - PositiveIntegers, - call_name=('sage.sets.positive_integers', 'PositiveIntegers')) diff --git a/src/sage/combinat/words/finite_word.py b/src/sage/combinat/words/finite_word.py index aec56d7fbd4..8e6fcc27ef1 100644 --- a/src/sage/combinat/words/finite_word.py +++ b/src/sage/combinat/words/finite_word.py @@ -7047,8 +7047,8 @@ def is_cube_free(self): L = self.length() if L < 3: return True - for start in range(0, L - 2): - for end in range(start+3, L+1, 3): + for start in range(L - 2): + for end in range(start + 3, L + 1, 3): if self[start:end].is_cube(): return False return True @@ -7140,6 +7140,38 @@ def is_christoffel(self): else: return False + def minimal_conjugate(self): + r""" + Return the lexicographically minimal conjugate of this word (see + :wikipedia:`Lexicographically_minimal_string_rotation`). + + EXAMPLES:: + + sage: Word('213').minimal_conjugate() + word: 132 + sage: Word('11').minimal_conjugate() + word: 11 + sage: Word('12112').minimal_conjugate() + word: 11212 + sage: Word('211').minimal_conjugate() + word: 112 + sage: Word('211211211').minimal_conjugate() + word: 112112112 + + TESTS:: + + sage: Word().minimal_conjugate() + word: + """ + if not self: + return self + p = self.primitive() + q = self.length() // p.length() + end = 0 + for factor in (p ** 2).lyndon_factorization(): + end += factor.length() + if end >= p.length(): + return factor ** q ####################################################################### diff --git a/src/sage/combinat/words/morphism.py b/src/sage/combinat/words/morphism.py index 1879e4e1833..bc6a540c2d0 100644 --- a/src/sage/combinat/words/morphism.py +++ b/src/sage/combinat/words/morphism.py @@ -106,46 +106,45 @@ from sage.combinat.words.words import FiniteWords, FiniteOrInfiniteWords -def get_cycles(f, domain=None): +def get_cycles(f, domain): r""" - Return the cycle of the function ``f`` on the finite set domain. It is - assumed that f is an endomorphism. + Return the list of cycles of the function ``f`` contained in ``domain``. INPUT: - ``f`` - function. - - ``domain`` - set (default: None) - the domain of ``f``. If none, then - tries to use ``f.domain()``. + - ``domain`` - iterable, a subdomain of the domain of definition of ``f``. EXAMPLES:: sage: from sage.combinat.words.morphism import get_cycles - sage: get_cycles(lambda i: (i+1)%3, domain=[0,1,2]) + sage: get_cycles(lambda i: (i+1)%3, [0,1,2]) [(0, 1, 2)] - sage: get_cycles(lambda i: [0,0,0][i], domain=[0,1,2]) + sage: get_cycles(lambda i: [0,0,0][i], [0,1,2]) [(0,)] - sage: get_cycles(lambda i: [1,1,1][i], domain=[0,1,2]) + sage: get_cycles(lambda i: [1,1,1][i], [0,1,2]) [(1,)] + sage: get_cycles(lambda i: [2,3,0][i], [0,1,2]) + [(0, 2)] + sage: d = {'a': 'a', 'b': 'b'} + sage: get_cycles(d.__getitem__, 'ba') + [('b',), ('a',)] """ - if domain is None: - try: - domain = f.domain() - except AttributeError: - raise ValueError("you should specify the domain of the function f") cycles = [] - not_seen = dict((letter,True) for letter in domain) - for a in not_seen: - if not_seen[a]: - not_seen[a] = False - cycle = [a] - b = f(a) - while not_seen[b]: - not_seen[b] = False - cycle.append(b) - b = f(b) - if b in cycle: - cycles.append(tuple(cycle[cycle.index(b):])) + not_seen = set(domain) + for a in domain: + if a not in not_seen: + continue + cycle = [a] + b = f(a) + not_seen.remove(a) + while b in not_seen: + not_seen.remove(b) + cycle.append(b) + b = f(b) + if b in cycle: + cycles.append(tuple(cycle[cycle.index(b):])) return cycles @@ -1947,12 +1946,18 @@ def periodic_point(self, letter): Traceback (most recent call last): ... TypeError: self must be prolongable on a + + Make sure that :trac:`31759` is fixed:: + + sage: WordMorphism('a->b,b->a').periodic_point('a') + word: a """ if not self.is_growing(letter): - w = self(letter) - w2 = self(w) - while w2 != w: - w,w2 = w2, self(w2) + w = self.domain()(letter) + prev = set() + while w not in prev: + prev.add(w) + w = self(w) return w elif self.is_erasing(): @@ -2024,7 +2029,7 @@ def periodic_points(self): res = [] parent = self.codomain().shift() - for cycle in get_cycles(CallableDict(d),A): + for cycle in get_cycles(CallableDict(d), A): if cycle[0] in G: P = PeriodicPointIterator(self, cycle) res.append([parent(P._cache[i]) for i in range(len(cycle))]) @@ -2038,6 +2043,8 @@ def _language_naive(self, n, u): The language of the substitution is the DOL language which consist of factors of `s^n(u)`. + This method assumes this substitution is non-erasing. + INPUT: - ``n`` -- non-negative integer - length of the words in the language @@ -2057,19 +2064,38 @@ def _language_naive(self, n, u): sage: s._language_naive(3, W()) set() + sage: W([1, 1]) in s._language_naive(3, W([1, 1])) + True """ - L = set(u.parent()()) - todo = [u] + L = set() + todo = [] + for i in range(len(u)): + for j in range(i+1, min(len(u)+1, i+n)): + f = u[i:j] + if f not in L: + todo.append(f) + L.add(f) while todo: u = todo.pop() v = self(u) - for i in range(len(v)): - for j in range(i+1, min(len(v)+1, i+n)): - f = v[i:j] - if f not in L: - todo.append(f) - L.add(f) - + if u.length() == 1: + for i in range(len(v)): + for j in range(i+1, min(len(v)+1, i+n)): + f = v[i:j] + if f not in L: + todo.append(f) + L.add(f) + else: + l = self._morph[u[0]].length() + r = self._morph[u[-1]].length() + m = v.length() - l - r + x = n - 1 - m + for i in range(l - min(x - 1, l), l): + for j in range(l + m + 1, l + m + 1 + min(x - l + i, r)): + f = v[i:j] + if f not in L: + todo.append(f) + L.add(f) return L def language(self, n, u=None): @@ -3080,27 +3106,14 @@ def is_growing(self, letter=None): Combinatorics, automata and number theory, 163--247, Encyclopedia Math. Appl., 135, Cambridge Univ. Press, Cambridge, 2010. """ - if self.is_primitive() and len(self._morph) > 1: - return True - if letter is None: - I = range(self.domain().alphabet().cardinality()) + if not letter: + return self.domain().alphabet().cardinality() == len(self.growing_letters()) else: - if letter not in self.domain().alphabet(): - raise TypeError("letter (=%s) is not in the domain of self" % letter) - I = [self.domain().alphabet().rank(letter)] - - last_coef = 0 - coefs = self.incidence_matrix().charpoly().coefficients(sparse=False) - while coefs[last_coef] == 0: - last_coef += 1 - V = self.abelian_rotation_subspace() + (self.incidence_matrix()**last_coef).right_kernel().change_ring(QQ) - basis = V.ambient_vector_space().basis() - - return not any(basis[i] in V for i in I) + return letter in self.growing_letters() def growing_letters(self): r""" - Returns the list of growing letters. + Return the list of growing letters. See :meth:`.is_growing` for more information. @@ -3112,6 +3125,10 @@ def growing_letters(self): ['0'] sage: WordMorphism('0->01,1->0,2->1',codomain=Words('012')).growing_letters() ['0', '1', '2'] + sage: WordMorphism('a->b,b->a').growing_letters() + [] + sage: WordMorphism('a->b,b->c,c->d,d->c', codomain=Words('abcd')).growing_letters() + [] TESTS: @@ -3120,17 +3137,73 @@ def growing_letters(self): sage: WordMorphism('a->a').growing_letters() [] """ - if self.is_primitive() and len(self._morph) > 1: - return self.domain().alphabet().list() - last_coef = 0 - coefs = self.incidence_matrix().charpoly().coefficients(sparse=False) - while coefs[last_coef] == 0: - last_coef += 1 - V = self.abelian_rotation_subspace() + (self.incidence_matrix()**last_coef).right_kernel().change_ring(QQ) - basis = V.ambient_vector_space().basis() - A = self.domain().alphabet() + # Remove letters that vanish, ie sigma^n(letter) is ultimately empty + immortal = set(self.immortal_letters()) + new_morph = {x: [z for z in self._morph[x] if z in immortal] for x in immortal} + + # Remove cycles of letters + graph_one = {x : y[0] for x, y in new_morph.items() if len(y) == 1} + no_loops = set(new_morph) + for cycle in get_cycles(graph_one.__getitem__, graph_one): + 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. + new_morph = WordMorphism(new_morph, domain=self.domain(), codomain=self.codomain()) + return new_morph.immortal_letters() + + def immortal_letters(self): + r""" + Return the list of immortal letters. + + 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. - return list(A.unrank(i) for i in range(A.cardinality()) if basis[i] not in V) + Requires this morphism to be an endomorphism. + + EXAMPLES:: + + sage: WordMorphism('a->a').immortal_letters() + ['a'] + sage: WordMorphism('a->b,b->a').immortal_letters() + ['a', 'b'] + sage: WordMorphism('a->abcd,b->cd,c->dd,d->').immortal_letters() + ['a'] + sage: WordMorphism('a->bc,b->cac,c->de,d->,e->').immortal_letters() + ['a', 'b'] + sage: WordMorphism('a->', domain=Words('a'), codomain=Words('a')).immortal_letters() + [] + """ + if not self.is_endomorphism(): + raise TypeError(f'self ({self}) is not an endomorphism') + + forward = {} + backward = {letter: set() for letter in self._morph} + stack = [] + for letter, image in self._morph.items(): + if not image: + stack.append(letter) + forward[letter] = set() + else: + simage = set(image) + forward[letter] = simage + for occurrence in simage: + backward[occurrence].add(letter) + + while stack: + letter = stack.pop() + for preimage in backward[letter]: + forward[preimage].remove(letter) + if not forward[preimage]: + stack.append(preimage) + del forward[letter] + del backward[letter] + + return sorted(forward, key=self.domain().alphabet().rank) def abelian_rotation_subspace(self): r""" @@ -3184,3 +3257,517 @@ def abelian_rotation_subspace(self): basis.extend((factor[0])(M).right_kernel().basis()) return M._column_ambient_module().change_ring(QQ).subspace(basis) + + def is_injective(self): + """ + Return whether this morphism is injective. + + ALGORITHM: + + Uses a version of :wikipedia:`Sardinas–Patterson_algorithm`. + Time complexity is on average quadratic with regards to the size of the + morphism. + + EXAMPLES:: + + sage: WordMorphism('a->0,b->10,c->110,d->111').is_injective() + True + sage: WordMorphism('a->00,b->01,c->012,d->20001').is_injective() + False + """ + def check(u, v): + if u.is_prefix(v): + tail = v[u.length():] + if tail not in tails: + tails.add(tail) + todo.append(tail) + + if self.is_erasing(): + return False + images = self.images() + tails = set() + todo = [] + + for i in range(len(images)): + for j in range(i + 1, len(images)): + if images[i] == images[j]: + return False + check(images[i], images[j]) + check(images[j], images[i]) + while todo: + u = todo.pop() + for v in images: + if u == v: + return False + check(u, v) + check(v, u) + + return True + + def is_pushy(self, w=None): + r""" + Return whether the language `\{m^n(w) | n \ge 0\}` is pushy, + where `m` is this morphism and `w` is a word inputted as a parameter. + + Requires this morphism to be an endomorphism. + + A language created by iterating a morphism is pushy, if its words + contain an infinite number of factors containing no growing letters. It + turns out that this is equivalent to having at least one infinite + repetition containing no growing letters. + + See :meth:`infinite_repetitions_primitive_roots` and :meth:`is_growing`. + + INPUT: + + - ``w`` -- finite iterable (default: ``self.domain().alphabet()``). + Represents a word used to start the language. + + EXAMPLES:: + + sage: WordMorphism('a->abca,b->bc,c->').is_pushy() + False + sage: WordMorphism('a->abc,b->,c->bcb').is_pushy() + True + """ + return bool(self.infinite_repetitions_primitive_roots(w, False)) + + def is_unboundedly_repetitive(self, w=None): + r""" + Return whether the language `\{m^n(w) | n \ge 0\}` is unboundedly repetitive, + where `m` is this morphism and `w` is a word inputted as a parameter. + + Requires this morphism to be an endomorphism. + + A language created by iterating a morphism is unboundedly repetitive, if + it has at least one infinite repetition containing at least one growing + letter. + + See :meth:`infinite_repetitions_primitive_roots` and :meth:`is_growing`. + + INPUT: + + - ``w`` -- finite iterable (default: ``self.domain().alphabet()``). + Represents a word used to start the language. + + EXAMPLES:: + + sage: WordMorphism('a->abca,b->bc,c->').is_unboundedly_repetitive() + True + sage: WordMorphism('a->abc,b->,c->bcb').is_unboundedly_repetitive() + False + """ + return bool(self.infinite_repetitions_primitive_roots(w, True)) + + def is_repetitive(self, w=None): + r""" + Return whether the language `\{m^n(w) | n \ge 0\}` is repetitive, + where `m` is this morphism and `w` is a word inputted as a parameter. + + Requires this morphism to be an endomorphism. + + A language is repetitive, if for each positive integer `k` there exists + a word `u` such that `u^k` is a factor of some word of the language. + + It turns out that for languages created by iterating a morphism this is + equivalent to having at least one infinite repetition (this property is + also known as strong repetitiveness). + + See :meth:`infinite_repetitions_primitive_roots`. + + INPUT: + + - ``w`` -- finite iterable (default: ``self.domain().alphabet()``). + Represents a word used to start the language. + + EXAMPLES: + + This method can be used to check whether a purely morphic word is not + k-power free for all positive integers k. For example, the language + containing just the Thue-Morse word and its prefixes is not repetitive, + since the Thue-Morse word is cube-free:: + + sage: WordMorphism('a->ab,b->ba').is_repetitive('a') + False + + Similarly, the Hanoi word is square-free:: + + sage: WordMorphism('a->aC,A->ac,b->cB,B->cb,c->bA,C->ba').is_repetitive('a') + False + + However, this method solves a more general problem, as it can be called + on any morphism `m` and with any word `w`:: + + sage: WordMorphism('a->c,b->cda,c->a,d->abc').is_repetitive('bd') + True + """ + return self.is_pushy(w) or self.is_unboundedly_repetitive(w) + + def infinite_repetitions_primitive_roots(self, w=None, allow_growing=None): + r""" + Return the set of primitive roots (up to conjugacy) of infinite + repetitions from the language `\{m^n(w) | n \ge 0\}`, where `m` is this + morphism and `w` is a word inputted as a parameter. + + Requires this morphism to be an endomorphism. + + The word `v^\omega` is an infinite repetition (in other words, an + infinite periodic factor) of a language, if `v` is a non-empty word and + for each positive integer `k` the word `v^k` is a factor of some word + from the language. It turns out that a language created by iterating a + morphism has a finite number of primitive roots of infinite repetitions. + + If `v` is a primitive root of an infinite repetition, then all its + conjugations are also primitive roots of an infinite repetition. For + simplicity's sake this method returns only the lexicographically minimal + one from each conjugacy class. + + INPUT: + + - ``w`` -- finite iterable (default: ``self.domain().alphabet()``). + Represents a word used to start the language. + + - ``allow_growing`` -- boolean or ``None`` (default: ``None``). If + ``False``, return only the primitive roots that contain no growing + letters. If ``True``, return only the primitive roots that contain at + least one growing letter. If ``None``, return both. + + ALGORITHM: + + The algorithm used is described in detail in [KS2015]_. + + EXAMPLES:: + + sage: m = WordMorphism('a->aba,b->aba,c->cd,d->e,e->d') + sage: inf_reps = m.infinite_repetitions_primitive_roots('ac') + sage: sorted(inf_reps) + [word: aab, word: de] + + ``allow_growing`` parameter:: + + sage: sorted(m.infinite_repetitions_primitive_roots('ac', True)) + [word: aab] + sage: sorted(m.infinite_repetitions_primitive_roots('ac', False)) + [word: de] + + Incomplete check that these words are indeed the primitive roots of + infinite repetitions:: + + sage: SL = m._language_naive(10, Word('ac')) + sage: all(x in SL for x in inf_reps) + True + sage: all(x^2 in SL for x in inf_reps) + True + sage: all(x^3 in SL for x in inf_reps) + True + + Large example:: + + sage: m = WordMorphism('a->1b5,b->fcg,c->dae,d->432,e->678,f->f,g->g,1->2,2->3,3->4,4->1,5->6,6->7,7->8,8->5') + sage: sorted(m.infinite_repetitions_primitive_roots('a')) + [word: 1432f2143f3214f4321f, word: 5678g8567g7856g6785g] + + TESTS:: + + sage: m = WordMorphism('a->Cab,b->1c1,c->E2bd5,d->BbaA,5->6,6->7,7->8,8->9,9->5,1->2,2->1,A->B,B->C,C->D,D->E,E->') + sage: sorted(m.infinite_repetitions_primitive_roots()) + [word: 1, word: 1519181716, word: 2, word: 2529282726] + + sage: m = WordMorphism('a->b,b->b', codomain=FiniteWords('ab')) + sage: m.infinite_repetitions_primitive_roots() + set() + + sage: m = WordMorphism('c->d,d->c,e->fc,f->ed') + sage: sorted(m.infinite_repetitions_primitive_roots()) + [word: c, word: d] + + sage: m = WordMorphism('a->bcb,b->ada,c->d,d->c') + sage: sorted(m.infinite_repetitions_primitive_roots()) + [word: ad, word: bc] + + sage: m = WordMorphism('b->c,c->bcb') + sage: sorted(m.infinite_repetitions_primitive_roots()) + [word: bc] + + sage: m = WordMorphism('a->abc,b->dab,c->abc,d->dab') + sage: sorted(m.infinite_repetitions_primitive_roots()) + [word: ababcd] + """ + def impl_no_growing(g, k): + U = {} + for x in unbounded: + xg = g.image(x) + for i, y in enumerate(reversed(xg)): + if y in unbounded: + break + U[x] = y, xg[xg.length() - i:] + for cycle in get_cycles(lambda x: U[x][0], domain=unbounded): + if all(not U[x][1] for x in cycle): + continue + gq = gb ** len(cycle) + for cycle in g.domain()(cycle).conjugates_iterator(): + u = g.domain()() + for x in cycle: + u = U[x][1] + gb(u) + inf_rep = g.domain()() + history = set() + while u not in history: + history.add(u) + inf_rep += u + u = gq(u) + yield k(inf_rep.primitive()).primitive() + + if w is None: + w = self._morph + reach = self._language_naive(2, self._domain(w)) + f = self.restrict_domain([x[0] for x in reach]) + f._codomain = f._domain + g, _, k, _ = f.simplify_until_injective() + g._codomain = g._domain + unbounded = set(g.growing_letters()) + result = set() + + if allow_growing is not True: + gb = g.restrict_domain(set(g._morph) - unbounded) + for x in impl_no_growing(g, k): # UR. + result.add(x.minimal_conjugate()) + for x in impl_no_growing(g.reversal(), k.reversal()): # UL. + result.add(self.domain()(reversed(x)).minimal_conjugate()) + + if allow_growing is not False: + for periodic_orbit in g.periodic_points(): + gq = g ** len(periodic_orbit) + for periodic_point in periodic_orbit: + # Check if this periodic point is a periodic infinite word. + periodic_point = periodic_point[:1] + occurred = set(periodic_point) + one_unbounded_twice = False + for _ in g.domain().alphabet(): + previous_length = periodic_point.length() + periodic_point = gq(periodic_point) + for i, letter in enumerate(periodic_point[previous_length:]): + if letter in unbounded: + if letter in occurred: + one_unbounded_twice = True + break + occurred.add(letter) + if one_unbounded_twice: + break + if not one_unbounded_twice or letter != periodic_point[0]: + break + v = periodic_point[:previous_length + i] + vq = gq(v) + m = 0 + while vq[m * v.length() : (m + 1) * v.length()] == v: + m += 1 + if m * v.length() != vq.length(): + break + result.add(k(v).primitive().minimal_conjugate()) + + return result + + def simplify_alphabet_size(self, Z=None): + r""" + If this morphism is simplifiable, return morphisms `h` and `k` such that + this morphism is simplifiable with respect to `h` and `k`, otherwise + raise ``ValueError``. + + This method is quite fast if this morphism is non-injective, but very + slow if it is injective. + + Let `f: X^* \rightarrow Y^*` be a morphism. Then `f` is simplifiable + with respect to morphisms `h: X^* \rightarrow Z^*` and + `k: Z^* \rightarrow Y^*`, if `f = k \circ h` and `|Z| < |X|`. If also + `Y \subseteq X`, then the morphism `g: Z^* \rightarrow Z^* = h \circ k` + is a simplification of `f` (with respect to `h` and `k`). + + Loosely speaking, a morphism is simplifiable if it contains "more letters + than is needed". Non-injectivity implies simplifiability. Simplification + preserves some properties of the original morphism (e.g. repetitiveness). + + For more information see Section 3 in [KO2000]_. + + INPUT: + + - ``Z`` -- iterable (default: ``self.domain().alphabet()``), whose + elements are used as an alphabet for the simplification. + + EXAMPLES: + + Example of a simplifiable (non-injective) morphism:: + + sage: f = WordMorphism('a->aca,b->badc,c->acab,d->adc') + sage: h, k = f.simplify_alphabet_size('xyz'); h, k + (WordMorphism: a->x, b->zy, c->xz, d->y, WordMorphism: x->aca, y->adc, z->b) + sage: k * h == f + True + sage: g = h * k; g + WordMorphism: x->xxzx, y->xyxz, z->zy + + Example of a simplifiable (injective) morphism:: + + sage: f = WordMorphism('a->abcc,b->abcd,c->abdc,d->abdd') + sage: h, k = f.simplify_alphabet_size('xyz'); h, k + (WordMorphism: a->xyy, b->xyz, c->xzy, d->xzz, WordMorphism: x->ab, y->c, z->d) + sage: k * h == f + True + sage: g = h * k; g + WordMorphism: x->xyyxyz, y->xzy, z->xzz + + Example of a non-simplifiable morphism:: + + sage: WordMorphism('a->aa').simplify_alphabet_size() + Traceback (most recent call last): + ... + ValueError: self (a->aa) is not simplifiable + + Example of an erasing morphism:: + + sage: f = WordMorphism('a->abc,b->cc,c->') + sage: h, k = f.simplify_alphabet_size(); h, k + (WordMorphism: a->a, b->b, c->, WordMorphism: a->abc, b->cc) + sage: k * h == f + True + sage: g = h * k; g + WordMorphism: a->ab, b-> + + Example of a morphism, that is not an endomorphism:: + + sage: f = WordMorphism('a->xx,b->xy,c->yx,d->yy') + sage: h, k = f.simplify_alphabet_size(NN); h, k + (WordMorphism: a->00, b->01, c->10, d->11, WordMorphism: 0->x, 1->y) + sage: k * h == f + True + sage: len(k.domain().alphabet()) < len(f.domain().alphabet()) + True + """ + def try_create_h(f, k): + h = {} + for letter1, image1 in f.items(): + image3 = [] + while image1: + for letter2, image2 in k.items(): + if image2.is_prefix(image1): + image1 = image1[image2.length():] + image3.append(letter2) + break + else: # nobreak + return None + h[letter1] = image3 + return h + + X = self.domain().alphabet() + Y = self.codomain().alphabet() + f = self._morph + + if self.is_erasing(): # Trivial case #1. + k = {letter: image for letter, image in f.items() if image} + h = {letter: [letter] if image else [] for letter, image in f.items()} + elif len(Y) < len(X): # Trivial case #2. + k = {x: [y] for x, y in zip(X, Y)} + k_inverse = {y: x for y, x in zip(Y, X)} + h = {x: [k_inverse[y] for y in image] for x, image in f.items()} + elif not self.is_injective(): # Non-trivial but a fast case. + k = dict(f) + to_do = set(k) + while to_do: + to_remove = [] + # min() and remove() instead of pop() to have deterministic output. + letter1 = min(to_do) + to_do.remove(letter1) + image1 = k[letter1] + for letter2, image2 in k.items(): + if letter1 == letter2: + continue + if image1 == image2: + to_remove.append(letter2) + to_do.discard(letter2) + elif image1.is_prefix(image2): + k[letter2] = image2[image1.length():] + to_do.add(letter2) + elif image2.is_prefix(image1): + k[letter1] = image1[image2.length():] + to_do.add(letter1) + break + for letter in to_remove: + del k[letter] + h = try_create_h(f, k) + else: # Non-trivial and a slow case. + factors = set() + for image in f.values(): + factors.update(x.primitive() for x in image.factor_iterator()) + factors.remove(self.codomain()()) + factors = sorted(factors) # For deterministic output. + from itertools import combinations + for comb in combinations(factors, len(X) - 1): + if any(x.is_proper_prefix(y) for x in comb for y in comb): + continue + k = {x: image for x, image in zip(X, comb)} + h = try_create_h(f, k) + if h: + break + else: # nobreak + raise ValueError(f'self ({self}) is not simplifiable') + + k = WordMorphism(k, codomain=self.codomain()) + h = WordMorphism(h, domain=self.domain(), codomain=k.domain()) + + if Z is not None: # Custom alphabet. + old_Z_star = k.domain() + old_Z = old_Z_star.alphabet() + Z = [z for z, _ in zip(Z, old_Z)] + if len(Z) < len(old_Z): + raise ValueError(f'Z should have length at least {len(old_Z)}, is {len(Z)}') + Z_star = FiniteWords(Z) + h_new = {old: [new] for old, new in zip(old_Z, Z)} + k_new = {new: [old] for new, old in zip(Z, old_Z)} + h_new = WordMorphism(h_new, domain=old_Z_star, codomain=Z_star) + k_new = WordMorphism(k_new, domain=Z_star, codomain=old_Z_star) + h = h_new * h + k = k * k_new + + return h, k + + def simplify_until_injective(self): + r""" + Return a quadruplet `(g, h, k, i)`, where `g` is an injective + simplification of this morphism with respect to `h`, `k` and `i`. + + Requires this morphism to be an endomorphism. + + This methods basically calls :meth:`simplify_alphabet_size` until the + returned simplification is injective. If this morphism is already + injective, a quadruplet `(g, h, k, i)` is still returned, where `g` + is this morphism, `h` and `k` are the identity morphisms and `i` is 0. + + Let `f: X^* \rightarrow Y^*` be a morphism and `Y \subseteq X`. Then + `g: Z^* \rightarrow Z^*` is an injective simplification of `f` with + respect to morphisms `h: X^* \rightarrow Z^*` and + `k: Z^* \rightarrow Y^*` and a positive integer `i`, if `g` is + injective, `|Z| < |X|`, `g^i = h \circ k` and `f^i = k \circ h`. + + For more information see Section 4 in [KO2000]_. + + EXAMPLES:: + + sage: f = WordMorphism('a->abc,b->a,c->bc') + sage: g, h, k, i = f.simplify_until_injective(); g, h, k, i + (WordMorphism: a->aa, WordMorphism: a->aa, b->a, c->a, WordMorphism: a->abc, 2) + sage: g.is_injective() + True + sage: g ** i == h * k + True + sage: f ** i == k * h + True + """ + if not self.is_endomorphism(): + raise TypeError(f'self ({self}) is not an endomorphism') + + g = self + h = self.domain().identity_morphism() + k = self.codomain().identity_morphism() + i = 0 + while not g.is_injective(): + h_new, k_new = g.simplify_alphabet_size() + g, h, k, i = h_new * k_new, h_new * h, k * k_new, i + 1 + return g, h, k, i diff --git a/src/sage/crypto/lattice.py b/src/sage/crypto/lattice.py index 84da16e3401..b593bac9b03 100644 --- a/src/sage/crypto/lattice.py +++ b/src/sage/crypto/lattice.py @@ -222,7 +222,8 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, set_random_seed(seed) if type == 'random': - if n != 1: raise ValueError('random bases require n = 1') + if n != 1: + raise ValueError('random bases require n = 1') ZZ = IntegerRing() ZZ_q = IntegerModRing(q) @@ -272,8 +273,10 @@ def gen_lattice(type='modular', n=4, m=8, q=11, seed=None, # switch from representatives 0,...,(q-1) to (1-q)/2,....,(q-1)/2 def minrep(a): - if abs(a-q) < abs(a): return a-q - else: return a + if abs(a-q) < abs(a): + return a-q + else: + return a A_prime = A[n:m].lift().apply_map(minrep) if not dual: diff --git a/src/sage/crypto/mq/sr.py b/src/sage/crypto/mq/sr.py index ab047b5f6cb..ca032bf4b27 100644 --- a/src/sage/crypto/mq/sr.py +++ b/src/sage/crypto/mq/sr.py @@ -1315,11 +1315,9 @@ def __call__(self, P, K): R[10].output 3925841D02DC09FBDC118597196A0B32 sage: set_verbose(0) """ - r,c,e = self.r,self.c,self.e + r, c, e = self.r, self.c, self.e F = self.base_ring() - _type = self.state_array - if isinstance(P, str): P = self.state_array([F.fetch_int(ZZ(P[i:i+2], 16)) for i in range(0, len(P), 2)]) if isinstance(K, str): @@ -2650,7 +2648,8 @@ def phi(self, l, diffusion_matrix=False): return tuple(ret) elif is_Matrix(l): return Matrix(GF(2), l.ncols(), l.nrows()*self.e, ret).transpose() - else: raise TypeError + else: + raise TypeError def antiphi(self, l): r""" @@ -2831,14 +2830,12 @@ def _mul_matrix(self, x): sage: (a^2 + 1)*(a+1) a^3 + a^2 + a + 1 """ - a = self.k.gen() k = self.k e = self.e a = k.gen() - columns = [] - for i in reversed(range(e)): - columns.append( list(reversed((x * a**i)._vector_())) ) + columns = [list(reversed((x * a**i)._vector_())) + for i in reversed(range(e))] return Matrix(GF(2), e, e, columns).transpose() def _square_matrix(self): diff --git a/src/sage/data_structures/bitset_base.pxd b/src/sage/data_structures/bitset_base.pxd index f36a530be57..44baaedaec9 100644 --- a/src/sage/data_structures/bitset_base.pxd +++ b/src/sage/data_structures/bitset_base.pxd @@ -33,13 +33,13 @@ AUTHORS: from libc.string cimport strlen from cysignals.memory cimport check_calloc, check_reallocarray, sig_malloc, sig_free +from memory_allocator cimport MemoryAllocator +from cython.operator import preincrement as preinc from sage.cpython.string cimport char_to_str, str_to_bytes, bytes_to_str from sage.libs.gmp.mpn cimport * from sage.libs.gmp.types cimport * from sage.data_structures.sparse_bitset cimport sparse_bitset_t -from cython.operator import preincrement as preinc -from sage.ext.memory_allocator cimport MemoryAllocator cdef extern from *: diff --git a/src/sage/databases/cremona.py b/src/sage/databases/cremona.py index 680543209de..e388d0558b0 100644 --- a/src/sage/databases/cremona.py +++ b/src/sage/databases/cremona.py @@ -1437,7 +1437,8 @@ def _init_allcurves(self, ftpdata, largest_conductor=0): curve_data = [] for L in open(ftpdata + "/" + F).readlines(): N, iso, num, ainvs, r, tor = L.split() - if largest_conductor and int(N) > largest_conductor: break + if largest_conductor and int(N) > largest_conductor: + break cls = N+iso cur = cls+num if num == "1": @@ -1452,7 +1453,8 @@ def _init_allcurves(self, ftpdata, largest_conductor=0): print("Committing...") print("num_iso_classes =", num_iso_classes) self.commit() - if largest_conductor and int(N) > largest_conductor: break + if largest_conductor and int(N) > largest_conductor: + break return num_curves, num_iso_classes @@ -1586,13 +1588,15 @@ def _init_degphi(self, ftpdata, largest_conductor=0): class_data = [] for L in open(ftpdata + "/" + F).readlines(): N, iso, num, degree, primes, curve = L.split() - if largest_conductor and int(N) > largest_conductor: break + if largest_conductor and int(N) > largest_conductor: + break class_data.append((degree,N+iso)) con.executemany('UPDATE t_class SET deg=? WHERE class=?', class_data) print("Committing...") self.commit() - if largest_conductor and int(N) > largest_conductor: break + if largest_conductor and int(N) > largest_conductor: + break def _init_allbsd(self, ftpdata, largest_conductor=0): """ @@ -1620,7 +1624,8 @@ def _init_allbsd(self, ftpdata, largest_conductor=0): class_data = [] for L in open(ftpdata + "/" + F).readlines(): N, iso, num, eqn, rank, tor, cp, om, L, reg, sha = L.split() - if largest_conductor and int(N) > largest_conductor: break + if largest_conductor and int(N) > largest_conductor: + break cls = N+iso if num == "1": class_data.append((L,cls)) @@ -1630,7 +1635,8 @@ def _init_allbsd(self, ftpdata, largest_conductor=0): + "curve=?", curve_data) print("Committing...") self.commit() - if largest_conductor and int(N) > largest_conductor: break + if largest_conductor and int(N) > largest_conductor: + break def _init_allgens(self, ftpdata, largest_conductor=0): """ @@ -1657,13 +1663,15 @@ def _init_allgens(self, ftpdata, largest_conductor=0): curve_data = [] for L in open(ftpdata + "/" + F).readlines(): v = L.split() - if largest_conductor and int(v[0]) > largest_conductor: break + if largest_conductor and int(v[0]) > largest_conductor: + break gens = '['+','.join(v[6:6+int(v[4])]).replace(':',',')+']' curve_data.append((gens,''.join(v[:3]))) con.executemany("UPDATE t_curve SET gens=? WHERE curve=?", curve_data) print("Committing...") - if largest_conductor and int(v[0]) > largest_conductor: break + if largest_conductor and int(v[0]) > largest_conductor: + break _db = None diff --git a/src/sage/databases/knotinfo_db.py b/src/sage/databases/knotinfo_db.py new file mode 100644 index 00000000000..9eb36c4d575 --- /dev/null +++ b/src/sage/databases/knotinfo_db.py @@ -0,0 +1,1036 @@ +# -*- coding: utf-8 -*- +r""" +KnotInfo Database + +This module contains the class :class:`KnotInfoDataBase` and auxilary classes +for it which serves as an interface to the lists of named knots and links provided +at the web-pages `KnotInfo `__ and +`LinkInfo `__. + + +AUTHORS: + +- Sebastian Oehms August 2020: initial version +""" + + +############################################################################## +# Copyright (C) 2020 Sebastian Oehms +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +############################################################################## + + +import os +from enum import Enum + +from sage.structure.sage_object import SageObject +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.persist import save, load +from sage.misc.verbose import verbose +from sage.misc.cachefunc import cached_method + + +class KnotInfoColumnTypes(Enum): + r""" + Enum class to specify if a column from the table of knots and links provided + at the web-pages `KnotInfo `__ and + `LinkInfo `__. is used for knots only, + links only or both. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoColumnTypes + sage: [col_type for col_type in KnotInfoColumnTypes] + [, + , + ] + """ + + OnlyKnots = 'K' # column that is only used in the KnotInfo table + OnlyLinks = 'L' # column that is only used in the LinkInfo table + KnotsAndLinks = 'B' # column that is only used in both tables + + +class KnotInfoColumns(Enum): + r""" + Enum class to select a column from the table of knots and links provided + at the web-pages `KnotInfo `__ and + `LinkInfo `__. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: cols = ki_db.columns(); cols + + sage: from sage.databases.knotinfo_db import KnotInfoColumns + sage: isinstance(cols.name, KnotInfoColumns) + True + + sage: def only_links(c): + ....: return c.column_type() == c.types.OnlyLinks + sage: [c.column_name() for c in cols if only_links(c)] # optional - database_knotinfo + ['Name - Unoriented', + 'Orientation', + 'Unoriented Rank', + 'PD Notation (vector)', + 'PD Notation (KnotTheory)', + 'Multivariable Alexander Polynomial', + 'HOMFLYPT Polynomial', + 'Unoriented', + 'Arc Notation', + 'Linking Matrix', + 'Rolfsen Name', + 'Components', + 'DT code', + 'Splitting Number', + 'Nullity', + 'Unlinking Number', + 'Weak Splitting Number'] + """ + @property + def types(self): + r""" + Return :class:`KnotInfoColumnTypes` to be used for checks. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: cols = ki_db.columns() + sage: cols.dt_code.column_type() == cols.dt_code.types.OnlyLinks + True + """ + return KnotInfoColumnTypes + + def column_name(self): + r""" + Return the name of ``self`` displayed on the KnotInfo web-page. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: cols = ki_db.columns() + sage: cols.dt_code.column_name() + 'DT code' + """ + return self.value[0] + + def column_type(self): + r""" + Return the type of ``self``. That is an instance of ``Enum`` + :class:`KnotInfoColumnTypes`. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: cols = ki_db.columns() + sage: cols.homfly_polynomial.column_type() + + sage: cols.homflypt_polynomial.column_type() + + sage: cols.name.column_type() + + """ + return self.value[1] + + def description_webpage(self, new=0, autoraise=True): + r""" + Launch the description page of ``self`` in the standard web browser. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: cols = ki_db.columns() + sage: cols.pd_notation.description_webpage() # not tested + True + sage: cols.homflypt_polynomial.description_webpage() # not tested + True + """ + import webbrowser + if self.column_type() == self.types.OnlyLinks: + url = KnotInfoFilename.links.description_url(self) + else: + url = KnotInfoFilename.knots.description_url(self) + return webbrowser.open(url, new=new, autoraise=autoraise) + + + + +class KnotInfoFilename(Enum): + r""" + Enum for the different data files. The following choices are possible: + + - ``knots`` -- contains the the data from KnotInfo + - ``links`` -- contains the the data for proper links from LinkInfo + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename + + """ + + def url(self): + r""" + Return the URL to download the data from the web-page. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.url() + 'https://knotinfo.math.indiana.edu/' + """ + if self == KnotInfoFilename.knots: + return self.value[0] + else: + return self.value[0] + + def excel(self): + r""" + Return the Excel-file name to download the data from the web-page. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.excel() + 'knotinfo_data_complete.xls' + """ + if self == KnotInfoFilename.knots: + return '%s.xls' %(self.value[1]) + else: + return '%s.xlsx' %(self.value[1]) + + def csv(self): + r""" + Return the file name under which the data from the web-page + are stored as csv file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.csv() + 'knotinfo_data_complete.csv' + """ + return '%s.csv' %(self.value[1]) + + def num_knots(self): + r""" + Return the file name under which the number of knots is stored + in an sobj-file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.num_knots() + 'num_knots.sobj' + """ + return 'num_knots.sobj' + + def sobj_row(self): + r""" + Return the file name under which the row-data of the csv-File + is stored as python dictionary in a sobj-file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.sobj_row() + 'row_dict.sobj' + """ + return 'row_dict.sobj' + + def sobj_column(self): + r""" + Return the file name under which the column-data of the csv-File + is stored as python dictionary in a sobj-file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.sobj_column() + 'column_dict.sobj' + """ + return 'column_dict.sobj' + + + def sobj_data(self, column): + r""" + Return the file name under which the data of the given + column is stored as python list in a sobj-file. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.sobj_data(ki_db.columns().braid_notation) + 'knotinfo_braid_notation' + """ + if column.column_type() == column.types.OnlyLinks: + return 'linkinfo_%s' %(column.name) + else: + return 'knotinfo_%s' %(column.name) + + def description_url(self, column): + r""" + Return the url of the description page of the given column. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.description_url(ki_db.columns().braid_notation) + 'https://knotinfo.math.indiana.edu/descriptions/braid_notation.html' + """ + return '%sdescriptions/%s.html' %(self.url(), column.name) + + def diagram_url(self, fname, single=False): + r""" + Return the url of the diagram page of the given link. + + Examples:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots.diagram_url('3_1-50.png') + 'https://knotinfo.math.indiana.edu/diagram_display.php?3_1-50.png' + sage: ki_db.filename.knots.diagram_url('3_1', single=True) + 'https://knotinfo.math.indiana.edu/diagrams/3_1' + """ + if single: + return '%sdiagrams/%s' %(self.url(), fname) + else: + return '%sdiagram_display.php?%s' %(self.url(), fname) + + + knots = ['https://knotinfo.math.indiana.edu/', 'knotinfo_data_complete'] + links = ['https://linkinfo.sitehost.iu.edu/', 'linkinfo_data_complete'] + + + + +#---------------------------------------------------------------------------------------------------------------------------- +# Class to provide data for knots and links from the KnotInfo web-page +#---------------------------------------------------------------------------------------------------------------------------- +class KnotInfoDataBase(SageObject, UniqueRepresentation): + r""" + Database interface to KnotInfo + + The original data are obtained from KnotInfo web-page (URL see the example + below). In order to have these data installed during the build process as + a sage-package they are converted as csv files into a tarball. This tarball + has been created using the method :meth:`create_spkg_tarball`. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.knots + + """ + + filename = KnotInfoFilename + + def __init__(self, install=False): + r""" + Python constructor. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.filename.links + + """ + # some constants + self._names_column = 'name' + self._components_column = 'components' + self._knot_prefix = 'K' + + self._knot_list = None + self._link_list = None + self._demo = None + self._num_knots = None + + from sage.features.databases import DatabaseKnotInfo + from sage.env import DOT_SAGE + self._feature = DatabaseKnotInfo() + self._sobj_path = os.path.join(DOT_SAGE, 'knotinfo') + + def reset_filecache(self): + r""" + Reset the internal files containing the database. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.reset_filecache() # optional - database_knotinfo + """ + if not self._feature.is_present(): + return + sobj_path = self._sobj_path + os.system('rm -rf %s' %sobj_path) + from sage.misc.misc import sage_makedirs + sage_makedirs(sobj_path) + + num_knots_file = os.path.join(sobj_path, self.filename.knots.num_knots()) + knot_list = self.knot_list() + num_knots = len(knot_list) - 1 + save(num_knots, num_knots_file) + self._num_knots = num_knots + self._create_col_dict_sobj() + self._create_data_sobj() + return + + + def demo_version(self): + r""" + Return whether the KnotInfo databases are installed completely or + just the demo version is used. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.demo_version() # optional - database_knotinfo + False + """ + if self._demo is None: + if self._feature.is_present(): + num_knots_file = os.path.join(self._sobj_path, self.filename.knots.num_knots()) + from builtins import FileNotFoundError + try: + self._num_knots = load(num_knots_file) + except FileNotFoundError: + self.reset_filecache() + self._demo = False + else: + self._demo = True + self._num_knots = len([v for v in row_demo_sample.values() if v[1]==1]) + return self._demo + + def knot_list(self): + r""" + Return the KnotInfo table rows as Python list. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: len(ki_db.knot_list()) # not tested (just used on installation) + """ + if self._knot_list: + return self._knot_list + + from database_knotinfo import link_list + self._knot_list = link_list() + return self._knot_list + + + def link_list(self): + r""" + Return the LinkInfo table rows as Python list. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: len(ki_db.link_list()) # not tested (just used on installation) + """ + if self._link_list: + return self._link_list + + from database_knotinfo import link_list + self._link_list = link_list(proper_links=True) + return self._link_list + + def _create_col_dict_sobj(self): + r""" + Create ``sobj`` files containing the number of knots and a dictionary + for the columns of the table. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db._create_col_dict_sobj() # not tested (used on installation) + """ + knot_list = self.knot_list() + knot_column_names = knot_list[0] + + link_list = self.link_list() + link_column_names = link_list[0] + + from sage.misc.misc import sage_makedirs + sage_makedirs(self._sobj_path) + + column_dict = {} + + # ---------------------------------------------------------------- + # Columns that exist for knots and links + # ---------------------------------------------------------------- + for col in knot_column_names: + + name = knot_column_names[col] + if not name and col not in ['knot_atlas_anon', 'knotilus_page_anon']: + # not of interest + continue + + col_type = KnotInfoColumnTypes.OnlyKnots + if col in link_column_names: + col_type = KnotInfoColumnTypes.KnotsAndLinks + column_dict[col] = [name, col_type] + + # ---------------------------------------------------------------- + # Columns that exist for links only + # ---------------------------------------------------------------- + for col in link_column_names: + + name = link_column_names[col] + if not name and col not in ['knot_atlas_anon', 'knotilus_page_anon']: + # not of interest + continue + + if col in knot_column_names: + # already used + continue + + col_type = KnotInfoColumnTypes.OnlyLinks + column_dict[col] = [name, col_type] + + save(column_dict, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_column())) + + + + def _create_data_sobj(self): + r""" + Create ``sobj`` files containing the contents of the whole table. + To each column there is created one file containing a list of + strings giving the entries of the database table. + + The length of these lists depends on the type of the corresponding + column. If a column is used in both tables + (``KnotInfoColumnTypes.KnotsAndLinks``) the list of proper links + is appended to the list of knots. In both other cases the lenght + of the list corresponds to the number of listed knots and proper + links respectively. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db._create_data_sobj() # not tested (just used on installation) + """ + knot_list = self.knot_list() + link_list = self.link_list() + len_knots = len(knot_list) + len_links = len(link_list) + + row_dict = {} + + # ---------------------------------------------------------------- + # Columns that exist for knots and links + # ---------------------------------------------------------------- + for col in self.columns(): + val_list = [] + + if col.column_type() != col.types.OnlyLinks: + for i in range(1 , len_knots): + if col.name == self._names_column: + row_dict[self._knot_prefix + knot_list[i][col.name]] = [i - 1 , 1] + + val_list.append(knot_list[i][col.name]) + + if col.column_type() != col.types.OnlyKnots: + for i in range(1 , len_links): + if col.name == self._names_column: + link_name = link_list[i][col.name] + link_name = link_name.replace('{', '_') + link_name = link_name.replace(',', '_') + link_name = link_name.replace('}', '') + + num_comp = int(link_list[i][self._components_column]) + row_dict[link_name] = [i + len_knots - 2 , num_comp] + + val_list.append(link_list[i][col.name]) + + if val_list: + save(val_list, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_data(col))) + + save(row_dict, '%s/%s' %(self._sobj_path, self.filename.knots.sobj_row())) + + + @cached_method + def columns(self): + r""" + Return the columns ot the databese table as instances of enum class + :class:`KnotInfoColumns`. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: cols = ki_db.columns() + sage: [col.column_name() for col in cols if col.name.startswith('pd')] # optional - database_knotinfo + ['PD Notation', 'PD Notation (vector)', 'PD Notation (KnotTheory)'] + """ + column_dict = self.read_column_dict() + return KnotInfoColumns('Columns', column_dict) + + + # ------------------------------------------------------------------------------------------------------------- + # read the dictionary for the column names from sobj-file + # ------------------------------------------------------------------------------------------------------------- + @cached_method + def read_column_dict(self): + r""" + Read the dictionary for the column names from the according sobj-file + + OUTPUT: + + A python dictionary containing the column names and types + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: len(ki_db.read_column_dict()) # optional - database_knotinfo + 125 + """ + if self.demo_version(): + return column_demo_sample + sobj_path = self._sobj_path + filename = self.filename.knots.sobj_column() + return load('%s/%s' %(sobj_path, filename)) + + # ------------------------------------------------------------------------------------------------------------- + # read the dictionary for the row names that is the knot and link names from sobj-file + # ------------------------------------------------------------------------------------------------------------- + @cached_method + def read_row_dict(self): + r""" + Read the dictionary for the row names that is the knot and link names + from the according sobj-file + + OUTPUT: + + A python dictionary containing the names of the knots and links + together with their table index and the corresponding number of + components + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.read_row_dict()['K7_1'] + [8, 1] + """ + if self.demo_version(): + return row_demo_sample + sobj_path = self._sobj_path + filename = self.filename.knots.sobj_row() + return load('%s/%s' %(sobj_path, filename)) + + # ------------------------------------------------------------------------------------------------------------- + # return a dictionary to obtain the original name to a row_dict key + # ------------------------------------------------------------------------------------------------------------- + @cached_method + def row_names(self): + r""" + Return a dictionary to obtain the original name to a row_dict key + + OUTPUT: + + A python dictionary containing the names of the knots and links + together with their original names from the database, + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.row_names()['K7_1'] # optional - database_knotinfo + '7_1' + """ + row_dict = self.read_row_dict() + names = self.read(self.columns().name) + return {k:names[v[0]] for k, v in row_dict.items()} + + + # ------------------------------------------------------------------------------------------------------------- + # read the number of knots contained in the database (without proper links) from the according sobj-file. + # ------------------------------------------------------------------------------------------------------------- + def read_num_knots(self): + r""" + Read the number of knots contained in the database (without + proper links) from the according sobj-file. + + OUTPUT: + + Integer + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: ki_db.read_num_knots() # optional - database_knotinfo + 2978 + """ + if not self._num_knots: + self.demo_version() + return self._num_knots + + + # ------------------------------------------------------------------------------------------------------------- + # read an sobj-file obtained from KnotInfo database + # ------------------------------------------------------------------------------------------------------------- + @cached_method + def read(self, column): + r""" + Access a column of KnotInfo / LinkInfo + + INPUT: + + ``column`` -- instance of enum :class:`KnotInfoColumns` + to select the data to be read in + + OUTPUT: + + A python list containing the data corresponding to the column. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + """ + if not isinstance(column, KnotInfoColumns): + raise TypeError('column must be an instance of enum %s' %(KnotInfoColumns)) + + if self.demo_version(): + return data_demo_sample[column] + + sobj_path = self._sobj_path + if column.column_type() == column.types.OnlyLinks: + filename = self.filename.links.sobj_data(column) + else: + filename = self.filename.knots.sobj_data(column) + + verbose('loading data library %s ...' %(filename)) + res = load('%s/%s' %(sobj_path, filename)) + verbose('... finished!') + + return res + + def _test_database(self, **options): + r""" + Method used by TestSuite. Performs :meth:`KnotInfoBase.is_recoverable`. + + EXAMPLES:: + + sage: from sage.databases.knotinfo_db import KnotInfoDataBase + sage: ki_db = KnotInfoDataBase() + sage: TestSuite(ki_db).run() # long time + """ + from sage.knots.knotinfo import KnotInfo + from sage.misc.misc import some_tuples + tester = options['tester'] + max_samples = tester._max_samples + if not max_samples: + max_samples = 20 + l = list(KnotInfo) + sample = some_tuples(l, 1, len(l), max_samples=max_samples) + tester.assertTrue(all(L.is_recoverable(unique=False) for L, in sample)) + + + +column_demo_sample = { + 'name': ['Name', KnotInfoColumnTypes.KnotsAndLinks], + 'name_unoriented': ['Name - Unoriented', KnotInfoColumnTypes.OnlyLinks], + 'dt_notation': ['DT Notation', KnotInfoColumnTypes.OnlyKnots], + 'gauss_notation': ['Gauss Notation', KnotInfoColumnTypes.KnotsAndLinks], + 'pd_notation': ['PD Notation', KnotInfoColumnTypes.OnlyKnots], + 'pd_notation_vector': ['PD Notation (vector)', KnotInfoColumnTypes.OnlyLinks], + 'crossing_number': ['Crossing Number', KnotInfoColumnTypes.KnotsAndLinks], + 'braid_index': ['Braid Index', KnotInfoColumnTypes.OnlyKnots], + 'braid_length': ['Braid Length', KnotInfoColumnTypes.OnlyKnots], + 'braid_notation': ['Braid Notation', KnotInfoColumnTypes.KnotsAndLinks], + 'alternating': ['Alternating', KnotInfoColumnTypes.KnotsAndLinks], + 'alexander_polynomial': ['Alexander', KnotInfoColumnTypes.OnlyKnots], + 'jones_polynomial': ['Jones', KnotInfoColumnTypes.KnotsAndLinks], + 'conway_polynomial': ['Conway', KnotInfoColumnTypes.KnotsAndLinks], + 'homfly_polynomial': ['HOMFLY', KnotInfoColumnTypes.OnlyKnots], + 'homflypt_polynomial': ['HOMFLYPT Polynomial', KnotInfoColumnTypes.OnlyLinks], + 'kauffman_polynomial': ['Kauffman', KnotInfoColumnTypes.KnotsAndLinks], + 'determinant': ['Determinant', KnotInfoColumnTypes.KnotsAndLinks], + 'positive': ['Positive', KnotInfoColumnTypes.OnlyKnots], + 'fibered': ['Fibered', KnotInfoColumnTypes.OnlyKnots], + 'unoriented': ['Unoriented', KnotInfoColumnTypes.OnlyLinks], + 'symmetry_type': ['Symmetry Type', KnotInfoColumnTypes.OnlyKnots], + 'width': ['Width', KnotInfoColumnTypes.OnlyKnots], + 'arc_notation': ['Arc Notation', KnotInfoColumnTypes.OnlyLinks], + 'dt_code': ['DT code', KnotInfoColumnTypes.OnlyLinks] +} + + +row_demo_sample = { + 'K0_1': [0, 1], + 'K3_1': [1, 1], + 'K4_1': [2, 1], + 'K5_1': [3, 1], + 'K5_2': [4, 1], + 'K6_1': [5, 1], + 'K6_2': [6, 1], + 'K6_3': [7, 1], + 'K7_1': [8, 1], + 'K7_2': [9, 1], + 'L2a1_0': [10, 2], + 'L2a1_1': [11, 2], + 'L4a1_0': [12, 2], + 'L4a1_1': [13, 2], + 'L5a1_0': [14, 2], + 'L5a1_1': [15, 2], + 'L6a1_0': [16, 2], + 'L6a1_1': [17, 2], + 'L6a2_0': [18, 2], + 'L6a2_1': [19, 2] +} + +db = KnotInfoDataBase() +dc = db.columns() + + +data_demo_sample = { + dc.name: ['0_1', '3_1', '4_1', '5_1', '5_2', '6_1', '6_2', '6_3', '7_1', '7_2', + 'L2a1{0}', 'L2a1{1}', 'L4a1{0}', 'L4a1{1}', 'L5a1{0}', 'L5a1{1}', + 'L6a1{0}', 'L6a1{1}', 'L6a2{0}', 'L6a2{1}', 'L6a3{0}' + ], + dc.name_unoriented: ['L2a1', 'L2a1', 'L4a1', 'L4a1', 'L5a1', 'L5a1', 'L6a1', 'L6a1', 'L6a2', 'L6a2', 'L6a3'], + dc.crossing_number: ['0', '3', '4', '5', '5', '6', '6', '6', '7', '7', '2', '2', '4', '4', '5', '5', '6', '6', '6', '6', '6'], + dc.braid_notation: [ + '', + '{1,1,1}', + '{1,-2,1,-2}', + '{1,1,1,1,1}', + '{1,1,1,2,-1,2}', + '{1,1,2,-1,-3,2,-3}', + '{1,1,1,-2,1,-2}', + '{1,1,-2,1,-2,-2}', + '{1,1,1,1,1,1,1}', + '{1,1,1,2,-1,2,3,-2,3}', + '{2, {-1, -1}}', + '{2, {1, 1}}', + '{4, {1, -2, 3, -2, -1, -2, -3, -2}}', + '{2, {1, 1, 1, 1}}', + '{3, {-1, 2, -1, 2, -1}}', + '{3, {-1, 2, -1, 2, -1}}', + '{4, {1, -2, 3, -2, 1, -2, -3, -2}}', + '{4, {1, 2, 3, 2, 2, -1, 2, 2, -3, 2}}', + '{4, {1, -2, -2, -2, 3, -2, -1, -2, -3, -2}}', + '{4, {1, 2, -3, 2, -1, 2, 3, 2, 2, 2}}', + '{2, {-1, -1, -1, -1, -1, -1}}' + ], + dc.braid_index: ['1', '2', '3', '2', '3', '4', '3', '3', '2', '4'], + dc.braid_length: ['', '3', '4', '5', '6', '7', '6', '6', '7', '9'], + dc.determinant: ['0', '3', '5', '5', '7', '9', '11', '13', '7', '11', '2', '2', '4', '4', '8', '8', '12', '12', '10', '10', '6'], + dc.positive: ['', 'Y', 'N', 'Y', 'Y', 'N', 'N', 'N', 'Y', 'Y'], + dc.fibered: ['', 'Y', 'Y', 'Y', 'N', 'N', 'Y', 'Y', 'Y', 'N'], + dc.unoriented: ['Y', 'N', 'Y', 'N', 'Y', 'N', 'Y', 'N', 'Y', 'N', 'Y'], + dc.pd_notation: [ + '', + '[[1,5,2,4],[3,1,4,6],[5,3,6,2]]', + '[[4,2,5,1],[8,6,1,5],[6,3,7,4],[2,7,3,8]]', + '[[2,8,3,7],[4,10,5,9],[6,2,7,1],[8,4,9,3],[10,6,1,5]]', + '[[1,5,2,4],[3,9,4,8],[5,1,6,10],[7,3,8,2],[9,7,10,6]]', + '[[1,7,2,6],[3,10,4,11],[5,3,6,2],[7,1,8,12],[9,4,10,5],[11,9,12,8]]', + '[[1,8,2,9],[3,11,4,10],[5,1,6,12],[7,2,8,3],[9,7,10,6],[11,5,12,4]]', + '[[4,2,5,1],[8,4,9,3],[12,9,1,10],[10,5,11,6],[6,11,7,12],[2,8,3,7]]', + '[[1,9,2,8],[3,11,4,10],[5,13,6,12],[7,1,8,14],[9,3,10,2],[11,5,12,4],[13,7,14,6]]', + '[[2,10,3,9],[4,14,5,13],[6,12,7,11],[8,2,9,1],[10,8,11,7],[12,6,13,5],[14,4,1,3]]', + ], + dc.pd_notation_vector: [ + '{{4, 1, 3, 2}, {2, 3, 1, 4}}', + '{{4, 2, 3, 1}, {2, 4, 1, 3}}', + '{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}', + '{{6, 2, 7, 1}, {8, 4, 5, 3}, {2, 8, 3, 7}, {4, 6, 1, 5}}', + '{{6, 1, 7, 2}, {10, 7, 5, 8}, {4, 5, 1, 6}, {2, 10, 3, 9}, {8, 4, 9, 3}}', + '{{8, 2, 9, 1}, {10, 7, 5, 8}, {4, 10, 1, 9}, {2, 5, 3, 6}, {6, 3, 7, 4}}', + '{{6, 1, 7, 2}, {10, 3, 11, 4}, {12, 8, 5, 7}, {8, 12, 9, 11}, {2, 5, 3, 6}, {4, 9, 1, 10}}', + '{{10, 2, 11, 1}, {6, 4, 7, 3}, {12, 10, 5, 9}, {8, 6, 9, 5}, {2, 12, 3, 11}, {4, 8, 1, 7}}', + '{{8, 1, 9, 2}, {12, 5, 7, 6}, {10, 3, 11, 4}, {4, 11, 5, 12}, {2, 7, 3, 8}, {6, 9, 1, 10}}', + '{{10, 2, 11, 1}, {12, 6, 7, 5}, {8, 4, 9, 3}, {4, 8, 5, 7}, {2, 12, 3, 11}, {6, 10, 1, 9}}', + '{{8, 1, 9, 2}, {2, 9, 3, 10}, {10, 3, 11, 4}, {12, 5, 7, 6}, {6, 7, 1, 8}, {4, 11, 5, 12}}' + ], + dc.dt_notation: [ + '', + '[4, 6, 2]', + '[4, 6, 8, 2]', + '[6, 8, 10, 2, 4]', + '[4, 8, 10, 2, 6]', + '[4, 8, 12, 10, 2, 6]', + '[4, 8, 10, 12, 2, 6]', + '[4, 8, 10, 2, 12, 6]', + '[8, 10, 12, 14, 2, 4, 6]', + '[4, 10, 14, 12, 2, 8, 6]' + ], + dc.dt_code: [ + '[{4}, {2}]', + '[{4}, {2}]', + '[{6, 8}, {2, 4}]', + '[{6, 8}, {4, 2}]', + '[{6, 8}, {4, 10, 2}]', + '[{8, 6}, {2, 10, 4}]', + '[{6, 10}, {2, 12, 4, 8}]', + '[{10, 6}, {8, 4, 12, 2}]', + '[{8, 10, 12}, {2, 6, 4}]', + '[{10, 8, 12}, {4, 6, 2}]', + '[{8, 10, 12}, {6, 2, 4}]' + ], + dc.gauss_notation: [ + '', + '{1, -2, 3, -1, 2, -3}', + '{-1, 2, -3, 1, -4, 3, -2, 4}', + '{-1, 2, -3, 4, -5, 1, -2, 3, -4, 5}', + '{1, -2, 3, -1, 4, -5, 2, -3, 5, -4}', + '{1, -2, 3, -4, 2, -1, 5, -6, 4, -3, 6, -5}', + '{1, -2, 3, -4, 5, -6, 2, -1, 6, -3, 4, -5}', + '{-1, 2, -3, 1, -4, 5, -2, 3, -6, 4, -5, 6}', + '{1, -2, 3, -4, 5, -6, 7, -1, 2, -3, 4, -5, 6, -7}', + '{-1, 2, -3, 4, -5, 6, -7, 1, -2, 7, -6, 5, -4, 3}', + '{{1, -2}, {2, -1}}', + '{{1, -2}, {2, -1}}', + '{{1, -3, 2, -4}, {3, -1, 4, -2}}', + '{{1, -3, 2, -4}, {4, -1, 3, -2}}', + '{{1, -4, 5, -3}, {3, -1, 2, -5, 4, -2}}', + '{{1, -4, 5, -3}, {4, -5, 2, -1, 3, -2}}', + '{{1, -5, 2, -6}, {5, -1, 3, -4, 6, -2, 4, -3}}', + '{{1, -5, 2, -6}, {4, -2, 6, -4, 3, -1, 5, -3}}', + '{{1, -5, 3, -4, 2, -6}, {5, -1, 6, -3, 4, -2}}', + '{{1, -5, 3, -4, 2, -6}, {4, -3, 6, -1, 5, -2}}', + '{{1, -2, 3, -6, 4, -5}, {5, -1, 2, -3, 6, -4}}' + ], + dc.arc_notation: [ + '{{4, 2}, {3, 1}, {4, 2}, {1, 3}}', + '{{2, 4}, {3, 1}, {2, 4}, {3, 1}}', + '{{6, 4}, {3, 5}, {4, 2}, {1, 3}, {2, 6}, {5, 1}}', + '{{3, 6}, {2, 5}, {6, 4}, {1, 3}, {5, 2}, {4, 1}}', + '{{6, 2}, {1, 4}, {3, 5}, {4, 7}, {2, 6}, {7, 3}, {5, 1}}', + '{{3, 5}, {6, 4}, {5, 2}, {7, 3}, {1, 6}, {2, 7}, {4, 1}}', + '{{8, 4}, {3, 5}, {4, 2}, {6, 3}, {5, 7}, {1, 6}, {2, 8}, {7, 1}}', + '{{2, 8}, {1, 7}, {8, 4}, {5, 3}, {4, 2}, {3, 6}, {7, 5}, {6, 1}}', + '{{8, 3}, {2, 7}, {3, 1}, {4, 8}, {5, 2}, {6, 4}, {7, 5}, {1, 6}}', + '{{3, 8}, {2, 7}, {8, 4}, {1, 3}, {5, 2}, {4, 6}, {7, 5}, {6, 1}}', + '{{8, 2}, {1, 3}, {2, 4}, {3, 5}, {4, 6}, {5, 7}, {6, 8}, {7, 1}}' + ], + dc.alternating: ['Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y'], + dc.symmetry_type: [ + '', + 'reversible', + 'fully amphicheiral', + 'reversible', + 'reversible', + 'reversible', + 'reversible', + 'fully amphicheiral', + 'reversible', + 'reversible' + ], + dc.homfly_polynomial: [ + '', + '(2*v^2-v^4)+(v^2)*z^2', + '(v^(-2)-1+ v^2)+ (-1)*z^2', + '(3*v^4-2*v^6)+ (4*v^4-v^6)*z^2+ (v^4)*z^4', + '(v^2+ v^4-v^6)+ (v^2+ v^4)*z^2', + '(v^(-2)-v^2+ v^4)+ (-1-v^2)*z^2', + '(2-2*v^2+ v^4)+ (1-3*v^2+ v^4)*z^2+ (-v^2)*z^4', + '(-v^(-2)+ 3-v^2)+ (-v^(-2)+ 3-v^2)*z^2+ (1)*z^4', + '(4*v^6-3*v^8)+ (10*v^6-4*v^8)*z^2+ (6*v^6-v^8)*z^4+ (v^6)*z^6', + '(v^2+ v^6-v^8)+ (v^2+ v^4+ v^6)*z^2' + ], + dc.homflypt_polynomial: [ + '1/(v^3*z)-1/(v*z)-z/v', + 'v/z-v^3/z + v*z', + '1/(v^5*z)-1/(v^3*z)-z/v^3-z/v', + 'v^3/z-v^5/z + 3*v^3*z-v^5*z + v^3*z^3', + '1/(v*z)-v/z-z/v^3 + (2*z)/v-v*z + z^3/v', + '1/(v*z)-v/z-z/v^3 + (2*z)/v-v*z + z^3/v', + '1/(v^5*z)-1/(v^3*z)-(2*z)/v^3 + z/v-v*z + z^3/v', + 'v^3/z-v^5/z + 2*v^3*z + v^5*z-v^7*z + v^3*z^3 + v^5*z^3', + '1/(v^7*z)-1/(v^5*z) + z/v^7-(2*z)/v^5-(2*z)/v^3-z^3/v^5-z^3/v^3', + 'v^5/z-v^7/z + 2*v^3*z + 2*v^5*z-v^7*z + v^3*z^3 + v^5*z^3', + '1/(v^7*z)-1/(v^5*z) + (3*z)/v^7-(6*z)/v^5 + z^3/v^7-(5*z^3)/v^5-z^5/v^5' + ], + dc.kauffman_polynomial: [ + '', + '(-a^(-4)-2*a^(-2))*z^(0)+ (a^(-5)+ a^(-3))*z^(1)+ (a^(-4)+ a^(-2))*z^(2)', + '(-a^(-2)-1-a^2)*z^(0)+ (-a^(-1)-a)*z^(1)+ (a^(-2)+ 2+ a^2)*z^(2)+ (a^(-1)+ a)*z^(3)', + '(2*a^(-6)+ 3*a^(-4))*z^(0)+ (a^(-9)-a^(-7)-2*a^(-5))*z^(1)+ (a^(-8)-3*a^(-6)-4*a^(-4))*z^(2)+ (a^(-7)+ a^(-5))*z^(3)+ (a^(-6)+ a^(-4))*z^(4)', + '(a^(-6)+ a^(-4)-a^(-2))*z^(0)+ (-2*a^(-7)-2*a^(-5))*z^(1)+ (-2*a^(-6)-a^(-4)+ a^(-2))*z^(2)+ (a^(-7)+ 2*a^(-5)+ a^(-3))*z^(3)+ (a^(-6)+ a^(-4))*z^(4)', + '(a^(-4)+ a^(-2)-a^2)*z^(0)+ (2*a^(-3)+ 2*a^(-1))*z^(1)+ (-3*a^(-4)-4*a^(-2)+ a^2)*z^(2)+ (-3*a^(-3)-2*a^(-1)+ a)*z^(3)+ (a^(-4)+ 2*a^(-2)+ 1)*z^(4)+ (a^(-3)+ a^(-1))*z^(5)', + '(a^(-4)+ 2*a^(-2)+ 2)*z^(0)+ (-a^(-5)-a^(-3))*z^(1)+ (a^(-6)-2*a^(-4)-6*a^(-2)-3)*z^(2)+ (2*a^(-5)-2*a^(-1))*z^(3)+ (2*a^(-4)+ 3*a^(-2)+ 1)*z^(4)+ (a^(-3)+ a^(-1))*z^(5)', + '(a^(-2)+ 3+ a^2)*z^(0)+ (-a^(-3)-2*a^(-1)-2*a-a^3)*z^(1)+ (-3*a^(-2)-6-3*a^2)*z^(2)+ (a^(-3)+ a^(-1)+ a+ a^3)*z^(3)+ (2*a^(-2)+ 4+ 2*a^2)*z^(4)+ (a^(-1)+ a)*z^(5)', + '(-3*a^(-8)-4*a^(-6))*z^(0)+ (a^(-13)-a^(-11)+ a^(-9)+ 3*a^(-7))*z^(1)+ (a^(-12)-2*a^(-10)+ 7*a^(-8)+ 10*a^(-6))*z^(2)+ (a^(-11)-3*a^(-9)-4*a^(-7))*z^(3)+ (a^(-10)-5*a^(-8)-6*a^(-6))*z^(4)+ (a^(-9)+ a^(-7))*z^(5)+ (a^(-8)+ a^(-6))*z^(6)', + '(-a^(-8)-a^(-6)-a^(-2))*z^(0)+ (3*a^(-9)+ 3*a^(-7))*z^(1)+ (4*a^(-8)+ 3*a^(-6)+ a^(-2))*z^(2)+ (-4*a^(-9)-6*a^(-7)-a^(-5)+ a^(-3))*z^(3)+ (-4*a^(-8)-3*a^(-6)+ a^(-4))*z^(4)+ (a^(-9)+ 2*a^(-7)+ a^(-5))*z^(5)+ (a^(-8)+ a^(-6))*z^(6)', + 'a^2-a/z-a^3/z + a*z + a^3*z', + 'a^(-2)-1/(a^3*z)-1/(a*z) + z/a^3 + z/a', + '-a^4 + a^3/z + a^5/z + a*z-2*a^3*z-3*a^5*z + a^2*z^2 + a^4*z^2 + a^3*z^3 + a^5*z^3', + '-a^(-4) + 1/(a^5*z) + 1/(a^3*z) + z/a^7-(2*z)/a^5-(3*z)/a^3 + z^2/a^6 + z^2/a^4 + z^3/a^5 + z^3/a^3', + '-1 + 1/(a*z) + a/z-(2*z)/a-4*a*z-2*a^3*z-z^2 + a^4*z^2 + z^3/a + 3*a*z^3 + 2*a^3*z^3 + z^4 + a^2*z^4', + '-1 + 1/(a*z) + a/z-(2*z)/a-4*a*z-2*a^3*z-z^2 + a^4*z^2 + z^3/a + 3*a*z^3 + 2*a^3*z^3 + z^4 + a^2*z^4', + '-a^4 + a^3/z + a^5/z-z/a-a^3*z-2*a^5*z-3*z^2-3*a^2*z^2 + z^3/a + a^5*z^3 + 2*z^4 + 3*a^2*z^4 + a^4*z^4 + a*z^5 + a^3*z^5', + '-a^(-4) + 1/(a^5*z) + 1/(a^3*z)-z/a^9-z/a^5-(2*z)/a^3-(3*z^2)/a^8-(3*z^2)/a^6 + z^3/a^9 + z^3/a^3 + (2*z^4)/a^8 + (3*z^4)/a^6 + z^4/a^4 + z^5/a^7 + z^5/a^5', + 'a^6-a^5/z-a^7/z-2*a^3*z + 3*a^5*z + 3*a^7*z-2*a^9*z-a^4*z^2-2*a^6*z^2-a^8*z^2 + a^3*z^3-2*a^5*z^3-2*a^7*z^3 + a^9*z^3 + a^4*z^4 + 2*a^6*z^4 + a^8*z^4 + a^5*z^5 + a^7*z^5', + 'a^(-6)-1/(a^7*z)-1/(a^5*z)-(2*z)/a^9 + (3*z)/a^7 + (3*z)/a^5-(2*z)/a^3-z^2/a^8-(2*z^2)/a^6-z^2/a^4 + z^3/a^9-(2*z^3)/a^7-(2*z^3)/a^5 + z^3/a^3 + z^4/a^8 + (2*z^4)/a^6 + z^4/a^4 + z^5/a^7 + z^5/a^5', + 'a^6-a^5/z-a^7/z + 6*a^5*z + 4*a^7*z-a^9*z + a^11*z-3*a^6*z^2-2*a^8*z^2 + a^10*z^2-5*a^5*z^3-4*a^7*z^3 + a^9*z^3 + a^6*z^4 + a^8*z^4 + a^5*z^5 + a^7*z^5' + ], + dc.jones_polynomial: [ + '1', + 't+ t^3-t^4', + 't^(-2)-t^(-1)+ 1-t+ t^2', + 't^2+ t^4-t^5+ t^6-t^7', + 't-t^2+ 2*t^3-t^4+ t^5-t^6', + 't^(-2)-t^(-1)+ 2-2*t+ t^2-t^3+ t^4', + 't^(-1)-1+ 2*t-2*t^2+ 2*t^3-2*t^4+ t^5', + '-t^(-3)+ 2*t^(-2)-2*t^(-1)+ 3-2*t+ 2*t^2-t^3', + 't^3+ t^5-t^6+ t^7-t^8+ t^9-t^10', + 't-t^2+ 2*t^3-2*t^4+ 2*t^5-t^6+ t^7-t^8', + '-x^(-5)-x^(-1)', + '-x-x^5', + '-x^(-9)-x^(-5) + x^(-3)-x^(-1)', + '-x^3-x^7 + x^9-x^11', + 'x^(-7)-2/x^5 + x^(-3)-2/x + x-x^3', + 'x^(-7)-2/x^5 + x^(-3)-2/x + x-x^3', + '-x^(-9) + x^(-7)-3/x^5 + 2/x^3-2/x + 2*x-x^3', + '-x^3 + x^5-3*x^7 + 2*x^9-2*x^11 + 2*x^13-x^15', + '-x^(-15) + x^(-13)-2/x^11 + 2/x^9-2/x^7 + x^(-5)-x^(-3)', + '-x^3 + x^5-2*x^7 + 2*x^9-2*x^11 + x^13-x^15', + '-x^(-17) + x^(-15)-x^(-13) + x^(-11)-x^(-9)-x^(-5)' + ], + dc.alexander_polynomial: [ + '1', + '1-t+ t^2', + '1-3*t+ t^2', + '1-t+ t^2-t^3+ t^4', + '2-3*t+ 2*t^2', + '2-5*t+ 2*t^2', + '1-3*t+ 3*t^2-3*t^3+ t^4', + '1-3*t+ 5*t^2-3*t^3+ t^4', + '1-t+ t^2-t^3+ t^4-t^5+ t^6', + '3-5*t+ 3*t^2'] +} diff --git a/src/sage/databases/oeis.py b/src/sage/databases/oeis.py index ee8f31a75c4..4ad63ab25d9 100644 --- a/src/sage/databases/oeis.py +++ b/src/sage/databases/oeis.py @@ -48,12 +48,13 @@ sage: c.examples() # optional -- internet 0: Pi = 3.1415926535897932384... 1: = 3 + 1/(7 + 1/(15 + 1/(1 + 1/(292 + ...)))) - 2: = [a_0; a_1, a_2, a_3, ...] = [3; 7, 15, 1, 292, ...] + 2: = [a_0; a_1, a_2, a_3, ...] = [3; 7, 15, 1, 292, ...]. sage: c.comments() # optional -- internet 0: The first 5821569425 terms were computed by _Eric W. Weisstein_ on Sep 18 2011. 1: The first 10672905501 terms were computed by _Eric W. Weisstein_ on Jul 17 2013. 2: The first 15000000000 terms were computed by _Eric W. Weisstein_ on Jul 27 2013. + 3: The first 30113021586 terms were computed by _Syed Fahad_ on Apr 27 2021. :: @@ -1706,7 +1707,7 @@ def examples(self): sage: c.examples() # optional -- internet 0: Pi = 3.1415926535897932384... 1: = 3 + 1/(7 + 1/(15 + 1/(1 + 1/(292 + ...)))) - 2: = [a_0; a_1, a_2, a_3, ...] = [3; 7, 15, 1, 292, ...] + 2: = [a_0; a_1, a_2, a_3, ...] = [3; 7, 15, 1, 292, ...]. TESTS:: diff --git a/src/sage/databases/sql_db.py b/src/sage/databases/sql_db.py index 7567ada4c2c..6189e4d3d91 100644 --- a/src/sage/databases/sql_db.py +++ b/src/sage/databases/sql_db.py @@ -672,7 +672,8 @@ def show(self, **kwds): C^ [2, 2, 3, 3] C~ [3, 3, 3, 3] """ - if not self.__query_string__: return self.__database__.show() + if not self.__query_string__: + return self.__database__.show() try: cur = self.__database__.__connection__.cursor() @@ -761,13 +762,16 @@ def intersect(self, other, join_table=None, join_dict=None, \ if not self.__query_string__: self.__query_string__ = other.__query_string__ self.__param_tuple__ = other.__param_tuple__ - elif not other.__query_string__: return + elif not other.__query_string__: + return else: self._merge_queries(other, self, join_table, join_dict, 'AND') else: from copy import copy - if not self.__query_string__: return copy(other) - if not other.__query_string__: return copy(self) + if not self.__query_string__: + return copy(other) + if not other.__query_string__: + return copy(self) return self._merge_queries(other, copy(self), join_table, \ join_dict, 'AND') @@ -882,8 +886,10 @@ def union(self, other, join_table=None, join_dict=None, in_place=False): self._merge_queries(other, self, join_table, join_dict, 'OR') else: from copy import copy - if not self.__query_string__: return copy(self) - if not other.__query_string__: return copy(other) + if not self.__query_string__: + return copy(self) + if not other.__query_string__: + return copy(other) return self._merge_queries(other, copy(self), join_table, \ join_dict, 'OR') @@ -1637,7 +1643,8 @@ def _rebuild_table(self, table_name, col_name=None, default=''): self.__skeleton__[table_name] if \ self.__skeleton__[table_name][col]['index'] and not \ self.__skeleton__[table_name][col]['primary_key']]) - if index_statement: self.__connection__.executescript(index_statement) + if index_statement: + self.__connection__.executescript(index_statement) # Now we can plop our data into the *new* table: self.__connection__.executescript(""" diff --git a/src/sage/databases/stein_watkins.py b/src/sage/databases/stein_watkins.py index d8fe4c73351..f326925ac95 100644 --- a/src/sage/databases/stein_watkins.py +++ b/src/sage/databases/stein_watkins.py @@ -346,7 +346,6 @@ def ecdb_num_curves(max_level=200000): 0, 8, 0, 6, 11, 4] """ i = 0 - N = 1 d = SteinWatkinsAllData(i) v = [int(0) for _ in range(max_level + 1)] while True: @@ -361,5 +360,3 @@ def ecdb_num_curves(max_level=200000): break v[N] += len(C.curves) return v - - diff --git a/src/sage/databases/symbolic_data.py b/src/sage/databases/symbolic_data.py index 7f636c498c6..ccd7fb3eadc 100644 --- a/src/sage/databases/symbolic_data.py +++ b/src/sage/databases/symbolic_data.py @@ -201,7 +201,8 @@ def trait_names(self): 'Curves__curve10_20', 'Curves__curve10_30'] """ - if hasattr(self,"__ideals"): return self.__ideals + if hasattr(self,"__ideals"): + return self.__ideals try: __ideals = [s.replace('.xml','') for s in os.listdir(self.__intpath)] __ideals += [s.replace('.xml','') for s in os.listdir(self.__genpath)] diff --git a/src/sage/docs/conf.py b/src/sage/docs/conf.py index d249f2cdafe..8489dde24ce 100644 --- a/src/sage/docs/conf.py +++ b/src/sage/docs/conf.py @@ -20,8 +20,6 @@ extensions = ['sage_docbuild.ext.inventory_builder', 'sage_docbuild.ext.multidocs', 'sage_docbuild.ext.sage_autodoc', - 'sphinx.ext.graphviz', - 'sphinx.ext.inheritance_diagram', 'sphinx.ext.todo', 'sphinx.ext.extlinks', 'IPython.sphinxext.ipython_directive', @@ -157,12 +155,6 @@ def sphinx_plot(graphics, **kwds): highlighting.lexers['ipython'] = IPyLexer() highlight_language = 'ipycon' -# GraphViz includes dot, neato, twopi, circo, fdp. -graphviz_dot = 'dot' -inheritance_graph_attrs = { 'rankdir' : 'BT' } -inheritance_node_attrs = { 'height' : 0.5, 'fontsize' : 12, 'shape' : 'oval' } -inheritance_edge_attrs = {} - # Extension configuration # ----------------------- @@ -400,18 +392,29 @@ def set_intersphinx_mappings(app, config): \DeclareUnicodeCharacter{03A5}{\ensuremath{\Upsilon}} \DeclareUnicodeCharacter{2113}{\ell} - \DeclareUnicodeCharacter{221A}{\ensuremath{\sqrt{}}} - \DeclareUnicodeCharacter{2264}{\leq} - \DeclareUnicodeCharacter{2265}{\geq} - \DeclareUnicodeCharacter{221E}{\infty} - \DeclareUnicodeCharacter{2211}{\sum} + \DeclareUnicodeCharacter{2148}{\id} + \DeclareUnicodeCharacter{2202}{\partial} + \DeclareUnicodeCharacter{2205}{\ensuremath{\emptyset}} \DeclareUnicodeCharacter{2208}{\in} \DeclareUnicodeCharacter{2209}{\notin} - \DeclareUnicodeCharacter{2202}{\partial} + \DeclareUnicodeCharacter{2211}{\sum} + \DeclareUnicodeCharacter{221A}{\ensuremath{\sqrt{}}} + \DeclareUnicodeCharacter{221E}{\infty} + \DeclareUnicodeCharacter{2227}{\ensuremath{\wedge}} + \DeclareUnicodeCharacter{2228}{\ensuremath{\vee}} + \DeclareUnicodeCharacter{2229}{\ensuremath{\cap}} + \DeclareUnicodeCharacter{222A}{\ensuremath{\cup}} \DeclareUnicodeCharacter{222B}{\ensuremath{\int}} - \DeclareUnicodeCharacter{2148}{\id} \DeclareUnicodeCharacter{2248}{\approx} \DeclareUnicodeCharacter{2260}{\neq} + \DeclareUnicodeCharacter{2264}{\leq} + \DeclareUnicodeCharacter{2265}{\geq} + \DeclareUnicodeCharacter{2293}{\ensuremath{\sqcap}} + \DeclareUnicodeCharacter{2294}{\ensuremath{\sqcup}} + \DeclareUnicodeCharacter{22C0}{\ensuremath{\bigwedge}} + \DeclareUnicodeCharacter{22C1}{\ensuremath{\bigvee}} + \DeclareUnicodeCharacter{22C2}{\ensuremath{\bigcap}} + \DeclareUnicodeCharacter{22C3}{\ensuremath{\bigcup}} \DeclareUnicodeCharacter{00B1}{\pm} \DeclareUnicodeCharacter{2A02}{\otimes} \DeclareUnicodeCharacter{2A01}{\oplus} @@ -449,6 +452,8 @@ def set_intersphinx_mappings(app, config): \DeclareUnicodeCharacter{2089}{\ensuremath{{}_9}} \DeclareUnicodeCharacter{208A}{\ensuremath{{}_+}} \DeclareUnicodeCharacter{208B}{\ensuremath{{}_-}} + \DeclareUnicodeCharacter{1D62}{\ensuremath{{}_i}} + \DeclareUnicodeCharacter{2C7C}{\ensuremath{{}_j}} \newcommand{\sageMexSymbol}[1] {{\fontencoding{OMX}\fontfamily{cmex}\selectfont\raisebox{0.75em}{\symbol{#1}}}} diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 9dcc1d6b549..6bc3518983a 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -231,7 +231,11 @@ def init_sage(controller=None): # We import readline before forking, otherwise Pdb doesn't work # on OS X: http://trac.sagemath.org/14289 - import readline + try: + import readline + except ModuleNotFoundError: + # Do not require readline for running doctests (Trac #31160). + pass try: import sympy diff --git a/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py b/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py index 34ee7c1248e..a385b8a54b9 100644 --- a/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py +++ b/src/sage/dynamics/arithmetic_dynamics/wehlerK3.py @@ -97,13 +97,9 @@ def random_WehlerK3Surface(PP): EXAMPLES:: sage: PP. = ProductProjectiveSpaces([2, 2], GF(3)) - sage: random_WehlerK3Surface(PP) - Closed subscheme of Product of projective spaces P^2 x P^2 over Finite Field of size 3 defined by: - x0*y0 + x1*y1 + x2*y2, - -x1^2*y0^2 - x2^2*y0^2 + x0^2*y0*y1 - x0*x1*y0*y1 - x1^2*y0*y1 - + x1*x2*y0*y1 + x0^2*y1^2 + x0*x1*y1^2 - x1^2*y1^2 + x0*x2*y1^2 - - x0^2*y0*y2 - x0*x1*y0*y2 + x0*x2*y0*y2 + x1*x2*y0*y2 + x0*x1*y1*y2 - - x1^2*y1*y2 - x1*x2*y1*y2 - x0^2*y2^2 + x0*x1*y2^2 - x1^2*y2^2 - x0*x2*y2^2 + sage: w = random_WehlerK3Surface(PP) + sage: type(w) + """ CR = PP.coordinate_ring() diff --git a/src/sage/dynamics/complex_dynamics/mandel_julia.py b/src/sage/dynamics/complex_dynamics/mandel_julia.py index 680020cf816..19185da190e 100644 --- a/src/sage/dynamics/complex_dynamics/mandel_julia.py +++ b/src/sage/dynamics/complex_dynamics/mandel_julia.py @@ -683,7 +683,7 @@ def julia_plot(f=None, **kwds): if f is not None and period is None: # f user-specified and no period given # try to coerce f to live in a polynomial ring - S = PolynomialRing(CC,names='z') + S = PolynomialRing(CC, names='z') z = S.gen() try: f_poly = S(f) diff --git a/src/sage/dynamics/complex_dynamics/mandel_julia_helper.pyx b/src/sage/dynamics/complex_dynamics/mandel_julia_helper.pyx index 7cff9ac6347..7a6ee5aa51a 100644 --- a/src/sage/dynamics/complex_dynamics/mandel_julia_helper.pyx +++ b/src/sage/dynamics/complex_dynamics/mandel_julia_helper.pyx @@ -34,6 +34,7 @@ from sage.rings.real_double import RDF from sage.rings.complex_double import CDF from sage.ext.fast_callable import fast_callable from sage.calculus.all import symbolic_expression +from sage.symbolic.ring import SR from sage.calculus.var import var from sage.rings.fraction_field import is_FractionField from sage.categories.function_fields import FunctionFields @@ -843,9 +844,9 @@ cpdef polynomial_mandelbrot(f, parameter=None, double x_center=0, # critical points for each c. else: # Solve for critical points symbollically. - w = var('w') - df = f.derivative(z).polynomial(z).subs({z:w}) - critical_pts = solve(symbolic_expression(df)==0, w) + with SR.temp_var() as w: + df = f.derivative(z).polynomial(z).subs({z:w}) + critical_pts = solve(symbolic_expression(df)==0, w) c_pts = [] for pt in critical_pts: c_pts.append(fast_callable(pt.rhs(), vars=[c], domain=CDF)) diff --git a/src/sage/env.py b/src/sage/env.py index b1cae78754d..4a70a3362d3 100644 --- a/src/sage/env.py +++ b/src/sage/env.py @@ -190,6 +190,10 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st DOT_SAGE = var("DOT_SAGE", join(os.environ.get("HOME"), ".sage")) SAGE_STARTUP_FILE = var("SAGE_STARTUP_FILE", join(DOT_SAGE, "init.sage")) +# for sage_setup.setenv +SAGE_ARCHFLAGS = var("SAGE_ARCHFLAGS", "unset") +SAGE_PKG_CONFIG_PATH = var("SAGE_PKG_CONFIG_PATH") + # installation directories for various packages CONWAY_POLYNOMIALS_DATA_DIR = var("CONWAY_POLYNOMIALS_DATA_DIR", join(SAGE_SHARE, "conway_polynomials")) GRAPHS_DATA_DIR = var("GRAPHS_DATA_DIR", join(SAGE_SHARE, "graphs")) @@ -203,11 +207,12 @@ def var(key: str, *fallbacks: Optional[str], force: bool = False) -> Optional[st JMOL_DIR = var("JMOL_DIR", join(SAGE_SHARE, "jmol")) MATHJAX_DIR = var("MATHJAX_DIR", join(SAGE_SHARE, "mathjax")) MTXLIB = var("MTXLIB", join(SAGE_SHARE, "meataxe")) -THREEJS_DIR = var("THREEJS_DIR", join(SAGE_SHARE, "threejs")) +THREEJS_DIR = var("THREEJS_DIR", join(SAGE_SHARE, "threejs-sage")) SINGULARPATH = var("SINGULARPATH", join(SAGE_SHARE, "singular")) PPLPY_DOCS = var("PPLPY_DOCS", join(SAGE_SHARE, "doc", "pplpy")) MAXIMA = var("MAXIMA", "maxima") MAXIMA_FAS = var("MAXIMA_FAS") +KENZO_FAS = var("KENZO_FAS") SAGE_NAUTY_BINS_PREFIX = var("SAGE_NAUTY_BINS_PREFIX", "") ARB_LIBRARY = var("ARB_LIBRARY", "arb") CBLAS_PC_MODULES = var("CBLAS_PC_MODULES", "cblas:openblas:blas") @@ -312,7 +317,7 @@ def _get_shared_lib_path(*libnames: str) -> Optional[str]: GAP_SO = var("GAP_SO", _get_shared_lib_path("gap", "")) # post process -if ' ' in DOT_SAGE: +if DOT_SAGE is not None and ' ' in DOT_SAGE: if UNAME[:6] == 'CYGWIN': # on windows/cygwin it is typical for the home directory # to have a space in it. Fortunately, users also have @@ -373,14 +378,18 @@ def sage_include_directories(use_sources=False): sage: any(os.path.isfile(os.path.join(d, file)) for d in dirs) True """ - import numpy import distutils.sysconfig TOP = SAGE_SRC if use_sources else SAGE_LIB - return [TOP, - distutils.sysconfig.get_python_inc(), - numpy.get_include()] + dirs = [TOP, + distutils.sysconfig.get_python_inc()] + try: + import numpy + dirs.append(numpy.get_include()) + except ModuleNotFoundError: + pass + return dirs def get_cblas_pc_module_name() -> str: """ diff --git a/src/sage/ext/memory_allocator.pxd b/src/sage/ext/memory_allocator.pxd index 2e1fd267c51..1be5ba69552 100644 --- a/src/sage/ext/memory_allocator.pxd +++ b/src/sage/ext/memory_allocator.pxd @@ -64,6 +64,8 @@ cdef class MemoryAllocator: ....: ptr = mem.aligned_malloc(2**i, 4048) ....: assert ptr == ( ptr) & ~(2**i-1) ....: ''') + doctest:...: DeprecationWarning: this class is deprecated; use the class from the python package `memory_allocator` + See https://trac.sagemath.org/31591 for details. """ cdef size_t extra = alignment - 1 return align(self.malloc(size + extra), alignment) diff --git a/src/sage/ext/memory_allocator.pyx b/src/sage/ext/memory_allocator.pyx index a5609cf40b3..b5ffe70ccc1 100644 --- a/src/sage/ext/memory_allocator.pyx +++ b/src/sage/ext/memory_allocator.pyx @@ -1,4 +1,5 @@ from cysignals.memory cimport * +from sage.misc.superseded import deprecation cdef class MemoryAllocator: @@ -23,6 +24,8 @@ cdef class MemoryAllocator: ....: mem.aligned_calloc(16, n, 16) ....: mem.aligned_allocarray(8, n, 8) ....: ''') + doctest:...: DeprecationWarning: this class is deprecated; use the class from the python package `memory_allocator` + See https://trac.sagemath.org/31591 for details. """ def __cinit__(self): """ @@ -39,6 +42,7 @@ cdef class MemoryAllocator: 1 16 """ + deprecation(31591, "this class is deprecated; use the class from the python package `memory_allocator`") self.n = 0 self.size = 16 self.pointers = self.static_pointers @@ -132,6 +136,8 @@ cdef class MemoryAllocator: ....: mem2.realloc(ptr, 21) ....: ''') sage: test_realloc_good() + doctest:...: DeprecationWarning: this class is deprecated; use the class from the python package `memory_allocator` + See https://trac.sagemath.org/31591 for details. sage: test_realloc_NULL() sage: test_realloc_bad() Traceback (most recent call last): diff --git a/src/sage/ext_data/kenzo/README.txt b/src/sage/ext_data/kenzo/README.txt index 9943fbd25fb..601f7e7f523 100644 --- a/src/sage/ext_data/kenzo/README.txt +++ b/src/sage/ext_data/kenzo/README.txt @@ -10,7 +10,7 @@ Kenzo: - https://www-fourier.ujf-grenoble.fr/~sergerar/Kenzo/ - https://github.com/gheber/kenzo -The results for CP^2, CP^3, and CP^4 have been saved in the corresponding text files. The file S4.txt includes the 4-sphere, just for testing purposes. These files can be processed by the function "simplicial_data_from_kenzo_output" in sage/homology/simplicial_set.py. To get a simplicial set structure for CP^n using Kenzo in sbcl, do the following. +The results for CP^2, CP^3, and CP^4 have been saved in the corresponding text files. The file S4.txt includes the 4-sphere, just for testing purposes. These files can be processed by the function "simplicial_data_from_kenzo_output" in sage/topology/simplicial_set.py. To get a simplicial set structure for CP^n using Kenzo in sbcl, do the following. ;; ;; Start Kenzo. diff --git a/src/sage/ext_data/pari/simon/ell.gp b/src/sage/ext_data/pari/simon/ell.gp index 74f07866468..21cff9cbb36 100644 --- a/src/sage/ext_data/pari/simon/ell.gp +++ b/src/sage/ext_data/pari/simon/ell.gp @@ -1038,7 +1038,7 @@ if( DEBUGLEVEL_ell >= 1, print(" trivial points on E(K) = "); KS2gen = KS2gen[1]; for( i = 1, #KS2gen, KS2gen[i] = nfbasistoalg(bnf, KS2gen[i])); - KS2gen = concat(Mod(lift(bnf.tufu),bnf.pol),KS2gen); + KS2gen = concat(Mod(lift(concat(bnf.tu[2], bnf.fu)),bnf.pol),KS2gen); if( DEBUGLEVEL_ell >= 2, print(" #K(b,2)gen = ",#KS2gen); print(" K(b,2)gen = ",KS2gen)); @@ -1072,7 +1072,7 @@ if( DEBUGLEVEL_ell >= 1, KS2gen = KS2gen[1]; for( i = 1, #KS2gen, KS2gen[i] = nfbasistoalg(bnf, KS2gen[i])); - KS2gen = concat(Mod(lift(bnf.tufu),bnf.pol),KS2gen); + KS2gen = concat(Mod(lift(concat(bnf.tu[2], bnf.fu)),bnf.pol),KS2gen); if( DEBUGLEVEL_ell >= 2, print(" #K(a^2-4b,2)gen = ",#KS2gen); print(" K(a^2-4b,2)gen = ",KS2gen)); @@ -1244,11 +1244,11 @@ if( DEBUGLEVEL_ell >= 4, print(" bbbnf.clgp = ",bbbnf.clgp)); SL1 = idealmul(bbbnf,SL0,rnfeltup(rrrnf,bleg)); SL = idealfactor(bbbnf,SL1)[,1]~; sunL = bnfsunit(bbbnf,SL); - fondsunL = concat(bbbnf.futu,vector(#sunL[1],i,nfbasistoalg(bbbnf,sunL[1][i]))); + fondsunL = concat(concat(bbbnf.fu, bbbnf.tu[2]),vector(#sunL[1],i,nfbasistoalg(bbbnf,sunL[1][i]))); normfondsunL = vector(#fondsunL, i, norm(rnfeltabstorel(rrrnf,fondsunL[i]))); SK = idealfactor(bnf,idealnorm(bbbnf,SL1))[,1]~; sunK = bnfsunit(bnf,SK); - fondsunK = concat(bnf.futu,vector(#sunK[1],i,nfbasistoalg(bnf,sunK[1][i]))); + fondsunK = concat(concat(bnf.fu, bnf.tu[2]),vector(#sunK[1],i,nfbasistoalg(bnf,sunK[1][i]))); vecbleg = bnfissunit(bnf,sunK,bleg); matnorm = matrix(#fondsunK,#normfondsunL,i,j,0); for( i = 1, #normfondsunL, @@ -1345,7 +1345,7 @@ if( DEBUGLEVEL_ell >= 4, print("on factorise bb = ",bb)); sun = bnfsunit(bnf,idealfactor(bnf,bb)[,1]~); fact = lift(bnfissunit(bnf,sun,bb)); if( DEBUGLEVEL_ell >= 4, print("fact = ",fact)); - suni = concat(bnf.futu,vector(#sun[1],i,nfbasistoalg(bnf,sun[1][i]))); + suni = concat(concat(bnf.fu, bnf.tu[2]),vector(#sun[1],i,nfbasistoalg(bnf,sun[1][i]))); for( i = 1, #suni, if( (f = fact[i]>>1), test =0; @@ -1554,7 +1554,7 @@ if( DEBUGLEVEL_ell >= 3, print(" KS2gen = ",KS2gen[1])); LS2gen = LS2gen[1]; LS2 = vector(#LS2gen,i,lift(nfbasistoalg(Lrnf,LS2gen[i]))); - LS2 = concat(lift(Lrnf.futu),LS2); + LS2 = concat(lift(concat(Lrnf.fu, Lrnf.tu[2])),LS2); LS2 = subst(LS2,'x,ttheta); LS2 = LS2*Mod(1,polrel); @@ -1992,7 +1992,7 @@ if( DEBUGLEVEL_ell >= 2, print(" Algorithm of complete 2-descent")); KS2gen = KS2gen[1]; for( i = 1, #KS2gen, KS2gen[i] = nfbasistoalg(bnf, KS2gen[i])); - KS2gen = concat(Mod(lift(bnf.tufu),bnf.pol),KS2gen); + KS2gen = concat(Mod(lift(concat(bnf.tu[2], bnf.fu)),bnf.pol),KS2gen); if( DEBUGLEVEL_ell >= 2, print(" #K(S,2)gen = ",#KS2gen); print(" K(S,2)gen = ",KS2gen) diff --git a/src/sage/ext_data/pari/simon/ellQ.gp b/src/sage/ext_data/pari/simon/ellQ.gp index 1cfe6318f82..420af8f6a29 100644 --- a/src/sage/ext_data/pari/simon/ellQ.gp +++ b/src/sage/ext_data/pari/simon/ellQ.gp @@ -1162,7 +1162,7 @@ if( DEBUGLEVEL_ell >= 4, print(" kerval = ",kerval)); LS2gen[j]^kerval[j,i])); \\ Add the units - LS2gen = concat(Mod(bnf[8][5],bnf.pol),LS2gen); \\ LS2gen = concat(bnf.fu,LS2gen); + LS2gen = concat(bnf.fu,LS2gen); \\ LS2gen = concat(bnf.fu,LS2gen); \\ Add also the torsion unit if its order is divisible by p. if( bnf[8][4][1]%p == 0, \\ if( bnf.tu[1]%p == 0, LS2gen = concat( [Mod(bnf[8][4][2],bnf.pol)], LS2gen)); \\ LS2gen = concat( [Mod(bnf.tu[2],bnf.pol)], LS2gen)); diff --git a/src/sage/ext_data/threejs/threejs-version.txt b/src/sage/ext_data/threejs/threejs-version.txt new file mode 100644 index 00000000000..98ffb8e64ea --- /dev/null +++ b/src/sage/ext_data/threejs/threejs-version.txt @@ -0,0 +1 @@ +r122 diff --git a/src/sage/features/databases.py b/src/sage/features/databases.py index ed0fb65f07c..0dcef2751e2 100644 --- a/src/sage/features/databases.py +++ b/src/sage/features/databases.py @@ -3,9 +3,8 @@ Testing for databases at runtime """ -import os -from . import StaticFile +from . import StaticFile, PythonModule from sage.env import CREMONA_MINI_DATA_DIR, CREMONA_LARGE_DATA_DIR CREMONA_DATA_DIRS = set([CREMONA_MINI_DATA_DIR, CREMONA_LARGE_DATA_DIR]) @@ -64,3 +63,28 @@ def __init__(self): StaticFile.__init__(self, "John Jones's tables of number fields", filename='jones/jones.sobj', spkg="database_jones_numfield") + + +class DatabaseKnotInfo(PythonModule): + r""" + A :class:`Feature` which describes the presence of the databases at the + web-pages `KnotInfo `__ and + `LinkInfo `__. + + + + EXAMPLES:: + + sage: from sage.features.databases import DatabaseKnotInfo + sage: DatabaseKnotInfo().is_present() # optional: database_knotinfo + FeatureTestResult('sage.knots.knotinfo', True) + """ + def __init__(self): + r""" + TESTS:: + + sage: from sage.features.databases import DatabaseKnotInfo + sage: isinstance(DatabaseKnotInfo(), DatabaseKnotInfo) + True + """ + PythonModule.__init__(self, 'database_knotinfo', spkg='database_knotinfo') diff --git a/src/sage/features/kenzo.py b/src/sage/features/kenzo.py index c3c68ede71b..b6c068eac83 100644 --- a/src/sage/features/kenzo.py +++ b/src/sage/features/kenzo.py @@ -46,7 +46,12 @@ def _is_present(self): ecl_eval("(setf *standard-output* *dev-null*)") try: - ecl_eval("(require :kenzo)") + from sage.env import KENZO_FAS + if KENZO_FAS: + ecl_eval("(require :kenzo \"{}\")".format(KENZO_FAS)) + else: + ecl_eval("(require :kenzo)") + except RuntimeError: return FeatureTestResult(self, False, reason="Unable to make ECL require kenzo") return FeatureTestResult(self, True) diff --git a/src/sage/finance/markov_multifractal.py b/src/sage/finance/markov_multifractal.py index 30c8b0d950a..7ff249c4e4e 100644 --- a/src/sage/finance/markov_multifractal.py +++ b/src/sage/finance/markov_multifractal.py @@ -222,10 +222,14 @@ def simulation(self, n): EXAMPLES:: sage: msm = finance.MarkovSwitchingMultifractal(8,1.4,1.0,0.95,3) - sage: msm.simulation(5) + sage: m = msm.simulation(5); m # random [0.0059, -0.0097, -0.0101, -0.0110, -0.0067] - sage: msm.simulation(3) + sage: len(m) + 5 + sage: m = msm.simulation(3); m # random [0.0055, -0.0084, 0.0141] + sage: len(m) + 3 """ return self.simulations(n, 1)[0] diff --git a/src/sage/functions/exp_integral.py b/src/sage/functions/exp_integral.py index ddc11735210..b20f7779f1a 100644 --- a/src/sage/functions/exp_integral.py +++ b/src/sage/functions/exp_integral.py @@ -810,7 +810,7 @@ def __init__(self): sage: sin_integral(x)._fricas_init_() 'Si(x)' sage: sin_integral(x)._giac_() - Si(x) + Si(sageVARx) """ BuiltinFunction.__init__(self, "sin_integral", nargs=1, latex_name=r'\operatorname{Si}', @@ -986,7 +986,7 @@ def __init__(self): sage: cos_integral(x)._fricas_init_() 'Ci(x)' sage: cos_integral(x)._giac_() - Ci(x) + Ci(sageVARx) """ BuiltinFunction.__init__(self, "cos_integral", nargs=1, latex_name=r'\operatorname{Ci}', diff --git a/src/sage/functions/gamma.py b/src/sage/functions/gamma.py index b1cd9a80e3b..08f549777c2 100644 --- a/src/sage/functions/gamma.py +++ b/src/sage/functions/gamma.py @@ -551,9 +551,9 @@ def _eval_(self, x, y): from sage.rings.infinity import Infinity return Infinity elif x == 1: - return 1-exp(-y) - elif (2*x).is_integer(): - return self(x,y,hold=True)._sympy_() + return 1 - exp(-y) + elif (2 * x).is_integer(): + return self(x, y, hold=True)._sympy_() else: return None @@ -618,7 +618,7 @@ def _derivative_(self, x, y, diff_param=None): raise NotImplementedError("cannot differentiate gamma_inc_lower in the" " first parameter") else: - return exp(-y)*y**(x - 1) + return exp(-y) * y**(x - 1) def _mathematica_init_evaled_(self, *args): r""" @@ -715,7 +715,7 @@ def gamma(a, *args, **kwds): if not args: return gamma1(a, **kwds) if len(args) > 1: - raise TypeError("Symbolic function gamma takes at most 2 arguments (%s given)"% (len(args) + 1)) + raise TypeError("Symbolic function gamma takes at most 2 arguments (%s given)" % (len(args) + 1)) return gamma_inc(a, args[0], **kwds) @@ -790,11 +790,15 @@ def __init__(self): -5.28903989659219 sage: psi(x)._sympy_() polygamma(0, x) + sage: psi(x)._fricas_() # optional - fricas + digamma(x) """ GinacFunction.__init__(self, "psi", nargs=1, latex_name=r'\psi', conversions=dict(mathematica='PolyGamma', maxima='psi[0]', - sympy='digamma')) + sympy='digamma', + fricas='digamma')) + class Function_psi2(GinacFunction): def __init__(self): @@ -841,11 +845,14 @@ def __init__(self): psi(2, x) + 1 sage: psi(2, x)._sympy_() polygamma(2, x) + sage: psi(2, x)._fricas_() # optional - fricas + polygamma(2,x) """ GinacFunction.__init__(self, "psi", nargs=2, latex_name=r'\psi', conversions=dict(mathematica='PolyGamma', sympy='polygamma', - giac='Psi')) + giac='Psi', + fricas='polygamma')) def _maxima_init_evaled_(self, *args): """ @@ -868,11 +875,12 @@ def _maxima_init_evaled_(self, *args): else: args_maxima.append(str(a)) n, x = args_maxima - return "psi[%s](%s)"%(n, x) + return "psi[%s](%s)" % (n, x) psi1 = Function_psi1() psi2 = Function_psi2() + def psi(x, *args, **kwds): r""" The digamma function, `\psi(x)`, is the logarithmic derivative of the @@ -924,6 +932,7 @@ def psi(x, *args, **kwds): # two functions with different number of arguments and the same name symbol_table['functions']['psi'] = psi + def _swap_psi(a, b): return psi(b, a) register_symbol(_swap_psi, {'giac': 'Psi'}) diff --git a/src/sage/functions/generalized.py b/src/sage/functions/generalized.py index 5839d82231f..6c38ef5621c 100644 --- a/src/sage/functions/generalized.py +++ b/src/sage/functions/generalized.py @@ -248,7 +248,7 @@ def __init__(self): sage: heaviside(x)._sympy_() Heaviside(x) sage: heaviside(x)._giac_() - Heaviside(x) + Heaviside(sageVARx) sage: h(x) = heaviside(x) sage: h(pi).numerical_approx() 1.00000000000000 @@ -405,7 +405,7 @@ class FunctionSignum(BuiltinFunction): sage: sgn(x)._fricas_init_() '(x+->abs(x)/x)(x)' sage: sgn(x)._giac_() - sign(x) + sign(sageVARx) Test for :trac:`31085`:: diff --git a/src/sage/functions/min_max.py b/src/sage/functions/min_max.py index 771ec204020..746c1067acc 100644 --- a/src/sage/functions/min_max.py +++ b/src/sage/functions/min_max.py @@ -7,22 +7,22 @@ Here you can see some differences:: - sage: max(x,x^2) + sage: max(x, x^2) x - sage: max_symbolic(x,x^2) + sage: max_symbolic(x, x^2) max(x, x^2) - sage: f(x) = max_symbolic(x,x^2); f(1/2) + sage: f(x) = max_symbolic(x, x^2); f(1/2) 1/2 This works as expected for more than two entries:: - sage: max(3,5,x) + sage: max(3, 5, x) 5 - sage: min(3,5,x) + sage: min(3, 5, x) 3 - sage: max_symbolic(3,5,x) + sage: max_symbolic(3, 5, x) max(x, 5) - sage: min_symbolic(3,5,x) + sage: min_symbolic(3, 5, x) min(x, 3) """ @@ -45,10 +45,15 @@ def eval_helper(self, this_f, builtin_f, initial_val, args): """ EXAMPLES:: - sage: max_symbolic(3,5,x) # indirect doctest + sage: max_symbolic(3, 5, x) # indirect doctest max(x, 5) - sage: min_symbolic(3,5,x) + sage: max_symbolic([5.0r]) # indirect doctest + 5.0 + sage: min_symbolic(3, 5, x) min(x, 3) + sage: min_symbolic([5.0r]) # indirect doctest + 5.0 + """ # __call__ ensures that if args is a singleton, the element is iterable arg_is_iter = False @@ -65,49 +70,40 @@ def eval_helper(self, this_f, builtin_f, initial_val, args): else: num_non_symbolic_args += 1 if res is None: - # Any argument is greater or less than None res = x else: res = builtin_f(res, x) # if no symbolic arguments, return the result if len(symb_args) == 0: - if res is None: - # this is a hack to get the function to return None to the user - # the convention to leave a given symbolic function unevaluated - # is to return None from the _eval_ function, so we need - # a trick to indicate that the return value of the function is - # really None - # this is caught in the __call__ method, which knows to return - # None in this case - raise ValueError("return None") return res # if all arguments were symbolic return if num_non_symbolic_args <= 1 and not arg_is_iter: return None - if res is not None: symb_args.append(res) + if res is not None: + symb_args.append(res) return this_f(*symb_args) def __call__(self, *args, **kwds): """ EXAMPLES:: - sage: max_symbolic(3,5,x) + sage: max_symbolic(3, 5, x) max(x, 5) - sage: max_symbolic(3,5,x, hold=True) + sage: max_symbolic(3, 5, x, hold=True) max(3, 5, x) - sage: max_symbolic([3,5,x]) + sage: max_symbolic([3, 5, x]) max(x, 5) :: - sage: min_symbolic(3,5,x) + sage: min_symbolic(3, 5, x) min(x, 3) - sage: min_symbolic(3,5,x, hold=True) + sage: min_symbolic(3, 5, x, hold=True) min(3, 5, x) - sage: min_symbolic([3,5,x]) + sage: min_symbolic([3, 5, x]) min(x, 3) TESTS: @@ -119,17 +115,6 @@ def __call__(self, *args, **kwds): ... ValueError: number of arguments must be > 0 - Check if we return None, when the builtin function would:: - - sage: max_symbolic([None]) is None # py2 on Python 3 None is not ordered - True - sage: max_symbolic([None, None]) is None # py2 - True - sage: min_symbolic([None]) is None # py2 - True - sage: min_symbolic([None, None]) is None # py2 - True - Check if a single argument which is not iterable works:: sage: max_symbolic(None) @@ -158,15 +143,13 @@ def __call__(self, *args, **kwds): if len(args) == 1: try: args = (SR._force_pyobject(iter(args[0])),) - except TypeError as e: - raise e + except TypeError: + raise try: return BuiltinFunction.__call__(self, *args, **kwds) - except ValueError as e: - if e.args[0] == "return None": - return None - + except ValueError: + pass class MaxSymbolic(MinMax_base): def __init__(self): @@ -185,14 +168,14 @@ def __init__(self): 5 sage: max_symbolic(3, 5, x) max(x, 5) - sage: max_symbolic([3,5,x]) + sage: max_symbolic([3, 5, x]) max(x, 5) TESTS:: - sage: loads(dumps(max_symbolic(x,5))) + sage: loads(dumps(max_symbolic(x, 5))) max(x, 5) - sage: latex(max_symbolic(x,5)) + sage: latex(max_symbolic(x, 5)) \max\left(x, 5\right) sage: max_symbolic(x, 5)._sympy_() Max(5, x) @@ -206,17 +189,17 @@ def _eval_(self, *args): sage: t = max_symbolic(x, 5); t max(x, 5) - sage: t.subs(x=3) # indirect doctest + sage: t.subs(x=3) # indirect doctest 5 - sage: max_symbolic(5,3) + sage: max_symbolic(5, 3) 5 - sage: u = max_symbolic(*(list(range(10))+[x])); u + sage: u = max_symbolic(*(list(range(10)) + [x])); u max(x, 9) sage: u.subs(x=-1) 9 sage: u.subs(x=10) 10 - sage: max_symbolic([0,x]) + sage: max_symbolic([0, x]) max(x, 0) TESTS:: @@ -276,14 +259,14 @@ def __init__(self): 3 sage: min_symbolic(3, 5, x) min(x, 3) - sage: min_symbolic([3,5,x]) + sage: min_symbolic([3, 5, x]) min(x, 3) TESTS:: - sage: loads(dumps(min_symbolic(x,5))) + sage: loads(dumps(min_symbolic(x, 5))) min(x, 5) - sage: latex(min_symbolic(x,5)) + sage: latex(min_symbolic(x, 5)) \min\left(x, 5\right) sage: min_symbolic(x, 5)._sympy_() Min(5, x) @@ -297,17 +280,17 @@ def _eval_(self, *args): sage: t = min_symbolic(x, 5); t min(x, 5) - sage: t.subs(x=3) # indirect doctest + sage: t.subs(x=3) # indirect doctest 3 - sage: min_symbolic(5,3) + sage: min_symbolic(5, 3) 3 - sage: u = min_symbolic(*(list(range(10))+[x])); u + sage: u = min_symbolic(*(list(range(10)) + [x])); u min(x, 0) sage: u.subs(x=-1) -1 sage: u.subs(x=10) 0 - sage: min_symbolic([3,x]) + sage: min_symbolic([3, x]) min(x, 3) TESTS:: diff --git a/src/sage/functions/other.py b/src/sage/functions/other.py index 506cf52c218..a8764be01a0 100644 --- a/src/sage/functions/other.py +++ b/src/sage/functions/other.py @@ -1936,8 +1936,8 @@ def __init__(self): sage: isinstance(r.operator(), ....: sage.functions.other.Function_prod) # known bug True - sage: giac(sprod(m, m, 1, n)) - n! + sage: giac(sprod(m, m, 1, n)).sage() + factorial(n) """ BuiltinFunction.__init__(self, "product", nargs=4, conversions=dict(maxima='product', @@ -2285,3 +2285,75 @@ def _evalf_(self, poly, index, parent=None, algorithm=None): complex_root_of = Function_crootof() + +class Function_elementof(BuiltinFunction): + """ + Formal set membership function that is only accessible internally. + + This function is called to express a set membership statement, + usually as part of a solution set returned by ``solve()``. + See :class:`sage.sets.set.Set` and :class:`sage.sets.real_set.RealSet` + for possible set arguments. + + EXAMPLES:: + + sage: from sage.functions.other import element_of + sage: element_of(x, SR(ZZ)) + element_of(x, Integer Ring) + sage: element_of(sin(x), SR(QQ)) + element_of(sin(x), Rational Field) + sage: element_of(x, SR(RealSet.open_closed(0,1))) + element_of(x, (0, 1]) + sage: element_of(x, SR(Set([4,6,8]))) + element_of(x, {8, 4, 6}) + """ + def __init__(self): + """ + EXAMPLES:: + + sage: from sage.functions.other import element_of + sage: loads(dumps(element_of)) + element_of + """ + BuiltinFunction.__init__(self, "element_of", nargs=2, + conversions=dict(sympy='Contains')) + + def _eval_(self, x, s): + """ + EXAMPLES:: + + sage: from sage.functions.other import element_of + sage: element_of(x, SR(RealSet(-oo, oo))) + element_of(x, (-oo, +oo)) + sage: element_of(x, 0) + Traceback (most recent call last): + ... + ValueError: not a set: 0 + """ + from sage.categories.sets_cat import Sets + if not s in Sets(): + raise ValueError("not a set: {}".format(s)) + + def _latex_(self): + r""" + EXAMPLES:: + + sage: from sage.functions.other import element_of + sage: latex(element_of) + \in + """ + return r'\in' + + def _print_latex_(self, ex, s): + r""" + EXAMPLES:: + + sage: from sage.functions.other import element_of + sage: latex(element_of(x, SR(ZZ))) + x \in \Bold{Z} + sage: latex(element_of(x, SR(Set([4,6,8])))) + x \in \left\{8, 4, 6\right\} + """ + return r"{} \in {}".format(latex(ex), latex(s)) + +element_of = Function_elementof() diff --git a/src/sage/functions/piecewise.py b/src/sage/functions/piecewise.py index 41f5876616c..27398c96eaf 100644 --- a/src/sage/functions/piecewise.py +++ b/src/sage/functions/piecewise.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Piecewise-defined Functions @@ -527,7 +528,7 @@ def restriction(self, parameters, variable, restricted_domain): sage: f = piecewise([((-oo, oo), x)]); f piecewise(x|-->x on (-oo, +oo); x) sage: f.restriction([[-1,1], [3,3]]) - piecewise(x|-->x on [-1, 1] + {3}; x) + piecewise(x|-->x on [-1, 1] ∪ {3}; x) """ restricted_domain = RealSet(*restricted_domain) new_param = [] @@ -559,7 +560,7 @@ def extension(self, parameters, variable, extension, extension_domain=None): ValueError: point 3 is not in the domain sage: g = f.extension(0); g - piecewise(x|-->x on (-1, 1), x|-->0 on (-oo, -1] + [1, +oo); x) + piecewise(x|-->x on (-1, 1), x|-->0 on (-oo, -1] ∪ [1, +oo); x) sage: g(3) 0 @@ -583,7 +584,7 @@ def unextend_zero(self, parameters, variable): sage: f = piecewise([((-1,1), x)]); f piecewise(x|-->x on (-1, 1); x) sage: g = f.extension(0); g - piecewise(x|-->x on (-1, 1), x|-->0 on (-oo, -1] + [1, +oo); x) + piecewise(x|-->x on (-1, 1), x|-->0 on (-oo, -1] ∪ [1, +oo); x) sage: g(3) 0 sage: h = g.unextend_zero() @@ -652,7 +653,7 @@ def piecewise_add(self, parameters, variable, other): sage: f = piecewise([([0,1], 1), ((2,3), x)]) sage: g = piecewise([((1/2, 2), x)]) sage: f.piecewise_add(g).unextend_zero() - piecewise(x|-->1 on (0, 1/2], x|-->x + 1 on (1/2, 1], x|-->x on (1, 2) + (2, 3); x) + piecewise(x|-->1 on (0, 1/2], x|-->x + 1 on (1/2, 1], x|-->x on (1, 2) ∪ (2, 3); x) """ points = ([minus_infinity] + sorted(set(self.end_points() + other.end_points())) + @@ -946,8 +947,6 @@ def convolution(self, parameters, variable, other): g = other if len(f.end_points())*len(g.end_points()) == 0: raise ValueError('one of the piecewise functions is nowhere defined') - tt = SR.var('tt') - uu = SR.var('uu') fd, f0 = parameters[0] gd, g0 = next(other.items()) if len(f)==1 and len(g)==1: @@ -957,12 +956,14 @@ def convolution(self, parameters, variable, other): a2 = fd[0].upper() b1 = gd[0].lower() b2 = gd[0].upper() - i1 = f0.subs({variable: uu}) - i2 = g0.subs({variable: tt-uu}) - fg1 = definite_integral(i1*i2, uu, a1, tt-b1).subs(tt = variable) - fg2 = definite_integral(i1*i2, uu, tt-b2, tt-b1).subs(tt = variable) - fg3 = definite_integral(i1*i2, uu, tt-b2, a2).subs(tt = variable) - fg4 = definite_integral(i1*i2, uu, a1, a2).subs(tt = variable) + with SR.temp_var() as tt: + with SR.temp_var() as uu: + i1 = f0.subs({variable: uu}) + i2 = g0.subs({variable: tt-uu}) + fg1 = definite_integral(i1*i2, uu, a1, tt-b1).subs({tt:variable}) + fg2 = definite_integral(i1*i2, uu, tt-b2, tt-b1).subs({tt:variable}) + fg3 = definite_integral(i1*i2, uu, tt-b2, a2).subs({tt:variable}) + fg4 = definite_integral(i1*i2, uu, a1, a2).subs({tt:variable}) if a1-b1 -# Copyright (C) 2012 Andrey Novoseltsev -# Copyright (C) 2010 William Stein +# Copyright (C) 2010-2014 Volker Braun +# Copyright (C) 2010-2018 Andrey Novoseltsev +# Copyright (C) 2010 William Stein +# Copyright (C) 2012 Christian Stump +# Copyright (C) 2014-2018 Frédéric Chapoton +# Copyright (C) 2014 Peter Bruin +# Copyright (C) 2015-2017 Jori Mäntysalo +# Copyright (C) 2015-2020 Michael Orlitzky +# Copyright (C) 2016-2020 John H. Palmieri +# Copyright (C) 2018 David Coudert +# Copyright (C) 2019-2020 Jonathan Kliem +# Copyright (C) 2020-2021 Matthias Koeppe # # 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 @@ -205,6 +214,7 @@ from sage.geometry.toric_lattice import (ToricLattice, is_ToricLattice, is_ToricLatticeQuotient) from sage.geometry.toric_plotter import ToricPlotter, label_list +from sage.geometry.relative_interior import RelativeInterior from sage.graphs.digraph import DiGraph from sage.matrix.all import column_matrix, matrix, MatrixSpace from sage.misc.all import cached_method, flatten, latex @@ -213,6 +223,7 @@ from sage.structure.all import SageObject, parent from sage.structure.richcmp import richcmp_method, richcmp from sage.geometry.integral_points import parallelotope_points +from sage.geometry.convex_set import ConvexSet_closed from sage.misc.lazy_import import lazy_import from sage.features import PythonModule @@ -970,6 +981,26 @@ def lattice(self): """ return self._lattice + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient lattice (:meth:`lattice`) tensored with a field. + + INPUT: + + - ``base_field`` -- (default: the rationals) a field. + + EXAMPLES:: + + sage: c = Cone([(1,0)]) + sage: c.ambient_vector_space() + Vector space of dimension 2 over Rational Field + sage: c.ambient_vector_space(AA) + Vector space of dimension 2 over Algebraic Real Field + """ + return self.lattice().vector_space(base_field=base_field) + @cached_method def dual_lattice(self): r""" @@ -1009,6 +1040,8 @@ def lattice_dim(self): r""" Return the dimension of the ambient lattice of ``self``. + An alias is :meth:`ambient_dim`. + OUTPUT: - integer. @@ -1023,6 +1056,8 @@ def lattice_dim(self): """ return self.lattice().dimension() + ambient_dim = lattice_dim + def nrays(self): r""" Return the number of rays of ``self``. @@ -1210,8 +1245,11 @@ def codim(self): sage: K.codim() == K.dual().lineality() True """ + # same as ConvexSet_base.codim; the main point is the much more detailed + # docstring. return (self.lattice_dim() - self.dim()) + codimension = codim def span(self, base_ring=None): r""" @@ -1375,7 +1413,7 @@ def classify_cone_2d(ray0, ray1, check=True): # and ``ambient_ray_indices`` keyword parameters. See ``intersection`` method # for an example why this is needed. @richcmp_method -class ConvexRationalPolyhedralCone(IntegralRayCollection, Container): +class ConvexRationalPolyhedralCone(IntegralRayCollection, Container, ConvexSet_closed): r""" Create a convex rational polyhedral cone. @@ -1711,6 +1749,43 @@ def interior_contains(self, *args): point = point[0] return self._contains(point, 'interior') + @cached_method + def interior(self): + r""" + Return the interior of ``self``. + + OUTPUT: + + - either ``self``, an empty polyhedron, or an instance of + :class:`~sage.geometry.relative_interior.RelativeInterior`. + + EXAMPLES:: + + sage: c = Cone([(1,0,0), (0,1,0)]); c + 2-d cone in 3-d lattice N + sage: c.interior() + The empty polyhedron in ZZ^3 + + sage: origin = cones.trivial(2); origin + 0-d cone in 2-d lattice N + sage: origin.interior() + The empty polyhedron in ZZ^2 + + sage: K = cones.nonnegative_orthant(2); K + 2-d cone in 2-d lattice N + sage: K.interior() + Relative interior of 2-d cone in 2-d lattice N + + sage: K2 = Cone([(1,0),(-1,0),(0,1),(0,-1)]); K2 + 2-d cone in 2-d lattice N + sage: K2.interior() is K2 + True + + """ + if self.is_solid(): + return self.relative_interior() + return Polyhedron(ambient_dim=self.lattice_dim()) + def relative_interior_contains(self, *args): r""" Check if a given point is contained in the relative interior of ``self``. @@ -1752,6 +1827,42 @@ def relative_interior_contains(self, *args): point = point[0] return self._contains(point, 'relative interior') + @cached_method + def relative_interior(self): + r""" + Return the relative interior of ``self``. + + OUTPUT: + + - either ``self`` or an instance of + :class:`~sage.geometry.relative_interior.RelativeInterior`. + + EXAMPLES:: + + sage: c = Cone([(1,0,0), (0,1,0)]); c + 2-d cone in 3-d lattice N + sage: c.relative_interior() + Relative interior of 2-d cone in 3-d lattice N + + sage: origin = cones.trivial(2); origin + 0-d cone in 2-d lattice N + sage: origin.relative_interior() is origin + True + + sage: K1 = Cone([(1,0), (-1,0)]); K1 + 1-d cone in 2-d lattice N + sage: K1.relative_interior() is K1 + True + + sage: K2 = Cone([(1,0),(-1,0),(0,1),(0,-1)]); K2 + 2-d cone in 2-d lattice N + sage: K2.relative_interior() is K2 + True + """ + if self.is_relatively_open(): + return self + return RelativeInterior(self) + def cartesian_product(self, other, lattice=None): r""" Return the Cartesian product of ``self`` with ``other``. @@ -3190,6 +3301,21 @@ def is_smooth(self): return False return self.rays().matrix().elementary_divisors() == [1] * self.nrays() + def is_empty(self): + """ + Return whether ``self`` is the empty set. + + Because a cone always contains the origin, this method returns ``False``. + + EXAMPLES:: + + sage: trivial_cone = cones.trivial(3) + sage: trivial_cone.is_empty() + False + + """ + return False + def is_trivial(self): """ Checks if the cone has no rays. @@ -3208,6 +3334,8 @@ def is_trivial(self): """ return self.nrays() == 0 + is_compact = is_trivial + def is_strictly_convex(self): r""" Check if ``self`` is strictly convex. @@ -3504,8 +3632,8 @@ def solid_restriction(self): sage: K = Cone([(1,0,0), (0,1,0)]) sage: K.solid_restriction().rays() - N(1, 0), - N(0, 1) + N(0, 1), + N(1, 0) in 2-d lattice N The solid restriction of a single ray has the same @@ -3624,7 +3752,7 @@ def _split_ambient_lattice(self): sage: C2_Z2 = Cone([(1,0),(1,2)]) sage: C2_Z2._split_ambient_lattice() sage: C2_Z2._sublattice - Sublattice + Sublattice Trivial cone:: @@ -3695,9 +3823,9 @@ def sublattice(self, *args, **kwds): sage: cone.rays().basis().matrix().det() -4 sage: cone.sublattice() - Sublattice + Sublattice sage: matrix( cone.sublattice().gens() ).det() - 1 + -1 Another example:: @@ -3709,7 +3837,7 @@ def sublattice(self, *args, **kwds): N(4, -5, 1) in 3-d lattice N sage: c.sublattice() - Sublattice + Sublattice sage: c.sublattice(5, -3, 4) N(5, -3, 4) sage: c.sublattice(1, 0, 0) @@ -3805,14 +3933,14 @@ def sublattice_complement(self, *args, **kwds): sage: c = Cone([(1,2,3), (4,-5,1)]) sage: c.sublattice() - Sublattice + Sublattice sage: c.sublattice_complement() - Sublattice + Sublattice sage: m = matrix( c.sublattice().gens() + c.sublattice_complement().gens() ) sage: m - [ 1 2 3] [ 4 -5 1] - [ 0 -6 -5] + [ 1 2 3] + [ 2 -3 0] sage: m.det() -1 """ @@ -3856,7 +3984,7 @@ def orthogonal_sublattice(self, *args, **kwds): Sublattice <> sage: c12 = Cone([(1,1,1), (1,-1,1)]) sage: c12.sublattice() - Sublattice + Sublattice sage: c12.orthogonal_sublattice() Sublattice @@ -3922,15 +4050,15 @@ def relative_quotient(self, subcone): sage: sigma = Cone([(1,1,1,3),(1,-1,1,3),(-1,-1,1,3),(-1,1,1,3)]) sage: rho = Cone([(-1, -1, 1, 3), (-1, 1, 1, 3)]) sage: sigma.sublattice() - Sublattice + Sublattice sage: rho.sublattice() - Sublattice + Sublattice sage: sigma.relative_quotient(rho) 1-d lattice, quotient - of Sublattice + of Sublattice by Sublattice sage: sigma.relative_quotient(rho).gens() - (N[1, 1, 0, 0],) + (N[1, 0, 0, 0],) More complicated example:: @@ -3938,12 +4066,12 @@ def relative_quotient(self, subcone): sage: sigma = Cone([(1, 2, 3), (1, -1, 1), (-1, 1, 1), (-1, -1, 1)]) sage: N_sigma = sigma.sublattice() sage: N_sigma - Sublattice + Sublattice sage: N_rho = rho.sublattice() sage: N_rho Sublattice sage: sigma.relative_quotient(rho).gens() - (N[0, 1, 1],) + (N[-1, -1, -2],) sage: N = rho.lattice() sage: N_sigma == N.span(N_rho.gens() + tuple(q.lift() ....: for q in sigma.relative_quotient(rho).gens())) @@ -3958,12 +4086,12 @@ def relative_quotient(self, subcone): Sublattice sage: sigma1.relative_quotient(rho) 1-d lattice, quotient - of Sublattice + of Sublattice by Sublattice sage: sigma1.relative_quotient(rho).gens() - (N[0, 1, 1],) + (N[-1, -1, -2],) sage: sigma2.relative_quotient(rho).gens() - (N[-1, 0, -2],) + (N[0, 2, 1],) """ try: cached_values = self._relative_quotient @@ -4263,9 +4391,9 @@ def Hilbert_basis(self): sage: Cone([[1,0],[3,4]]).dual().Hilbert_basis() M(0, 1), M(4, -3), - M(3, -2), + M(1, 0), M(2, -1), - M(1, 0) + M(3, -2) in 2-d lattice M sage: cone = Cone([[1,2,3,4],[0,1,0,7],[3,1,0,2],[0,0,1,0]]).dual() sage: cone.Hilbert_basis() # long time @@ -4275,16 +4403,16 @@ def Hilbert_basis(self): M(15, -63, 25, 9), M( 2, -3, 0, 1), M( 1, -4, 1, 1), - M(-1, 3, 0, 0), M( 4, -4, 0, 1), + M(-1, 3, 0, 0), M( 1, -5, 2, 1), M( 3, -5, 1, 1), M( 6, -5, 0, 1), M( 3, -13, 5, 2), M( 2, -6, 2, 1), M( 5, -6, 1, 1), - M( 0, 1, 0, 0), M( 8, -6, 0, 1), + M( 0, 1, 0, 0), M(-2, 8, 0, -1), M(10, -42, 17, 6), M( 7, -28, 11, 4), @@ -4295,9 +4423,9 @@ def Hilbert_basis(self): M( 4, -7, 2, 1), M( 7, -7, 1, 1), M( 0, 0, 1, 0), - M(-3, 14, 0, -2), + M( 1, 0, 0, 0), M(-1, 7, 0, -1), - M( 1, 0, 0, 0) + M(-3, 14, 0, -2) in 4-d lattice M Not a strictly convex cone:: @@ -4447,6 +4575,8 @@ def is_solid(self): A cone is said to be solid if it has nonempty interior. That is, if its extreme rays span the entire ambient space. + An alias is :meth:`is_full_dimensional`. + OUTPUT: ``True`` if this cone is solid, and ``False`` otherwise. @@ -4486,6 +4616,8 @@ def is_solid(self): """ return (self.dim() == self.lattice_dim()) + is_full_dimensional = is_solid + def is_proper(self): r""" Check if this cone is proper. @@ -4536,6 +4668,8 @@ def is_full_space(self): r""" Check if this cone is equal to its ambient vector space. + An alias is :meth:`is_universe`. + OUTPUT: ``True`` if this cone equals its entire ambient vector @@ -4573,6 +4707,8 @@ def is_full_space(self): """ return self.linear_subspace() == self.lattice().vector_space() + is_universe = is_full_space + def lineality(self): r""" Return the lineality of this cone. @@ -4645,6 +4781,27 @@ def lineality(self): """ return self.linear_subspace().dimension() + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: K = cones.nonnegative_orthant(3) + sage: K.is_relatively_open() + False + + sage: K1 = Cone([(1,0), (-1,0)]); K1 + 1-d cone in 2-d lattice N + sage: K1.is_relatively_open() + True + """ + return self.lineality() == self.dim() + @cached_method def discrete_complementarity_set(self): r""" diff --git a/src/sage/geometry/convex_set.py b/src/sage/geometry/convex_set.py new file mode 100644 index 00000000000..d43ba7dbfff --- /dev/null +++ b/src/sage/geometry/convex_set.py @@ -0,0 +1,739 @@ +r""" +Convex Sets +""" + +# **************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# 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.structure.sage_object import SageObject +from sage.misc.abstract_method import abstract_method + +class ConvexSet_base(SageObject): + """ + Abstract base class for convex sets. + """ + + def is_empty(self): + r""" + Test whether ``self`` is the empty set. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: p = LatticePolytope([], lattice=ToricLattice(3).dual()); p + -1-d lattice polytope in 3-d lattice M + sage: p.is_empty() + True + """ + return self.dim() < 0 + + def is_universe(self): + r""" + Test whether ``self`` is the whole ambient space. + + OUTPUT: + + Boolean. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.is_universe() + Traceback (most recent call last): + ... + NotImplementedError: + """ + if not self.is_full_dimensional(): + return False + raise NotImplementedError + + @abstract_method + def dim(self): + r""" + Return the dimension of ``self``. + + Subclasses must provide an implementation of this method. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.dim() + Traceback (most recent call last): + ... + NotImplementedError: + """ + + def dimension(self): + r""" + Return the dimension of ``self``. + + This is the same as :meth:`dim`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def dim(self): + ....: return 42 + sage: ExampleSet().dimension() + 42 + """ + return self.dim() + + @abstract_method + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + Subclasses must provide an implementation of this method. + + The default implementations of :meth:`ambient`, :meth:`ambient_dim`, + :meth:`ambient_dimension` use this method. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.ambient_vector_space() + Traceback (most recent call last): + ... + NotImplementedError: + """ + + def ambient(self): + r""" + Return the ambient convex set or space. + + The default implementation delegates to :meth:`ambient_vector_space`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def ambient_vector_space(self, base_field=None): + ....: return (base_field or QQ)^2001 + sage: ExampleSet().ambient() + Vector space of dimension 2001 over Rational Field + """ + return self.ambient_vector_space() + + def ambient_dim(self): + r""" + Return the dimension of the ambient convex set or space. + + The default implementation obtains it from :meth:`ambient`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def ambient(self): + ....: return QQ^7 + sage: ExampleSet().ambient_dim() + 7 + """ + return self.ambient().dimension() + + def ambient_dimension(self): + r""" + Return the dimension of the ambient convex set or space. + + This is the same as :meth:`ambient_dim`. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def ambient_dim(self): + ....: return 91 + sage: ExampleSet().ambient_dimension() + 91 + """ + return self.ambient_dim() + + def codimension(self): + r""" + Return the codimension of ``self`` in `self.ambient()``. + + EXAMPLES:: + + sage: P = Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]) + sage: P.codimension() + 2 + + An alias is :meth:`codim`:: + + sage: P.codim() + 2 + """ + return self.ambient_dim() - self.dim() + + codim = codimension + + def is_full_dimensional(self): + r""" + Return whether ``self`` is full dimensional. + + OUTPUT: + + Boolean. Whether the polyhedron is not contained in any strict + affine subspace. + + EXAMPLES:: + + sage: c = Cone([(1,0)]) + sage: c.is_full_dimensional() + False + + sage: polytopes.hypercube(3).is_full_dimensional() + True + sage: Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]).is_full_dimensional() + False + """ + return self.dim() == self.ambient_dim() + + def is_open(self): + r""" + Return whether ``self`` is open. + + The default implementation of this method only knows that the + empty set and the ambient space are open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def is_empty(self): + ....: return False + ....: def is_universe(self): + ....: return True + sage: ExampleSet().is_open() + True + """ + if self.is_empty() or self.is_universe(): + return True + raise NotImplementedError + + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + The default implementation of this method only knows that open + sets are also relatively open, and in addition singletons are + relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def is_open(self): + ....: return True + sage: ExampleSet().is_relatively_open() + True + """ + if self.is_open(): + return True + if self.dim() == 0: + return True + raise NotImplementedError + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + The default implementation of this method only knows that the + empty set, a singleton set, and the ambient space are closed. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def dim(self): + ....: return 0 + sage: ExampleSet().is_closed() + True + """ + if self.is_empty() or self.dim() == 0 or self.is_universe(): + return True + raise NotImplementedError + + def is_compact(self): + r""" + Return whether ``self`` is compact. + + The default implementation of this method only knows that a + non-closed set cannot be compact, and that the empty set and + a singleton set are compact. + + OUTPUT: + + Boolean. + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: class ExampleSet(ConvexSet_base): + ....: def dim(self): + ....: return 0 + sage: ExampleSet().is_compact() + True + """ + if not self.is_closed(): + return False + if self.dim() < 1: + return True + raise NotImplementedError + + def closure(self): + r""" + Return the topological closure of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_closed + sage: C = ConvexSet_closed() + sage: C.closure() is C + True + """ + if self.is_closed(): + return self + raise NotImplementedError + + def interior(self): + r""" + Return the topological interior of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: C = ConvexSet_open() + sage: C.interior() is C + True + """ + if self.is_open(): + return self + raise NotImplementedError + + def relative_interior(self): + r""" + Return the relative interior of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_relatively_open + sage: C = ConvexSet_relatively_open() + sage: C.relative_interior() is C + True + """ + if self.is_relatively_open(): + return self + raise NotImplementedError + + def _test_convex_set(self, tester=None, **options): + """ + Run some tests on the methods of :class:`ConvexSet_base`. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: class FaultyConvexSet(ConvexSet_open): + ....: def ambient(self): + ....: return QQ^55 + ....: def ambient_vector_space(self, base_field=None): + ....: return QQ^16 + ....: def is_universe(self): + ....: return True + ....: def dim(self): + ....: return 42 + ....: def ambient_dim(self): + ....: return 91 + sage: TestSuite(FaultyConvexSet()).run(skip=('_test_pickling', '_test_contains')) + Failure in _test_convex_set: + ... + The following tests failed: _test_convex_set + + sage: class BiggerOnTheInside(ConvexSet_open): + ....: def dim(self): + ....: return 100000 + ....: def ambient_vector_space(self): + ....: return QQ^3 + ....: def ambient(self): + ....: return QQ^3 + ....: def ambient_dim(self): + ....: return 3 + sage: TestSuite(BiggerOnTheInside()).run(skip=('_test_pickling', '_test_contains')) + Failure in _test_convex_set: + ... + The following tests failed: _test_convex_set + + """ + if tester is None: + tester = self._tester(**options) + dim = self.dim() + codim = self.codim() + tester.assertTrue(dim <= self.ambient_dim()) + if dim >= 0: + tester.assertTrue(dim + codim == self.ambient_dim()) + if self.is_empty(): + tester.assertTrue(dim == -1) + if self.is_universe(): + tester.assertTrue(self.is_full_dimensional()) + cl_self = self.closure() + try: + int_self = self.interior() + except NotImplementedError: + int_self = None + try: + relint_self = self.relative_interior() + except NotImplementedError: + relint_self = None + if self.is_full_dimensional(): + tester.assertTrue(int_self == relint_self) + if self.is_relatively_open(): + tester.assertTrue(self == relint_self) + if self.is_open(): + tester.assertTrue(self == int_self) + if self.is_closed(): + tester.assertTrue(self == cl_self) + if self.is_compact(): + tester.assertTrue(self.is_closed()) + from sage.misc.sage_unittest import TestSuite + if relint_self is not None and relint_self is not self: + tester.info("\n Running the test suite of self.relative_interior()") + TestSuite(relint_self).run(verbose=tester._verbose, + prefix=tester._prefix + " ") + tester.info(tester._prefix + " ", newline=False) + + # Optional methods + + @abstract_method(optional=True) + def affine_hull(self): + r""" + Return the affine hull of ``self``. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.affine_hull() + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable + """ + + @abstract_method(optional=True) + def cartesian_product(self, other): + """ + Return the Cartesian product. + + INPUT: + + - ``other`` -- another convex set + + OUTPUT: + + The Cartesian product of ``self`` and ``other``. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.cartesian_product(C) + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable + """ + + @abstract_method(optional=True) + def contains(self, point): + """ + Test whether ``self`` contains the given ``point``. + + INPUT: + + - ``point`` -- a point or its coordinates + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.contains(vector([0, 0])) + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable + """ + + def _test_contains(self, tester=None, **options): + """ + Test the ``contains`` method. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_closed + sage: class FaultyConvexSet(ConvexSet_closed): + ....: def ambient_vector_space(self, base_field=QQ): + ....: return base_field^2 + ....: ambient = ambient_vector_space + ....: def contains(self, point): + ....: if isinstance(point, (tuple, list)): + ....: return all(x in ZZ for x in point) + ....: return point.parent() == ZZ^2 + sage: FaultyConvexSet()._test_contains() + Traceback (most recent call last): + ... + AssertionError: False != True + + sage: class AlsoFaultyConvexSet(ConvexSet_closed): + ....: def ambient_vector_space(self, base_field=QQ): + ....: return base_field^2 + ....: def ambient(self): + ....: return ZZ^2 + ....: def contains(self, point): + ....: return point in ZZ^2 + sage: AlsoFaultyConvexSet()._test_contains() + Traceback (most recent call last): + ... + AssertionError: True != False + """ + if tester is None: + tester = self._tester(**options) + ambient = self.ambient() + space = self.ambient_vector_space() + try: + ambient_point = ambient.an_element() + except (AttributeError, NotImplementedError): + ambient_point = None + space_point = space.an_element() + else: + space_point = space(ambient_point) + space_coords = space.coordinates(space_point) + if self.contains != NotImplemented: + contains_space_point = self.contains(space_point) + if ambient_point is not None: + tester.assertEqual(contains_space_point, self.contains(ambient_point)) + tester.assertEqual(contains_space_point, self.contains(space_coords)) + if space.base_ring().is_exact(): + from sage.rings.qqbar import AA + ext_space = self.ambient_vector_space(AA) + ext_space_point = ext_space(space_point) + tester.assertEqual(contains_space_point, self.contains(ext_space_point)) + from sage.symbolic.ring import SR + symbolic_space = self.ambient_vector_space(SR) + symbolic_space_point = symbolic_space(space_point) + # Only test that it can accept SR vectors without error. + self.contains(symbolic_space_point) + + @abstract_method(optional=True) + def intersection(self, other): + r""" + Return the intersection of ``self`` and ``other``. + + INPUT: + + - ``other`` -- another convex set + + OUTPUT: + + The intersection. + + TESTS:: + + sage: from sage.geometry.convex_set import ConvexSet_base + sage: C = ConvexSet_base() + sage: C.intersection(C) + Traceback (most recent call last): + ... + TypeError: 'NotImplementedType' object is not callable + """ + + +class ConvexSet_closed(ConvexSet_base): + r""" + Abstract base class for closed convex sets. + """ + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: hcube = polytopes.hypercube(5) + sage: hcube.is_closed() + True + """ + return True + + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: hcube = polytopes.hypercube(5) + sage: hcube.is_open() + False + + sage: zerocube = polytopes.hypercube(0) + sage: zerocube.is_open() + True + """ + return self.is_empty() or self.is_universe() + + +class ConvexSet_compact(ConvexSet_closed): + r""" + Abstract base class for compact convex sets. + """ + + def is_universe(self): + r""" + Return whether ``self`` is the whole ambient space + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: cross3 = lattice_polytope.cross_polytope(3) + sage: cross3.is_universe() + False + sage: point0 = LatticePolytope([[]]); point0 + 0-d reflexive polytope in 0-d lattice M + sage: point0.is_universe() + True + """ + return self.ambient_dim() == 0 and not self.is_empty() + + def is_compact(self): + r""" + Return whether ``self`` is compact. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: cross3 = lattice_polytope.cross_polytope(3) + sage: cross3.is_compact() + True + """ + return True + + is_relatively_open = ConvexSet_closed.is_open + + +class ConvexSet_relatively_open(ConvexSet_base): + r""" + Abstract base class for relatively open convex sets. + """ + + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior() + sage: ri_segment.is_relatively_open() + True + """ + return True + + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior() + sage: ri_segment.is_open() + False + """ + return self.is_empty() or self.is_full_dimensional() + + +class ConvexSet_open(ConvexSet_relatively_open): + r""" + Abstract base class for open convex sets. + """ + + def is_open(self): + r""" + Return whether ``self`` is open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: b = ConvexSet_open() + sage: b.is_open() + True + """ + return True + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: from sage.geometry.convex_set import ConvexSet_open + sage: class OpenBall(ConvexSet_open): + ....: def dim(self): + ....: return 3 + ....: def is_universe(self): + ....: return False + sage: OpenBall().is_closed() + False + """ + return self.is_empty() or self.is_universe() diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 4a28419762a..df4c8174f4b 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -3087,8 +3087,8 @@ def virtual_rays(self, *args): sage: f = Fan([Cone([(1,0,1,0), (0,1,1,0)])]) sage: f.virtual_rays() - N(0, 0, 0, 1), - N(0, 0, 1, 0) + N(1, 0, 0, 0), + N(0, 0, 0, 1) in 4-d lattice N sage: f.rays() @@ -3097,14 +3097,14 @@ def virtual_rays(self, *args): in 4-d lattice N sage: f.virtual_rays([0]) - N(0, 0, 0, 1) + N(1, 0, 0, 0) in 4-d lattice N You can also give virtual ray indices directly, without packing them into a list:: sage: f.virtual_rays(0) - N(0, 0, 0, 1) + N(1, 0, 0, 0) in 4-d lattice N Make sure that :trac:`16344` is fixed and one can compute diff --git a/src/sage/geometry/fan_isomorphism.py b/src/sage/geometry/fan_isomorphism.py index 04732ab0c20..18a6c9b199a 100644 --- a/src/sage/geometry/fan_isomorphism.py +++ b/src/sage/geometry/fan_isomorphism.py @@ -96,9 +96,9 @@ def fan_isomorphism_generator(fan1, fan2): ....: Cone([m2*vector([-1,-14]), m2*vector([-100, -5])])]) sage: sorted(fan_isomorphism_generator(fan1, fan2)) [ - [18 1 -5] - [ 4 0 -1] - [ 5 0 -1] + [-12 1 -5] + [ -4 0 -1] + [ -5 0 -1] ] sage: m0 = identity_matrix(ZZ, 2) @@ -125,15 +125,15 @@ def fan_isomorphism_generator(fan1, fan2): ] sage: sorted(fan_isomorphism_generator(fan1, fan2)) [ - [ 6 -3 7] [18 1 -5] - [ 1 -1 2] [ 4 0 -1] - [ 2 -1 2], [ 5 0 -1] + [-24 -3 7] [-12 1 -5] + [ -7 -1 2] [ -4 0 -1] + [ -8 -1 2], [ -5 0 -1] ] sage: sorted(fan_isomorphism_generator(fan2, fan1)) [ - [ 0 -1 1] [ 0 -1 1] - [ 1 -7 2] [ 2 -2 -5] - [ 0 -5 4], [ 1 0 -3] + [ 0 1 -1] [ 0 1 -1] + [ 1 -13 8] [ 2 -8 1] + [ 0 -5 4], [ 1 0 -3] ] """ if not fan_isomorphic_necessary_conditions(fan1, fan2): diff --git a/src/sage/geometry/hasse_diagram.py b/src/sage/geometry/hasse_diagram.py index 31884a61161..29c46604e56 100644 --- a/src/sage/geometry/hasse_diagram.py +++ b/src/sage/geometry/hasse_diagram.py @@ -103,7 +103,8 @@ def lattice_from_incidences(atom_to_coatoms, coatom_to_atoms, and we can compute the lattice as :: - sage: L = sage.geometry.cone.lattice_from_incidences( + sage: from sage.geometry.cone import lattice_from_incidences + sage: L = lattice_from_incidences( ....: atom_to_coatoms, coatom_to_atoms) sage: L Finite lattice containing 8 elements with distinguished linear extension diff --git a/src/sage/geometry/hyperplane_arrangement/arrangement.py b/src/sage/geometry/hyperplane_arrangement/arrangement.py index e45d4abcb23..f7b00495313 100644 --- a/src/sage/geometry/hyperplane_arrangement/arrangement.py +++ b/src/sage/geometry/hyperplane_arrangement/arrangement.py @@ -741,50 +741,145 @@ def cone(self, variable='t'): return H(*hyperplanes, backend=self._backend) @cached_method - def intersection_poset(self): + def intersection_poset(self, element_label="int"): r""" Return the intersection poset of the hyperplane arrangement. + INPUT: + + - ``element_label`` -- (default: ``"int"``) specify how an + intersection should be represented; must be one of the following: + + * ``"subspace"`` - as a subspace + * ``"subset"`` - as a subset of the defining hyperplanes + * ``"int"`` - as an integer + OUTPUT: - The poset of non-empty intersections of hyperplanes. + The poset of non-empty intersections of hyperplanes, with intersections + represented by integers, subsets of integers or subspaces (see the + examples for more details). - EXAMPLES:: + EXAMPLES: - sage: a = hyperplane_arrangements.coordinate(2) - sage: a.intersection_poset() + By default, the elements of the poset are the integers from `0` through + the cardinality of the poset *minus one*. The element labelled `0` + always corresponds to the ambient vector space, and the hyperplanes + themselves are labelled `1, 2, \ldots, n`, where `n` is the number + of hyperplanes of the arrangement. :: + + sage: A = hyperplane_arrangements.coordinate(2) + sage: L = A.intersection_poset(); L Finite poset containing 4 elements + sage: sorted(L) + [0, 1, 2, 3] + sage: L.level_sets() + [[0], [1, 2], [3]] + + :: sage: A = hyperplane_arrangements.semiorder(3) - sage: A.intersection_poset() + sage: L = A.intersection_poset(); L Finite poset containing 19 elements - """ + sage: sorted(L) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] + sage: [sorted(level_set) for level_set in L.level_sets()] + [[0], [1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]] + + By passing the argument ``element_label="subset"``, each element of the + intesection poset is labelled by the set of indices of the hyperplanes + whose intersection is said element. The index of a hyperplane is its + index in ``self.hyperplanes()``. :: + + sage: A = hyperplane_arrangements.semiorder(3) + sage: L = A.intersection_poset(element_label='subset') + sage: [sorted(level, key=sorted) for level in L.level_sets()] + [[{}], + [{0}, {1}, {2}, {3}, {4}, {5}], + [{0, 2}, {0, 3}, {0, 4}, {0, 5}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 4}, {2, 5}, {3, 4}, {3, 5}]] + + :: + + sage: H. = HyperplaneArrangements(QQ) + sage: A = H((y , (y-1) , (y+1) , (x - y) , (x + y))) + sage: L = A.intersection_poset(element_label='subset') + sage: sorted(L, key=sorted) + [{}, {0}, {0, 3}, {0, 4}, {1}, {1, 3, 4}, {2}, {2, 3}, {2, 4}, {3}, {4}] + + One can instead use affine subspaces as elements, + which is what is used to compute the poset in the first place:: + + sage: A = hyperplane_arrangements.coordinate(2) + sage: L = A.intersection_poset(element_label='subspace'); L + Finite poset containing 4 elements + sage: sorted(L, key=lambda S: (S.dimension(), S.linear_part().basis_matrix())) + [Affine space p + W where: + p = (0, 0) + W = Vector space of degree 2 and dimension 0 over Rational Field + Basis matrix: + [], Affine space p + W where: + p = (0, 0) + W = Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [0 1], Affine space p + W where: + p = (0, 0) + W = Vector space of degree 2 and dimension 1 over Rational Field + Basis matrix: + [1 0], Affine space p + W where: + p = (0, 0) + W = Vector space of dimension 2 over Rational Field] + """ + if element_label == "int": + def update(mapping, val, I): + mapping[val] = len(mapping) + elif element_label == "subset": + from sage.sets.set import Set + def update(mapping, val, I): + mapping[val] = Set(val) + elif element_label == "subspace": + def update(mapping, val, I): + mapping[val] = I + else: + raise ValueError("invalid element label type") + K = self.base_ring() from sage.geometry.hyperplane_arrangement.affine_subspace import AffineSubspace + whole_space = AffineSubspace(0, VectorSpace(K, self.dimension())) - L = [[whole_space]] - active = True - codim = 0 - while active: - active = False - new_level = [] - for T in L[codim]: - for H in self: - I = H._affine_subspace().intersection(T) - if I is not None and I != T and I not in new_level: - new_level.append(I) - active = True - if active: - L.append(new_level) - codim += 1 - from sage.misc.flatten import flatten - L = flatten(L) - t = {} - for i in range(len(L)): - t[i] = L[i] - cmp_fn = lambda p, q: t[q] < t[p] + hyperplanes = [H._affine_subspace() for H in self.hyperplanes()] + + mapping = {} + update(mapping, frozenset(), whole_space) + for i, H in enumerate(hyperplanes): + update(mapping, frozenset([i]), H) + + hasse = {frozenset(): [frozenset([i]) for i in range(len(hyperplanes))]} + cur_level = [(frozenset([i]), H) for i, H in enumerate(hyperplanes)] + + while cur_level: + new_level = {} + for label, T in cur_level: + edges = [] + for i, H in enumerate(hyperplanes): + I = H.intersection(T) + if I is not None and I != T: + try: + target = new_level[I] + except KeyError: + target = set(label) + new_level[I] = target + target.add(i) + edges.append(target) + hasse[label] = edges + for label, T in cur_level: + # Freeze them in place now + hasse[label] = [frozenset(X) for X in hasse[label]] + cur_level = [(frozenset(X), T) for T, X in new_level.items()] + for label, T in cur_level: + update(mapping, label, T) + from sage.combinat.posets.posets import Poset - return Poset((t, cmp_fn)) + return Poset({mapping[i]: [mapping[j] for j in val] for i, val in hasse.items()}) def _slow_characteristic_polynomial(self): """ diff --git a/src/sage/geometry/integral_points.pyx b/src/sage/geometry/integral_points.pyx index 765b0a5bb55..37e1d23339d 100644 --- a/src/sage/geometry/integral_points.pyx +++ b/src/sage/geometry/integral_points.pyx @@ -108,13 +108,13 @@ cpdef tuple parallelotope_points(spanning_points, lattice): sage: from sage.geometry.integral_points import parallelotope_points sage: rays = list(map(vector, [(2,0), (0,2)])) sage: parallelotope_points(rays, ZZ^2) - ((0, 0), (1, 0), (0, 1), (1, 1)) + ((0, 0), (0, 1), (1, 0), (1, 1)) The rays can also be toric lattice points:: sage: rays = list(map(ToricLattice(2), [(2,0), (0,2)])) sage: parallelotope_points(rays, ToricLattice(2)) - (N(0, 0), N(1, 0), N(0, 1), N(1, 1)) + (N(0, 0), N(0, 1), N(1, 0), N(1, 1)) A non-smooth cone:: diff --git a/src/sage/geometry/lattice_polytope.py b/src/sage/geometry/lattice_polytope.py index cc5d4303897..9d42d71f275 100644 --- a/src/sage/geometry/lattice_polytope.py +++ b/src/sage/geometry/lattice_polytope.py @@ -135,6 +135,7 @@ from sage.structure.all import Sequence from sage.structure.sage_object import SageObject from sage.structure.richcmp import richcmp_method, richcmp +from sage.geometry.convex_set import ConvexSet_compact from copy import copy from collections.abc import Hashable @@ -464,7 +465,7 @@ def is_LatticePolytope(x): return isinstance(x, LatticePolytopeClass) @richcmp_method -class LatticePolytopeClass(SageObject, Hashable): +class LatticePolytopeClass(ConvexSet_compact, Hashable): r""" Create a lattice polytope. @@ -517,6 +518,8 @@ def __init__(self, points=None, compute_vertices=None, sage: LatticePolytope([(1,2,3), (4,5,6)]) # indirect test 1-d lattice polytope in 3-d lattice M + sage: TestSuite(_).run() + """ if ambient is None: self._ambient = self @@ -2596,6 +2599,8 @@ def lattice_dim(self): r""" Return the dimension of the ambient lattice of ``self``. + An alias is :meth:`ambient_dim`. + OUTPUT: - integer. @@ -2610,6 +2615,28 @@ def lattice_dim(self): """ return self.lattice().dimension() + ambient_dim = lattice_dim + + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient lattice (:meth:`lattice`) tensored with a field. + + INPUT: + + - ``base_field`` -- (default: the rationals) a field. + + EXAMPLES:: + + sage: p = LatticePolytope([(1,0)]) + sage: p.ambient_vector_space() + Vector space of dimension 2 over Rational Field + sage: p.ambient_vector_space(AA) + Vector space of dimension 2 over Algebraic Real Field + """ + return self.lattice().vector_space(base_field=base_field) + def linearly_independent_vertices(self): r""" Return a maximal set of linearly independent vertices. diff --git a/src/sage/geometry/polyhedron/backend_cdd.py b/src/sage/geometry/polyhedron/backend_cdd.py index 44236fee102..d839bc3ca5e 100644 --- a/src/sage/geometry/polyhedron/backend_cdd.py +++ b/src/sage/geometry/polyhedron/backend_cdd.py @@ -252,8 +252,8 @@ def _init_from_cdd_output(self, cddout): An inequality (1, 0) x - 1 >= 0, An equation (1, 1) x - 3 == 0) sage: [x.ambient_Hrepresentation() for x in P.facets()] - [(An equation (1, 1) x - 3 == 0, An inequality (1, 0) x - 1 >= 0), - (An equation (1, 1) x - 3 == 0, An inequality (0, 1) x - 1 >= 0)] + [(An inequality (1, 0) x - 1 >= 0, An equation (1, 1) x - 3 == 0), + (An inequality (0, 1) x - 1 >= 0, An equation (1, 1) x - 3 == 0)] """ cddout = cddout.splitlines() diff --git a/src/sage/geometry/polyhedron/backend_normaliz.py b/src/sage/geometry/polyhedron/backend_normaliz.py index 95c489c8669..e6e137a0602 100644 --- a/src/sage/geometry/polyhedron/backend_normaliz.py +++ b/src/sage/geometry/polyhedron/backend_normaliz.py @@ -258,7 +258,8 @@ def _nmz_result(self, normaliz_cone, property): sage: p = Polyhedron(vertices=[(0,0),(1,1),(a,3),(-1,a**2)], rays=[(-1,-a)], backend='normaliz') # optional - pynormaliz sage: sorted(p._nmz_result(p._normaliz_cone, 'VerticesOfPolyhedron')) # optional - pynormaliz [[-1, a^2, 1], [1, 1, 1], [a, 3, 1]] - sage: sorted(p._nmz_result(p._normaliz_cone, 'TriangulationGenerators')) # optional - pynormaliz + sage: triangulation_generators = p._nmz_result(p._normaliz_cone, 'Triangulation')[1] # optional - pynormaliz + sage: sorted(triangulation_generators) # optional - pynormaliz [[-a^2, -3, 0], [-1, a^2, 1], [0, 0, 1], [1, 1, 1], [a, 3, 1]] sage: p._nmz_result(p._normaliz_cone, 'AffineDim') == 2 # optional - pynormaliz True @@ -1272,12 +1273,28 @@ def __getstate__(self): A vertex at (0, 0, 1, 0), A vertex at (0, 1, 0, 0), A vertex at (1, 0, 0, 0)), - '_normaliz_field': Rational Field}) + '_normaliz_field': Rational Field, + '_pickle_equations': [(-1, 1, 1, 1, 1)], + '_pickle_inequalities': [(0, 0, 0, 0, 1), + (0, 0, 0, 1, 0), + (0, 0, 1, 0, 0), + (0, 1, 0, 0, 0)], + '_pickle_lines': [], + '_pickle_rays': [], + '_pickle_vertices': [(0, 0, 0, 1), + (0, 0, 1, 0), + (0, 1, 0, 0), + (1, 0, 0, 0)]}) """ state = super(Polyhedron_normaliz, self).__getstate__() state = (state[0], state[1].copy()) # Remove the unpicklable entries. del state[1]['_normaliz_cone'] + state[1]["_pickle_vertices"] = [v._vector for v in self.vertices()] + state[1]["_pickle_rays"] = [v._vector for v in self.rays()] + state[1]["_pickle_lines"] = [v._vector for v in self.lines()] + state[1]["_pickle_inequalities"] = [v._vector for v in self.inequalities()] + state[1]["_pickle_equations"] = [v._vector for v in self.equations()] return state def __setstate__(self, state): @@ -1326,7 +1343,22 @@ def __setstate__(self, state): sage: P2 = Polyhedron_normaliz(P1.parent(), None, None, P1._normaliz_cone, normaliz_field=P1._normaliz_field) # optional - pynormaliz sage: P == P2 # optional - pynormaliz True + + Test that :trac:`31820` is fixed:: + + sage: P = polytopes.cube(backend='normaliz') # optional - pynormaliz + sage: v = P.Vrepresentation()[0] # optional - pynormaliz + sage: v1 = loads(v.dumps()) # optional - pynormaliz """ + if "_pickle_vertices" in state[1]: + vertices = state[1].pop("_pickle_vertices") + rays = state[1].pop("_pickle_rays") + lines = state[1].pop("_pickle_lines") + inequalities = state[1].pop("_pickle_inequalities") + equations = state[1].pop("_pickle_equations") + else: + vertices = None + super(Polyhedron_normaliz, self).__setstate__(state) if self.is_empty(): @@ -1334,9 +1366,16 @@ def __setstate__(self, state): self._normaliz_cone = None return + if vertices is None: + vertices = self.vertices() + rays = self.rays() + lines = self.lines() + inequalities = self.inequalities() + equations = self.equations() + self._normaliz_cone = \ self._cone_from_Vrepresentation_and_Hrepresentation( - self.vertices(), self.rays(), self.lines(), self.inequalities(), self.equations()) + vertices, rays, lines, inequalities, equations) def integral_hull(self): r""" @@ -1592,19 +1631,18 @@ def _triangulate_normaliz(self): # Compute the triangulation. assert cone - nmz_triangulation = self._nmz_result(cone, "Triangulation") # Normaliz does not guarantee that the order of generators is kept during # computation of the triangulation. # Those are the generators that the indices of the triangulation correspond to: - nmz_new_generators = self._nmz_result(cone, "TriangulationGenerators") + nmz_triangulation, nmz_triangulation_generators = self._nmz_result(cone, "Triangulation") base_ring = self.base_ring() v_list = self.vertices_list() r_list = self.rays_list() new_to_old = {} - for i, g in enumerate(nmz_new_generators): + for i, g in enumerate(nmz_triangulation_generators): if self.is_compact(): d = base_ring(g[-1]) vertex = [base_ring(x) / d for x in g[:-1]] diff --git a/src/sage/geometry/polyhedron/base.py b/src/sage/geometry/polyhedron/base.py index 23ffed867da..cf2e2f24ada 100644 --- a/src/sage/geometry/polyhedron/base.py +++ b/src/sage/geometry/polyhedron/base.py @@ -51,8 +51,10 @@ from sage.functions.other import sqrt, floor, ceil from sage.groups.matrix_gps.finitely_generated import MatrixGroup from sage.graphs.graph import Graph +from sage.geometry.convex_set import ConvexSet_closed from .constructor import Polyhedron +from sage.geometry.relative_interior import RelativeInterior from sage.categories.sets_cat import EmptySetError ######################################################################### @@ -98,7 +100,7 @@ def is_Polyhedron(X): ######################################################################### -class Polyhedron_base(Element): +class Polyhedron_base(Element, ConvexSet_closed): """ Base class for Polyhedron objects @@ -720,6 +722,16 @@ def _richcmp_(self, other, op): False sage: P == Q False + + Test that we have fixed a problem revealed in :trac:`31701`, + where neither of the two polyhedra contains the other:: + + sage: P = Polyhedron(vertices=[(1, 1), (0, 0), (1, 2)]) + sage: Q = Polyhedron(vertices=[(1, 2), (0, 0), (0, 2)]) + sage: Q < P + False + sage: P > Q + False """ if self._Vrepresentation is None or other._Vrepresentation is None: raise RuntimeError('some V representation is missing') @@ -732,10 +744,12 @@ def _richcmp_(self, other, op): c1 = other._is_subpolyhedron(self) if c0 and c1: return rich_to_bool(op, 0) - if c0: + elif c0: return rich_to_bool(op, -1) - else: + elif c1: return rich_to_bool(op, 1) + else: + return op == op_NE @coerce_binop def _is_subpolyhedron(self, other): @@ -2460,7 +2474,7 @@ def bounded_edges(self): def Vrepresentation_space(self): r""" - Return the ambient vector space. + Return the ambient free module. OUTPUT: @@ -2478,6 +2492,36 @@ def Vrepresentation_space(self): ambient_space = Vrepresentation_space + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient free module (:meth:`Vrepresentation_space`) tensored + with a field. + + INPUT: + + - ``base_field`` -- (default: the fraction field of the base ring) a field. + + EXAMPLES:: + + sage: poly_test = Polyhedron(vertices = [[1,0,0,0],[0,1,0,0]]) + sage: poly_test.ambient_vector_space() + Vector space of dimension 4 over Rational Field + sage: poly_test.ambient_vector_space() is poly_test.ambient() + True + + sage: poly_test.ambient_vector_space(AA) + Vector space of dimension 4 over Algebraic Real Field + sage: poly_test.ambient_vector_space(RR) + Vector space of dimension 4 over Real Field with 53 bits of precision + sage: poly_test.ambient_vector_space(SR) + Vector space of dimension 4 over Symbolic Ring + """ + return self.Vrepresentation_space().vector_space(base_field=base_field) + + ambient = ambient_vector_space + def Hrepresentation_space(self): r""" Return the linear space containing the H-representation vectors. @@ -2764,7 +2808,7 @@ def boundary_complex(self): ... ValueError: self should be compact """ - from sage.homology.simplicial_complex import SimplicialComplex + from sage.topology.simplicial_complex import SimplicialComplex if not self.is_compact(): raise ValueError("self should be compact") @@ -3567,6 +3611,22 @@ def combinatorial_polyhedron(self): 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 simplicity(self): r""" Return the largest integer `k` such that the polytope is `k`-simple. @@ -4765,6 +4825,11 @@ def product(self, other): sage: P1 * 2.0 A 1-dimensional polyhedron in RDF^1 defined as the convex hull of 2 vertices + An alias is :meth:`cartesian_product`:: + + sage: P1.cartesian_product(P2) == P1.product(P2) + True + TESTS: Check that :trac:`15253` is fixed:: @@ -4815,6 +4880,8 @@ def product(self, other): _mul_ = product + cartesian_product = product + def _test_product(self, tester=None, **options): """ Run tests on the method :meth:`.product`. @@ -6921,11 +6988,307 @@ def facets(self): 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) + 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): + 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 @@ -6981,7 +7344,7 @@ def f_vector(self): sage: Q.f_vector.is_in_cache() True """ - return self.combinatorial_polyhedron().f_vector() + return self.combinatorial_polyhedron().f_vector(num_threads, parallelization_depth) def flag_f_vector(self, *args): r""" @@ -8228,18 +8591,29 @@ def volume(self, measure='ambient', engine='auto', **kwds): else: raise TypeError("the measure should be `ambient`, `induced`, `induced_rational`, or `induced_lattice`") - def integrate(self, polynomial, **kwds): + def integrate(self, function, measure='ambient', **kwds): r""" - Return the integral of a polynomial over a polytope. + Return the integral of ``function`` over this polytope. INPUT: - - ``P`` -- Polyhedron + - ``self`` -- Polyhedron + + - ``function`` -- a multivariate polynomial or + a valid LattE description string for polynomials - - ``polynomial`` -- A multivariate polynomial or a valid LattE description string for - polynomials + - ``measure`` -- string, the measure to use + + Allowed values are: - - ``**kwds`` -- additional keyword arguments that are passed to the engine + * ``ambient`` (default): Lebesgue measure of ambient space, + * ``induced``: Lebesgue measure of the affine hull, + * ``induced_nonnormalized``: Lebesgue measure of the affine hull + without the normalization by `\sqrt{\det(A^\top A)}` (with + `A` being the affine transformation matrix; see :meth:`affine_hull`). + + - ``**kwds`` -- additional keyword arguments that + are passed to the engine OUTPUT: @@ -8261,8 +8635,8 @@ def integrate(self, polynomial, **kwds): be obtained if we transform to rational coordinates:: sage: P = 1.4142*polytopes.cube() - sage: P_QQ = Polyhedron(vertices = [[QQ(vi) for vi in v] for v in P.vertex_generator()]) - sage: RDF(P_QQ.integrate(x^2*y^2*z^2)) # optional - latte_int + sage: P_QQ = Polyhedron(vertices=[[QQ(vi) for vi in v] for v in P.vertex_generator()]) + sage: RDF(P_QQ.integrate(x^2*y^2*z^2)) # optional - latte_int 6.703841212195228 Integral over a non full-dimensional polytope:: @@ -8270,9 +8644,34 @@ def integrate(self, polynomial, **kwds): sage: x, y = polygens(QQ, 'x, y') sage: P = Polyhedron(vertices=[[0,0],[1,1]]) sage: P.integrate(x*y) # optional - latte_int - Traceback (most recent call last): - ... - NotImplementedError: the polytope must be full-dimensional + 0 + sage: ixy = P.integrate(x*y, measure='induced'); ixy # optional - latte_int + 0.4714045207910317? + sage: ixy.parent() # optional - latte_int + Algebraic Real Field + + Convert to a symbolic expression:: + + sage: ixy.radical_expression() # optional - latte_int + 1/3*sqrt(2) + + Another non full-dimensional polytope integration:: + + sage: R. = QQ[] + sage: P = polytopes.simplex(2) + sage: V = AA(P.volume(measure='induced')); V.radical_expression() + 1/2*sqrt(3) + sage: P.integrate(R(1), measure='induced') == V # optional - latte_int + True + + Computing the mass center:: + + sage: (P.integrate(x, measure='induced') / V).radical_expression() # optional - latte_int + 1/3 + sage: (P.integrate(y, measure='induced') / V).radical_expression() # optional - latte_int + 1/3 + sage: (P.integrate(z, measure='induced') / V).radical_expression() # optional - latte_int + 1/3 TESTS: @@ -8305,15 +8704,119 @@ def integrate(self, polynomial, **kwds): Traceback (most recent call last): ... TypeError: LattE integrale cannot be applied over inexact rings + + Integration of zero-polynomial:: + + sage: R. = QQ[] + sage: P = polytopes.simplex(2) + sage: P.integrate(R(0)) + 0 + sage: P.integrate('[]') # with LattE description string + 0 + + :: + + sage: R. = QQ[] + sage: P = Polyhedron(vertices=[(0, 0, 1), (0, 1, 0)]) + sage: P.integrate(x^2) + 0 + """ + if function == 0 or function == '[]': + return self.base_ring().zero() + + if not self.is_compact(): + raise NotImplementedError( + 'integration over non-compact polyhedra not allowed') + + if measure == 'ambient': + if not self.is_full_dimensional(): + return self.base_ring().zero() + + return self._integrate_latte_(function, **kwds) + + elif measure == 'induced' or measure == 'induced_nonnormalized': + # if polyhedron is actually full-dimensional, + # return with ambient measure + if self.is_full_dimensional(): + return self.integrate(function, measure='ambient', **kwds) + + if isinstance(function, str): + raise NotImplementedError( + 'LattE description strings for polynomials not allowed ' + 'when using measure="induced"') + + # use an orthogonal transformation + affine_hull_data = self.affine_hull_projection(orthogonal=True, return_all_data=True) + polyhedron = affine_hull_data.polyhedron + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(affine_hull_data.section_linear_map.base_ring(), 'x', self.dim()) + coordinate_images = affine_hull_data.section_linear_map.matrix().transpose() * vector(R.gens()) + affine_hull_data.section_translation + + hom = function.parent().hom(coordinate_images) + function_in_affine_hull = hom(function) + + I = polyhedron.integrate(function_in_affine_hull, + measure='ambient', **kwds) + if measure == 'induced_nonnormalized': + return I + else: + A = affine_hull_data.projection_linear_map.matrix() + Adet = (A.transpose() * A).det() + try: + Adet = AA.coerce(Adet) + except TypeError: + pass + return I / sqrt(Adet) + + else: + raise ValueError('unknown measure "{}"'.format(measure)) + + def _integrate_latte_(self, polynomial, **kwds): + r""" + Return the integral of a polynomial over this polytope by calling LattE. + + INPUT: + + - ``polynomial`` -- a multivariate polynomial or + a valid LattE description string for polynomials + + - ``**kwds`` -- additional keyword arguments that are passed + to the engine + + OUTPUT: + + The integral of the polynomial over the polytope. + + .. NOTE:: + + The polytope triangulation algorithm is used. This function depends + on LattE (i.e., the ``latte_int`` optional package). + + TESTS:: + + sage: P = polytopes.cube() + sage: x, y, z = polygens(QQ, 'x, y, z') + sage: P._integrate_latte_(x^2 + y^2*z^2) # optional - latte_int + 32/9 + + :: + + sage: R = PolynomialRing(QQ, '', 0) + sage: Polyhedron(vertices=[()]).integrate(R(42)) + 42 """ + from sage.interfaces.latte import integrate + if self.base_ring() == RDF: raise TypeError("LattE integrale cannot be applied over inexact rings") - elif not self.is_full_dimensional(): - raise NotImplementedError("the polytope must be full-dimensional") - else: - from sage.interfaces.latte import integrate - return integrate(self.cdd_Hrepresentation(), polynomial, - cdd=True, **kwds) + if self.dimension() == 0: + vertices = self.vertices() + assert len(self.vertices()) == 1 + vertex = tuple(vertices[0]) + return polynomial(vertex) + return integrate(self.cdd_Hrepresentation(), + polynomial, + cdd=True, **kwds) def contains(self, point): """ @@ -8394,6 +8897,46 @@ def contains(self, point): __contains__ = contains + @cached_method + def interior(self): + """ + The interior of ``self``. + + OUTPUT: + + - either an empty polyhedron or an instance of + :class:`~sage.geometry.relative_interior.RelativeInterior` + + EXAMPLES: + + If the polyhedron is full-dimensional, the result is the + same as that of :meth:`relative_interior`:: + + sage: P_full = Polyhedron(vertices=[[0,0],[1,1],[1,-1]]) + sage: P_full.interior() + Relative interior of + a 2-dimensional polyhedron in ZZ^2 defined as the convex hull of 3 vertices + + If the polyhedron is of strictly smaller dimension than the + ambient space, its interior is empty:: + + sage: P_lower = Polyhedron(vertices=[[0,1], [0,-1]]) + sage: P_lower.interior() + The empty polyhedron in ZZ^2 + + TESTS:: + + sage: Empty = Polyhedron(ambient_dim=2); Empty + The empty polyhedron in ZZ^2 + sage: Empty.interior() is Empty + True + """ + if self.is_open(): + return self + if not self.is_full_dimensional(): + return self.parent().element_class(self.parent(), None, None) + return self.relative_interior() + def interior_contains(self, point): """ Test whether the interior of the polyhedron contains the @@ -8451,6 +8994,71 @@ def interior_contains(self, point): return False return True + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: P = Polyhedron(vertices=[(1,0), (-1,0)]); P + A 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: P.is_relatively_open() + False + + sage: P0 = Polyhedron(vertices=[[1, 2]]); P0 + A 0-dimensional polyhedron in ZZ^2 defined as the convex hull of 1 vertex + sage: P0.is_relatively_open() + True + + sage: Empty = Polyhedron(ambient_dim=2); Empty + The empty polyhedron in ZZ^2 + sage: Empty.is_relatively_open() + True + + sage: Line = Polyhedron(vertices=[(1, 1)], lines=[(1, 0)]); Line + A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: Line.is_relatively_open() + True + + """ + return not self.inequalities() + + @cached_method + def relative_interior(self): + """ + Return the relative interior of ``self``. + + EXAMPLES:: + + sage: P = Polyhedron(vertices=[(1,0), (-1,0)]) + sage: ri_P = P.relative_interior(); ri_P + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: (0, 0) in ri_P + True + sage: (1, 0) in ri_P + False + + sage: P0 = Polyhedron(vertices=[[1, 2]]) + sage: P0.relative_interior() is P0 + True + + sage: Empty = Polyhedron(ambient_dim=2) + sage: Empty.relative_interior() is Empty + True + + sage: Line = Polyhedron(vertices=[(1, 1)], lines=[(1, 0)]) + sage: Line.relative_interior() is Line + True + """ + if self.is_relatively_open(): + return self + return RelativeInterior(self) + def relative_interior_contains(self, point): """ Test whether the relative interior of the polyhedron @@ -9715,24 +10323,6 @@ def edge_label(i, j, c_ij): else: return MatrixGroup(matrices) - def is_full_dimensional(self): - """ - Return whether the polyhedron is full dimensional. - - OUTPUT: - - Boolean. Whether the polyhedron is not contained in any strict - affine subspace. - - EXAMPLES:: - - sage: polytopes.hypercube(3).is_full_dimensional() - True - sage: Polyhedron(vertices=[(1,2,3)], rays=[(1,0,0)]).is_full_dimensional() - False - """ - return self.dim() == self.ambient_dim() - def is_combinatorially_isomorphic(self, other, algorithm='bipartite_graph'): r""" Return whether the polyhedron is combinatorially isomorphic to another polyhedron. @@ -10149,7 +10739,7 @@ def affine_hull_projection(self, as_polyhedron=None, as_affine_map=False, orthog sage: AA(Pgonal.volume()^2) == (Pnormal.volume()^2)*AA(Adet) True - An other example with ``as_affine_map=True``:: + Another example with ``as_affine_map=True``:: sage: P = polytopes.permutahedron(4) sage: A, b = P.affine_hull_projection(orthonormal=True, as_affine_map=True, extend=True) @@ -10516,6 +11106,153 @@ def _test_affine_hull_projection(self, tester=None, verbose=False, **options): if self.base_ring() is not AA: tester.assertFalse(data.polyhedron.base_ring() is AA) + def affine_hull_manifold(self, name=None, latex_name=None, start_index=0, ambient_space=None, + ambient_chart=None, names=None, **kwds): + r""" + Return the affine hull of ``self`` as a manifold. + + If ``self`` is full-dimensional, it is just the ambient Euclidean space. + Otherwise, it is a Riemannian submanifold of the ambient Euclidean space. + + INPUT: + + - ``ambient_space`` -- a :class:`~sage.manifolds.differentiable.examples.euclidean.EuclideanSpace` + of the ambient dimension (default: the manifold of ``ambient_chart``, if provided; + otherwise, a new instance of ``EuclideanSpace``). + + - ``ambient_chart`` -- a chart on ``ambient_space``. + + - ``names`` -- names for the coordinates on the affine hull. + + - optional arguments accepted by :meth:`~sage.geometry.polyhedron.base.affine_hull_projection`. + + The default chart is determined by the optional arguments of + :meth:`~sage.geometry.polyhedron.base.affine_hull_projection`. + + EXAMPLES:: + + sage: triangle = Polyhedron([(1,0,0), (0,1,0), (0,0,1)]); triangle + A 2-dimensional polyhedron in ZZ^3 defined as the convex hull of 3 vertices + sage: A = triangle.affine_hull_manifold(name='A'); A + 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 + sage: A.embedding().display() + A --> E^3 + (x0, x1) |--> (x, y, z) = (t0 + x0, t0 + x1, t0 - x0 - x1 + 1) + sage: A.embedding().inverse().display() + E^3 --> A + (x, y, z) |--> (x0, x1) = (x, y) + sage: A.adapted_chart() + [Chart (E^3, (x0_E3, x1_E3, t0_E3))] + sage: A.normal().display() + n = 1/3*sqrt(3) e_x + 1/3*sqrt(3) e_y + 1/3*sqrt(3) e_z + sage: A.induced_metric() # Need to call this before volume_form + Riemannian metric gamma on the 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 + sage: A.volume_form() + 2-form eps_gamma on the 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 + + Orthogonal version:: + + sage: A = triangle.affine_hull_manifold(name='A', orthogonal=True); A + 2-dimensional Riemannian submanifold A embedded in the Euclidean space E^3 + sage: A.embedding().display() + A --> E^3 + (x0, x1) |--> (x, y, z) = (t0 - 1/2*x0 - 1/3*x1 + 1, t0 + 1/2*x0 - 1/3*x1, t0 + 2/3*x1) + sage: A.embedding().inverse().display() + E^3 --> A + (x, y, z) |--> (x0, x1) = (-x + y + 1, -1/2*x - 1/2*y + z + 1/2) + + Arrangement of affine hull of facets:: + + sage: D = polytopes.dodecahedron() + sage: E3 = EuclideanSpace(3) + sage: submanifolds = [ + ....: F.as_polyhedron().affine_hull_manifold(name=f'F{i}', orthogonal=True, ambient_space=E3) + ....: for i, F in enumerate(D.facets())] + sage: sum(FM.plot({}, srange(-2, 2, 0.1), srange(-2, 2, 0.1), opacity=0.2) # not tested + ....: for FM in submanifolds) + D.plot() + Graphics3d Object + + Full-dimensional case:: + + sage: cube = polytopes.cube(); cube + A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 8 vertices + sage: cube.affine_hull_manifold() + Euclidean space E^3 + + """ + if ambient_space is None: + if ambient_chart is not None: + ambient_space = ambient_chart.manifold() + else: + from sage.manifolds.differentiable.examples.euclidean import EuclideanSpace + ambient_space = EuclideanSpace(self.ambient_dim(), start_index=start_index) + if ambient_space.dimension() != self.ambient_dim(): + raise ValueError('ambient_space and ambient_chart must match the ambient dimension') + + if self.is_full_dimensional(): + return ambient_space + + if ambient_chart is None: + ambient_chart = ambient_space.default_chart() + CE = ambient_chart + + from sage.manifolds.manifold import Manifold + if name is None: + name, latex_name = self._affine_hull_name_latex_name() + H = Manifold(self.dim(), name, ambient=ambient_space, structure="Riemannian", + latex_name=latex_name, start_index=start_index) + if names is None: + names = tuple(f'x{i}' for i in range(self.dim())) + CH = H.chart(names=names) + + data = self.affine_hull_projection(return_all_data=True, **kwds) + projection_matrix = data.projection_linear_map.matrix().transpose() + projection_translation_vector = data.projection_translation + section_matrix = data.section_linear_map.matrix().transpose() + section_translation_vector = data.section_translation + + from sage.symbolic.ring import SR + # We use the slacks of the (linear independent) equations as the foliation parameters + foliation_parameters = vector(SR.var(f't{i}') for i in range(self.ambient_dim() - self.dim())) + normal_matrix = matrix(equation.A() for equation in self.equation_generator()).transpose() + slack_matrix = normal_matrix.pseudoinverse() + + phi = H.diff_map(ambient_space, {(CH, CE): + (section_matrix * vector(CH._xx) + section_translation_vector + + normal_matrix * foliation_parameters).list()}) + phi_inv = ambient_space.diff_map(H, {(CE, CH): + (projection_matrix * vector(CE._xx) + projection_translation_vector).list()}) + + foliation_scalar_fields = {parameter: + ambient_space.scalar_field({CE: slack_matrix.row(i) * (vector(CE._xx) - section_translation_vector)}) + for i, parameter in enumerate(foliation_parameters)} + + H.set_embedding(phi, inverse=phi_inv, + var=list(foliation_parameters), t_inverse=foliation_scalar_fields) + return H + + def _affine_hull_name_latex_name(self, name=None, latex_name=None): + r""" + Return the default name of the affine hull. + + EXAMPLES:: + + sage: polytopes.cube()._affine_hull_name_latex_name('C', r'\square') + ('aff_C', '\\mathop{\\mathrm{aff}}(\\square)') + + sage: Polyhedron(vertices=[[0, 1], [1, 0]])._affine_hull_name_latex_name() + ('aff_P', '\\mathop{\\mathrm{aff}}(P)') + """ + + if name is None: + name = 'P' + if latex_name is None: + latex_name = name + operator = 'aff' + aff_name = f'{operator}_{name}' + aff_latex_name = r'\mathop{\mathrm{' + operator + '}}(' + latex_name + ')' + return aff_name, aff_latex_name + def _polymake_init_(self): """ Return a polymake "Polytope" object corresponding to ``self``. diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd index 59c77ec0faa..4fc293961c5 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd @@ -1,5 +1,5 @@ cimport cython -from sage.ext.memory_allocator cimport MemoryAllocator +from memory_allocator cimport MemoryAllocator from sage.structure.sage_object cimport SageObject from .face_iterator cimport FaceIterator, CombinatorialFace from .list_of_faces cimport ListOfFaces @@ -12,12 +12,12 @@ cdef class CombinatorialPolyhedron(SageObject): # Do not assume any of those attributes to be initialized, use the corresponding methods instead. cdef tuple _Vrep # the names of VRep, if they exist - cdef tuple _facet_names # the names of HRep without equalities, if they exist - cdef tuple _equalities # stores equalities, given on input (might belong to Hrep) + cdef tuple _facet_names # the names of HRep without equations, if they exist + cdef tuple _equations # stores equations, given on input (might belong to Hrep) cdef int _dimension # stores dimension, -2 on init - cdef unsigned int _n_Hrepresentation # Hrep might include equalities + cdef unsigned int _n_Hrepresentation # Hrep might include equations cdef unsigned int _n_Vrepresentation # Vrep might include rays/lines - cdef size_t _n_facets # length Hrep without equalities + cdef size_t _n_facets # length Hrep without equations cdef bint _bounded # ``True`` iff Polyhedron is bounded cdef ListOfFaces _bitrep_facets # facets in bit representation cdef ListOfFaces _bitrep_Vrep # vertices in bit representation @@ -44,6 +44,7 @@ cdef class CombinatorialPolyhedron(SageObject): cdef tuple Vrep(self) cdef tuple facet_names(self) + cdef tuple equations(self) cdef tuple equalities(self) cdef unsigned int n_Vrepresentation(self) cdef unsigned int n_Hrepresentation(self) @@ -62,7 +63,7 @@ cdef class CombinatorialPolyhedron(SageObject): cdef tuple _mem_tuple cdef FaceIterator _face_iter(self, bint dual, int dimension) - cdef int _compute_f_vector(self, bint compute_edges=*, given_dual=*) except -1 + cdef int _compute_f_vector(self, size_t num_threads, size_t parallelization_depth) except -1 cdef inline int _compute_edges(self, dual) except -1: return self._compute_edges_or_ridges(dual, True) @@ -71,7 +72,10 @@ cdef class CombinatorialPolyhedron(SageObject): return self._compute_edges_or_ridges(dual, False) cdef int _compute_edges_or_ridges(self, bint dual, bint do_edges) except -1 - cdef size_t _compute_edges_or_ridges_with_iterator(self, FaceIterator face_iter, bint do_atom_rep, size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt, MemoryAllocator mem) except -1 + cdef size_t _compute_edges_or_ridges_with_iterator( + self, FaceIterator face_iter, const bint do_atom_rep, const bint do_f_vector, + size_t ***edges_pt, size_t *counter_pt, size_t *current_length_pt, + size_t* f_vector, MemoryAllocator mem) 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 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx index d11e8577d88..7557e0442a8 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx @@ -12,7 +12,7 @@ the ridges and the face lattice. Terminology used in this module: - Vrep -- ``[vertices, rays, lines]`` of the polyhedron. -- Hrep -- inequalities and equalities of the polyhedron. +- Hrep -- inequalities and equations of the polyhedron. - Facets -- facets of the polyhedron. - Vrepresentation -- represents a face by the list of Vrep it contains. - Hrepresentation -- represents a face by a list of Hrep it is contained in. @@ -104,6 +104,7 @@ 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_iterator cimport iter_t, parallel_f_vector cdef extern from "Python.h": int unlikely(int) nogil # Defined by Cython @@ -337,7 +338,7 @@ cdef class CombinatorialPolyhedron(SageObject): self._edges = NULL self._ridges = NULL self._face_lattice_incidences = NULL - self._equalities = () + self._equations = () self._all_faces = None self._mem_tuple = () cdef MemoryAllocator mem @@ -413,20 +414,20 @@ cdef class CombinatorialPolyhedron(SageObject): Vinv = None if facets: - # store facets names and compute equalities + # store facets names and compute equations facets = tuple(facets) - test = [1] * len(facets) # 0 if that facet is an equality + test = [1] * len(facets) # 0 if that facet is an equation for i in range(len(facets)): if hasattr(facets[i], "is_inequality"): - # We remove equalities. - # At the moment only equalities with this attribute ``True`` + # We remove equations. + # At the moment only equations with this attribute ``True`` # will be detected. if not facets[i].is_inequality(): test[i] = 0 self._facet_names = tuple(facets[i] for i in range(len(facets)) if test[i]) - self._equalities = tuple(facets[i] for i in range(len(facets)) if not test[i]) + self._equations = tuple(facets[i] for i in range(len(facets)) if not test[i]) else: self._facet_names = None @@ -642,13 +643,31 @@ cdef class CombinatorialPolyhedron(SageObject): """ # Give a constructor by list of facets. if not self.is_bounded(): - return (CombinatorialPolyhedron, (self.facets(), + return (CombinatorialPolyhedron, (self.incidence_matrix(), self.Vrepresentation(), self.Hrepresentation(), True, self.far_face_tuple())) else: - return (CombinatorialPolyhedron, (self.facets(), + return (CombinatorialPolyhedron, (self.incidence_matrix(), self.Vrepresentation(), self.Hrepresentation())) + def _test_bitsets(self, tester=None, **options): + """ + Test if the bitsets are consistent. + + TESTS:: + + sage: P = polytopes.cube() + sage: C = CombinatorialPolyhedron(P) + sage: C._test_bitsets() + """ + if tester is None: + tester = self._tester(**options) + + cdef ListOfFaces facets = self.bitrep_facets() + cdef ListOfFaces Vrep = self.bitrep_Vrep() + + tester.assertEqual(facets.matrix(), Vrep.matrix().transpose()) + def Vrepresentation(self): r""" Return a list of names of ``[vertices, rays, lines]``. @@ -682,20 +701,20 @@ cdef class CombinatorialPolyhedron(SageObject): def Hrepresentation(self): r""" - Return a list of names of facets and possibly some equalities. + Return a list of names of facets and possibly some equations. EXAMPLES:: sage: P = polytopes.permutahedron(3) sage: C = CombinatorialPolyhedron(P) sage: C.Hrepresentation() - (An equation (1, 1, 1) x - 6 == 0, - An inequality (1, 1, 0) x - 3 >= 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) + An inequality (0, -1, 0) x + 3 >= 0, + An equation (1, 1, 1) x - 6 == 0) sage: points = [(1,0,0), (0,1,0), (0,0,1), ....: (-1,0,0), (0,-1,0), (0,0,-1)] @@ -716,7 +735,7 @@ cdef class CombinatorialPolyhedron(SageObject): (M(0, 1), M(1, 0)) """ if self.facet_names() is not None: - return self.equalities() + self.facet_names() + return self.facet_names() + self.equations() else: return tuple(smallInteger(i) for i in range(self.n_Hrepresentation())) @@ -978,7 +997,7 @@ cdef class CombinatorialPolyhedron(SageObject): sage: C.facets() () """ - if unlikely(self.dimension() == 0): + if unlikely(self.dimension() <= 0): # Special attention for this trivial case. # Facets are defined to be nontrivial faces of codimension 1. # The empty face is trivial. @@ -1041,7 +1060,7 @@ cdef class CombinatorialPolyhedron(SageObject): :: - sage: P = polytopes.permutahedron(5) + sage: P = polytopes.permutahedron(5, backend='field') sage: C = P.combinatorial_polyhedron() sage: C.incidence_matrix.clear_cache() sage: C.incidence_matrix() == P.incidence_matrix() @@ -1091,17 +1110,17 @@ cdef class CombinatorialPolyhedron(SageObject): incidence_matrix.set_immutable() return incidence_matrix - # If equalities are present, we add them as first columns. - n_equalities = 0 + # If equations are present, we add them as last columns. + n_facets = self.n_facets() if self.facet_names() is not None: - n_equalities = len(self.equalities()) - for Hindex in range(n_equalities): + n_equations = len(self.equations()) + for Hindex in range(n_facets, n_facets + n_equations): for Vindex in range(self.n_Vrepresentation()): incidence_matrix.set_unsafe_si(Vindex, Hindex, 1) facet_iter = self.face_iter(self.dimension() - 1, dual=False) for facet in facet_iter: - Hindex = facet.ambient_H_indices()[0] + n_equalities + Hindex = facet.ambient_H_indices()[0] for Vindex in facet.ambient_V_indices(): incidence_matrix.set_unsafe_si(Vindex, Hindex, 1) @@ -1249,7 +1268,7 @@ cdef class CombinatorialPolyhedron(SageObject): deprecation(28603, "the method edge_graph of CombinatorialPolyhedron is deprecated; use vertex_graph", 3) return Graph(self.edges(names=names), format="list_of_edges") - def ridges(self, add_equalities=False, names=True): + def ridges(self, add_equations=False, names=True, add_equalities=False): r""" Return the ridges. @@ -1263,7 +1282,7 @@ cdef class CombinatorialPolyhedron(SageObject): INPUT: - - ``add_equalities`` -- if ``True``, then equalities of the polyhedron + - ``add_equations`` -- if ``True``, then equations of the polyhedron will be added (only applicable when ``names`` is ``True``) - ``names`` -- if ``False``, then the facets are given by their indices @@ -1279,7 +1298,7 @@ cdef class CombinatorialPolyhedron(SageObject): sage: C = CombinatorialPolyhedron(P) sage: C.ridges() ((An inequality (1, 0) x - 1 >= 0, An inequality (-1, 0) x + 2 >= 0),) - sage: C.ridges(add_equalities=True) + sage: C.ridges(add_equations=True) (((An inequality (1, 0) x - 1 >= 0, An equation (1, 1) x - 3 == 0), (An inequality (-1, 0) x + 2 >= 0, An equation (1, 1) x - 3 == 0)),) @@ -1330,12 +1349,26 @@ cdef class CombinatorialPolyhedron(SageObject): TESTS: - Testing that ``add_equalities`` is ignored if ``names`` is ``False``:: + Testing that ``add_equations`` is ignored if ``names`` is ``False``:: sage: C = CombinatorialPolyhedron(polytopes.simplex()) - sage: C.ridges(names=False, add_equalities=True) + sage: C.ridges(names=False, add_equations=True) ((2, 3), (1, 3), (0, 3), (1, 2), (0, 2), (0, 1)) + + The keyword ``add_equalities`` is deprecated:: + + sage: C = CombinatorialPolyhedron(polytopes.simplex()) + sage: r = C.ridges(add_equations=True) + sage: r1 = C.ridges(add_equalities=True) + doctest:...: DeprecationWarning: the keyword ``add_equalities`` is deprecated; use ``add_equations`` + See https://trac.sagemath.org/31834 for details. + sage: r == r1 + True """ + if add_equalities: + from sage.misc.superseded import deprecation + deprecation(31834, "the keyword ``add_equalities`` is deprecated; use ``add_equations``", 3) + add_equations = True if self._ridges is NULL: # compute the ridges. if not self.is_bounded(): @@ -1364,11 +1397,11 @@ cdef class CombinatorialPolyhedron(SageObject): return f(self._get_edge(self._ridges, i, 1)) cdef size_t j - if add_equalities and names: - # Also getting the equalities for each facet. + if add_equations and names: + # Also getting the equations for each facet. return tuple( - (((facet_one(i),) + self.equalities()), - ((facet_two(i),) + self.equalities())) + (((facet_one(i),) + self.equations()), + ((facet_two(i),) + self.equations())) for i in range(n_ridges)) else: return tuple((facet_one(i), facet_two(i)) @@ -1414,7 +1447,7 @@ cdef class CombinatorialPolyhedron(SageObject): V = list(facet.ambient_Hrepresentation() for facet in face_iter) else: V = list(facet.ambient_V_indices() for facet in face_iter) - E = self.ridges(names=names, add_equalities=True) + E = self.ridges(names=names, add_equations=True) if not names: # If names is false, the ridges are given as tuple of indices, # i.e. (1,2) instead of (('f1',), ('f2',)). @@ -1554,13 +1587,20 @@ cdef class CombinatorialPolyhedron(SageObject): return DiGraph([vertices, edges], format='vertices_and_edges', immutable=True) @cached_method - def f_vector(self): + def f_vector(self, num_threads=None, parallelization_depth=None): r""" Compute the ``f_vector`` of the polyhedron. The ``f_vector`` contains the number of faces of dimension `k` for each `k` in ``range(-1, self.dimension() + 1)``. + INPUT: + + - ``num_threads`` -- integer (optional); specify the number of threads + + - ``parallelization_depth`` -- integer (optional); specify + how deep in the lattice the parallelization is done + .. NOTE:: To obtain edges and/or ridges as well, first do so. This might @@ -1578,13 +1618,35 @@ cdef class CombinatorialPolyhedron(SageObject): sage: C.f_vector() (1, 10, 45, 120, 185, 150, 50, 1) + Using two threads:: + + sage: P = polytopes.permutahedron(5) + sage: C = CombinatorialPolyhedron(P) + sage: C.f_vector(num_threads=2) + (1, 120, 240, 150, 30, 1) + TESTS:: sage: type(C.f_vector()) """ + if num_threads is None: + from sage.parallel.ncpus import ncpus + num_threads = ncpus() + + if parallelization_depth is None: + # Setting some reasonable defaults. + if num_threads == 0: + parallelization_depth = 0 + elif num_threads <= 3: + parallelization_depth = 1 + elif num_threads <= 8: + parallelization_depth = 2 + else: + parallelization_depth = 3 + if not self._f_vector: - self._compute_f_vector() + self._compute_f_vector(num_threads, parallelization_depth) if not self._f_vector: raise ValueError("could not determine f_vector") from sage.modules.free_module_element import vector @@ -2239,7 +2301,7 @@ cdef class CombinatorialPolyhedron(SageObject): vertex_iter = self._face_iter(True, 0) n_facets = self.n_facets() for vertex in vertex_iter: - if vertex.n_ambient_Hrepresentation() == n_facets - 1: + if vertex.n_ambient_Hrepresentation(add_equations=False) == n_facets - 1: if certificate: return (True, vertex.ambient_Vrepresentation()[0]) return True @@ -2248,6 +2310,56 @@ cdef class CombinatorialPolyhedron(SageObject): return (False, None) return False + def join_of_Vrep(self, *indices): + r""" + Return the smallest face containing all Vrepresentatives indicated by the indices. + + .. SEEALSO:: + + :meth:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator_base.join_of_Vrep`. + + EXAMPLES:: + + sage: P = polytopes.permutahedron(4) + sage: C = CombinatorialPolyhedron(P) + sage: C.join_of_Vrep(0,1) + A 1-dimensional face of a 3-dimensional combinatorial polyhedron + sage: C.join_of_Vrep(0,11).ambient_V_indices() + (0, 1, 10, 11, 12, 13) + sage: C.join_of_Vrep(8).ambient_V_indices() + (8,) + sage: C.join_of_Vrep().ambient_V_indices() + () + """ + return self.face_iter().join_of_Vrep(*indices) + + def meet_of_Hrep(self, *indices): + r""" + Return the largest face contained in all facets indicated by the indices. + + .. SEEALSO:: + + :meth:`~sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator_base.meet_of_Hrep`. + + EXAMPLES:: + + sage: P = polytopes.dodecahedron() + sage: C = CombinatorialPolyhedron(P) + sage: C.meet_of_Hrep(0) + A 2-dimensional face of a 3-dimensional combinatorial polyhedron + sage: C.meet_of_Hrep(0).ambient_H_indices() + (0,) + sage: C.meet_of_Hrep(0,1).ambient_H_indices() + (0, 1) + sage: C.meet_of_Hrep(0,2).ambient_H_indices() + (0, 2) + sage: C.meet_of_Hrep(0,2,3).ambient_H_indices() + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + sage: C.meet_of_Hrep().ambient_H_indices() + () + """ + return self.face_iter().meet_of_Hrep(*indices) + def face_iter(self, dimension=None, dual=None): r""" Iterator over all proper faces of specified dimension. @@ -2296,11 +2408,11 @@ cdef class CombinatorialPolyhedron(SageObject): An inequality (0, 0, 0, -1, 0) x + 5 >= 0, An equation (1, 1, 1, 1, 1) x - 15 == 0) sage: face.ambient_H_indices() - (25, 29) + (25, 29, 30) sage: face = next(it); face A 2-dimensional face of a 4-dimensional combinatorial polyhedron sage: face.ambient_H_indices() - (24, 29) + (24, 29, 30) sage: face.ambient_V_indices() (32, 89, 90, 94) @@ -2691,13 +2803,18 @@ cdef class CombinatorialPolyhedron(SageObject): """ return self._facet_names - cdef tuple equalities(self): + cdef tuple equations(self): r""" - Return the names of the equalities. + Return the names of the equations. - If not equalities are given, return ``None``. + If not equations are given, return ``None``. """ - return self._equalities + return self._equations + + cdef tuple equalities(self): + from sage.misc.superseded import deprecation + deprecation(31834, "the method equalities of CombinatorialPolyhedron is deprecated; use equations", 3) + return self.equations() cdef unsigned int n_Vrepresentation(self): r""" @@ -2756,6 +2873,21 @@ cdef class CombinatorialPolyhedron(SageObject): """ return self._far_face_tuple + def __eq__(self, other): + r""" + Return whether ``self`` and ``other`` are equal. + """ + if not isinstance(other, CombinatorialPolyhedron): + return False + cdef CombinatorialPolyhedron other_C = other + return (self.n_facets() == other.n_facets() + and self.Vrepresentation() == other.Vrepresentation() + and self.facet_names() == other_C.facet_names() + and self.equations() == other_C.equations() + and self.dimension() == other.dimension() + and self.far_face_tuple() == other_C.far_face_tuple() + and self.incidence_matrix() == other.incidence_matrix()) + # Methods to obtain a different combinatorial polyhedron. @@ -2888,18 +3020,27 @@ cdef class CombinatorialPolyhedron(SageObject): # Internal methods. - cdef int _compute_f_vector(self, bint compute_edges=False, given_dual=None) except -1: + cdef int _compute_f_vector(self, size_t num_threads, size_t parallelization_depth) except -1: r""" Compute the ``f_vector`` of the polyhedron. - If ``compute_edges`` computes the edges in non-dual mode as well. - In dual mode the ridges. - - See :meth:`f_vector` and :meth:`_compute_edges`. + See :meth:`f_vector`. """ if self._f_vector: return 0 # There is no need to recompute the f_vector. + cdef int dim = self.dimension() + cdef int d # dimension of the current face of the iterator + cdef MemoryAllocator mem = MemoryAllocator() + + if num_threads == 0: + # No need to complain. + num_threads = 1 + + if parallelization_depth > dim - 1: + # Is a very bad choice anyway, but prevent segmenation faults. + parallelization_depth = dim - 1 + cdef bint dual if not self.is_bounded() or self.n_facets() <= self.n_Vrepresentation(): # In this case the non-dual approach is faster.. @@ -2907,49 +3048,21 @@ cdef class CombinatorialPolyhedron(SageObject): else: # In this case the dual approach is faster. dual = True - if given_dual is not None: - dual = given_dual + cdef FaceIterator face_iter + cdef iter_t* structs = mem.allocarray(num_threads, sizeof(iter_t)) + cdef size_t i - cdef FaceIterator face_iter = self._face_iter(dual, -2) - - cdef int dim = self.dimension() - cdef int d # dimension of the current face of the iterator - cdef MemoryAllocator mem = MemoryAllocator() - - # In case we compute the edges as well. - cdef size_t **edges - cdef size_t counter = 0 # the number of edges so far - cdef size_t current_length # dynamically enlarge **edges - cdef size_t a,b # vertices of an edge - if compute_edges: - edges = mem.malloc(sizeof(size_t**)) - current_length = 1 + # For each thread an independent structure. + face_iters = [self._face_iter(dual, -2) for _ in range(num_threads)] + for i in range(num_threads): + face_iter = face_iters[i] + structs[i][0] = face_iter.structure[0] # Initialize ``f_vector``. cdef size_t *f_vector = mem.calloc((dim + 2), sizeof(size_t)) - f_vector[0] = 1 # Face iterator will only visit proper faces. - f_vector[dim + 1] = 1 # Face iterator will only visit proper faces. - - # For each face in the iterator, add `1` to the corresponding entry in - # ``f_vector``. - if self.n_facets() > 0 and dim > 0: - d = face_iter.next_dimension() - while (d < dim): - sig_check() - f_vector[d+1] += 1 - - if compute_edges and d == 1: - # If it is an edge. - # Set up face_iter.atom_rep - face_iter.set_atom_rep() - - # Copy the information. - a = face_iter.structure.atom_rep[0] - b = face_iter.structure.atom_rep[1] - self._set_edge(a, b, &edges, &counter, ¤t_length, mem) - d = face_iter.next_dimension() + parallel_f_vector(structs, num_threads, parallelization_depth, f_vector) # Copy ``f_vector``. if dual: @@ -2970,21 +3083,6 @@ cdef class CombinatorialPolyhedron(SageObject): self._f_vector = tuple(smallInteger(f_vector[i]) for i in range(dim+2)) - if compute_edges: - # Success, copy the data to ``CombinatorialPolyhedron``. - if dual: - sig_block() - self._n_ridges = counter - self._ridges = edges - self._mem_tuple += (mem,) - sig_unblock() - else: - sig_block() - self._n_edges = counter - self._edges = edges - self._mem_tuple += (mem,) - sig_unblock() - cdef int _compute_edges_or_ridges(self, bint dual, bint do_edges) except -1: r""" Compute the edges of the polyhedron if ``edges`` else the ridges. @@ -3008,6 +3106,9 @@ cdef class CombinatorialPolyhedron(SageObject): 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 + 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) @@ -3017,18 +3118,46 @@ cdef class CombinatorialPolyhedron(SageObject): # Prevent an error when calling the face iterator. pass - elif 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. - return self._compute_f_vector(compute_edges=True, given_dual=dual) - else: - # Only compute the edges/ridges. - face_iter = self._face_iter(dual, output_dim_init) - - self._compute_edges_or_ridges_with_iterator(face_iter, (dual ^ do_edges), &edges, &counter, ¤t_length, 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 = 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) # 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") + + # 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 @@ -3043,32 +3172,45 @@ cdef class CombinatorialPolyhedron(SageObject): sig_unblock() cdef size_t _compute_edges_or_ridges_with_iterator( - self, FaceIterator face_iter, bint do_atom_rep, size_t ***edges_pt, - size_t *counter_pt, size_t *current_length_pt, - MemoryAllocator mem) except -1: + 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: r""" See :meth:`CombinatorialPolyhedron._compute_edges`. """ cdef size_t a,b # facets of an edge cdef int dim = self.dimension() + + # The dimension in which to record the edges or ridges. cdef output_dimension = 1 if do_atom_rep else dim - 2 - while face_iter.next_dimension() == output_dimension: - if do_atom_rep: - # Set up face_iter.atom_rep - face_iter.set_atom_rep() + cdef int d = face_iter.next_dimension() + while d < dim: + sig_check() + if do_f_vector: + f_vector[d + 1] += 1 + + # If ``not do_f_vector`` the iterator is set up + # for ``output_dimension`` and + # ``d < dim`` implies + # ``d == ouput_dimension``. + if not do_f_vector or d == output_dimension: + if do_atom_rep: + # Set up face_iter.atom_rep + face_iter.set_atom_rep() - # Copy the information. - a = face_iter.structure.atom_rep[0] - b = face_iter.structure.atom_rep[1] - else: - # Set up face_iter.coatom_rep - face_iter.set_coatom_rep() + # Copy the information. + a = face_iter.structure.atom_rep[0] + b = face_iter.structure.atom_rep[1] + else: + # Set up face_iter.coatom_rep + face_iter.set_coatom_rep() - # 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) + # 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) + d = face_iter.next_dimension() cdef int _compute_face_lattice_incidences(self) except -1: r""" diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd index 64dd767cc94..f3902b46a91 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd @@ -1,5 +1,5 @@ cimport cython -from sage.ext.memory_allocator cimport MemoryAllocator +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 @@ -12,7 +12,9 @@ cdef class CombinatorialFace(SageObject): 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 + cdef size_t _n_coatom_rep cdef int _dimension # dimension of current face, dual dimension if ``dual`` cdef int _ambient_dimension # dimension of the polyhedron @@ -23,12 +25,15 @@ cdef class CombinatorialFace(SageObject): cdef bint _initialized_from_face_lattice # some copies from ``CombinatorialPolyhedron`` - cdef tuple _ambient_Vrep, _ambient_facets, _equalities + cdef tuple _ambient_Vrep, _ambient_facets, _equations + cdef size_t _n_equations, _n_ambient_facets + cdef bint _ambient_bounded # Atoms and coatoms are the vertices/facets of the Polyedron. # If ``dual == 0``, then coatoms are facets, atoms vertices and vice versa. cdef ListOfFaces atoms, coatoms + cpdef dimension(self) cdef size_t n_atom_rep(self) except -1 cdef size_t set_coatom_rep(self) except -1 cdef size_t set_atom_rep(self) except -1 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx index 1d790726f23..91d8878cfb4 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx @@ -72,8 +72,9 @@ 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 +from .face_data_structure cimport face_len_atoms, face_init, 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 @@ -181,8 +182,11 @@ cdef class CombinatorialFace(SageObject): self._ambient_dimension = it.structure.dimension self._ambient_Vrep = it._Vrep self._ambient_facets = it._facet_names - self._equalities = it._equalities + self._n_ambient_facets = it._n_facets + self._equations = it._equations + self._n_equations = it._n_equations self._hash_index = it.structure._index + self._ambient_bounded = it._bounded self._initialized_from_face_lattice = False @@ -206,7 +210,13 @@ cdef class CombinatorialFace(SageObject): self._ambient_dimension = all_faces.dimension self._ambient_Vrep = all_faces._Vrep self._ambient_facets = all_faces._facet_names - self._equalities = all_faces._equalities + self._equations = all_faces._equations + self._n_equations = len(self._equations) if self._equations else 0 + if self._dual: + self._n_ambient_facets = self.atoms.n_faces() + else: + self._n_ambient_facets = self.coatoms.n_faces() + self._ambient_bounded = all_faces._bounded self._initialized_from_face_lattice = True @@ -347,7 +357,127 @@ cdef class CombinatorialFace(SageObject): # They are faces of the same polyhedron obtained in the same way. return hash(self) < hash(other) - def dimension(self): + def is_subface(self, CombinatorialFace other): + r""" + Return whether ``self`` is contained in ``other``. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: C = P.combinatorial_polyhedron() + sage: it = C.face_iter() + sage: face = next(it) + sage: face.ambient_V_indices() + (0, 3, 4, 5) + sage: face2 = next(it) + sage: face2.ambient_V_indices() + (0, 1, 5, 6) + sage: face.is_subface(face2) + False + sage: face2.is_subface(face) + False + sage: it.only_subfaces() + sage: face3 = next(it) + sage: face3.ambient_V_indices() + (0, 5) + sage: face3.is_subface(face2) + True + sage: face3.is_subface(face) + True + + Works for faces of the same combinatorial polyhedron; + also from different iterators:: + + sage: it = C.face_iter(dual=True) + sage: v7 = next(it); v7.ambient_V_indices() + (7,) + sage: v6 = next(it); v6.ambient_V_indices() + (6,) + sage: v5 = next(it); v5.ambient_V_indices() + (5,) + sage: face.ambient_V_indices() + (0, 3, 4, 5) + sage: face.is_subface(v7) + False + sage: v7.is_subface(face) + False + sage: v6.is_subface(face) + False + sage: v5.is_subface(face) + True + sage: face2.ambient_V_indices() + (0, 1, 5, 6) + sage: face2.is_subface(v7) + False + sage: v7.is_subface(face2) + False + sage: v6.is_subface(face2) + True + sage: v5.is_subface(face2) + True + + Only implemented for faces of the same combintatorial polyhedron:: + + sage: P1 = polytopes.cube() + sage: C1 = P1.combinatorial_polyhedron() + sage: it = C1.face_iter() + sage: other_face = next(it) + sage: other_face.ambient_V_indices() + (0, 3, 4, 5) + sage: face.ambient_V_indices() + (0, 3, 4, 5) + sage: C is C1 + False + sage: face.is_subface(other_face) + Traceback (most recent call last): + ... + NotImplementedError: is_subface only implemented for faces of the same polyhedron + """ + cdef size_t length_self, length_other, counter_self, counter_other + cdef size_t* self_v_indices + cdef size_t* other_v_indices + + if self._dual == other._dual: + if self.atoms is other.atoms: + if not self._dual: + return face_issubset(self.face, other.face) + else: + return face_issubset(other.face, self.face) + else: + raise NotImplementedError("is_subface only implemented for faces of the same polyhedron") + else: + if self.atoms is other.coatoms: + if self.dimension() > other.dimension(): + return False + if self._dual: + length_self = self.set_coatom_rep() + self_v_indices = self.coatom_rep + length_other = other.set_atom_rep() + other_v_indices = other.atom_rep + else: + length_self = self.set_atom_rep() + self_v_indices = self.atom_rep + length_other = other.set_coatom_rep() + other_v_indices = other.coatom_rep + if length_self > length_other: + return False + + # Check if every element in self_v_indices is contained in other_v_indices. + counter_self = 0 + counter_other = 0 + while counter_self < length_self and counter_other < length_other: + if self_v_indices[counter_self] > other_v_indices[counter_other]: + counter_other += 1 + elif self_v_indices[counter_self] == other_v_indices[counter_other]: + counter_self += 1 + counter_other += 1 + else: + return False + return counter_self == length_self + else: + raise NotImplementedError("is_subface only implemented for faces of the same polyhedron") + + cpdef dimension(self): r""" Return the dimension of the face. @@ -580,7 +710,7 @@ cdef class CombinatorialFace(SageObject): defining the face. It consists of the facets/inequalities that contain the face - and the equalities defining the ambient polyhedron. + and the equations defining the ambient polyhedron. EXAMPLES:: @@ -626,28 +756,43 @@ cdef class CombinatorialFace(SageObject): # if not dual, the facet-representation corresponds to the coatom-representation length = self.set_coatom_rep() # fill self.coatom_repr_face return tuple(self._ambient_facets[self.coatom_rep[i]] - for i in range(length)) + self._equalities + for i in range(length)) + self._equations else: # if dual, the facet-representation corresponds to the atom-representation length = self.set_atom_rep() # fill self.atom_repr_face return tuple(self._ambient_facets[self.atom_rep[i]] - for i in range(length)) + self._equalities + for i in range(length)) + self._equations - def ambient_H_indices(self): + def ambient_H_indices(self, add_equations=True): r""" Return the indices of the Hrepresentation objects of the ambient polyhedron defining the face. + INPUT: + + - ``add_equations`` -- boolean (default: ``True``); whether or not to include the equations + EXAMPLES:: sage: P = polytopes.permutahedron(5) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter(2) - sage: next(it).ambient_H_indices() + sage: face = next(it) + sage: face.ambient_H_indices(add_equations=False) (28, 29) - sage: next(it).ambient_H_indices() + sage: face2 = next(it) + sage: face2.ambient_H_indices(add_equations=False) (25, 29) + Add the indices of the equation:: + + sage: face.ambient_H_indices(add_equations=True) + (28, 29, 30) + sage: face2.ambient_H_indices(add_equations=True) + (25, 29, 30) + + Another example:: + sage: P = polytopes.cyclic_polytope(4,6) sage: C = CombinatorialPolyhedron(P) sage: it = C.face_iter() @@ -668,17 +813,26 @@ cdef class CombinatorialFace(SageObject): :meth:`ambient_Hrepresentation`. """ - cdef size_t length + cdef size_t length, i + cdef tuple equations + + if add_equations and self._equations: + equations = tuple(smallInteger(i) + for i in range(self._n_ambient_facets, + self._n_ambient_facets + self._n_equations)) + else: + equations = () + if not self._dual: # if not dual, the facet-representation corresponds to the coatom-representation length = self.set_coatom_rep() # fill self.coatom_repr_face return tuple(smallInteger(self.coatom_rep[i]) - for i in range(length)) + for i in range(length)) + equations else: # if dual, the facet-representation corresponds to the atom-representation length = self.set_atom_rep() # fill self.atom_repr_face return tuple(smallInteger(self.atom_rep[i]) - for i in range(length)) + for i in range(length)) + equations def Hrepr(self, names=True): r""" @@ -694,7 +848,7 @@ cdef class CombinatorialFace(SageObject): and equations of the face. The facet-representation consists of the facets - that contain the face and of the equalities of the polyhedron. + that contain the face and of the equations of the polyhedron. INPUT: @@ -720,12 +874,16 @@ cdef class CombinatorialFace(SageObject): else: return self.ambient_H_indices() - def n_ambient_Hrepresentation(self): + def n_ambient_Hrepresentation(self, add_equations=True): r""" Return the length of the :meth:`CombinatorialFace.ambient_H_indices`. Might be faster than then using ``len``. + INPUT: + + - ``add_equations`` -- boolean (default: ``True``); whether or not to count the equations + EXAMPLES:: sage: P = polytopes.cube() @@ -734,6 +892,17 @@ cdef class CombinatorialFace(SageObject): sage: all(face.n_ambient_Hrepresentation() == len(face.ambient_Hrepresentation()) for face in it) True + Specifying whether to count the equations or not:: + + sage: P = polytopes.permutahedron(5) + sage: C = CombinatorialPolyhedron(P) + sage: it = C.face_iter(2) + sage: f = next(it) + sage: f.n_ambient_Hrepresentation(add_equations=True) + 3 + sage: f.n_ambient_Hrepresentation(add_equations=False) + 2 + TESTS:: sage: P = polytopes.cube() @@ -744,18 +913,170 @@ cdef class CombinatorialFace(SageObject): doctest:...: DeprecationWarning: n_Hrepr is deprecated. Please use n_ambient_Hrepresentation instead. See https://trac.sagemath.org/28614 for details. """ + cdef size_t n_equations = self._n_equations if add_equations else 0 if not self._dual: - return smallInteger(self.set_coatom_rep()) + return smallInteger(self.set_coatom_rep() + n_equations) else: - return smallInteger(self.n_atom_rep()) + return smallInteger(self.n_atom_rep() + n_equations) n_Hrepr = deprecated_function_alias(28614, n_ambient_Hrepresentation) + def as_combinatorial_polyhedron(self, quotient=False): + r""" + Return ``self`` as combinatorial polyhedron. + + If ``quotient`` is ``True``, return the quotient of the + polyhedron by ``self``. + Let ``G`` be the face corresponding to ``self`` in the dual/polar polytope. + The ``quotient`` is the dual/polar of ``G``. + + Let `[\hat{0], \hat{1}]` be the face lattice of the ambient polyhedron + and `F` be ``self`` as element of the face lattice. + The face lattice of ``self`` as polyhedron corresponds to + `[\hat{0}, F]` and the face lattice of the quotient by ``self`` + corresponds to `[F, \hat{1}]`. + + EXAMPLES:: + + sage: P = polytopes.cyclic_polytope(7,11) + sage: C = CombinatorialPolyhedron(P) + sage: it = C.face_iter(4) + sage: f = next(it); f + A 4-dimensional face of a 7-dimensional combinatorial polyhedron + sage: F = f.as_combinatorial_polyhedron(); F + A 4-dimensional combinatorial polyhedron with 5 facets + sage: F.f_vector() + (1, 5, 10, 10, 5, 1) + sage: F_alt = polytopes.cyclic_polytope(4,5).combinatorial_polyhedron() + sage: F_alt.vertex_facet_graph().is_isomorphic(F.vertex_facet_graph()) + True + + Obtaining the quotient:: + + sage: Q = f.as_combinatorial_polyhedron(quotient=True); Q + A 2-dimensional combinatorial polyhedron with 6 facets + sage: Q + A 2-dimensional combinatorial polyhedron with 6 facets + sage: Q.f_vector() + (1, 6, 6, 1) + + The Vrepresentation of the face as polyhedron is given by the + ambient Vrepresentation of the face in that order:: + + sage: P = polytopes.cube() + sage: C = CombinatorialPolyhedron(P) + sage: it = C.face_iter(2) + sage: f = next(it) + sage: F = f.as_combinatorial_polyhedron() + sage: C.Vrepresentation() + (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: f.ambient_Vrepresentation() + (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: F.Vrepresentation() + (0, 1, 2, 3) + + To obtain the facets of the face as polyhedron, + we compute the meet of each facet with the face. + The first representative of each element strictly + contained in the face is kept:: + + sage: C.facets(names=False) + ((0, 1, 2, 3), + (1, 2, 6, 7), + (2, 3, 4, 7), + (4, 5, 6, 7), + (0, 1, 5, 6), + (0, 3, 4, 5)) + sage: F.facets(names=False) + ((0, 1), (1, 2), (2, 3), (0, 3)) + + The Hrepresentation of the quotient by the face is given by the + ambient Hrepresentation of the face in that order:: + + sage: it = C.face_iter(1) + sage: f = next(it) + sage: Q = f.as_combinatorial_polyhedron(quotient=True) + sage: C.Hrepresentation() + (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 (1, 0, 0) x + 1 >= 0, + An inequality (0, 0, 1) x + 1 >= 0, + An inequality (0, 1, 0) x + 1 >= 0) + sage: f.ambient_Hrepresentation() + (An inequality (0, 0, 1) x + 1 >= 0, An inequality (0, 1, 0) x + 1 >= 0) + sage: Q.Hrepresentation() + (0, 1) + + To obtain the vertices of the face as polyhedron, + we compute the join of each vertex with the face. + The first representative of each element strictly + containing the face is kept:: + + sage: [g.ambient_H_indices() for g in C.face_iter(0)] + [(3, 4, 5), + (0, 4, 5), + (2, 3, 5), + (0, 2, 5), + (1, 3, 4), + (0, 1, 4), + (1, 2, 3), + (0, 1, 2)] + sage: [g.ambient_H_indices() for g in Q.face_iter(0)] + [(1,), (0,)] + + The method is not implemented for unbounded polyhedra:: + + sage: P = Polyhedron(rays=[[0,1]])*polytopes.cube() + sage: C = CombinatorialPolyhedron(P) + sage: it = C.face_iter(2) + sage: f = next(it) + sage: f.as_combinatorial_polyhedron() + Traceback (most recent call last): + ... + NotImplementedError: only implemented for bounded polyhedra + + REFERENCES: + + For more information, see Exercise 2.9 of [Zie2007]_. + + .. NOTE:: + + This method is tested in + :meth:`~sage.geometry.polyhedron.base.Polyhedron_base._test_combinatorial_face_as_combinatorial_polyhedron`. + """ + if not self._ambient_bounded: + raise NotImplementedError("only implemented for bounded polyhedra") + + cdef ListOfFaces facets = self.atoms if self._dual else self.coatoms + cdef ListOfFaces Vrep = self.atoms if not self._dual else self.coatoms + + if not quotient: + return CombinatorialPolyhedron(face_as_combinatorial_polyhedron(facets, Vrep, self.face, self._dual)) + else: + # We run ``face_as_combinatorial_polyhedron`` for the dual setting. + + # We then interchange the output of it, to obtain the quotient. + new_Vrep, new_facets = face_as_combinatorial_polyhedron(Vrep, facets, self.face, not self._dual) + return CombinatorialPolyhedron((new_facets, new_Vrep)) + cdef size_t n_atom_rep(self) except -1: r""" Compute the number of atoms in the current face by counting the number of set bits. """ + if self.atom_rep is not NULL: + return self._n_atom_rep return face_len_atoms(self.face) cdef size_t set_coatom_rep(self) except -1: @@ -763,16 +1084,18 @@ cdef class CombinatorialFace(SageObject): Set ``coatom_rep`` to be the coatom-representation of the current face. Return its length. """ - if not self.coatom_rep: + if self.coatom_rep is NULL: self.coatom_rep = self._mem.allocarray(self.coatoms.n_faces(), sizeof(size_t)) - return bit_rep_to_coatom_rep(self.face, self.coatoms.data, self.coatom_rep) + self._n_coatom_rep = bit_rep_to_coatom_rep(self.face, self.coatoms.data, self.coatom_rep) + return self._n_coatom_rep cdef size_t set_atom_rep(self) except -1: r""" Set ``atom_rep`` to be the atom-representation of the current face. Return its length. """ - if not self.atom_rep: + if self.atom_rep is NULL: self.atom_rep = self._mem.allocarray(self.coatoms.n_atoms(), sizeof(size_t)) - return bit_rep_to_Vrep_list(self.face, self.atom_rep) + 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/conversions.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx index ff7cfd5ef31..eb1666b99f6 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx @@ -67,17 +67,20 @@ AUTHOR: # http://www.gnu.org/licenses/ #***************************************************************************** +from memory_allocator cimport MemoryAllocator + from sage.structure.element import is_Matrix +from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense from .list_of_faces cimport ListOfFaces -from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense -from sage.ext.memory_allocator cimport MemoryAllocator from .face_data_structure cimport face_next_atom, face_add_atom_safe, facet_set_coatom, face_clear from .face_list_data_structure cimport face_list_t + cdef extern from "Python.h": int unlikely(int) nogil # Defined by Cython + def _Vrep_list_to_bit_rep_wrapper(tup): r""" A function to allow doctesting of :func:`Vrep_list_to_bit_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 2b509aaec01..a81db2a1390 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd @@ -11,7 +11,7 @@ Cython data structure for combinatorial faces. # https://www.gnu.org/licenses/ # **************************************************************************** -from sage.ext.memory_allocator cimport MemoryAllocator +from memory_allocator cimport MemoryAllocator from sage.data_structures.bitset_base cimport * ctypedef int simple @@ -151,6 +151,14 @@ cdef inline long face_next_atom(face_t face, mp_bitcnt_t n): """ return bitset_next(face.atoms, n) +cdef inline long face_first_missing_atom(face_t face): + """ + Return the index of the first atom not in ``face``. + + In case there are none, return ``-1``. + """ + return bitset_first_in_complement(face.atoms) + cdef inline long face_len_atoms(face_t face) nogil: """ Calculate the number of atoms in the face. @@ -186,3 +194,6 @@ cdef inline void swap_faces(face_t a, face_t b) nogil: tmp[0] = a[0] a[0] = b[0] b[0] = tmp[0] + +cdef inline bint faces_are_identical(face_t a, face_t b) nogil: + return a.atoms.limbs == b.atoms.limbs diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd index 63ed7858024..56cce81f9f1 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd @@ -1,5 +1,5 @@ cimport cython -from sage.ext.memory_allocator cimport MemoryAllocator +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 @@ -9,13 +9,14 @@ from .combinatorial_face cimport CombinatorialFace cdef struct iter_s: bint dual # if 1, then iterate over dual Polyhedron face_t face # the current face of the iterator - int face_status # 0 not initialized, 1 initialized, 2 added to visited_all + int face_status # 0 not initialized, 1 initialized, 2 added to visited_all, 3 only visit subsets size_t *atom_rep # a place where atom-representaion of face will be stored size_t *coatom_rep # a place where coatom-representaion of face will be stored int current_dimension # dimension of current face, dual dimension if ``dual`` int dimension # dimension of the polyhedron int output_dimension # only faces of this (dual?) dimension are considered int lowest_dimension # don't consider faces below this (dual?) dimension + int highest_dimension # don't consider faces above this (dual?) dimension size_t _index # this counts the number of seen faces, useful for hasing the faces # ``visited_all`` points to faces, of which we have visited all faces already. @@ -35,7 +36,7 @@ cdef struct iter_s: face_list_t* new_faces # After having visited a face completely, we want to add it to ``visited_all``. - # ``first_dim[i]`` will indicate, whether there is one more face in + # ``first_time[i]`` will indicate, wether there is one more face in # ``newfaces[i]`` then ``n_newfaces[i]`` suggests # that has to be added to ``visited_all``. # If ``first_time[i] == False``, we still need to @@ -45,6 +46,7 @@ cdef struct iter_s: # The number of elements in newfaces[current_dimension], # that have not been visited yet. size_t yet_to_visit + size_t n_coatoms ctypedef iter_s iter_t[1] @@ -55,8 +57,10 @@ cdef class FaceIterator_base(SageObject): cdef MemoryAllocator _mem # some copies from ``CombinatorialPolyhedron`` - cdef tuple _Vrep, _facet_names, _equalities + cdef tuple _Vrep, _facet_names, _equations + cdef size_t _n_equations, _n_facets cdef bint _bounded + cdef face_t _far_face # Atoms and coatoms are the vertices/facets of the Polyedron. # If ``dual == 0``, then coatoms are facets, atoms vertices and vice versa. @@ -69,6 +73,8 @@ cdef class FaceIterator_base(SageObject): cdef size_t set_coatom_rep(self) except -1 cdef size_t set_atom_rep(self) except -1 cdef int ignore_subsets(self) except -1 + cdef int only_subsets(self) except -1 + cdef int find_face(self, face_t face) except -1 @cython.final cdef class FaceIterator(FaceIterator_base): @@ -80,8 +86,10 @@ cdef class FaceIterator_geom(FaceIterator_base): cdef object _requested_dim # Dimension requested on init. cdef readonly object P # The original polyhedron. +cdef int parallel_f_vector(iter_t* structures, size_t num_threads, size_t parallelization_depth, size_t *f_vector) except -1 + # Nogil definitions of crucial functions. -cdef int next_dimension(iter_t structure) nogil except -1 +cdef int next_dimension(iter_t structure, size_t parallelization_depth=?) nogil except -1 cdef int next_face_loop(iter_t structure) nogil except -1 cdef size_t n_atom_rep(iter_t structure) nogil except -1 diff --git a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx index fc4929bf29f..6b7f436e956 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx @@ -1,3 +1,5 @@ +# distutils: extra_compile_args = OPENMP_CFLAGS +# distutils: extra_link_args = OPENMP_CFLAGS r""" Face iterator for polyhedra @@ -176,13 +178,15 @@ AUTHOR: from sage.rings.integer cimport smallInteger from cysignals.signals cimport sig_check -from .conversions cimport bit_rep_to_Vrep_list +from .conversions cimport bit_rep_to_Vrep_list, Vrep_list_to_bit_rep from .conversions import facets_tuple_to_bit_rep_of_facets 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 @@ -223,7 +227,8 @@ cdef class FaceIterator_base(SageObject): self.structure.dual = dual self.structure.face_status = 0 self.structure.dimension = C.dimension() - self.structure.current_dimension = self.structure.dimension -1 + 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. @@ -252,8 +257,14 @@ cdef class FaceIterator_base(SageObject): self.atoms = C.bitrep_Vrep() self._Vrep = C.Vrep() self._facet_names = C.facet_names() - self._equalities = C.equalities() + self._n_facets = C.bitrep_facets().n_faces() + self._equations = C.equations() + if self._equations: + self._n_equations = len(self._equations) + else: + self._n_equations = 0 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)) @@ -299,7 +310,7 @@ cdef class FaceIterator_base(SageObject): # needs to be at most ``n_facets - 1``. # Hence it is fine to use the first entry already for the far face, # as ``self.visited_all`` holds ``n_facets`` pointers. - add_face_shallow(self.structure.visited_all[self.structure.dimension-1], C._far_face) + 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)) @@ -308,6 +319,8 @@ cdef class FaceIterator_base(SageObject): self.structure.yet_to_visit = self.coatoms.n_faces() self.structure._index = 0 + self.structure.n_coatoms = self.coatoms.n_faces() + if C.is_bounded() and ((dual and C.is_simplicial()) or (not dual and C.is_simple())): # We are in the comfortable situation that for our iterator # all intervals not containing the 0 element are boolean. @@ -332,6 +345,25 @@ cdef class FaceIterator_base(SageObject): sage: it.reset() sage: next(it).ambient_V_indices() (0, 3, 4, 5) + + TESTS: + + Resetting will fix the order of the coatoms after ``only_subsets``:: + + sage: P = polytopes.Birkhoff_polytope(3) + sage: C = P.combinatorial_polyhedron() + sage: it = C.face_iter(dual=False) + sage: face = next(it) + sage: face.ambient_H_indices(add_equations=False) + (8,) + sage: face = next(it) + sage: face.ambient_H_indices(add_equations=False) + (7,) + sage: it.only_subfaces() + sage: it.reset() + sage: face = next(it) + sage: face.ambient_H_indices(add_equations=False) + (8,) """ if self.structure.dimension == 0 or self.coatoms.n_faces() == 0: # As we will only yield proper faces, @@ -347,11 +379,15 @@ cdef class FaceIterator_base(SageObject): self.structure.face_status = 0 self.structure.new_faces[self.structure.dimension - 1].n_faces = self.coatoms.n_faces() self.structure.current_dimension = self.structure.dimension - 1 + self.structure.highest_dimension = self.structure.dimension - 1 self.structure.first_time[self.structure.dimension - 1] = True self.structure.yet_to_visit = self.coatoms.n_faces() 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) + def __next__(self): r""" Must be implemented by a derived class. @@ -451,6 +487,32 @@ cdef class FaceIterator_base(SageObject): ....: sage: n_non_simplex_faces 127 + + Face iterator must not be in dual mode:: + + sage: it = C.face_iter(dual=True) + sage: _ = next(it) + sage: it.ignore_subfaces() + Traceback (most recent call last): + ... + ValueError: only possible when not in dual mode + + Ignoring the same face as was requested to visit only consumes the iterator:: + + sage: it = C.face_iter(dual=False) + sage: _ = next(it) + sage: it.only_subfaces() + sage: it.ignore_subfaces() + sage: list(it) + [] + + Face iterator must be set to a face first:: + + sage: it = C.face_iter(dual=False) + sage: it.ignore_subfaces() + Traceback (most recent call last): + ... + ValueError: iterator not set to a face yet """ if unlikely(self.dual): raise ValueError("only possible when not in dual mode") @@ -458,9 +520,9 @@ cdef class FaceIterator_base(SageObject): def ignore_supfaces(self): r""" - The iterator will not visit any faces of the current face. + The iterator will not visit any faces containing the current face. - Only possible when not in dual mode. + Only possible when in dual mode. EXAMPLES:: @@ -469,18 +531,476 @@ cdef class FaceIterator_base(SageObject): sage: it = C.face_iter(dual=True) sage: n_faces_with_non_simplex_quotient = 1 sage: for face in it: - ....: if face.n_ambient_Hrepresentation() > C.dimension() - face.dimension() + 1: + ....: n_facets = face.n_ambient_Hrepresentation(add_equations=False) + ....: if n_facets > C.dimension() - face.dimension() + 1: ....: n_faces_with_non_simplex_quotient += 1 ....: else: ....: it.ignore_supfaces() ....: sage: n_faces_with_non_simplex_quotient 4845 + + Face iterator must be in dual mode:: + + sage: it = C.face_iter(dual=False) + sage: _ = next(it) + sage: it.ignore_supfaces() + Traceback (most recent call last): + ... + ValueError: only possible when in dual mode """ if unlikely(not self.dual): raise ValueError("only possible when in dual mode") self.ignore_subsets() + def meet_of_Hrep(self, *indices): + r""" + Construct the meet of the facets indicated by the indices. + + This is the largest face contained in all facets with the given indices. + + The iterator must be reset if not newly initialized. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: it = P.face_generator() + sage: it.meet_of_Hrep(1,2) + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices + sage: it.meet_of_Hrep(1,2).ambient_H_indices() + (1, 2) + sage: it.meet_of_Hrep(1,3).ambient_H_indices() + (1, 3) + sage: it.meet_of_Hrep(1,5).ambient_H_indices() + (0, 1, 2, 3, 4, 5) + + sage: P = polytopes.cross_polytope(4) + sage: it = P.face_generator() + sage: it.meet_of_Hrep().ambient_H_indices() + () + sage: it.meet_of_Hrep(1,3).ambient_H_indices() + (1, 2, 3, 4) + sage: it.meet_of_Hrep(1,2).ambient_H_indices() + (1, 2) + sage: it.meet_of_Hrep(1,6).ambient_H_indices() + (1, 6) + sage: it.meet_of_Hrep(1,2,6).ambient_H_indices() + (1, 2, 6, 7) + sage: it.meet_of_Hrep(1,2,5,6).ambient_H_indices() + (0, 1, 2, 3, 4, 5, 6, 7) + + sage: s = cones.schur(4) + sage: C = CombinatorialPolyhedron(s) + sage: it = C.face_iter() + sage: it.meet_of_Hrep(1,2).ambient_H_indices() + (1, 2) + sage: it.meet_of_Hrep(1,2,3).ambient_H_indices() + Traceback (most recent call last): + ... + IndexError: coatoms out of range + + If the iterator has already been used, it must be reset before:: + + sage: P = polytopes.dodecahedron() + sage: it = P.face_generator() + sage: _ = next(it), next(it) + sage: next(it).ambient_V_indices() + (15, 16, 17, 18, 19) + sage: it.meet_of_Hrep(9,11) + Traceback (most recent call last): + ... + ValueError: please reset the face iterator + sage: it.reset() + sage: it.meet_of_Hrep(9,11).ambient_H_indices() + (9, 11) + + TESTS: + + Check that things work fine, if the face iterator was never properly initialized:: + + sage: P = Polyhedron() + sage: P.meet_of_Hrep() + A -1-dimensional face of a Polyhedron in ZZ^0 + sage: P = Polyhedron([[0,0]]) + sage: P.meet_of_Hrep() + A 0-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex + sage: P.meet_of_Hrep(0) + A 0-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex + sage: P = Polyhedron(lines=[[1]]) + sage: P.meet_of_Hrep() + A 1-dimensional face of a Polyhedron in ZZ^1 defined as the convex hull of 1 vertex and 1 line + sage: P = Polyhedron(lines=[[1, 1]]) + sage: P.meet_of_Hrep() + A 1-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 1 line + sage: P.meet_of_Hrep(0) + A 1-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 1 line + """ + # Ignore equations. + indices = [i for i in indices + if not (self._n_facets <= i < self._n_facets + self._n_equations)] + if self.dual: + return self._join_of_atoms(*indices) + else: + return self._meet_of_coatoms(*indices) + + def join_of_Vrep(self, *indices): + r""" + Construct the join of the Vrepresentatives indicated by the indices. + + This is the smallest face containing all Vrepresentatives with the given indices. + + The iterator must be reset if not newly initialized. + + .. NOTE:: + + In the case of unbounded polyhedra, the smallest face containing given Vrepresentatives + may not be well defined. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: it = P.face_generator() + sage: it.join_of_Vrep(1) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + sage: it.join_of_Vrep(1,2).ambient_V_indices() + (1, 2) + sage: it.join_of_Vrep(1,3).ambient_V_indices() + (0, 1, 2, 3) + sage: it.join_of_Vrep(1,5).ambient_V_indices() + (0, 1, 5, 6) + + sage: P = polytopes.cross_polytope(4) + sage: it = P.face_generator() + sage: it.join_of_Vrep().ambient_V_indices() + () + sage: it.join_of_Vrep(1,3).ambient_V_indices() + (1, 3) + sage: it.join_of_Vrep(1,2).ambient_V_indices() + (1, 2) + sage: it.join_of_Vrep(1,6).ambient_V_indices() + (0, 1, 2, 3, 4, 5, 6, 7) + sage: it.join_of_Vrep(8) + Traceback (most recent call last): + ... + IndexError: coatoms out of range + + If the iterator has already been used, it must be reset before:: + + sage: P = polytopes.dodecahedron() + sage: it = P.face_generator() + sage: _ = next(it), next(it) + sage: next(it).ambient_V_indices() + (15, 16, 17, 18, 19) + sage: it.join_of_Vrep(1,10) + Traceback (most recent call last): + ... + ValueError: please reset the face iterator + sage: it.reset() + sage: it.join_of_Vrep(1,10).ambient_V_indices() + (1, 10) + + In the case of an unbounded polyhedron, we try to make sense of the input:: + + sage: P = polytopes.cube()*Polyhedron(lines=[[1]]) + sage: it = P.face_generator() + sage: it.join_of_Vrep(1) + A 1-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 1 vertex and 1 line + sage: it.join_of_Vrep(0, 1) + A 1-dimensional face of a Polyhedron in ZZ^4 defined as the convex hull of 1 vertex and 1 line + sage: it.join_of_Vrep(0) + Traceback (most recent call last): + ... + ValueError: the join is not well-defined + + sage: P = Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,1]]) + sage: it = P.face_generator() + sage: it.join_of_Vrep(0) + A 0-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex + sage: it.join_of_Vrep(1) + A 0-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex + sage: it.join_of_Vrep(2) + Traceback (most recent call last): + ... + ValueError: the join is not well-defined + sage: it.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 = Polyhedron(rays=[[1,0], [0,1]]) + sage: it = P.face_generator() + sage: it.join_of_Vrep(0) + A 0-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex + sage: it.join_of_Vrep(1,2) + A 2-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 2 rays + + TESTS: + + Check that things work fine, if the face iterator was never properly initialized:: + + sage: P = Polyhedron() + sage: P.join_of_Vrep() + A -1-dimensional face of a Polyhedron in ZZ^0 + sage: P = Polyhedron([[0,0]]) + sage: P.join_of_Vrep() + A -1-dimensional face of a Polyhedron in ZZ^2 + sage: P.join_of_Vrep(0) + A 0-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex + sage: P = Polyhedron(lines=[[1]]) + sage: P.join_of_Vrep() + A -1-dimensional face of a Polyhedron in ZZ^1 + sage: P.join_of_Vrep(0) + A 1-dimensional face of a Polyhedron in ZZ^1 defined as the convex hull of 1 vertex and 1 line + sage: P = Polyhedron(lines=[[1, 1]]) + sage: P.join_of_Vrep() + A -1-dimensional face of a Polyhedron in ZZ^2 + sage: P.Vrepresentation() + (A line in the direction (1, 1), A vertex at (0, 0)) + sage: P.join_of_Vrep(0) + A 1-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 1 line + sage: P.join_of_Vrep(1) + A 1-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 1 line + sage: P = Polyhedron(lines=[[1, 0], [0, 1]]) + sage: P.join_of_Vrep() + A -1-dimensional face of a Polyhedron in ZZ^2 + sage: P.join_of_Vrep(0) + A 2-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 2 lines + sage: P.join_of_Vrep(0, 1) + A 2-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 2 lines + sage: P.join_of_Vrep(0, 1, 2) + A 2-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 1 vertex and 2 lines + """ + if not self.dual: + return self._join_of_atoms(*indices) + else: + return self._meet_of_coatoms(*indices) + + def _meet_of_coatoms(self, *indices): + r""" + Construct the meet of the coatoms indicated by the indices. + + The iterator must be reset if not newly initialized. + + .. SEEALSO:: + + :meth:`meet_of_Hrep`, + :meth:`join_of_Vrep`. + + EXAMPLES: + + In non-dual mode we construct the meet of facets:: + + sage: P = polytopes.cube() + sage: it = P.face_generator(dual=False) + sage: it._meet_of_coatoms(1,2) + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices + sage: it._meet_of_coatoms(1,2,3) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + sage: it._meet_of_coatoms(1,2,3).ambient_H_indices() + (1, 2, 3) + + In dual mode we construct the join of vertices/rays:: + + sage: P = polytopes.cube() + sage: it = P.face_generator(dual=True) + sage: it._meet_of_coatoms(1,2) + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices + sage: it._meet_of_coatoms(1,2,3) + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices + sage: it._meet_of_coatoms(1) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + + The face iterator must not have the output dimension specified:: + + sage: P = polytopes.dodecahedron() + sage: it = P.face_generator(2) + sage: it._meet_of_coatoms(1,2) + Traceback (most recent call last): + ... + ValueError: face iterator must not have the output dimension specified + + TESTS: + + We prevent a segmentation fault:: + + sage: P = polytopes.simplex() + sage: it = P.face_generator() + sage: it._meet_of_coatoms(-1) + Traceback (most recent call last): + ... + IndexError: coatoms out of range + sage: it._meet_of_coatoms(100) + Traceback (most recent call last): + ... + IndexError: coatoms out of range + + The empty face is detected correctly, even with lines or rays:: + + sage: P = polytopes.cube()*Polyhedron(lines=[[1]]) + sage: it = P.face_generator() + sage: it._meet_of_coatoms(1,2,4,5) + A -1-dimensional face of a Polyhedron in ZZ^4 + + sage: P = Polyhedron(vertices=[[1,0], [0,1]], rays=[[1,1]]) + sage: it = P.face_generator() + sage: it._meet_of_coatoms(0) + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 2 vertices + sage: it._meet_of_coatoms(1) + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 ray + sage: it._meet_of_coatoms(2) + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 ray + sage: it._meet_of_coatoms(1, 2) + A -1-dimensional face of a Polyhedron in QQ^2 + """ + if unlikely(self.structure.face_status != 0): + raise ValueError("please reset the face iterator") + if unlikely(self.structure.output_dimension != -2): + raise ValueError("face iterator must not have the output dimension specified") + + cdef size_t n_atoms = self.coatoms.n_atoms() + cdef size_t n_coatoms = self.coatoms.n_faces() + cdef ListOfFaces coatoms = self.coatoms + + cdef ListOfFaces face_mem = ListOfFaces(1, n_atoms, n_coatoms) + cdef face_t face = face_mem.data.faces[0] + cdef int i + cdef size_t j + + # Initialize the full polyhedron. + for j in range(n_atoms): + face_add_atom(face, j) + + for i in indices: + if not 0 <= i < n_coatoms: + raise IndexError("coatoms out of range") + face_intersection(face, face, coatoms.data.faces[i]) + + if not self._bounded and face_issubset(face, self._far_face): + # The meet is contained in the far face and therefore is the empty face. + face_clear(face) + + self.find_face(face) + output = self.current() + self.reset() + return output + + def _join_of_atoms(self, *indices): + r""" + Construct the join of atoms indicated by the indices. + + The iterator must be reset if not newly initialized. + + .. SEEALSO:: + + :meth:`meet_of_Hrep`, + :meth:`join_of_Vrep`. + + EXAMPLES: + + In dual mode we construct the meet of facets:: + + sage: P = polytopes.cube() + sage: it = P.face_generator(dual=True) + sage: it._join_of_atoms(1,2) + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices + sage: it._join_of_atoms(1,2,3) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + sage: it._join_of_atoms(1,2,3).ambient_H_indices() + (1, 2, 3) + + In non-dual mode we construct the join of vertices/rays:: + + sage: P = polytopes.cube() + sage: it = P.face_generator(dual=False) + sage: it._join_of_atoms(1,2) + A 1-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 2 vertices + sage: it._join_of_atoms(1,2,3) + A 2-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 4 vertices + sage: it._join_of_atoms(1) + A 0-dimensional face of a Polyhedron in ZZ^3 defined as the convex hull of 1 vertex + + If the iterator has already been used, it must be reset before:: + + sage: P = polytopes.dodecahedron() + sage: it = P.face_generator() + sage: _ = next(it), next(it) + sage: next(it).ambient_V_indices() + (15, 16, 17, 18, 19) + sage: it._join_of_atoms(1,10) + Traceback (most recent call last): + ... + ValueError: please reset the face iterator + sage: it.reset() + sage: it._join_of_atoms(1,10).ambient_V_indices() + (1, 10) + + The face iterator must not have the output dimension specified:: + + sage: P = polytopes.dodecahedron() + sage: it = P.face_generator(2) + sage: it._join_of_atoms(1,2) + Traceback (most recent call last): + ... + ValueError: face iterator must not have the output dimension specified + + TESTS: + + We prevent a segmentation fault:: + + sage: P = polytopes.simplex() + sage: it = P.face_generator() + sage: it._join_of_atoms(-1) + Traceback (most recent call last): + ... + IndexError: atoms out of range + sage: it._join_of_atoms(100) + Traceback (most recent call last): + ... + IndexError: atoms out of range + """ + if unlikely(self.structure.face_status != 0): + raise ValueError("please reset the face iterator") + if unlikely(self.structure.output_dimension != -2): + raise ValueError("face iterator must not have the output dimension specified") + + cdef size_t n_atoms = self.coatoms.n_atoms() + cdef size_t n_coatoms = self.coatoms.n_faces() + cdef ListOfFaces coatoms = self.coatoms + + cdef ListOfFaces face_mem = ListOfFaces(2, n_atoms, n_coatoms) + cdef face_t face = face_mem.data.faces[0] + cdef face_t pseudo_face = face_mem.data.faces[1] + cdef int j + cdef size_t i + + if not all(0 <= j < n_atoms for j in indices): + raise IndexError("atoms out of range") + + # Initialize a pseudo_face as indicated by the indices. + for i in indices: + face_add_atom(pseudo_face, i) + + # Initialize the full polyhedron. + for i in range(n_atoms): + face_add_atom(face, i) + + # Now we intersect all faces that contain our pseudo_face. + for i in range(n_coatoms): + if face_issubset(pseudo_face, coatoms.data.faces[i]): + face_intersection(face, face, coatoms.data.faces[i]) + + if not indices: + # The neutral element of the join. + face_clear(face) + elif not self._bounded and face_issubset(face, self._far_face): + # The join is not well-defined. + # We allow for unbounded polyhedra to compute the join, even with rays. + # However, the result is not necesarrily well-defined. + raise ValueError("the join is not well-defined") + + self.find_face(face) + output = self.current() + self.reset() + return output + cdef int ignore_subsets(self) except -1: r""" Ignore sub-/supfaces of the current face. @@ -493,6 +1013,11 @@ cdef class FaceIterator_base(SageObject): """ if unlikely(self.structure.face_status == 0): raise ValueError("iterator not set to a face yet") + if unlikely(self.structure.face_status == 3): + # The iterator is consumed, if it was just set to visit only subsets + # next thing to ignore subsets. + self.structure.current_dimension = self.structure.dimension + return 0 if unlikely(self.structure.face_status == 2): # Nothing to do. return 0 @@ -504,13 +1029,144 @@ cdef class FaceIterator_base(SageObject): add_face_shallow(self.structure.visited_all[self.structure.current_dimension], self.structure.face) self.structure.face_status = 2 + def only_subfaces(self): + r""" + The iterator will visit all (remaining) subfaces of the current face and then terminate. + + EXAMPLES:: + + sage: P = polytopes.cube() + sage: it = P.face_generator() + sage: next(it).ambient_H_indices() + () + sage: next(it).ambient_H_indices() + (0, 1, 2, 3, 4, 5) + sage: next(it).ambient_H_indices() + (5,) + sage: next(it).ambient_H_indices() + (4,) + sage: it.only_subfaces() + sage: list(f.ambient_H_indices() for f in it) + [(4, 5), (3, 4), (1, 4), (0, 4), (3, 4, 5), (0, 4, 5), (1, 3, 4), (0, 1, 4)] + + :: + + sage: P = polytopes.Birkhoff_polytope(4) + sage: C = P.combinatorial_polyhedron() + sage: it = C.face_iter() + sage: next(it).ambient_H_indices(add_equations=False) + (15,) + sage: next(it).ambient_H_indices(add_equations=False) + (14,) + sage: it.only_subfaces() + sage: all(14 in f.ambient_H_indices() for f in it) + True + + Face iterator needs to be set to a face first:: + + sage: it = C.face_iter() + sage: it.only_subfaces() + Traceback (most recent call last): + ... + ValueError: iterator not set to a face yet + + Face iterator must not be in dual mode:: + + sage: it = C.face_iter(dual=True) + sage: _ = next(it) + sage: it.only_subfaces() + Traceback (most recent call last): + ... + ValueError: only possible when not in dual mode + + Cannot run ``only_subfaces`` after ``ignore_subfaces:: + + sage: it = C.face_iter() + sage: _ = next(it) + sage: it.ignore_subfaces() + sage: it.only_subfaces() + Traceback (most recent call last): + ... + ValueError: cannot only visit subsets after ignoring a face + """ + if unlikely(self.dual): + raise ValueError("only possible when not in dual mode") + self.only_subsets() + + def only_supfaces(self): + r""" + The iterator will visit all (remaining) faces + containing the current face and then terminate. + + EXAMPLES:: + + sage: P = polytopes.cross_polytope(3) + sage: it = P.face_generator() + sage: next(it).ambient_V_indices() + (0, 1, 2, 3, 4, 5) + sage: next(it).ambient_V_indices() + () + sage: next(it).ambient_V_indices() + (5,) + sage: next(it).ambient_V_indices() + (4,) + sage: it.only_supfaces() + sage: list(f.ambient_V_indices() for f in it) + [(4, 5), (3, 4), (2, 4), (0, 4), (3, 4, 5), (2, 4, 5), (0, 3, 4), (0, 2, 4)] + + :: + + sage: P = polytopes.Birkhoff_polytope(4) + sage: C = P.combinatorial_polyhedron() + sage: it = C.face_iter(dual=True) + sage: next(it).ambient_V_indices() + (23,) + sage: next(it).ambient_V_indices() + (22,) + sage: it.only_supfaces() + sage: all(22 in f.ambient_V_indices() for f in it) + True + """ + if unlikely(not self.dual): + raise ValueError("only possible when in dual mode") + self.only_subsets() + + cdef int only_subsets(self) except -1: + r""" + Only visit sub-/supfaces of the current face and then + terminate. + + See :meth:`FaceIterator_base.only_subfaces` and + :meth:`FaceIterator_base.only_supfaces`. + """ + if unlikely(self.structure.face_status == 0): + raise ValueError("iterator not set to a face yet") + if unlikely(self.structure.face_status == 2): + raise ValueError("cannot only visit subsets after ignoring a face") + + cdef face_list_t* faces = &self.structure.new_faces[self.structure.current_dimension] + cdef size_t yet_to_visit = self.structure.yet_to_visit + + if unlikely(yet_to_visit >= faces[0].n_faces + or not faces_are_identical(faces[0].faces[yet_to_visit], self.structure.face)): + raise ValueError("iterator is not set to the correct face") + + swap_faces(faces[0].faces[yet_to_visit], faces[0].faces[faces[0].n_faces - 1]) + + self.structure.face_status = 3 + self.structure.yet_to_visit = 0 + # This will work: + # ``next_dimension`` will first call ``next_face_loop`` and then check + # for the dimension. By this time the current dimension has changed. + self.structure.highest_dimension = self.structure.current_dimension - 1 + cdef inline CombinatorialFace next_face(self): r""" Set attribute ``face`` to the next face and return it as :class:`sage.geometry.polyhedron.combinatorial_polyhedron.combinatorial_face.CombinatorialFace`. """ self.next_dimension() - if unlikely(self.structure.current_dimension == self.structure.dimension): + if unlikely(self.structure.current_dimension > self.structure.highest_dimension): return None return CombinatorialFace(self) @@ -572,6 +1228,47 @@ cdef class FaceIterator_base(SageObject): """ return bit_rep_to_Vrep_list(self.structure.face, self.structure.atom_rep) + cdef int find_face(self, face_t face) except -1: + """ + Iterate until the current face is ``face``. + + The value can then be obtained with :meth:`current`. + + The iterator is assumed to be newly initialized or reset. + See :meth:`FaceIterator_base._join_of_atoms` and + :meth:`FaceIterator_base._meet_of_coatoms`. + """ + cdef size_t n_atoms = face_len_atoms(face) + + if n_atoms == self.coatoms.n_atoms(): + # The face is the universe. + self.structure.face[0] = face[0] + self.structure.face_status = 1 + self.structure.current_dimension = self.structure.dimension + return 0 + elif n_atoms == 0: + # The face is the empty face. + self.structure.face[0] = face[0] + self.structure.face_status = 1 + self.structure.current_dimension = -1 + return 0 + + cdef int d = self.next_dimension() + while self.structure.current_dimension != self.structure.dimension: + if face_issubset(face, self.structure.face): + if face_issubset(self.structure.face, face): + # Found our face. + return 0 + else: + # The face is not a subface/supface of the current face. + self.ignore_subsets() + + d = self.next_dimension() + + 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. @@ -871,7 +1568,7 @@ cdef class FaceIterator(FaceIterator_base): A 1-dimensional face of a 3-dimensional combinatorial polyhedron] """ cdef CombinatorialFace face = self.next_face() - if unlikely(self.structure.current_dimension == self.structure.dimension): + if unlikely(self.structure.current_dimension > self.structure.highest_dimension): raise StopIteration return face @@ -1036,7 +1733,7 @@ cdef class FaceIterator_geom(FaceIterator_base): .. SEEALSO:: - See :class:`FaceIterator`. + :class:`FaceIterator_base`. """ def __init__(self, P, dual=None, output_dimension=None): r""" @@ -1164,7 +1861,7 @@ cdef class FaceIterator_geom(FaceIterator_base): return PolyhedronFace(self.P, [], range(self.P.n_Hrepresentation())) self.next_dimension() - if unlikely(self.structure.current_dimension == self.structure.dimension): + if unlikely(self.structure.current_dimension > self.structure.highest_dimension): raise StopIteration return self.current() @@ -1188,13 +1885,16 @@ cdef class FaceIterator_geom(FaceIterator_base): # Nogil definitions of crucial functions. -cdef inline int next_dimension(iter_t structure) nogil except -1: +cdef inline int next_dimension(iter_t structure, size_t parallelization_depth=0) nogil except -1: r""" See :meth:`FaceIterator.next_dimension`. + + ``parallelization_depth`` determines when to stop, + e.g. if it is ``1`` it will stop after having yield all faces of a facet """ - cdef int dim = structure.dimension + cdef int max_dim = structure.highest_dimension - parallelization_depth structure.face_status = 0 - while (not next_face_loop(structure)) and (structure.current_dimension < dim): + while (not next_face_loop(structure)) and (structure.current_dimension <= max_dim): sig_check() structure._index += 1 return structure.current_dimension @@ -1206,7 +1906,8 @@ cdef inline int next_face_loop(iter_t structure) nogil except -1: if unlikely(structure.current_dimension == structure.dimension): # The function is not supposed to be called, # just prevent it from crashing. - raise StopIteration + # Actually raising an error here results in a bad branch prediction. + return -1 # Getting ``[faces, n_faces, n_visited_all]`` according to dimension. cdef face_list_t* faces = &structure.new_faces[structure.current_dimension] @@ -1286,3 +1987,234 @@ cdef inline size_t n_atom_rep(iter_t structure) nogil except -1: # The face was not initialized properly. raise LookupError("``FaceIterator`` does not point to a face") + +# Parallel iteration over the faces. +# Currently only the f-vector is implemented, but slight +# modifications would allow collecting other information as well. + +cdef struct parallel_f_s: + # A structure carrying things that each thread should have exclusive access to. + size_t* f_vector + size_t* current_job_id + + # Keep track so that we can easily go from one job to the next. + size_t* original_n_faces + size_t* original_n_visited_all + +ctypedef parallel_f_s parallel_f_t[1] + +cdef int parallel_f_vector(iter_t* structures, size_t num_threads, size_t parallelization_depth, size_t *f_vector) except -1: + """ + Compute the ``f_vector`` in parallel. + + INPUT: + + - ``structures`` -- one structure per thread + + - ``num_threads`` -- the number of threads to use + + - ``parallelization_depth`` -- the codimension at which the threads are released + + - ``f_vector`` -- where the ``f_vector`` is output + """ + # One job per face of codimension ``parallelization_depth``. + cdef size_t n_jobs = structures[0].n_coatoms ** parallelization_depth + cdef size_t i + cdef int j + cdef int dim = structures[0].dimension + f_vector[0] = 1 # Face iterator will only visit proper faces. + f_vector[dim + 1] = 1 # Face iterator will only visit proper faces. + if dim <= 0 or structures[0].n_coatoms == 0: + # Iterator assumes at least one face and at least dimension 1. + return 0 + + if num_threads == 0: + num_threads = 1 + + cdef size_t thread_id + cdef MemoryAllocator mem = MemoryAllocator() + + # Setting up for each thread some storage space. + cdef parallel_f_t* parallel_structs = \ + mem.allocarray(num_threads, sizeof(parallel_f_t)) + + for i in range(num_threads): + # Partial f-vectors. + parallel_structs[i].f_vector = \ + mem.calloc(dim+2, sizeof(size_t)) + parallel_structs[i].current_job_id = \ + mem.calloc(parallelization_depth+1, sizeof(size_t)) + + # Keeping back of the original number of faces allows faster starting the next job. + parallel_structs[i].original_n_faces = \ + mem.calloc(parallelization_depth+1, sizeof(size_t)) + parallel_structs[i].original_n_faces[0] = \ + structures[0].new_faces[dim - 1].n_faces + + parallel_structs[i].original_n_visited_all = \ + mem.calloc(parallelization_depth+1, sizeof(size_t)) + parallel_structs[i].original_n_visited_all[0] = \ + structures[0].visited_all[dim - 1].n_faces + + for i in prange(n_jobs, schedule='dynamic', chunksize=1, + num_threads=num_threads, nogil=True): + _parallel_f_vector(structures[threadid()], + parallelization_depth, + parallel_structs[threadid()], + i) + + # Gather the results. + for i in range(num_threads): + for j in range(structures[0].dimension + 2): + f_vector[j] += parallel_structs[i].f_vector[j] + +cdef int _parallel_f_vector(iter_t structure, size_t parallelization_depth, + parallel_f_t parallel_struct, size_t job_id) nogil except -1: + """ + Set up a job and then visit all faces. + """ + cdef int max_dimension = structure.dimension - parallelization_depth + cdef int d + if prepare_face_iterator_for_partial_job(structure, parallelization_depth, + parallel_struct, job_id): + d = next_dimension(structure, parallelization_depth) + while (d < max_dimension): + parallel_struct.f_vector[d+1] += 1 + d = next_dimension(structure, parallelization_depth) + +cdef inline int prepare_face_iterator_for_partial_job( + iter_t structure, size_t parallelization_depth, + parallel_f_t parallel_struct, size_t job_id) nogil except -1: + """ + Set ``structure`` according to ``job_id``. + + ``job_id`` should be thought of as its digits with base ``structure.n_coatoms`` + padded to ``parallelization_depth``. + + The first digit determines which facet to visit. + The next digit determines which facet of the facet should be visited. + + OUTPUT: ``1`` if the job exists and ``0`` otherwise. + + In addition, the first job treating a face will "visit" this face + and increase the corresponding entry of the f-vector. + """ + cdef int d + cdef size_t current_depth + if (not structure.first_time[structure.current_dimension] + and structure.current_dimension == structure.dimension - parallelization_depth): + # Act as if we had not visited faces in the last depth. + # Set ``current_job_id[parallelization_depth - 1] = 0``. + d = structure.dimension - parallelization_depth + current_depth = parallelization_depth + + # Recover all faces. + structure.new_faces[d].n_faces = \ + parallel_struct.original_n_faces[current_depth -1] + structure.visited_all[d].n_faces = \ + parallel_struct.original_n_visited_all[current_depth -1] + structure.first_time[d] = True + structure.yet_to_visit = 0 # just to be on the safe side + + parallel_struct.current_job_id[current_depth -1] = 0 + + # If the job does not exist, we will set the next value to ``-1``. + if parallel_struct.original_n_faces[current_depth -1] == 0: + parallel_struct.current_job_id[current_depth] = -1 + else: + parallel_struct.current_job_id[current_depth] = 0 + + cdef size_t n_coatoms = structure.n_coatoms + cdef size_t job_id_c + cdef size_t i + cdef size_t diff + cdef size_t new_faces_counter + + for current_depth in range(1, parallelization_depth + 1): + d = structure.dimension - current_depth + + # Get the corresponding digit of ``job_id``. + job_id_c = get_digit(job_id, current_depth - 1, parallelization_depth, n_coatoms) + + # Set ``current_job_id[current_depth - 1]`` to ``job_id_c`` if possible. + + if job_id_c != parallel_struct.current_job_id[current_depth - 1]: + # Set ``current_job_id[current_depth -1] = 0``. + structure.current_dimension = d + structure.new_faces[d].n_faces = parallel_struct.original_n_faces[current_depth -1] + structure.visited_all[d].n_faces = parallel_struct.original_n_visited_all[current_depth -1] + parallel_struct.current_job_id[current_depth -1] = 0 + + # If the job does not exist, we will set the next value to ``-1``. + if parallel_struct.original_n_faces[current_depth -1] == 0: + parallel_struct.current_job_id[current_depth] = -1 + else: + parallel_struct.current_job_id[current_depth] = 0 + + structure.first_time[d] = True + structure.yet_to_visit = 0 # just to be on the safe side + + if parallel_struct.current_job_id[current_depth] == -1: + # The job does not exist. + return 0 + + if job_id_c > parallel_struct.current_job_id[current_depth -1]: + if job_id_c >= structure.new_faces[d].n_faces: + # The job does not exist. + return 0 + + for i in range(job_id_c): + # Fast forwarding the jobs. + add_face_shallow(structure.visited_all[d], structure.new_faces[d].faces[structure.new_faces[d].n_faces -1]) + structure.new_faces[d].n_faces -= 1 + + parallel_struct.current_job_id[current_depth -1] = job_id_c + + # Apparently the face exists. We add it to the f-vector, if it is the very first job for the face. + if job_id == 0 or get_digit(job_id -1, current_depth -1, parallelization_depth, n_coatoms) != job_id_c: + # Visit ``structure.new_faces[d].faces[structure.new_faces[d].n_faces - 1] + parallel_struct.f_vector[d + 1] += 1 + + if structure.current_dimension == d: + structure.yet_to_visit = 0 + + if structure.new_faces[d].n_faces == 0: + # The job will not exist. + parallel_struct.current_job_id[current_depth] = -1 + return 0 + + new_faces_counter = get_next_level( + structure.new_faces[d], structure.new_faces[d-1], structure.visited_all[d]) + + if new_faces_counter: + # Setting the variables correctly, for the next call. + structure.current_dimension -= 1 + structure.first_time[d-1] = True + structure.visited_all[d-1][0] = structure.visited_all[d][0] + structure.yet_to_visit = new_faces_counter + for i in range(current_depth, parallelization_depth + 1): + parallel_struct.current_job_id[i] = 0 + parallel_struct.original_n_faces[current_depth] = new_faces_counter + parallel_struct.original_n_visited_all[current_depth] = structure.visited_all[d-1].n_faces + else: + # The job does not exist. + parallel_struct.current_job_id[current_depth] = -1 + return 0 + + if structure.current_dimension != structure.dimension - parallelization_depth - 1: + return 0 + + return 1 + +cdef inline size_t get_digit(size_t job_id, size_t pos, size_t padto, size_t base) nogil: + """ + Get the digit ``pos`` of ``job_id`` with base ``base`` + padding the number of digits to ``pad_to``. + + Digits are enumerated started with ``0``. + """ + # Remove the last ``parallelization_depth - pos - 1`` digits. + cdef size_t current_output = job_id / base ** (padto - pos - 1) + + # Remove all digits before our current digit, which is digit ``pos``. + return current_output % base 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 fa28c8c5473..2f680590715 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 @@ -11,9 +11,12 @@ Inline cython methods for lists of faces. # https://www.gnu.org/licenses/ # **************************************************************************** +cdef extern from "Python.h": + int unlikely(int) nogil # Defined by Cython + from .face_data_structure cimport * from libc.string cimport memset -from cysignals.signals cimport sig_on, sig_off +from cysignals.signals cimport sig_check cdef struct face_list_s: face_t* faces @@ -89,9 +92,9 @@ cdef inline int add_face_shallow(face_list_t faces, face_t face) nogil except -1 """ Add a face to faces. """ - if not faces.total_n_faces >= faces.n_faces + 1: - with gil: - raise AssertionError + if unlikely(not faces.total_n_faces >= faces.n_faces + 1): + # Actually raising an error here results in a bad branch prediction. + return -1 faces.faces[faces.n_faces][0] = face[0] faces.n_faces += 1 @@ -103,6 +106,42 @@ cdef inline int add_face_deep(face_list_t faces, face_t face) except -1: face_copy(faces.faces[faces.n_faces], face) faces.n_faces += 1 +cdef inline void face_list_delete_faces_by_array(face_list_t faces, bint *delete): + r""" + Remove face ``i`` if and only if ``delete[i]`` decreasing ``faces.n_faces``. + + .. WARNING:: + + ``delete`` is assumed to be of length ``faces.n_faces``. + """ + cdef size_t n_newfaces = 0 + cdef size_t i + for i in range(faces.n_faces): + if not delete[i]: + faces.faces[n_newfaces][0] = faces.faces[i][0] + n_newfaces += 1 + + faces.n_faces = n_newfaces + +cdef inline void face_list_delete_faces_by_face(face_list_t faces, face_t face): + r""" + Remove all faces such that the ``i``-th bit in ``face`` is not set + descreasing ``faces.n_faces``. + + .. WARNING:: + + ``face`` is assumed to contain ``self.n_faces`` atoms. + """ + cdef size_t n_newfaces = 0 + cdef size_t i + for i in range(faces.n_faces): + if face_atom_in(face, i): + faces.faces[n_newfaces][0] = faces.faces[i][0] + n_newfaces += 1 + + faces.n_faces = n_newfaces + + ############################################################################# # Face Comparison ############################################################################# @@ -187,12 +226,12 @@ cdef inline int face_list_intersection_fused(face_list_t dest, face_list_t A, fa """ Set ``dest`` to be the intersection of each face of ``A`` with ``b``. """ - if not dest.total_n_faces >= A.n_faces: - with gil: - raise AssertionError - if not dest.n_atoms >= A.n_atoms: - with gil: - raise AssertionError + if unlikely(not dest.total_n_faces >= A.n_faces): + # Actually raising an error here results in a bad branch prediction. + return -1 + if unlikely(not dest.n_atoms >= A.n_atoms): + # Actually raising an error here results in a bad branch prediction. + return -1 dest.n_faces = A.n_faces dest.polyhedron_is_simple = A.polyhedron_is_simple @@ -250,6 +289,7 @@ cdef inline size_t get_next_level_fused( cdef size_t j for j in range(n_faces): + sig_check() if (is_not_maximal_fused(new_faces, j, algorithm, is_not_new_face) or # Step 2 is_contained_in_one_fused(new_faces.faces[j], visited_all, algorithm)): # Step 3 is_not_new_face[j] = True @@ -276,12 +316,10 @@ cdef inline size_t get_next_level( face_list_t visited_all) nogil except -1: cdef size_t output - sig_on() if faces.polyhedron_is_simple: output = get_next_level_fused(faces, new_faces, visited_all, 0) else: output = get_next_level_fused(faces, new_faces, visited_all, 0) - sig_off() return output cdef inline size_t bit_rep_to_coatom_rep(face_t face, face_list_t coatoms, size_t *output): 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 a571b8c1c84..e8da24c3366 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd @@ -1,6 +1,6 @@ cimport cython -from sage.ext.memory_allocator cimport MemoryAllocator -from .face_list_data_structure cimport face_list_t +from memory_allocator cimport MemoryAllocator +from .face_list_data_structure cimport face_list_t, face_t @cython.final cdef class ListOfFaces: @@ -10,6 +10,8 @@ cdef class ListOfFaces: # It will be of "type" ``uint64_t[n_faces][face_length]`` cdef face_list_t data + cpdef ListOfFaces __copy__(self) + cpdef int compute_dimension(self) except -2 cdef inline size_t n_faces(self): @@ -20,3 +22,11 @@ cdef class ListOfFaces: return self.data.n_coatoms cpdef ListOfFaces pyramid(self) + + cdef ListOfFaces delete_atoms_unsafe(self, bint* delete, face_t face) # not in place + cdef void delete_faces_unsafe(self, bint* delete, face_t face) # in place + + cdef void get_not_inclusion_maximal_unsafe(self, bint *not_inclusion_maximal) + cdef void get_faces_all_set_unsafe(self, bint *all_set) + +cdef tuple face_as_combinatorial_polyhedron(ListOfFaces facets, ListOfFaces Vrep, face_t face, bint dual) 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 8974a80ab3a..98b3d4df828 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx @@ -91,7 +91,6 @@ AUTHOR: # **************************************************************************** from sage.structure.element import is_Matrix - from sage.matrix.matrix_integer_dense cimport Matrix_integer_dense from .face_list_data_structure cimport * @@ -154,7 +153,7 @@ cdef class ListOfFaces: """ assert face_list_check_alignment(self.data) - def __copy__(self): + cpdef ListOfFaces __copy__(self): r""" Return a copy of self. @@ -357,6 +356,101 @@ cdef class ListOfFaces: return copy + cdef ListOfFaces delete_atoms_unsafe(self, bint *delete, face_t face): + r""" + Return a copy of ``self`` where bits in ``delete`` have been + removed/contracted. + + The the remaining bits will be shifted to the left. + + If ``delete`` is ``NULL``, keep exactly the bits set in ``face``. + + .. WARNING:: + + ``delete`` is assumed to be of length ``self.n_atoms`` or NULL. + ``face`` is assumed to be of length ``self.face_length`` if ``delete`` is not ``NULL``. + """ + + cdef output_n_atoms + cdef size_t i, j + if delete is NULL: + output_n_atoms = face_len_atoms(face) + else: + output_n_atoms = self.n_atoms() + for i in range(self.n_atoms()): + if delete[i]: + output_n_atoms -= 1 + + cdef ListOfFaces output = ListOfFaces(self.n_faces(), output_n_atoms, self.n_coatoms()) + cdef size_t counter = 0 + cdef size_t n_atoms = self.n_atoms() + cdef size_t n_faces = self.n_faces() + for i in range(n_atoms): + if ((delete is NULL and face_atom_in(face, i)) or + (delete is not NULL and not delete[i])): + # The atom will be kept. + for j in range(n_faces): + if face_atom_in(self.data.faces[j], i): + face_add_atom(output.data.faces[j], counter) + counter += 1 + + return output + + cdef void delete_faces_unsafe(self, bint *delete, face_t face): + r""" + Deletes face ``i`` if and only if ``delete[i]``. + + Alternatively, deletes all faces such that the ``i``-th bit in ``face`` is not set. + + This will modify ``self``. + + .. WARNING:: + + ``delete`` is assumed to be of length ``self.n_faces()`` or NULL. + ``face`` is assumed to contain ``self.n_faces()`` atoms if ``delete`` is not ``NULL``. + """ + if delete is not NULL: + face_list_delete_faces_by_array(self.data, delete) + else: + face_list_delete_faces_by_face(self.data, face) + + cdef void get_not_inclusion_maximal_unsafe(self, bint *not_inclusion_maximal): + r""" + Get all faces that are not inclusion maximal. + + Set ``not_inclusion_maximal[i]`` to one if ``self.data[i]`` is not + an inclusion-maximal face, otherwise to zero. + + If there are duplicates, all but the last duplicate will be marked as + not inclusion maximal. + + .. WARNING:: + + ``not_inclusion_maximal`` is assumed to be at least of length ``self.n_atoms`` or NULL. + """ + cdef size_t i + memset(not_inclusion_maximal, 0, sizeof(bint)*self.n_faces()) + for i in range(self.n_faces()): + not_inclusion_maximal[i] = is_not_maximal_fused(self.data, i, 0, not_inclusion_maximal) + + cdef void get_faces_all_set_unsafe(self, bint *all_set): + r""" + Get the faces that have all ``bits`` set. + + Set ``all_set[i]`` to one if ``self.data[i]`` + has all bits set, otherwise to zero. + + .. WARNING:: + + ``all_set`` is assumed to be at least of length ``self.n_atoms`` or NULL. + """ + cdef size_t i + for i in range(self.n_faces()): + if face_first_missing_atom(self.data.faces[i]) == -1: + all_set[i] = 1 + else: + all_set[i] = 0 + def matrix(self): r""" Obtain the matrix of self. @@ -398,3 +492,62 @@ cdef class ListOfFaces: M.set_immutable() return M + +cdef tuple face_as_combinatorial_polyhedron(ListOfFaces facets, ListOfFaces Vrep, face_t face, bint dual): + r""" + Obtain facets and Vrepresentation of ``face`` as new combinatorial polyhedron. + + INPUT: + + - ``facets`` -- facets of the polyhedron + - ``Vrep`` -- Vrepresentation of the polyhedron + - ``face`` -- face in Vrepresentation or ``NULL`` + - ``dual`` -- boolean + + OUTPUT: A tuple of new facets and new Vrepresentation as :class:`ListOfFaces`. + """ + cdef ListOfFaces new_facets, new_Vrep + cdef bint* delete + cdef MemoryAllocator mem = MemoryAllocator() + cdef size_t i + cdef face_t null_face + + # Delete all atoms not in the face. + if not dual: + new_facets = facets.delete_atoms_unsafe(NULL, face) + new_Vrep = Vrep.__copy__() + new_Vrep.delete_faces_unsafe(NULL, face) + + delete = mem.allocarray(new_facets.n_faces(), sizeof(bint)) + else: + delete = mem.allocarray(max(facets.n_faces(), facets.n_atoms()), sizeof(bint)) + + # Set ``delete[i]`` to one if ``i`` is not an vertex of ``face``. + for i in range(Vrep.n_faces()): + if face_issubset(face, Vrep.data.faces[i]): + delete[i] = 0 + else: + delete[i] = 1 + + new_facets = facets.delete_atoms_unsafe(delete, null_face) + new_Vrep = Vrep.__copy__() + new_Vrep.delete_faces_unsafe(delete, null_face) + + # Delete all facets that define the face. + new_facets.get_faces_all_set_unsafe(delete) + new_facets.delete_faces_unsafe(delete, null_face) + new_Vrep = new_Vrep.delete_atoms_unsafe(delete, null_face) + + # Now delete all facets that are not inclusion maximal. + # the last copy of each duplicate will remain. + new_facets.get_not_inclusion_maximal_unsafe(delete) + new_facets.delete_faces_unsafe(delete, null_face) + new_Vrep = new_Vrep.delete_atoms_unsafe(delete, null_face) + + # Finally set coatoms of the output. + for i in range(new_facets.n_faces()): + facet_set_coatom(new_facets.data.faces[i], i) + for i in range(new_Vrep.n_faces()): + facet_set_coatom(new_Vrep.data.faces[i], i) + + return (new_facets, new_Vrep) 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 951fd46db41..afd731ae9c6 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,5 @@ cimport cython -from sage.ext.memory_allocator cimport MemoryAllocator +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 @@ -15,7 +15,8 @@ cdef class PolyhedronFaceLattice: cdef size_t *coatom_rep # a place where coatom-representation of face will be stored # some copies from CombinatorialPolyhedron - cdef tuple _Vrep, _facet_names, _equalities + cdef tuple _Vrep, _facet_names, _equations + cdef bint _bounded # Atoms and coatoms are the Vrep/facets of the Polyedron. # If ``dual == 0``, then coatoms are facets, atoms Vrepresentatives and vice versa. 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 6c9f5c678b5..c47952d7527 100644 --- a/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +++ b/src/sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx @@ -9,7 +9,7 @@ the face lattice of a polyhedron. Terminology in this module: - Vrep -- ``[vertices, rays, lines]`` of the polyhedron. -- Hrep -- inequalities and equalities of the polyhedron. +- Hrep -- inequalities and equations of the polyhedron. - Facets -- facets of the polyhedron. - Coatoms -- the faces from which all others are constructed in the face iterator. This will be facets or Vrep. @@ -138,7 +138,8 @@ cdef class PolyhedronFaceLattice: cdef FaceIterator face_iter = C._face_iter(self.dual, -2) self._Vrep = C.Vrep() self._facet_names = C.facet_names() - self._equalities = C.equalities() + self._equations = C.equations() + self._bounded = C.is_bounded() # copy f_vector for later use f_vector = C.f_vector() diff --git a/src/sage/geometry/polyhedron/face.py b/src/sage/geometry/polyhedron/face.py index 7a00f46ab19..fe7aeaab449 100644 --- a/src/sage/geometry/polyhedron/face.py +++ b/src/sage/geometry/polyhedron/face.py @@ -73,17 +73,16 @@ # http://www.gnu.org/licenses/ ######################################################################## -from sage.structure.sage_object import SageObject from sage.structure.richcmp import richcmp_method, richcmp from sage.misc.all import cached_method from sage.modules.free_module_element import vector from sage.matrix.constructor import matrix - +from sage.geometry.convex_set import ConvexSet_closed ######################################################################### @richcmp_method -class PolyhedronFace(SageObject): +class PolyhedronFace(ConvexSet_closed): r""" A face of a polyhedron. @@ -121,6 +120,11 @@ class PolyhedronFace(SageObject): (An inequality (1, 1, 1) x + 1 >= 0,) sage: face.ambient_Vrepresentation() (A vertex at (-1, 0, 0), A vertex at (0, -1, 0), A vertex at (0, 0, -1)) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') + """ def __init__(self, polyhedron, V_indices, H_indices): @@ -147,6 +151,7 @@ def __init__(self, polyhedron, V_indices, H_indices): sage: from sage.geometry.polyhedron.face import PolyhedronFace sage: PolyhedronFace(Polyhedron(), [], []) # indirect doctest A -1-dimensional face of a Polyhedron in ZZ^0 + sage: TestSuite(_).run(skip='_test_pickling') """ self._polyhedron = polyhedron self._ambient_Vrepresentation_indices = tuple(V_indices) @@ -180,6 +185,10 @@ def vertex_generator(self): A vertex at (1, 1) sage: type(face.vertex_generator()) <... 'generator'> + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ for V in self.ambient_Vrepresentation(): if V.is_vertex(): @@ -200,6 +209,10 @@ def vertices(self): sage: face = triangle.faces(1)[2] sage: face.vertices() (A vertex at (0, 1), A vertex at (1, 0)) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ return tuple(self.vertex_generator()) @@ -218,6 +231,10 @@ def n_vertices(self): sage: face = Q.faces(2)[0] sage: face.n_vertices() 3 + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ return len(self.vertices()) @@ -231,6 +248,10 @@ def ray_generator(self): sage: face = pi.faces(1)[1] sage: next(face.ray_generator()) A ray in the direction (1, 0) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ for V in self.ambient_Vrepresentation(): if V.is_ray(): @@ -282,6 +303,10 @@ def line_generator(self): sage: face = pr.faces(1)[0] sage: next(face.line_generator()) A line in the direction (1, 0) + + TESTS:: + + sage: TestSuite(face).run(skip='_test_pickling') """ for V in self.ambient_Vrepresentation(): if V.is_line(): @@ -614,22 +639,32 @@ def _repr_(self): if self.n_vertices() > 0: desc += ' defined as the convex hull of ' desc += repr(self.n_vertices()) - if self.n_vertices() == 1: desc += ' vertex' - else: desc += ' vertices' + if self.n_vertices() == 1: + desc += ' vertex' + else: + desc += ' vertices' if self.n_rays() > 0: - if self.n_lines() > 0: desc += ", " - else: desc += " and " + if self.n_lines() > 0: + desc += ", " + else: + desc += " and " desc += repr(self.n_rays()) - if self.n_rays() == 1: desc += ' ray' - else: desc += ' rays' + if self.n_rays() == 1: + desc += ' ray' + else: + desc += ' rays' if self.n_lines() > 0: - if self.n_rays() > 0: desc += ", " - else: desc += " and " + if self.n_rays() > 0: + desc += ", " + else: + desc += " and " desc += repr(self.n_lines()) - if self.n_lines() == 1: desc += ' line' - else: desc += ' lines' + if self.n_lines() == 1: + desc += ' line' + else: + desc += ' lines' return desc @@ -649,6 +684,68 @@ def polyhedron(self): """ return self._polyhedron + ambient = polyhedron + + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + It is the ambient free module of the containing polyhedron tensored + with a field. + + INPUT: + + - ``base_field`` -- (default: the fraction field of the base ring) a field. + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.ambient_vector_space() + Vector space of dimension 2 over Rational Field + sage: line.ambient_vector_space(AA) + Vector space of dimension 2 over Algebraic Real Field + """ + return self.polyhedron().ambient_vector_space(base_field=base_field) + + def is_relatively_open(self): + r""" + Return whether ``self`` is relatively open. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.is_relatively_open() + True + """ + return self.as_polyhedron().is_relatively_open() + + def is_compact(self): + r""" + Return whether ``self`` is compact. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.is_compact() + False + """ + return not any(V.is_ray() or V.is_line() + for V in self.ambient_Vrepresentation()) + @cached_method def as_polyhedron(self): """ @@ -676,6 +773,49 @@ def as_polyhedron(self): Vrep = (self.vertices(), self.rays(), self.lines()) return P.__class__(parent, Vrep, None) + def contains(self, point): + """ + Test whether the polyhedron contains the given ``point``. + + INPUT: + + - ``point`` -- a point or its coordinates + + EXAMPLES:: + + sage: half_plane = Polyhedron(ieqs=[(0,1,0)]) + sage: line = half_plane.faces(1)[0]; line + A 1-dimensional face of a Polyhedron in QQ^2 defined as the convex hull of 1 vertex and 1 line + sage: line.contains([0, 1]) + True + + As a shorthand, one may use the usual ``in`` operator:: + + sage: [5, 7] in line + False + """ + # preprocess in the same way as Polyhedron_base.contains + try: + p = vector(point) + except TypeError: # point not iterable or no common ring for elements + if len(point) > 0: + return False + else: + p = vector(self.polyhedron().base_ring(), []) + + if len(p) != self.ambient_dim(): + return False + + if not self.polyhedron().contains(p): + return False + + for H in self.ambient_Hrepresentation(): + if H.eval(p) != 0: + return False + return True + + __contains__ = contains + @cached_method def normal_cone(self, direction='outer'): """ @@ -848,12 +988,12 @@ def combinatorial_face_to_polyhedral_face(polyhedron, combinatorial_face): if polyhedron.backend() in ('ppl',): # Equations before inequalities in Hrep. H_indices = tuple(range(n_equations)) - H_indices += tuple(x+n_equations for x in combinatorial_face.ambient_H_indices()) + H_indices += tuple(x+n_equations for x in combinatorial_face.ambient_H_indices(add_equations=False)) elif polyhedron.backend() in ('normaliz', 'cdd', 'field', 'polymake'): # Equations after the inequalities in Hrep. n_ieqs = polyhedron.n_inequalities() - H_indices = tuple(range(n_ieqs, n_ieqs + n_equations)) - H_indices += tuple(x for x in combinatorial_face.ambient_H_indices()) + H_indices = tuple(x for x in combinatorial_face.ambient_H_indices(add_equations=False)) + H_indices += tuple(range(n_ieqs, n_ieqs + n_equations)) else: raise NotImplementedError("unknown backend") diff --git a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py index 42364192763..5bf4d59b140 100644 --- a/src/sage/geometry/polyhedron/ppl_lattice_polytope.py +++ b/src/sage/geometry/polyhedron/ppl_lattice_polytope.py @@ -863,7 +863,7 @@ def base_projection_matrix(self, fiber): sage: proj = poly.base_projection(fiber) sage: proj_matrix = poly.base_projection_matrix(fiber) sage: [ proj(p) for p in poly.integral_points() ] - [(-1, -1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (1, 0), (0, 1)] + [(-1, -1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 1), (1, 0)] sage: [ proj_matrix*p for p in poly.integral_points() ] [(-1, -1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 1), (1, 0)] """ diff --git a/src/sage/geometry/polyhedron/representation.py b/src/sage/geometry/polyhedron/representation.py index 3c73a959ac7..dd538422e94 100644 --- a/src/sage/geometry/polyhedron/representation.py +++ b/src/sage/geometry/polyhedron/representation.py @@ -2,7 +2,7 @@ H(yperplane) and V(ertex) representation objects for polyhedra """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2008 Marshall Hampton # Copyright (C) 2011 Volker Braun # @@ -10,8 +10,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/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.structure.sage_object import SageObject @@ -22,7 +22,6 @@ from copy import copy - ######################################################################### # PolyhedronRepresentation # / \ @@ -103,8 +102,14 @@ def __richcmp__(self, other, op): """ Compare two representation objects - They are equal if and only if they define the same - vertex/ray/line or inequality/equation in the ambient space, + This method defines a linear order on the H/V-representation objects. + The order is first determined by the types of the objects, + such that inequality < equation < vertex < ray < line. + Then, representation objects with the same type are ordered + lexicographically according to their canonical vectors. + + Thus, two representation objects are equal if and only if they define + the same vertex/ray/line or inequality/equation in the ambient space, regardless of the polyhedron that they belong to. INPUT: @@ -132,6 +137,10 @@ def __richcmp__(self, other, op): sage: ieq != Polyhedron([(0,1,0)]).Vrepresentation(0) True + sage: H = Polyhedron(vertices=[(4,0)], rays=[(1,1)], lines=[(-1,1)]) + sage: H.vertices()[0] < H.rays()[0] < H.lines()[0] + True + TESTS: Check :trac:`30954`:: @@ -143,9 +152,8 @@ def __richcmp__(self, other, op): """ if not isinstance(other, PolyhedronRepresentation): return NotImplemented - if type(self) != type(other): - return NotImplemented - return richcmp(self._vector*self._comparison_scalar(), other._vector*other._comparison_scalar(), op) + return richcmp((self.type(), self._vector*self._comparison_scalar()), + (other.type(), other._vector*other._comparison_scalar()), op) def _comparison_scalar(self): r""" @@ -280,13 +288,13 @@ def index(self): Return an arbitrary but fixed number according to the internal storage order. - NOTES: + .. NOTE:: - H-representation and V-representation objects are enumerated - independently. That is, amongst all vertices/rays/lines there - will be one with ``index()==0``, and amongst all - inequalities/equations there will be one with ``index()==0``, - unless the polyhedron is empty or spans the whole space. + H-representation and V-representation objects are enumerated + independently. That is, amongst all vertices/rays/lines there + will be one with ``index()==0``, and amongst all + inequalities/equations there will be one with ``index()==0``, + unless the polyhedron is empty or spans the whole space. EXAMPLES:: @@ -598,14 +606,16 @@ def __mul__(self, Vobj): def eval(self, Vobj): r""" - Evaluates the left hand side `A\vec{x}+b` on the given + Evaluate the left hand side `A\vec{x}+b` on the given vertex/ray/line. - NOTES: + .. NOTE: * Evaluating on a vertex returns `A\vec{x}+b` + * Evaluating on a ray returns `A\vec{r}`. Only the sign or whether it is zero is meaningful. + * Evaluating on a line returns `A\vec{l}`. Only whether it is zero or not is meaningful. @@ -740,7 +750,6 @@ def type(self): """ return self.INEQUALITY - def is_inequality(self): """ Return True since this is, by construction, an inequality. @@ -918,7 +927,7 @@ def contains(self, Vobj): [True, True, False, True, False, True, False, False] """ try: - if Vobj.is_vector(): # assume we were passed a point + if Vobj.is_vector(): # assume we were passed a point return self.polyhedron()._is_nonneg( self.eval(Vobj) ) except AttributeError: pass @@ -1084,9 +1093,9 @@ def interior_contains(self, Vobj): boundary) defined by the inequality contains the given vertex/ray/line. - NOTE: + .. NOTE:: - Return False for any equation. + Return False for any equation. EXAMPLES:: @@ -1382,7 +1391,7 @@ def _repr_(self): sage: v.__repr__() 'A vertex at (1, 0)' """ - return 'A vertex at ' + repr(self.vector()); + return 'A vertex at ' + repr(self.vector()) def homogeneous_vector(self, base_ring=None): """ @@ -1499,7 +1508,7 @@ def _repr_(self): sage: a._repr_() 'A ray in the direction (0, 1)' """ - return 'A ray in the direction ' + repr(self.vector()); + return 'A ray in the direction ' + repr(self.vector()) def homogeneous_vector(self, base_ring=None): """ @@ -1597,7 +1606,7 @@ def _repr_(self): sage: a.__repr__() 'A line in the direction (0, 1, 0)' """ - return 'A line in the direction ' + repr(self.vector()); + return 'A line in the direction ' + repr(self.vector()) def homogeneous_vector(self, base_ring=None): """ diff --git a/src/sage/geometry/relative_interior.py b/src/sage/geometry/relative_interior.py new file mode 100644 index 00000000000..9e49420a5e4 --- /dev/null +++ b/src/sage/geometry/relative_interior.py @@ -0,0 +1,302 @@ +r""" +Relative Interiors of Polyhedra and Cones +""" + +# **************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# 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.geometry.convex_set import ConvexSet_relatively_open + + +class RelativeInterior(ConvexSet_relatively_open): + r""" + The relative interior of a polyhedron or cone + + This class should not be used directly. Use methods + :meth:`~sage.geometry.polyhedron.Polyhedron_base.relative_interior`, + :meth:`~sage.geometry.polyhedron.Polyhedron_base.interior`, + :meth:`~sage.geometry.cone.ConvexRationalPolyhedralCone.relative_interior`, + :meth:`~sage.geometry.cone.ConvexRationalPolyhedralCone.interior` instead. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: segment.relative_interior() + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)]) + sage: octant.relative_interior() + Relative interior of 3-d cone in 3-d lattice N + """ + + def __init__(self, polyhedron): + r""" + Initialize ``self``. + + INPUT: + + - ``polyhedron`` - an instance of :class:`Polyhedron_base` or + :class:`ConvexRationalPolyhedralCone`. + + TESTS:: + + sage: P = Polyhedron([[1, 2], [3, 4]]) + sage: from sage.geometry.relative_interior import RelativeInterior + sage: TestSuite(RelativeInterior(P)).run() + """ + self._polyhedron = polyhedron + + def __contains__(self, point): + r""" + Return whether ``self`` contains ``point``. + + EXAMPLES:: + + sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)]) + sage: ri_octant = octant.relative_interior(); ri_octant + Relative interior of 3-d cone in 3-d lattice N + sage: (1, 1, 1) in ri_octant + True + sage: (1, 0, 0) in ri_octant + False + """ + return self._polyhedron.relative_interior_contains(point) + + def ambient(self): + r""" + Return the ambient convex set or space. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.ambient() + Vector space of dimension 2 over Rational Field + """ + return self._polyhedron.ambient() + + def ambient_vector_space(self, base_field=None): + r""" + Return the ambient vector space. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.ambient_vector_space() + Vector space of dimension 2 over Rational Field + """ + return self._polyhedron.ambient_vector_space(base_field=base_field) + + def ambient_dim(self): + r""" + Return the dimension of the ambient space. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: segment.ambient_dim() + 2 + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.ambient_dim() + 2 + """ + return self._polyhedron.ambient_dim() + + def dim(self): + r""" + Return the dimension of ``self``. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: segment.dim() + 1 + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.dim() + 1 + """ + return self._polyhedron.dim() + + def interior(self): + r""" + Return the interior of ``self``. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.interior() + The empty polyhedron in ZZ^2 + + sage: octant = Cone([(1,0,0), (0,1,0), (0,0,1)]) + sage: ri_octant = octant.relative_interior(); ri_octant + Relative interior of 3-d cone in 3-d lattice N + sage: ri_octant.interior() is ri_octant + True + """ + return self._polyhedron.interior() + + def relative_interior(self): + r""" + Return the relative interior of ``self``. + + As ``self`` is already relatively open, this method just returns ``self``. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.relative_interior() is ri_segment + True + """ + return self + + def closure(self): + r""" + Return the topological closure of ``self``. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.closure() is segment + True + """ + return self._polyhedron + + def is_universe(self): + r""" + Return whether ``self`` is the whole ambient space + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.is_universe() + False + """ + # Relies on ``self`` not set up for polyhedra that are already + # relatively open themselves. + assert not self._polyhedron.is_universe() + return False + + def is_closed(self): + r""" + Return whether ``self`` is closed. + + OUTPUT: + + Boolean. + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: ri_segment.is_closed() + False + """ + # Relies on ``self`` not set up for polyhedra that are already + # relatively open themselves. + assert not self._polyhedron.is_relatively_open() + return False + + def _repr_(self): + r""" + Return a description of ``self``. + + EXAMPLES:: + + sage: P = Polyhedron(vertices = [[1,2,3,4],[2,1,3,4],[4,3,2,1]]) + sage: P.relative_interior()._repr_() + 'Relative interior of a 2-dimensional polyhedron in ZZ^4 defined as the convex hull of 3 vertices' + sage: P.rename('A') + sage: P.relative_interior()._repr_() + 'Relative interior of A' + """ + repr_P = repr(self._polyhedron) + if repr_P.startswith('A '): + repr_P = 'a ' + repr_P[2:] + return 'Relative interior of ' + repr_P + + def __eq__(self, other): + r""" + Compare ``self`` and ``other``. + + INPUT: + + - ``other`` -- any object + + EXAMPLES:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: segment2 = Polyhedron([[1, 2], [3, 4]], base_ring=AA) + sage: ri_segment2 = segment2.relative_interior(); ri_segment2 + Relative interior of + a 1-dimensional polyhedron in AA^2 defined as the convex hull of 2 vertices + sage: ri_segment == ri_segment2 + True + + TESTS:: + + sage: empty = Polyhedron(ambient_dim=2) + sage: ri_segment == empty + False + """ + if type(self) != type(other): + return False + return self._polyhedron == other._polyhedron + + def __ne__(self, other): + r""" + Compare ``self`` and ``other``. + + INPUT: + + - ``other`` -- any object + + TESTS:: + + sage: segment = Polyhedron([[1, 2], [3, 4]]) + sage: ri_segment = segment.relative_interior(); ri_segment + Relative interior of + a 1-dimensional polyhedron in ZZ^2 defined as the convex hull of 2 vertices + sage: segment2 = Polyhedron([[1, 2], [3, 4]], base_ring=AA) + sage: ri_segment2 = segment2.relative_interior(); ri_segment2 + Relative interior of + a 1-dimensional polyhedron in AA^2 defined as the convex hull of 2 vertices + sage: ri_segment != ri_segment2 + False + """ + return not (self == other) diff --git a/src/sage/geometry/riemannian_manifolds/parametrized_surface3d.py b/src/sage/geometry/riemannian_manifolds/parametrized_surface3d.py index b4fb5fbbe67..2a7460b3050 100644 --- a/src/sage/geometry/riemannian_manifolds/parametrized_surface3d.py +++ b/src/sage/geometry/riemannian_manifolds/parametrized_surface3d.py @@ -1519,21 +1519,21 @@ def _create_geodesic_ode_system(self): u1 = self.variables[1] u2 = self.variables[2] - v1 = SR.var('v1', domain='real') - v2 = SR.var('v2', domain='real') C = self.connection_coefficients() - dv1 = - C[(1,1,1)]*v1**2 - 2*C[(1,2,1)]*v1*v2 - C[(2,2,1)]*v2**2 - dv2 = - C[(1,1,2)]*v1**2 - 2*C[(1,2,2)]*v1*v2 - C[(2,2,2)]*v2**2 - fun1 = fast_float(dv1, str(u1), str(u2), str(v1), str(v2)) - fun2 = fast_float(dv2, str(u1), str(u2), str(v1), str(v2)) + with SR.temp_var(domain='real') as v1: + with SR.temp_var(domain='real') as v2: + dv1 = - C[(1,1,1)]*v1**2 - 2*C[(1,2,1)]*v1*v2 - C[(2,2,1)]*v2**2 + dv2 = - C[(1,1,2)]*v1**2 - 2*C[(1,2,2)]*v1*v2 - C[(2,2,2)]*v2**2 + fun1 = fast_float(dv1, str(u1), str(u2), str(v1), str(v2)) + fun2 = fast_float(dv2, str(u1), str(u2), str(v1), str(v2)) - geodesic_ode = ode_solver() - geodesic_ode.function = ( - lambda t, u1_u2_v1_v2: - [u1_u2_v1_v2[2], u1_u2_v1_v2[3], fun1(*u1_u2_v1_v2), fun2(*u1_u2_v1_v2)]) - return geodesic_ode + geodesic_ode = ode_solver() + geodesic_ode.function = ( + lambda t, u1_u2_v1_v2: + [u1_u2_v1_v2[2], u1_u2_v1_v2[3], fun1(*u1_u2_v1_v2), fun2(*u1_u2_v1_v2)]) + return geodesic_ode def geodesics_numerical(self, p0, v0, tinterval): @@ -1629,8 +1629,6 @@ def _create_pt_ode_system(self, curve, t): u1 = self.variables[1] u2 = self.variables[2] - v1 = SR.var('v1', domain='real') - v2 = SR.var('v2', domain='real') du1 = diff(curve[0], t) du2 = diff(curve[1], t) @@ -1639,16 +1637,18 @@ def _create_pt_ode_system(self, curve, t): for coef in C: C[coef] = C[coef].subs({u1: curve[0], u2: curve[1]}) - dv1 = - C[(1,1,1)]*v1*du1 - C[(1,2,1)]*(du1*v2 + du2*v1) - \ - C[(2,2,1)]*du2*v2 - dv2 = - C[(1,1,2)]*v1*du1 - C[(1,2,2)]*(du1*v2 + du2*v1) - \ - C[(2,2,2)]*du2*v2 - fun1 = fast_float(dv1, str(t), str(v1), str(v2)) - fun2 = fast_float(dv2, str(t), str(v1), str(v2)) - - pt_ode = ode_solver() - pt_ode.function = lambda t, v1_v2: [fun1(t, v1_v2[0], v1_v2[1]), fun2(t, v1_v2[0], v1_v2[1])] - return pt_ode + with SR.temp_var(domain='real') as v1: + with SR.temp_var(domain='real') as v2: + dv1 = - C[(1,1,1)]*v1*du1 - C[(1,2,1)]*(du1*v2 + du2*v1) - \ + C[(2,2,1)]*du2*v2 + dv2 = - C[(1,1,2)]*v1*du1 - C[(1,2,2)]*(du1*v2 + du2*v1) - \ + C[(2,2,2)]*du2*v2 + fun1 = fast_float(dv1, str(t), str(v1), str(v2)) + fun2 = fast_float(dv2, str(t), str(v1), str(v2)) + + pt_ode = ode_solver() + pt_ode.function = lambda t, v1_v2: [fun1(t, v1_v2[0], v1_v2[1]), fun2(t, v1_v2[0], v1_v2[1])] + return pt_ode def parallel_translation_numerical(self,curve,t,v0,tinterval): diff --git a/src/sage/geometry/toric_lattice.py b/src/sage/geometry/toric_lattice.py index 4444d662be5..4d8ef3fba62 100644 --- a/src/sage/geometry/toric_lattice.py +++ b/src/sage/geometry/toric_lattice.py @@ -425,7 +425,7 @@ def __call__(self, *args, **kwds): sage: N3 = ToricLattice(3, 'N3') sage: Q = N3 / N3.span([ N3(1,2,3) ]) sage: Q.an_element() - N3[0, 0, 1] + N3[1, 0, 0] sage: N2 = ToricLattice(2, 'N2') sage: N2( Q.an_element() ) N2(1, 0) @@ -1297,9 +1297,9 @@ class ToricLattice_quotient_element(FGP_Element): sage: e == e2 True sage: e.vector() - (4) + (-4) sage: e2.vector() - (4) + (-4) """ def _latex_(self): @@ -1407,7 +1407,7 @@ class ToricLattice_quotient(FGP_Module_class): sage: Q 1-d lattice, quotient of 3-d lattice N by Sublattice sage: Q.gens() - (N[0, 0, 1],) + (N[1, 0, 0],) Here, ``sublattice`` happens to be of codimension one in ``N``. If you want to prescribe the sign of the quotient generator, you can @@ -1416,15 +1416,15 @@ class ToricLattice_quotient(FGP_Module_class): sage: Q = N.quotient(sublattice, positive_point=N(0,0,-1)); Q 1-d lattice, quotient of 3-d lattice N by Sublattice sage: Q.gens() - (N[0, 0, -1],) + (N[1, 0, 0],) or:: sage: M = N.dual() - sage: Q = N.quotient(sublattice, positive_dual_point=M(0,0,-1)); Q + sage: Q = N.quotient(sublattice, positive_dual_point=M(1,0,0)); Q 1-d lattice, quotient of 3-d lattice N by Sublattice sage: Q.gens() - (N[0, 0, -1],) + (N[1, 0, 0],) TESTS:: diff --git a/src/sage/geometry/triangulation/element.py b/src/sage/geometry/triangulation/element.py index fe7a6560946..b19b9170258 100644 --- a/src/sage/geometry/triangulation/element.py +++ b/src/sage/geometry/triangulation/element.py @@ -576,7 +576,7 @@ def simplicial_complex(self): OUTPUT: - A :class:`~sage.homology.simplicial_complex.SimplicialComplex`. + A :class:`~sage.topology.simplicial_complex.SimplicialComplex`. EXAMPLES:: @@ -590,7 +590,7 @@ def simplicial_complex(self): sage: sc.homology() {0: 0, 1: 0, 2: 0, 3: 0} """ - from sage.homology.simplicial_complex import SimplicialComplex + from sage.topology.simplicial_complex import SimplicialComplex return SimplicialComplex(self) diff --git a/src/sage/geometry/triangulation/point_configuration.py b/src/sage/geometry/triangulation/point_configuration.py index 593c3b6b560..d710fa1015a 100644 --- a/src/sage/geometry/triangulation/point_configuration.py +++ b/src/sage/geometry/triangulation/point_configuration.py @@ -646,8 +646,8 @@ def _TOPCOM_exec(cls, executable, input_string, verbose=True): if verbose: print('# Still running ' + str(executable)) continue - if len(line)==0: # EOF - break; + if len(line) == 0: # EOF + break if verbose: print("# " + line) sys.stdout.flush() @@ -1110,8 +1110,8 @@ def convex_hull(self): pass from sage.geometry.polyhedron.constructor import Polyhedron - pts = [ p.reduced_affine() for p in self.points() ]; - self._polyhedron = Polyhedron(vertices=pts); + pts = [p.reduced_affine() for p in self.points()] + self._polyhedron = Polyhedron(vertices=pts) return self._polyhedron @cached_method @@ -1206,14 +1206,13 @@ def face_codimension(self, point): try: p = vector(self.point(point).reduced_affine()) except TypeError: - p = vector(point); + p = vector(point) inequalities = [] for ieq in self.convex_hull().inequality_generator(): if (ieq.A()*p + ieq.b() == 0): - inequalities += [ ieq.vector() ]; - return matrix(inequalities).rank(); - + inequalities += [ ieq.vector() ] + return matrix(inequalities).rank() def face_interior(self, dim=None, codim=None): """ diff --git a/src/sage/graphs/asteroidal_triples.pyx b/src/sage/graphs/asteroidal_triples.pyx index 8a86d969f4c..d9836672922 100644 --- a/src/sage/graphs/asteroidal_triples.pyx +++ b/src/sage/graphs/asteroidal_triples.pyx @@ -62,10 +62,10 @@ Functions from libc.stdint cimport uint32_t from cysignals.signals cimport sig_on, sig_off +from memory_allocator cimport MemoryAllocator from sage.data_structures.bitset_base cimport * from sage.graphs.base.static_sparse_graph cimport short_digraph, init_short_digraph, free_short_digraph -from sage.ext.memory_allocator cimport MemoryAllocator def is_asteroidal_triple_free(G, certificate=False): """ diff --git a/src/sage/graphs/base/static_sparse_graph.pyx b/src/sage/graphs/base/static_sparse_graph.pyx index 11782242daa..690cd85454c 100644 --- a/src/sage/graphs/base/static_sparse_graph.pyx +++ b/src/sage/graphs/base/static_sparse_graph.pyx @@ -189,12 +189,12 @@ from libc.math cimport sqrt from libcpp.vector cimport vector from cysignals.memory cimport check_allocarray, check_calloc, sig_free from cysignals.signals cimport sig_on, sig_off +from memory_allocator cimport MemoryAllocator from sage.data_structures.bitset_base cimport * from sage.graphs.base.c_graph cimport CGraph from .static_sparse_backend cimport StaticSparseCGraph from .static_sparse_backend cimport StaticSparseBackend -from sage.ext.memory_allocator cimport MemoryAllocator cdef extern from "fenv.h": int FE_TONEAREST diff --git a/src/sage/graphs/centrality.pyx b/src/sage/graphs/centrality.pyx index 420f264e209..d73787a4b10 100755 --- a/src/sage/graphs/centrality.pyx +++ b/src/sage/graphs/centrality.pyx @@ -20,12 +20,12 @@ from libc.string cimport memset from libc.stdint cimport uint32_t from cysignals.memory cimport check_allocarray, sig_free from cysignals.signals cimport sig_check +from memory_allocator cimport MemoryAllocator from sage.data_structures.bitset_base cimport * from sage.graphs.base.static_sparse_graph cimport * from sage.libs.gmp.mpq cimport * from sage.rings.rational cimport Rational -from sage.ext.memory_allocator cimport MemoryAllocator from sage.graphs.base.boost_graph import shortest_paths as boost_shortest_paths import random diff --git a/src/sage/graphs/chrompoly.pyx b/src/sage/graphs/chrompoly.pyx index 8d7cda13a77..e1b9c80d2a0 100644 --- a/src/sage/graphs/chrompoly.pyx +++ b/src/sage/graphs/chrompoly.pyx @@ -25,11 +25,11 @@ REFERENCE: #***************************************************************************** from cysignals.signals cimport sig_check +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.ext.memory_allocator cimport MemoryAllocator from sage.misc.all import prod diff --git a/src/sage/graphs/connectivity.pxd b/src/sage/graphs/connectivity.pxd index d28543974fd..36898d75e76 100644 --- a/src/sage/graphs/connectivity.pxd +++ b/src/sage/graphs/connectivity.pxd @@ -1,4 +1,4 @@ -from sage.ext.memory_allocator cimport MemoryAllocator +from memory_allocator cimport MemoryAllocator from sage.graphs.generic_graph_pyx cimport GenericGraph_pyx ctypedef struct _LinkedListNode: diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index eecd1bed77b..160795f5c4b 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -3317,12 +3317,28 @@ def layout_acyclic_dummy(self, heights=None, rankdir='up', **options): ... ValueError: `self` should be an acyclic graph + TESTS: + + :trac:`31681` is fixed:: + + sage: H = DiGraph({0: [1], 'X': [1]}, format='dict_of_lists') + sage: pos = H.layout_acyclic_dummy(rankdir='up') + sage: pos['X'][1] == 0 and pos[0][1] == 0 + True + sage: pos[1][1] == 1 + True """ if heights is None: if not self.is_directed_acyclic(): raise ValueError("`self` should be an acyclic graph") levels = self.level_sets() - levels = [sorted(z) for z in levels] + # Sort vertices in each level in best effort mode + for i in range(len(levels)): + try: + l = sorted(levels[i]) + levels[i] = l + except: + continue if rankdir=='down' or rankdir=='left': levels.reverse() heights = {i: levels[i] for i in range(len(levels))} diff --git a/src/sage/graphs/distances_all_pairs.pyx b/src/sage/graphs/distances_all_pairs.pyx index 92b1d503f6a..5e92e023d13 100644 --- a/src/sage/graphs/distances_all_pairs.pyx +++ b/src/sage/graphs/distances_all_pairs.pyx @@ -128,10 +128,10 @@ from libc.stdint cimport uint64_t, UINT64_MAX from libc.stdint cimport uint32_t, INT32_MAX, UINT32_MAX from cysignals.memory cimport sig_malloc, sig_calloc, sig_free from cysignals.signals cimport sig_on, sig_off +from memory_allocator cimport MemoryAllocator from sage.graphs.base.c_graph cimport CGraphBackend from sage.graphs.base.c_graph cimport CGraph -from sage.ext.memory_allocator cimport MemoryAllocator from sage.graphs.base.static_sparse_graph cimport (short_digraph, init_short_digraph, diff --git a/src/sage/graphs/generators/random.py b/src/sage/graphs/generators/random.py index 31c1a8810f5..e06bc8a6d87 100644 --- a/src/sage/graphs/generators/random.py +++ b/src/sage/graphs/generators/random.py @@ -1798,7 +1798,7 @@ def RandomTriangulation(n, set_position=False, k=3): .. SEEALSO:: :meth:`~sage.graphs.graph_generators.GraphGenerators.triangulations`, - :func:`~sage.homology.examples.RandomTwoSphere`. + :func:`~sage.topology.simplicial_complex_examples.RandomTwoSphere`. EXAMPLES:: diff --git a/src/sage/graphs/generic_graph_pyx.pxd b/src/sage/graphs/generic_graph_pyx.pxd index b106a9c708b..23f45e38080 100644 --- a/src/sage/graphs/generic_graph_pyx.pxd +++ b/src/sage/graphs/generic_graph_pyx.pxd @@ -1,6 +1,6 @@ from sage.structure.sage_object cimport SageObject from sage.graphs.base.dense_graph cimport DenseGraph -from sage.ext.memory_allocator cimport MemoryAllocator +from memory_allocator cimport MemoryAllocator ctypedef int * D_TWO ctypedef char * D_THREE diff --git a/src/sage/graphs/generic_graph_pyx.pyx b/src/sage/graphs/generic_graph_pyx.pyx index 2f7d83f2db0..d9643cef474 100644 --- a/src/sage/graphs/generic_graph_pyx.pyx +++ b/src/sage/graphs/generic_graph_pyx.pyx @@ -28,11 +28,11 @@ import cython from sage.data_structures.binary_matrix cimport * from libc.math cimport sqrt, fabs from libc.string cimport memset +from memory_allocator cimport MemoryAllocator from sage.cpython.string cimport char_to_str from sage.libs.gmp.mpz cimport * from sage.misc.prandom import random -from sage.ext.memory_allocator cimport MemoryAllocator from sage.graphs.base.static_sparse_graph cimport short_digraph from sage.graphs.base.static_sparse_graph cimport init_short_digraph from sage.graphs.base.static_sparse_graph cimport free_short_digraph diff --git a/src/sage/graphs/genus.pyx b/src/sage/graphs/genus.pyx index db971c877d8..a4fe3776eaa 100644 --- a/src/sage/graphs/genus.pyx +++ b/src/sage/graphs/genus.pyx @@ -38,7 +38,7 @@ described throughout the file. #***************************************************************************** from libc.string cimport memcpy -from sage.ext.memory_allocator cimport MemoryAllocator +from memory_allocator cimport MemoryAllocator from cysignals.signals cimport sig_on, sig_off cimport sage.combinat.permutation_cython diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 7fea475c191..c63df92f844 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -2727,7 +2727,7 @@ def is_edge_transitive(self): A = self.automorphism_group() e = next(self.edge_iterator(labels=False)) e = [A._domain_to_gap[e[0]], A._domain_to_gap[e[1]]] - + e.sort() return libgap(A).OrbitLength(e, libgap.OnSets) == self.size() @doc_index("Graph properties") @@ -7037,8 +7037,8 @@ def clique_complex(self): """ if self.is_directed() or self.has_loops() or self.has_multiple_edges(): raise ValueError("Self must be an undirected simple graph to have a clique complex.") - import sage.homology.simplicial_complex - C = sage.homology.simplicial_complex.SimplicialComplex(self.cliques_maximal(), maximality_check=True) + import sage.topology.simplicial_complex + C = sage.topology.simplicial_complex.SimplicialComplex(self.cliques_maximal(), maximality_check=True) C._graph = self return C diff --git a/src/sage/graphs/graph_decompositions/bandwidth.pyx b/src/sage/graphs/graph_decompositions/bandwidth.pyx index 96a07a355c7..91ffeeab130 100644 --- a/src/sage/graphs/graph_decompositions/bandwidth.pyx +++ b/src/sage/graphs/graph_decompositions/bandwidth.pyx @@ -113,10 +113,10 @@ Functions from libc.stdint cimport uint16_t from cysignals.signals cimport sig_check +from memory_allocator cimport MemoryAllocator from sage.graphs.distances_all_pairs cimport all_pairs_shortest_path_BFS from sage.graphs.base.boost_graph import bandwidth_heuristics -from sage.ext.memory_allocator cimport MemoryAllocator ctypedef uint16_t index_t diff --git a/src/sage/graphs/graph_decompositions/clique_separators.pyx b/src/sage/graphs/graph_decompositions/clique_separators.pyx index 1158ed629f3..dbcbdd5ee79 100644 --- a/src/sage/graphs/graph_decompositions/clique_separators.pyx +++ b/src/sage/graphs/graph_decompositions/clique_separators.pyx @@ -23,18 +23,16 @@ Methods from libcpp.pair cimport pair from libcpp.vector cimport vector +from libc.stdint cimport uint32_t +from cysignals.signals cimport sig_on, sig_off, sig_check +from memory_allocator cimport MemoryAllocator -from sage.ext.memory_allocator cimport MemoryAllocator from sage.graphs.base.static_sparse_graph cimport short_digraph from sage.graphs.base.static_sparse_graph cimport init_short_digraph from sage.graphs.base.static_sparse_graph cimport free_short_digraph from sage.graphs.base.static_sparse_graph cimport has_edge -from libc.stdint cimport uint32_t - -from cysignals.signals cimport sig_on, sig_off, sig_check from sage.sets.set import Set - from sage.graphs.traversals cimport maximum_cardinality_search_M_short_digraph def make_tree(atoms, cliques): diff --git a/src/sage/graphs/graph_decompositions/rankwidth.pyx b/src/sage/graphs/graph_decompositions/rankwidth.pyx index 093d0121ac8..1a907e4bebe 100644 --- a/src/sage/graphs/graph_decompositions/rankwidth.pyx +++ b/src/sage/graphs/graph_decompositions/rankwidth.pyx @@ -30,7 +30,7 @@ achieving the minimal *rank-width*. **RW -- The original source code :** RW is a program that calculates rank-width and -rank-decompositions. It is based on ideas from : +rank-decompositions. It is based on ideas from: * "Computing rank-width exactly" by Sang-il Oum [Oum2009]_ * "Sopra una formula numerica" by Ernesto Pascal @@ -94,15 +94,15 @@ Methods ------- """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2011 Nathann Cohen # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from cysignals.memory cimport check_allocarray, sig_free from cysignals.signals cimport * diff --git a/src/sage/graphs/graph_plot.py b/src/sage/graphs/graph_plot.py index 073d36fd56e..558eb4f22c8 100644 --- a/src/sage/graphs/graph_plot.py +++ b/src/sage/graphs/graph_plot.py @@ -8,20 +8,20 @@ sage: G = graphs.WheelGraph(15) sage: P = G.plot() - sage: P.show() # long time + sage: P.show() # long time .. PLOT:: sphinx_plot(graphs.WheelGraph(15)) -If you create a graph in Sage using the ``Graph`` command, then plot that graph, -the positioning of nodes is determined using the spring-layout algorithm. For -the special graph constructors, which you get using ``graphs.[tab]``, the -positions are preset. For example, consider the Petersen graph with default node -positioning vs. the Petersen graph constructed by this database:: +When plotting a graph created using Sage's ``Graph`` command, +node positions are determined using the spring-layout algorithm. +Special graphs available from ``graphs.*`` have preset positions. +For example, compare the two plots of the Petersen graph, +as obtained using ``Graph`` or as obtained from that database:: sage: petersen_spring = Graph(':I`ES@obGkqegW~') - sage: petersen_spring.show() # long time + sage: petersen_spring.show() # long time .. PLOT:: @@ -31,15 +31,15 @@ :: sage: petersen_database = graphs.PetersenGraph() - sage: petersen_database.show() # long time + sage: petersen_database.show() # long time .. PLOT:: petersen_database = graphs.PetersenGraph() sphinx_plot(petersen_database) -For all the constructors in this database (except some random graphs), the -position dictionary is filled in, instead of using the spring-layout algorithm. +All constructors in this database (except some random graphs) prefill +the position dictionary, bypassing the spring-layout positioning algorithm. **Plot options** @@ -65,21 +65,22 @@ Obviously, these values are overruled when arguments are given explicitly. -Here is how to define the default size of a graph drawing to be ``[6,6]``. The -first two calls to :meth:`~sage.graphs.generic_graph.GenericGraph.show` use this -option, while the third does not (a value for ``figsize`` is explicitly given):: +Here is how to define the default size of a graph drawing to be ``(6, 6)``. +The first two calls to :meth:`~sage.graphs.generic_graph.GenericGraph.show` +use this option, while the third does not (a value for ``figsize`` +is explicitly given):: sage: import sage.graphs.graph_plot - sage: sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS['figsize'] = [6,6] - sage: graphs.PetersenGraph().show() # long time + sage: sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS['figsize'] = (6, 6) + sage: graphs.PetersenGraph().show() # long time sage: graphs.ChvatalGraph().show() # long time - sage: graphs.PetersenGraph().show(figsize=[4,4]) # long time + sage: graphs.PetersenGraph().show(figsize=(4, 4)) # long time We can now reset the default to its initial value, and now display graphs as previously:: - sage: sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS['figsize'] = [4,4] - sage: graphs.PetersenGraph().show() # long time + sage: sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS['figsize'] = (4, 4) + sage: graphs.PetersenGraph().show() # long time sage: graphs.ChvatalGraph().show() # long time .. NOTE:: @@ -91,7 +92,7 @@ lines to `Sage's startup scripts <../../../repl/startup.html>`_. Example:: sage: import sage.graphs.graph_plot - sage: sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS['figsize'] = [4,4] + sage: sage.graphs.graph_plot.DEFAULT_SHOW_OPTIONS['figsize'] = (4, 4) **Index of methods and functions** @@ -108,76 +109,6 @@ :meth:`GraphPlot.layout_tree` | Compute a nice layout of a tree. """ -layout_options = { - 'layout': 'A layout algorithm -- one of : "acyclic", "circular" (plots the ' - 'graph with vertices evenly distributed on a circle), "ranked", ' - '"graphviz", "planar", "spring" (traditional spring layout, using the ' - 'graph\'s current positions as initial positions), or "tree" (the tree ' - 'will be plotted in levels, depending on minimum distance for the root).', - 'iterations': 'The number of times to execute the spring layout algorithm.', - 'heights': 'A dictionary mapping heights to the list of vertices at this height.', - 'spring': 'Use spring layout to finalize the current layout.', - 'tree_root': 'A vertex designation for drawing trees. A vertex of the tree ' - 'to be used as the root for the ``layout=\'tree\'`` option. If no root ' - 'is specified, then one is chosen close to the center of the tree. ' - 'Ignored unless ``layout=\'tree\'``.', - 'forest_roots': 'An iterable specifying which vertices to use as roots for ' - 'the ``layout=\'forest\'`` option. If no root is specified for a tree, ' - 'then one is chosen close to the center of the tree. ' - 'Ignored unless ``layout=\'forest\'``.', - 'tree_orientation': 'The direction of tree branches -- \'up\', \'down\', ' - '\'left\' or \'right\'.', - 'save_pos': 'Whether or not to save the computed position for the graph.', - 'dim': 'The dimension of the layout -- 2 or 3.', - 'prog': 'Which graphviz layout program to use -- one of "circo", "dot", ' - '"fdp", "neato", or "twopi".', - 'by_component': 'Whether to do the spring layout by connected component ' - '-- a boolean.', - } - -graphplot_options = layout_options.copy() - -graphplot_options.update( - {'pos': 'The position dictionary of vertices', - 'vertex_labels': 'Whether or not to draw vertex labels.', - 'vertex_color': 'Default color for vertices not listed ' - 'in vertex_colors dictionary.', - 'vertex_colors': 'Dictionary of vertex coloring : each ' - 'key is a color recognizable by matplotlib, and each ' - 'corresponding entry is a list of vertices. ', - 'vertex_size': 'The size to draw the vertices.', - 'vertex_shape': 'The shape to draw the vertices. ' - 'Currently unavailable for Multi-edged DiGraphs.', - 'edge_labels': 'Whether or not to draw edge labels.', - 'edge_style': 'The linestyle of the edges. It should be ' - 'one of "solid", "dashed", "dotted", dashdot", or ' - '"-", "--", ":", "-.", respectively. ', - 'edge_thickness': 'The thickness of the edges.', - 'edge_color': 'The default color for edges not listed in edge_colors.', - 'edge_colors': 'a dictionary specifying edge colors: each ' - 'key is a color recognized by matplotlib, and each ' - 'entry is a list of edges.', - 'color_by_label': 'Whether to color the edges according ' - 'to their labels. This also accepts a function or ' - 'dictionary mapping labels to colors.', - 'partition': 'A partition of the vertex set. If specified, ' - 'plot will show each cell in a different color. ' - 'vertex_colors takes precedence.', - 'loop_size': 'The radius of the smallest loop.', - 'dist': 'The distance between multiedges.', - 'max_dist': 'The max distance range to allow multiedges.', - 'talk': 'Whether to display the vertices in talk mode ' - '(larger and white).', - 'graph_border': 'Whether or not to draw a frame around the graph.', - 'edge_labels_background' : 'The color of the background of the edge labels'}) - - -_PLOT_OPTIONS_TABLE = "" -for key, value in graphplot_options.items(): - _PLOT_OPTIONS_TABLE += " ``"+str(key)+"`` | "+str(value)+"\n" -__doc__ = __doc__.format(PLOT_OPTIONS_TABLE=_PLOT_OPTIONS_TABLE) - - # **************************************************************************** # Copyright (C) 2009 Emily Kirkman # 2009 Robert L. Miller @@ -193,34 +124,135 @@ # # https://www.gnu.org/licenses/ # **************************************************************************** + +from collections import defaultdict +from math import sqrt, cos, sin, acos, atan, pi from sage.structure.sage_object import SageObject -from sage.plot.all import Graphics, scatter_plot, bezier_path, line, arrow, text, circle -from math import sqrt, cos, sin, atan, pi +from sage.plot.all import ( + Graphics, scatter_plot, bezier_path, line, arrow, text, arc, circle) + +layout_options = { + 'layout': + 'A layout algorithm -- one of : "acyclic", "circular" (plots the ' + 'graph with vertices evenly distributed on a circle), "ranked", ' + '"graphviz", "planar", "spring" (traditional spring layout, using ' + 'the graph\'s current positions as initial positions), or "tree" ' + '(the tree will be plotted in levels, depending on minimum distance ' + 'for the root).', + 'iterations': + 'The number of times to execute the spring layout algorithm.', + 'heights': + 'A dictionary mapping heights to the list of vertices at this height.', + 'spring': + 'Use spring layout to finalize the current layout.', + 'tree_root': + 'A vertex designation for drawing trees. A vertex of the tree to ' + 'be used as the root for the ``layout=\'tree\'`` option. If no root ' + 'is specified, then one is chosen close to the center of the tree. ' + 'Ignored unless ``layout=\'tree\'``.', + 'forest_roots': + 'An iterable specifying which vertices to use as roots for the ' + '``layout=\'forest\'`` option. If no root is specified for a tree, ' + 'then one is chosen close to the center of the tree. ' + 'Ignored unless ``layout=\'forest\'``.', + 'tree_orientation': + 'The direction of tree branches -- \'up\', \'down\', ' + '\'left\' or \'right\'.', + 'save_pos': + 'Whether or not to save the computed position for the graph.', + 'dim': + 'The dimension of the layout -- 2 or 3.', + 'prog': + 'Which graphviz layout program to use -- one of ' + '"circo", "dot", "fdp", "neato", or "twopi".', + 'by_component': + 'Whether to do the spring layout by connected component -- a boolean.', + } + +graphplot_options = layout_options.copy() + +graphplot_options.update({ + 'pos': + 'The position dictionary of vertices.', + 'vertex_labels': + 'Whether or not to draw vertex labels.', + 'vertex_color': + 'Default color for vertices not listed ' + 'in vertex_colors dictionary.', + 'vertex_colors': + 'A dictionary specifying vertex colors: ' + 'each key is a color recognizable by matplotlib, ' + 'and each corresponding value is a list of vertices.', + 'vertex_size': + 'The size to draw the vertices.', + 'vertex_shape': + 'The shape to draw the vertices. ' + 'Currently unavailable for Multi-edged DiGraphs.', + 'edge_labels': + 'Whether or not to draw edge labels.', + 'edge_style': + 'The linestyle of the edges. It should be ' + 'one of "solid", "dashed", "dotted", dashdot", ' + 'or "-", "--", ":", "-.", respectively. ', + 'edge_thickness': + 'The thickness of the edges.', + 'edge_color': + 'The default color for edges not listed in edge_colors.', + 'edge_colors': + 'A dictionary specifying edge colors: ' + 'each key is a color recognized by matplotlib, ' + 'and each corresponding value is a list of edges.', + 'color_by_label': + 'Whether to color the edges according to their labels. This also ' + 'accepts a function or dictionary mapping labels to colors.', + 'partition': + 'A partition of the vertex set. If specified, plot will show each ' + 'cell in a different color; vertex_colors takes precedence.', + 'loop_size': + 'The radius of the smallest loop.', + 'dist': + 'The distance between multiedges.', + 'max_dist': + 'The max distance range to allow multiedges.', + 'talk': + 'Whether to display the vertices in talk mode (larger and white).', + 'graph_border': + 'Whether or not to draw a frame around the graph.', + 'edge_labels_background': + 'The color of the background of the edge labels.', + }) + +_PLOT_OPTIONS_TABLE = "" + +for key, value in graphplot_options.items(): + _PLOT_OPTIONS_TABLE += f" ``{key}`` | {value}\n" + +__doc__ = __doc__.format(PLOT_OPTIONS_TABLE=_PLOT_OPTIONS_TABLE) DEFAULT_SHOW_OPTIONS = { - "figsize" : [4,4] + 'figsize' : (4, 4) } DEFAULT_PLOT_OPTIONS = { - "vertex_size" : 200, - "vertex_labels" : True, - "layout" : None, - "edge_style" : 'solid', - "edge_thickness" : 1, - "edge_color" : 'black', - "edge_colors" : None, - "edge_labels" : False, - "iterations" : 50, - "tree_orientation" : 'down', - "heights" : None, - "graph_border" : False, - "talk" : False, - "color_by_label" : False, - "partition" : None, - "dist" : .075, - "max_dist" : 1.5, - "loop_size" : .075, - "edge_labels_background" : "white" + 'vertex_size' : 200, + 'vertex_labels' : True, + 'layout' : None, + 'edge_style' : 'solid', + 'edge_thickness' : 1, + 'edge_color' : 'black', + 'edge_colors' : None, + 'edge_labels' : False, + 'iterations' : 50, + 'tree_orientation' : 'down', + 'heights' : None, + 'graph_border' : False, + 'talk' : False, + 'color_by_label' : False, + 'partition' : None, + 'dist' : .075, + 'max_dist' : 1.5, + 'loop_size' : .075, + 'edge_labels_background' : 'white' } class GraphPlot(SageObject): @@ -237,27 +269,26 @@ def __init__(self, graph, options): sage: from sage.graphs.graph_plot import GraphPlot sage: options = { - ....: 'vertex_size': 200, - ....: 'vertex_labels': True, - ....: 'layout': None, - ....: 'edge_style': 'solid', - ....: 'edge_color': 'black', - ....: 'edge_colors': None, - ....: 'edge_labels': False, - ....: 'iterations': 50, - ....: 'tree_orientation': 'down', - ....: 'heights': None, - ....: 'graph_border': False, - ....: 'talk': False, - ....: 'color_by_label': False, - ....: 'partition': None, - ....: 'dist': .075, - ....: 'max_dist': 1.5, - ....: 'loop_size': .075, - ....: 'edge_labels_background': 'transparent'} - sage: g = Graph({0:[1, 2], 2:[3], 4:[0, 1]}) + ....: 'vertex_size': 200, + ....: 'vertex_labels': True, + ....: 'layout': None, + ....: 'edge_style': 'solid', + ....: 'edge_color': 'black', + ....: 'edge_colors': None, + ....: 'edge_labels': False, + ....: 'iterations': 50, + ....: 'tree_orientation': 'down', + ....: 'heights': None, + ....: 'graph_border': False, + ....: 'talk': False, + ....: 'color_by_label': False, + ....: 'partition': None, + ....: 'dist': .075, + ....: 'max_dist': 1.5, + ....: 'loop_size': .075, + ....: 'edge_labels_background': 'transparent'} + sage: g = Graph({0: [1, 2], 2: [3], 4: [0, 1]}) sage: GP = GraphPlot(g, options) - """ # Setting the default values if needed for k, value in DEFAULT_PLOT_OPTIONS.items(): @@ -267,11 +298,11 @@ def __init__(self, graph, options): self._nodelist = list(graph) self._graph = graph self._options = options # contains both plot and show options - self.set_pos() self._arcs = self._graph.has_multiple_edges(to_undirected=True) self._loops = self._graph.has_loops() self._arcdigraph = self._graph.is_directed() and self._arcs + self.set_pos() self.set_vertices() self.set_edges() @@ -283,11 +314,11 @@ def _repr_(self): This function is called implicitly by the code below:: - sage: g = Graph({0:[1,2], 2:[3], 4:[0,1]}) - sage: g.graphplot() # indirect doctest + sage: g = Graph({0: [1, 2], 2: [3], 4: [0, 1]}) + sage: g.graphplot() # indirect doctest GraphPlot object for Graph on 5 vertices """ - return "GraphPlot object for %s"%self._graph + return f"GraphPlot object for {self._graph}" def set_pos(self): """ @@ -297,8 +328,8 @@ def set_pos(self): This function is called implicitly by the code below:: - sage: g = Graph({0:[1,2], 2:[3], 4:[0,1]}) - sage: g.graphplot(save_pos=True, layout='circular') # indirect doctest + sage: g = Graph({0: [1, 2], 2: [3], 4: [0, 1]}) + sage: g.graphplot(save_pos=True, layout='circular') # indirect doctest GraphPlot object for Graph on 5 vertices The following illustrates the format of a position dictionary, but due @@ -315,24 +346,24 @@ def set_pos(self): sage: T = list(graphs.trees(7)) sage: t = T[3] - sage: t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}) + sage: t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]}) Graphics object consisting of 14 graphics primitives .. PLOT:: - g = Graph({0:[1,2], 2:[3], 4:[0,1]}) - g.graphplot(save_pos=True, layout='circular') # indirect doctest + g = Graph({0: [1, 2], 2: [3], 4: [0, 1]}) + g.graphplot(save_pos=True, layout='circular') # indirect doctest T = list(graphs.trees(7)) t = T[3] - P = t.plot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}) + P = t.plot(heights={0: [0], 1: [4, 5, 1], 2: [2], 3: [3, 6]}) sphinx_plot(P) TESTS: - Make sure that vertex locations are floats. Not being floats isn't a - bug in itself but makes it too easy to accidentally introduce a bug + Make sure that vertex locations are floats. Not being floats isn't + a bug in itself but made it too easy to accidentally introduce a bug elsewhere, such as in :meth:`set_edges` (:trac:`10124`), via silent - truncating division of integers:: + truncating division of Python 2 integers:: sage: g = graphs.FruchtGraph() sage: gp = g.graphplot() @@ -349,7 +380,7 @@ def set_pos(self): Graphics object consisting of 6 graphics primitives """ self._pos = self._graph.layout(**self._options) - # make sure the positions are floats (trac #10124) + # Make sure the positions are floats (trac #10124) self._pos = {k: (float(v[0]), float(v[1])) for k, v in self._pos.items()} @@ -357,17 +388,18 @@ def set_vertices(self, **vertex_options): """ Set the vertex plotting parameters for this ``GraphPlot``. - This function is called by the constructor but can also be called to - make updates to the vertex options of an existing ``GraphPlot`` object. - Note that the changes are cumulative. + This function is called by the constructor but can also be + called to make updates to the vertex options of an existing + ``GraphPlot`` object. Note that the changes are cumulative. EXAMPLES:: sage: g = Graph({}, loops=True, multiedges=True, sparse=True) - sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - ....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - sage: GP = g.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, - ....: edge_style='dashed') + sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + ....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + ....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + sage: GP = g.graphplot(vertex_size=100, edge_labels=True, + ....: color_by_label=True, edge_style='dashed') sage: GP.set_vertices(talk=True) sage: GP.plot() Graphics object consisting of 22 graphics primitives @@ -377,25 +409,26 @@ def set_vertices(self, **vertex_options): .. PLOT:: - g = Graph({}, loops=True, multiedges=True, sparse=True) - g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),(0,1,'e'),(0,1,'f'), - (0,1,'f'),(2,1,'g'),(2,2,'h')]) - GP = g.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, - edge_style='dashed') - GP.set_vertices(talk=True) - sphinx_plot(GP) + g = Graph({}, loops=True, multiedges=True, sparse=True) + g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + GP = g.graphplot(vertex_size=100, edge_labels=True, + color_by_label=True, edge_style='dashed') + GP.set_vertices(talk=True) + sphinx_plot(GP) .. PLOT:: - g = Graph({}, loops=True, multiedges=True, sparse=True) - g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'),(0,1,'e'),(0,1,'f'), - (0,1,'f'),(2,1,'g'),(2,2,'h')]) - GP = g.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, - edge_style='dashed') - GP.set_vertices(talk=True) - GP.set_vertices(vertex_color='green', vertex_shape='^') - sphinx_plot(GP) - + g = Graph({}, loops=True, multiedges=True, sparse=True) + g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + GP = g.graphplot(vertex_size=100, edge_labels=True, + color_by_label=True, edge_style='dashed') + GP.set_vertices(talk=True) + GP.set_vertices(vertex_color='green', vertex_shape='^') + sphinx_plot(GP) """ # Handle base vertex options voptions = {} @@ -412,12 +445,14 @@ def set_vertices(self, **vertex_options): else: voptions['markersize'] = self._options['vertex_size'] - if 'vertex_color' not in self._options or self._options['vertex_color'] is None: + if ('vertex_color' not in self._options + or self._options['vertex_color'] is None): vertex_color = '#fec7b8' else: vertex_color = self._options['vertex_color'] - if 'vertex_colors' not in self._options or self._options['vertex_colors'] is None: + if ('vertex_colors' not in self._options + or self._options['vertex_colors'] is None): if self._options['partition'] is not None: from sage.plot.colors import rainbow partition = self._options['partition'] @@ -442,67 +477,61 @@ def set_vertices(self, **vertex_options): if not isinstance(vertex_colors, dict): voptions['facecolor'] = vertex_colors + pos = list(self._pos.values()) if self._arcdigraph: - self._plot_components['vertices'] = [circle(center, - self._vertex_radius, - fill=True, - facecolor=vertex_colors, - edgecolor='black', - clip=False) - for center in self._pos.values()] + self._plot_components['vertices'] = [ + circle(p, self._vertex_radius, fill=True, clip=False, + edgecolor='black', facecolor=vertex_colors) + for p in pos] else: - self._plot_components['vertices'] = scatter_plot(list(self._pos.values()), - clip=False, **voptions) + self._plot_components['vertices'] = ( + scatter_plot(pos, clip=False, **voptions)) else: # Color list must be ordered: pos = [] colors = [] for i in vertex_colors: - pos += [self._pos[j] for j in vertex_colors[i]] - colors += [i] * len(vertex_colors[i]) + pos.extend([self._pos[j] for j in vertex_colors[i]]) + colors.extend([i] * len(vertex_colors[i])) # If all the vertices have not been assigned a color if len(self._pos) != len(pos): leftovers = [j for j in self._pos.values() if j not in pos] - pos += leftovers - colors += [vertex_color] * len(leftovers) + pos.extend(leftovers) + colors.extend([vertex_color] * len(leftovers)) if self._arcdigraph: - self._plot_components['vertices'] = [circle(pos[i], - self._vertex_radius, - fill=True, - facecolor=colors[i], - edgecolor='black', - clip=False) - for i in range(len(pos))] + self._plot_components['vertices'] = [ + circle(p, self._vertex_radius, fill=True, clip=False, + facecolor=colors[i], edgecolor='black') + for i, p in enumerate(pos)] else: - self._plot_components['vertices'] = scatter_plot(pos, - facecolor=colors, - clip=False, **voptions) + self._plot_components['vertices'] = scatter_plot( + pos, facecolor=colors, clip=False, **voptions) if self._options['vertex_labels']: self._plot_components['vertex_labels'] = [] # TODO: allow text options for v in self._nodelist: - self._plot_components['vertex_labels'].append(text(str(v), - self._pos[v], rgbcolor=(0,0,0), zorder=8)) + self._plot_components['vertex_labels'].append( + text(str(v), self._pos[v], rgbcolor=(0, 0, 0), zorder=8)) def set_edges(self, **edge_options): """ - Set the edge (or arrow) plotting parameters for the ``GraphPlot`` - object. + Set edge plotting parameters for the ``GraphPlot`` object. This function is called by the constructor but can also be called to - make updates to the vertex options of an existing ``GraphPlot`` object. + update the vertex options of an existing ``GraphPlot`` object. Note that the changes are cumulative. EXAMPLES:: sage: g = Graph(loops=True, multiedges=True, sparse=True) - sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - ....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - sage: GP = g.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, - ....: edge_style='dashed') + sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + ....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + ....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + sage: GP = g.graphplot(vertex_size=100, edge_labels=True, + ....: color_by_label=True, edge_style='dashed') sage: GP.set_edges(edge_style='solid') sage: GP.plot() Graphics object consisting of 22 graphics primitives @@ -510,10 +539,11 @@ def set_edges(self, **edge_options): .. PLOT:: g = Graph(loops=True, multiedges=True, sparse=True) - g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) + g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) GP = g.graphplot(vertex_size=100, edge_labels=True, - color_by_label=True, edge_style='dashed') + color_by_label=True, edge_style='dashed') GP.set_edges(edge_style='solid') sphinx_plot(GP) @@ -526,10 +556,11 @@ def set_edges(self, **edge_options): .. PLOT:: g = Graph(loops=True, multiedges=True, sparse=True) - g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) + g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) GP = g.graphplot(vertex_size=100, edge_labels=True, - color_by_label=True, edge_style='dashed') + color_by_label=True, edge_style='dashed') GP.set_edges(edge_style='solid') GP.set_edges(edge_color='black') sphinx_plot(GP) @@ -537,10 +568,11 @@ def set_edges(self, **edge_options): :: sage: d = DiGraph(loops=True, multiedges=True, sparse=True) - sage: d.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - ....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - sage: GP = d.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, - ....: edge_style='dashed') + sage: d.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + ....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + ....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + sage: GP = d.graphplot(vertex_size=100, edge_labels=True, + ....: color_by_label=True, edge_style='dashed') sage: GP.set_edges(edge_style='solid') sage: GP.plot() Graphics object consisting of 24 graphics primitives @@ -548,10 +580,11 @@ def set_edges(self, **edge_options): .. PLOT:: d = DiGraph(loops=True, multiedges=True, sparse=True) - d.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - GP = d.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, - edge_style='dashed') + d.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + GP = d.graphplot(vertex_size=100, edge_labels=True, + color_by_label=True, edge_style='dashed') GP.set_edges(edge_style='solid') sphinx_plot(GP) @@ -564,10 +597,11 @@ def set_edges(self, **edge_options): .. PLOT:: d = DiGraph(loops=True, multiedges=True, sparse=True) - d.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - GP = d.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, - edge_style='dashed') + d.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + GP = d.graphplot(vertex_size=100, edge_labels=True, + color_by_label=True, edge_style='dashed') GP.set_edges(edge_style='solid') GP.set_edges(edge_color='black') sphinx_plot(GP) @@ -575,10 +609,11 @@ def set_edges(self, **edge_options): TESTS:: sage: G = Graph("Fooba") - sage: G.show(edge_colors={'red':[(3,6),(2,5)]}) + sage: G.show(edge_colors={'red':[(3, 6), (2, 5)]}) - Verify that default edge labels are pretty close to being between the vertices - in some cases where they weren't due to truncating division (:trac:`10124`):: + Check default edge labels are pretty close to halfway between + the vertices in some cases where they weren't due to Python 2 + truncating division (:trac:`10124`):: sage: test_graphs = graphs.FruchtGraph(), graphs.BullGraph() sage: tol = 0.001 @@ -593,8 +628,8 @@ def set_edges(self, **edge_options): ....: textobj = elab[0] ....: x, y, s = textobj.x, textobj.y, textobj.string ....: v0, v1 = map(int, s.split()) - ....: vn = vector(((x-(vx[v0]+vx[v1])/2.), y-(vy[v0]+vy[v1])/2.)).norm() - ....: assert vn < tol + ....: m = sum(vector((vx[v], vy[v])) for v in (v0, v1))/2 + ....: assert (vector((x, y)) - m).norm() < tol Ticket :trac:`24051` is fixed:: @@ -604,11 +639,11 @@ def set_edges(self, **edge_options): Ticket :trac:`31542` is fixed:: - sage: stnc = 'ABCCCCDABCDABCDA' + sage: s = 'ABCCCCDABCDABCDA' sage: g = DiGraph({}, loops=True, multiedges=True) - sage: for a, b in [(stnc[i], stnc[i + 1]) for i in range(len(stnc) - 1)]: + sage: for a, b in [(s[i], u) for i, u in enumerate(s[1:])]: ....: g.add_edge(a, b, b) - sage: g.plot(color_by_label=True, edge_style='solid', layout='circular') + sage: g.plot(color_by_label=True, layout='circular') Graphics object consisting of 23 graphics primitives """ for arg in edge_options: @@ -635,69 +670,65 @@ def set_edges(self, **edge_options): self._plot_components['edge_labels'] = [] # Make dict collection of all edges (keep label and edge color) - edges_to_draw = {} - - def append_or_set(key, label, color, head): - if key in edges_to_draw: - edges_to_draw[key].append((label, color, head)) - else: - edges_to_draw[key] = [(label, color, head)] + edges_to_draw = defaultdict(list) v_to_int = {v: i for i, v in enumerate(self._graph)} - if self._options['color_by_label'] or isinstance(self._options['edge_colors'], dict): + if (self._options['color_by_label'] + or isinstance(self._options['edge_colors'], dict)): if self._options['color_by_label']: - edge_colors = self._graph._color_by_label(format=self._options['color_by_label']) + edge_colors = self._graph._color_by_label( + format=self._options['color_by_label']) else: edge_colors = self._options['edge_colors'] edges_drawn = [] for color in edge_colors: for edge in edge_colors[color]: - if v_to_int[edge[0]] < v_to_int[edge[1]]: - key = (edge[0], edge[1]) + a, b = edge[0], edge[1] + if v_to_int[a] < v_to_int[b]: + key = (a, b) head = 1 else: - key = (edge[1], edge[0]) + key = (b, a) head = 0 if len(edge) < 3: - label = self._graph.edge_label(edge[0], edge[1]) + label = self._graph.edge_label(a, b) if isinstance(label, list): - append_or_set(key, label[-1], color, head) - edges_drawn.append((edge[0], edge[1], label[-1])) - for i in range(len(label) - 1): - edges_to_draw[key].append((label[i], color, head)) - edges_drawn.append((edge[0], edge[1], label[i])) + edges_to_draw[key].append((label[-1], color, head)) + edges_drawn.append((a, b, label[-1])) + for lab in label[:-1]: + edges_to_draw[key].append((lab, color, head)) + edges_drawn.append((a, b, lab)) else: - append_or_set(key, label, color, head) - edges_drawn.append((edge[0], edge[1], label)) + edges_to_draw[key].append((label, color, head)) + edges_drawn.append((a, b, label)) else: label = edge[2] - append_or_set(key, label, color, head) - edges_drawn.append((edge[0], edge[1], label)) + edges_to_draw[key].append((label, color, head)) + edges_drawn.append((a, b, label)) # Add unspecified edges (default color black set in DEFAULT_PLOT_OPTIONS) - for edge in self._graph.edge_iterator(): - if ((edge[0], edge[1], edge[2]) not in edges_drawn and - (self._graph.is_directed() or - (edge[1], edge[0], edge[2]) not in edges_drawn - )): - if v_to_int[edge[0]] < v_to_int[edge[1]]: - key = (edge[0], edge[1]) + for a, b, c in self._graph.edge_iterator(): + if ((a, b, c) not in edges_drawn + and (self._graph.is_directed() + or (b, a, c) not in edges_drawn)): + if v_to_int[a] < v_to_int[b]: + key = (a, b) head = 1 else: - key = (edge[1], edge[0]) + key = (b, a) head = 0 - append_or_set(key, edge[2], self._options['edge_color'], head) + edges_to_draw[key].append((c, self._options['edge_color'], head)) else: - for edge in self._graph.edge_iterator(): - if v_to_int[edge[0]] < v_to_int[edge[1]]: - key = (edge[0], edge[1]) + for a, b, c in self._graph.edge_iterator(): + if v_to_int[a] < v_to_int[b]: + key = (a, b) head = 1 else: - key = (edge[1], edge[0]) + key = (b, a) head = 0 - append_or_set(key, edge[2], self._options['edge_color'], head) + edges_to_draw[key].append((c, self._options['edge_color'], head)) if edges_to_draw: self._plot_components['edges'] = [] @@ -707,62 +738,68 @@ def append_or_set(key, label, color, head): # Check for multi-edges or loops if self._arcs or self._loops: tmp = edges_to_draw.copy() - dist = self._options['dist'] * 2. - loop_size = self._options['loop_size'] + dist = self._options['dist'] * 2 + min_loop_size = self._options['loop_size'] max_dist = self._options['max_dist'] from sage.functions.all import sqrt for a, b in tmp: if a == b: - # Loops + # Multiple loops need varying loop radius starting at + # minimum loop size and respecting other distances + loop_size = min_loop_size # current loop radius distance = dist local_labels = edges_to_draw.pop((a, b)) len_local_labels = len(local_labels) if len_local_labels * dist > max_dist: distance = float(max_dist) / len_local_labels - curr_loop_size = loop_size - for i in range(len_local_labels): - self._plot_components['edges'].append(circle((self._pos[a][0], - self._pos[a][1]-curr_loop_size), curr_loop_size, - rgbcolor=local_labels[i][1], **eoptions)) + loop_size_increment = distance / 4 + # Now add all the loops at this vertex, varying their size + for lab, col, _ in local_labels: + x, y = self._pos[a][0], self._pos[a][1] - loop_size + c = circle((x, y), loop_size, rgbcolor=col, **eoptions) + self._plot_components['edges'].append(c) if labels: - self._plot_components['edge_labels'].append(text(local_labels[i][0], - (self._pos[a][0], self._pos[a][1] - 2 * curr_loop_size), - background_color=self._options['edge_labels_background'])) - curr_loop_size += distance / 4 - elif len(edges_to_draw[a,b]) > 1: + bg = self._options['edge_labels_background'] + y -= loop_size # place label at bottom of loop + t = text(lab, (x, y), background_color=bg) + self._plot_components['edge_labels'].append(t) + loop_size += loop_size_increment + elif len(edges_to_draw[a, b]) > 1: # Multi-edge local_labels = edges_to_draw.pop((a,b)) # Compute perpendicular bisector p1 = self._pos[a] p2 = self._pos[b] - M = ((p1[0]+p2[0])/2., (p1[1]+p2[1])/2.) # midpoint + m = ((p1[0] + p2[0])/2., (p1[1] + p2[1])/2.) # midpoint if not p1[1] == p2[1]: - S = float(p1[0]-p2[0]) / (p2[1]-p1[1]) # perp slope - y = lambda x: S*x-S*M[0]+M[1] # perp bisector line + s = (p1[0] - p2[0])/(p2[1] - p1[1]) # perp slope + y = lambda x: s*(x - m[0]) + m[1] # perp bisector line - # f,g are functions of distance d to determine x values - # on line y at d from point M - f = lambda d: sqrt(d**2/(1.+S**2)) + M[0] - g = lambda d: -sqrt(d**2/(1.+S**2)) + M[0] + # f, g are functions to determine x-values of point + # on line y at distance d from point m (on each side) + f = lambda d: sqrt(d**2/(1. + s**2)) + m[0] + g = lambda d: -sqrt(d**2/(1. + s**2)) + m[0] odd_x = f even_x = g if p1[0] == p2[0]: - odd_y = lambda d: M[1] + odd_y = lambda d: m[1] even_y = odd_y else: odd_y = lambda x: y(f(x)) even_y = lambda x: y(g(x)) else: - odd_x = lambda d: M[0] + odd_x = lambda d: m[0] even_x = odd_x - odd_y = lambda d: M[1] + d - even_y = lambda d: M[1] - d + odd_y = lambda d: m[1] + d + even_y = lambda d: m[1] - d + odd_xy = lambda d: (odd_x(d), odd_y(d)) + even_xy = lambda d: (even_x(d), even_y(d)) - # We now have the control points for each bezier curve + # We now have the control points for each Bezier curve # in terms of distance parameter d. - # Also note that the label for each edge should be drawn at d/2. + # Also note each edge label should be drawn at d/2. # (This is because we're using the perp bisectors). distance = dist len_local_labels = len(local_labels) @@ -771,75 +808,95 @@ def append_or_set(key, label, color, head): for i in range(len_local_labels // 2): k = (i + 1.0) * distance if self._arcdigraph: - odd_start = self._polar_hack_for_multidigraph(p1, - [odd_x(k), odd_y(k)], self._vertex_radius)[0] - odd_end = self._polar_hack_for_multidigraph([odd_x(k), odd_y(k)], - p2, self._vertex_radius)[1] - even_start = self._polar_hack_for_multidigraph(p1, - [even_x(k), even_y(k)], self._vertex_radius)[0] - even_end = self._polar_hack_for_multidigraph([even_x(k), even_y(k)], - p2, self._vertex_radius)[1] - self._plot_components['edges'].append(arrow(path=[[odd_start, - [odd_x(k), odd_y(k)], odd_end]], head=local_labels[2*i][2], - zorder=1, rgbcolor=local_labels[2*i][1], **eoptions)) - self._plot_components['edges'].append(arrow(path=[[even_start, - [even_x(k), even_y(k)], even_end]], head=local_labels[2*i+1][2], - zorder=1, rgbcolor=local_labels[2*i+1][1], **eoptions)) + vr = self._vertex_radius + ph = self._polar_hack_for_multidigraph + odd_start = ph(p1, odd_xy(k), vr)[0] + odd_end = ph(odd_xy(k), p2, vr)[1] + even_start = ph(p1, even_xy(k), vr)[0] + even_end = ph(even_xy(k), p2, vr)[1] + self._plot_components['edges'].append( + arrow(path=[[odd_start, odd_xy(k), odd_end]], + head=local_labels[2*i][2], zorder=1, + rgbcolor=local_labels[2*i][1], + **eoptions)) + self._plot_components['edges'].append( + arrow(path=[[even_start, even_xy(k), even_end]], + head=local_labels[2*i + 1][2], zorder=1, + rgbcolor=local_labels[2*i + 1][1], + **eoptions)) else: - self._plot_components['edges'].append(bezier_path([[p1, - [odd_x(k), odd_y(k)], p2]], zorder=1, - rgbcolor=local_labels[2*i][1], **eoptions)) - self._plot_components['edges'].append(bezier_path([[p1, - [even_x(k), even_y(k)], p2]], zorder=1, - rgbcolor=local_labels[2*i+1][1], **eoptions)) + self._plot_components['edges'].append( + bezier_path([[p1, odd_xy(k), p2]], zorder=1, + rgbcolor=local_labels[2*i][1], + **eoptions)) + self._plot_components['edges'].append( + bezier_path([[p1, even_xy(k), p2]], zorder=1, + rgbcolor=local_labels[2*i + 1][1], + **eoptions)) if labels: j = k / 2.0 - self._plot_components['edge_labels'].append(text(local_labels[2*i][0], - [odd_x(j), odd_y(j)], background_color=self._options['edge_labels_background'])) - self._plot_components['edge_labels'].append(text(local_labels[2*i+1][0], - [even_x(j), even_y(j)], - background_color=self._options['edge_labels_background'])) + bg = self._options['edge_labels_background'] + self._plot_components['edge_labels'].append( + text(local_labels[2*i][0], odd_xy(j), + background_color=bg)) + self._plot_components['edge_labels'].append( + text(local_labels[2*i + 1][0], even_xy(j), + background_color=bg)) if len_local_labels % 2: - edges_to_draw[a,b] = [local_labels[-1]] # draw line for last odd + # draw line for last odd + edges_to_draw[a, b] = [local_labels[-1]] is_directed = self._graph.is_directed() for a,b in edges_to_draw: if self._arcdigraph: - C, D = self._polar_hack_for_multidigraph(self._pos[a], self._pos[b], self._vertex_radius) - self._plot_components['edges'].append(arrow(C, D, - rgbcolor=edges_to_draw[a,b][0][1], head=edges_to_draw[a,b][0][2], - **eoptions)) + ph = self._polar_hack_for_multidigraph + C, D = ph(self._pos[a], self._pos[b], self._vertex_radius) + self._plot_components['edges'].append( + arrow(C, D, + rgbcolor=edges_to_draw[a, b][0][1], + head=edges_to_draw[a, b][0][2], + **eoptions)) if labels: - self._plot_components['edge_labels'].append(text(str(edges_to_draw[a,b][0][0]), - [(C[0]+D[0])/2., (C[1]+D[1])/2.], - background_color=self._options['edge_labels_background'])) + bg = self._options['edge_labels_background'] + self._plot_components['edge_labels'].append( + text(str(edges_to_draw[a, b][0][0]), + [(C[0] + D[0])/2., (C[1] + D[1])/2.], + background_color=bg)) elif is_directed: - self._plot_components['edges'].append(arrow(self._pos[a], self._pos[b], - rgbcolor=edges_to_draw[a,b][0][1], arrowshorten=self._arrowshorten, - head=edges_to_draw[a,b][0][2], **eoptions)) + self._plot_components['edges'].append( + arrow(self._pos[a], self._pos[b], + rgbcolor=edges_to_draw[a, b][0][1], + arrowshorten=self._arrowshorten, + head=edges_to_draw[a, b][0][2], + **eoptions)) else: - self._plot_components['edges'].append(line([self._pos[a], self._pos[b]], - rgbcolor=edges_to_draw[a,b][0][1], **eoptions)) + self._plot_components['edges'].append( + line([self._pos[a], self._pos[b]], + rgbcolor=edges_to_draw[a, b][0][1], + **eoptions)) if labels and not self._arcdigraph: - self._plot_components['edge_labels'].append(text(str(edges_to_draw[a,b][0][0]), - [(self._pos[a][0] + self._pos[b][0])/2., - (self._pos[a][1] + self._pos[b][1])/2.], - background_color=self._options['edge_labels_background'])) + bg = self._options['edge_labels_background'] + self._plot_components['edge_labels'].append( + text(str(edges_to_draw[a, b][0][0]), + [(self._pos[a][0] + self._pos[b][0])/2., + (self._pos[a][1] + self._pos[b][1])/2.], + background_color=bg)) def _polar_hack_for_multidigraph(self, A, B, VR): """ - Helper function to quickly compute the two points of intersection of a - line segment from A to B (xy tuples) and circles centered at A and B, - both with radius VR. Returns a tuple of xy tuples representing the two - points. + Helper function to quickly compute the two points of intersection + of a line segment from ``A`` to ``B`` (provided as xy pairs) and + circles centered at ``A`` and ``B``, both with radius ``VR``. + Returns a pair of xy pairs representing the two points. EXAMPLES:: sage: d = DiGraph(loops=True, multiedges=True, sparse=True) - sage: d.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - ....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - sage: GP = d.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, - ....: edge_style='dashed') + sage: d.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + ....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + ....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + sage: GP = d.graphplot(vertex_size=100, edge_labels=True, + ....: color_by_label=True, edge_style='dashed') sage: GP._polar_hack_for_multidigraph((0, 1), (1, 1), .1) ([0.10..., 1.00...], [0.90..., 1.00...]) @@ -850,7 +907,8 @@ def _polar_hack_for_multidigraph(self, A, B, VR): sage: GP = DiGraph().graphplot() sage: GP._polar_hack_for_multidigraph((0, 1), (2, 2), .1) ([0.08..., 1.04...], [1.91..., 1.95...]) - sage: GP._polar_hack_for_multidigraph((int(0), int(1)), (int(2), int(2)), .1) + sage: GP._polar_hack_for_multidigraph((int(0), int(1)), + ....: (int(2), int(2)), .1) ([0.08..., 1.04...], [1.91..., 1.95...]) """ @@ -872,7 +930,7 @@ def _polar_hack_for_multidigraph(self, A, B, VR): def show(self, **kwds): """ - Show the (Di)Graph associated with this ``GraphPlot`` object. + Show the (di)graph associated with this ``GraphPlot`` object. INPUT: @@ -881,8 +939,8 @@ def show(self, **kwds): .. NOTE:: - - See :mod:`the module's documentation ` for - information on default values of this method. + - See :mod:`the module's documentation ` + for information on default values of this method. - Any options not used by plot will be passed on to the :meth:`~sage.plot.graphics.Graphics.show` method. @@ -890,13 +948,15 @@ def show(self, **kwds): EXAMPLES:: sage: C = graphs.CubeGraph(8) - sage: P = C.graphplot(vertex_labels=False, vertex_size=0, graph_border=True) + sage: P = C.graphplot(vertex_labels=False, vertex_size=0, + ....: graph_border=True) sage: P.show() .. PLOT:: C = graphs.CubeGraph(8) - P = C.graphplot(vertex_labels=False, vertex_size=0, graph_border=True) + P = C.graphplot(vertex_labels=False, vertex_size=0, + graph_border=True) sphinx_plot(P) """ @@ -913,9 +973,9 @@ def plot(self, **kwds): INPUT: - The options accepted by this method are to be found in the documentation - of the :mod:`sage.graphs.graph_plot` module, and the - :meth:`~sage.plot.graphics.Graphics.show` method. + The options accepted by this method are to be found in the + documentation of the :mod:`sage.graphs.graph_plot` module, + and the :meth:`~sage.plot.graphics.Graphics.show` method. .. NOTE:: @@ -926,15 +986,15 @@ def plot(self, **kwds): sage: from math import sin, cos, pi sage: P = graphs.PetersenGraph() - sage: d = {'#FF0000':[0,5], '#FF9900':[1,6], '#FFFF00':[2,7], '#00FF00':[3,8], - ....: '#0000FF':[4,9]} + sage: d = {'#FF0000': [0, 5], '#FF9900': [1, 6], '#FFFF00': [2, 7], + ....: '#00FF00': [3, 8], '#0000FF': [4,9]} sage: pos_dict = {} sage: for i in range(5): ....: x = float(cos(pi/2 + ((2*pi)/5)*i)) ....: y = float(sin(pi/2 + ((2*pi)/5)*i)) ....: pos_dict[i] = [x,y] ... - sage: for i in range(5,10): + sage: for i in range(5, 10): ....: x = float(0.5*cos(pi/2 + ((2*pi)/5)*i)) ....: y = float(0.5*sin(pi/2 + ((2*pi)/5)*i)) ....: pos_dict[i] = [x,y] @@ -946,15 +1006,15 @@ def plot(self, **kwds): from math import sin, cos, pi P = graphs.PetersenGraph() - d = {'#FF0000':[0,5], '#FF9900':[1,6], '#FFFF00':[2,7], '#00FF00':[3,8], - '#0000FF':[4,9]} + d = {'#FF0000': [0, 5], '#FF9900': [1, 6], '#FFFF00': [2, 7], + '#00FF00': [3, 8], '#0000FF': [4,9]} pos_dict = {} for i in range(5): x = float(cos(pi/2 + ((2*pi)/5)*i)) y = float(sin(pi/2 + ((2*pi)/5)*i)) pos_dict[i] = [x,y] - for i in range(5,10): + for i in range(5, 10): x = float(0.5*cos(pi/2 + ((2*pi)/5)*i)) y = float(0.5*sin(pi/2 + ((2*pi)/5)*i)) pos_dict[i] = [x,y] @@ -965,47 +1025,53 @@ def plot(self, **kwds): Here are some more common graphs with typical options:: sage: C = graphs.CubeGraph(8) - sage: P = C.graphplot(vertex_labels=False, vertex_size=0, graph_border=True) + sage: P = C.graphplot(vertex_labels=False, vertex_size=0, + ....: graph_border=True) sage: P.show() .. PLOT:: C = graphs.CubeGraph(8) - P = C.graphplot(vertex_labels=False, vertex_size=0, graph_border=True) + P = C.graphplot(vertex_labels=False, vertex_size=0, + graph_border=True) sphinx_plot(P) :: sage: G = graphs.HeawoodGraph().copy(sparse=True) - sage: for u,v,l in G.edges(): - ....: G.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')') + sage: for u, v, l in G.edges(): + ....: G.set_edge_label(u, v, f'({u},{v})') sage: G.graphplot(edge_labels=True).show() .. PLOT:: G = graphs.HeawoodGraph().copy(sparse=True) - for u,v,l in G.edges(): - G.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')') + for u, v, l in G.edges(): + G.set_edge_label(u, v, f'({u},{v})') sphinx_plot(G.graphplot(edge_labels=True)) The options for plotting also work with directed graphs:: - sage: D = DiGraph( { 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], - ....: 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], 9: [10, 13], - ....: 10: [11], 11: [12, 18], 12: [16, 13], 13: [14], 14: [15], 15: [16], - ....: 16: [17], 17: [18], 18: [19], 19: []}) - sage: for u,v,l in D.edges(): - ....: D.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')') + sage: D = DiGraph({ + ....: 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], + ....: 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], + ....: 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13], + ....: 13: [14], 14: [15], 15: [16], 16: [17], 17: [18], + ....: 18: [19], 19: []}) + sage: for u, v, l in D.edges(): + ....: D.set_edge_label(u, v, f'({u},{v})') sage: D.graphplot(edge_labels=True, layout='circular').show() .. PLOT:: - D = DiGraph( { 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], 4: [17, 5], - 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], 9: [10, 13], 10: [11], - 11: [12, 18],12: [16, 13], 13: [14], 14: [15], 15: [16], 16: [17], - 17: [18], 18: [19], 19: []}) - for u,v,l in D.edges(): - D.set_edge_label(u,v,'(' + str(u) + ',' + str(v) + ')') + D = DiGraph({ + 0: [1, 10, 19], 1: [8, 2], 2: [3, 6], 3: [19, 4], + 4: [17, 5], 5: [6, 15], 6: [7], 7: [8, 14], 8: [9], + 9: [10, 13], 10: [11], 11: [12, 18], 12: [16, 13], + 13: [14], 14: [15], 15: [16], 16: [17], 17: [18], + 18: [19], 19: []}) + for u, v, l in D.edges(): + D.set_edge_label(u, v, f'({u},{v})') sphinx_plot(D.graphplot(edge_labels=True, layout='circular')) This example shows off the coloring of edges:: @@ -1015,12 +1081,13 @@ def plot(self, **kwds): sage: R = rainbow(5) sage: edge_colors = {} sage: for i in range(5): - ....: edge_colors[R[i]] = [] - sage: for u,v,l in C.edges(): - ....: for i in range(5): - ....: if u[i] != v[i]: - ....: edge_colors[R[i]].append((u,v,l)) - sage: C.graphplot(vertex_labels=False, vertex_size=0, edge_colors=edge_colors).show() + ....: edge_colors[R[i]] = [] + sage: for u, v, l in C.edges(): + ....: for i in range(5): + ....: if u[i] != v[i]: + ....: edge_colors[R[i]].append((u, v, l)) + sage: C.graphplot(vertex_labels=False, vertex_size=0, + ....: edge_colors=edge_colors).show() .. PLOT:: @@ -1030,24 +1097,26 @@ def plot(self, **kwds): edge_colors = {} for i in range(5): edge_colors[R[i]] = [] - for u,v,l in C.edges(): + for u, v, l in C.edges(): for i in range(5): if u[i] != v[i]: - edge_colors[R[i]].append((u,v,l)) + edge_colors[R[i]].append((u, v, l)) sphinx_plot(C.graphplot(vertex_labels=False, vertex_size=0, - edge_colors=edge_colors)) + edge_colors=edge_colors)) With the ``partition`` option, we can separate out same-color groups of vertices:: sage: D = graphs.DodecahedralGraph() - sage: Pi = [[6,5,15,14,7],[16,13,8,2,4],[12,17,9,3,1],[0,19,18,10,11]] + sage: Pi = [[6, 5, 15, 14, 7], [16, 13, 8, 2, 4], + ....: [12, 17, 9, 3, 1], [0, 19, 18, 10, 11]] sage: D.show(partition=Pi) .. PLOT:: D = graphs.DodecahedralGraph() - Pi = [[6,5,15,14,7],[16,13,8,2,4],[12,17,9,3,1],[0,19,18,10,11]] + Pi = [[6, 5, 15, 14, 7], [16, 13, 8, 2, 4], + [12, 17, 9, 3, 1], [0, 19, 18, 10, 11]] sphinx_plot(D.plot(partition=Pi)) Loops are also plotted correctly:: @@ -1068,27 +1137,29 @@ def plot(self, **kwds): sage: D = DiGraph({0:[0,1], 1:[2], 2:[3]}, loops=True) sage: D.show() - sage: D.show(edge_colors={(0,1,0):[(0,1,None),(1,2,None)],(0,0,0):[(2,3,None)]}) + sage: D.show(edge_colors={(0, 1, 0): [(0, 1, None), (1, 2, None)], + ....: (0, 0, 0): [(2, 3, None)]}) .. PLOT:: D = DiGraph({0:[0,1], 1:[2], 2:[3]}, loops=True) - P = D.plot(edge_colors={(0,1,0):[(0,1,None),(1,2,None)],(0,0,0):[(2,3,None)]}) + P = D.plot(edge_colors={(0, 1, 0): [(0, 1, None), (1, 2, None)], + (0, 0, 0): [(2, 3, None)]}) sphinx_plot(P) More options:: - sage: pos = {0:[0.0, 1.5], 1:[-0.8, 0.3], 2:[-0.6, -0.8], - ....: 3:[0.6, -0.8], 4:[0.8, 0.3]} - sage: g = Graph({0:[1], 1:[2], 2:[3], 3:[4], 4:[0]}) + sage: pos = {0: [0.0, 1.5], 1: [-0.8, 0.3], 2: [-0.6, -0.8], + ....: 3:[0.6, -0.8], 4:[0.8, 0.3]} + sage: g = Graph({0: [1], 1: [2], 2: [3], 3: [4], 4: [0]}) sage: g.graphplot(pos=pos, layout='spring', iterations=0).plot() Graphics object consisting of 11 graphics primitives .. PLOT:: - pos = {0:[0.0, 1.5], 1:[-0.8, 0.3], 2:[-0.6, -0.8], - 3:[0.6, -0.8], 4:[0.8, 0.3]} - g = Graph({0:[1], 1:[2], 2:[3], 3:[4], 4:[0]}) + pos = {0: [0.0, 1.5], 1: [-0.8, 0.3], 2: [-0.6, -0.8], + 3: [0.6, -0.8], 4:[0.8, 0.3]} + g = Graph({0: [1], 1: [2], 2: [3], 3: [4], 4: [0]}) P = g.graphplot(pos=pos, layout='spring', iterations=0).plot() sphinx_plot(P) @@ -1107,52 +1178,63 @@ def plot(self, **kwds): sage: T = list(graphs.trees(7)) sage: t = T[3] - sage: t.graphplot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}).plot() + sage: t.graphplot(heights={0: [0], 1: [4, 5, 1], + ....: 2: [2], 3: [3, 6]} + ....: ).plot() Graphics object consisting of 14 graphics primitives .. PLOT:: T = list(graphs.trees(7)) t = T[3] - sphinx_plot(t.graphplot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]})) + sphinx_plot(t.graphplot(heights={0: [0], 1: [4, 5, 1], + 2: [2], 3: [3, 6]})) :: sage: T = list(graphs.trees(7)) sage: t = T[3] - sage: t.graphplot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}).plot() + sage: t.graphplot(heights={0: [0], 1: [4, 5, 1], + ....: 2: [2], 3: [3, 6]} + ....: ).plot() Graphics object consisting of 14 graphics primitives .. PLOT:: T = list(graphs.trees(7)) t = T[3] - sphinx_plot(t.graphplot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]})) + sphinx_plot(t.graphplot(heights={0: [0], 1: [4, 5, 1], + 2: [2], 3: [3, 6]})) :: - sage: t.set_edge_label(0,1,-7) - sage: t.set_edge_label(0,5,3) - sage: t.set_edge_label(0,5,99) - sage: t.set_edge_label(1,2,1000) - sage: t.set_edge_label(3,2,'spam') - sage: t.set_edge_label(2,6,3/2) - sage: t.set_edge_label(0,4,66) - sage: t.graphplot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}, edge_labels=True).plot() + sage: t.set_edge_label(0, 1, -7) + sage: t.set_edge_label(0, 5, 3) + sage: t.set_edge_label(0, 5, 99) + sage: t.set_edge_label(1, 2, 1000) + sage: t.set_edge_label(3, 2, 'spam') + sage: t.set_edge_label(2, 6, 3/2) + sage: t.set_edge_label(0, 4, 66) + sage: t.graphplot(heights={0: [0], 1: [4, 5, 1], + ....: 2: [2], 3: [3, 6]}, + ....: edge_labels=True + ....: ).plot() Graphics object consisting of 20 graphics primitives .. PLOT:: T = list(graphs.trees(7)) t = T[3] - t.set_edge_label(0,1,-7) - t.set_edge_label(0,5,3) - t.set_edge_label(0,5,99) - t.set_edge_label(1,2,1000) - t.set_edge_label(3,2,'spam') - t.set_edge_label(2,6,3/2) - t.set_edge_label(0,4,66) - sphinx_plot(t.graphplot(heights={0:[0], 1:[4,5,1], 2:[2], 3:[3,6]}, edge_labels=True)) + t.set_edge_label(0, 1, -7) + t.set_edge_label(0, 5, 3) + t.set_edge_label(0, 5, 99) + t.set_edge_label(1, 2, 1000) + t.set_edge_label(3, 2, 'spam') + t.set_edge_label(2, 6, 3/2) + t.set_edge_label(0, 4, 66) + sphinx_plot(t.graphplot(heights={0: [0], 1: [4, 5, 1], + 2: [2], 3: [3, 6]}, + edge_labels=True)) :: @@ -1169,12 +1251,15 @@ def plot(self, **kwds): The tree layout is also useful:: sage: t = DiGraph('JCC???@A??GO??CO??GO??') - sage: t.graphplot(layout='tree', tree_root=0, tree_orientation="up").show() + sage: t.graphplot(layout='tree', tree_root=0, + ....: tree_orientation="up" + ....: ).show() .. PLOT:: t = DiGraph('JCC???@A??GO??CO??GO??') - sphinx_plot(t.graphplot(layout='tree', tree_root=0, tree_orientation="up")) + sphinx_plot(t.graphplot(layout='tree', tree_root=0, + tree_orientation="up")) More examples:: @@ -1190,37 +1275,50 @@ def plot(self, **kwds): sage: D = DiGraph(multiedges=True, sparse=True) sage: for i in range(5): - ....: D.add_edge((i,i+1,'a')) - ....: D.add_edge((i,i-1,'b')) - sage: D.graphplot(edge_labels=True,edge_colors=D._color_by_label()).plot() + ....: D.add_edge((i, i + 1, 'a')) + ....: D.add_edge((i, i - 1, 'b')) + sage: D.graphplot(edge_labels=True, + ....: edge_colors=D._color_by_label() + ....: ).plot() Graphics object consisting of 34 graphics primitives .. PLOT:: D = DiGraph(multiedges=True, sparse=True) for i in range(5): - D.add_edge((i,i+1,'a')) - D.add_edge((i,i-1,'b')) - sphinx_plot(D.graphplot(edge_labels=True,edge_colors=D._color_by_label())) + D.add_edge((i, i + 1, 'a')) + D.add_edge((i, i - 1, 'b')) + sphinx_plot(D.graphplot(edge_labels=True, + edge_colors=D._color_by_label())) :: sage: g = Graph({}, loops=True, multiedges=True, sparse=True) - sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - ....: (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - sage: g.graphplot(edge_labels=True, color_by_label=True, edge_style='dashed').plot() + sage: g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + ....: (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + ....: (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + sage: g.graphplot(edge_labels=True, + ....: color_by_label=True, + ....: edge_style='dashed' + ....: ).plot() Graphics object consisting of 22 graphics primitives .. PLOT:: g = Graph({}, loops=True, multiedges=True, sparse=True) - g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), - (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) - sphinx_plot(g.graphplot(edge_labels=True, color_by_label=True, edge_style='dashed')) + g.add_edges([(0, 0, 'a'), (0, 0, 'b'), (0, 1, 'c'), + (0, 1, 'd'), (0, 1, 'e'), (0, 1, 'f'), + (0, 1, 'f'), (2, 1, 'g'), (2, 2, 'h')]) + sphinx_plot(g.graphplot(edge_labels=True, + color_by_label=True, + edge_style='dashed')) The ``edge_style`` option may be provided in the short format too:: - sage: g.graphplot(edge_labels=True, color_by_label=True, edge_style='--').plot() + sage: g.graphplot(edge_labels=True, + ....: color_by_label=True, + ....: edge_style='--' + ....: ).plot() Graphics object consisting of 22 graphics primitives TESTS: @@ -1240,16 +1338,18 @@ def plot(self, **kwds): Make sure that no graphics primitive is clipped:: - sage: tadpole = Graph({0:[0,1]}).plot() + sage: tadpole = Graph({0: [0, 1]}).plot() sage: bbox = tadpole.get_minmax_data() sage: for part in tadpole: ....: part_bbox = part.get_minmax_data() - ....: assert bbox['xmin'] <= part_bbox['xmin'] <= part_bbox['xmax'] <= bbox['xmax'] - ....: assert bbox['ymin'] <= part_bbox['ymin'] <= part_bbox['ymax'] <= bbox['ymax'] + ....: assert (bbox['xmin'] <= part_bbox['xmin'] + ....: <= part_bbox['xmax'] <= bbox['xmax']) + ....: assert (bbox['ymin'] <= part_bbox['ymin'] + ....: <= part_bbox['ymax'] <= bbox['ymax']) Check that one can plot immutable graphs (:trac:`17340`):: - sage: Graph({0:[0]},immutable=True).plot() + sage: Graph({0: [0]}, immutable=True).plot() Graphics object consisting of 3 graphics primitives """ G = Graphics() @@ -1306,17 +1406,18 @@ def layout_tree(self, root, orientation): sage: G = graphs.HoffmanSingletonGraph() sage: T = Graph() sage: T.add_edges(G.min_spanning_tree(starting_vertex=0)) - sage: T.show(layout='tree', tree_root=0) # indirect doctest + sage: T.show(layout='tree', tree_root=0) # indirect doctest """ T = self._graph if not self._graph.is_tree(): - raise RuntimeError("Cannot use tree layout on this graph: self.is_tree() returns False.") + raise RuntimeError("Cannot use tree layout on this graph: " + "self.is_tree() returns False.") children = {root: T.neighbors(root)} - #always make a copy of the children because they get eaten + # Always make a copy of the children because they get eaten stack = [[u for u in children[root]]] stick = [root] parent = {u: root for u in children[root]} @@ -1333,7 +1434,6 @@ def slide(v, dx): Precondition: v and its descendents have already had their positions computed. - """ level = [v] while level: @@ -1344,7 +1444,6 @@ def slide(v, dx): obstruction[y] = max(x + 1, obstruction[y]) pos[u] = x, y nextlevel += children[u] - level = nextlevel while stack: diff --git a/src/sage/graphs/hyperbolicity.pyx b/src/sage/graphs/hyperbolicity.pyx index 479dfd40616..1f479774b7e 100644 --- a/src/sage/graphs/hyperbolicity.pyx +++ b/src/sage/graphs/hyperbolicity.pyx @@ -151,6 +151,7 @@ Methods from libc.string cimport memset from cysignals.memory cimport check_allocarray, sig_free from cysignals.signals cimport sig_on, sig_off +from memory_allocator cimport MemoryAllocator from sage.graphs.graph import Graph from sage.graphs.distances_all_pairs cimport c_distances_all_pairs @@ -159,7 +160,6 @@ from sage.rings.integer_ring import ZZ from sage.rings.real_mpfr import RR from sage.functions.other import floor from sage.data_structures.bitset import Bitset -from sage.ext.memory_allocator cimport MemoryAllocator from sage.graphs.base.static_sparse_graph cimport short_digraph from sage.graphs.base.static_sparse_graph cimport init_short_digraph from sage.graphs.base.static_sparse_graph cimport free_short_digraph diff --git a/src/sage/graphs/mcqd.pyx b/src/sage/graphs/mcqd.pyx index 7916e7e3840..8b8e7d98d52 100644 --- a/src/sage/graphs/mcqd.pyx +++ b/src/sage/graphs/mcqd.pyx @@ -2,7 +2,7 @@ # sage_setup: distribution = sage-mcqd from cysignals.signals cimport sig_on, sig_off -from sage.ext.memory_allocator cimport MemoryAllocator +from memory_allocator cimport MemoryAllocator def mcqd(G): """ diff --git a/src/sage/graphs/spanning_tree.pyx b/src/sage/graphs/spanning_tree.pyx index 922ecc62ecb..da17cb2183e 100644 --- a/src/sage/graphs/spanning_tree.pyx +++ b/src/sage/graphs/spanning_tree.pyx @@ -37,7 +37,7 @@ Methods # **************************************************************************** cimport cython -from sage.ext.memory_allocator cimport MemoryAllocator +from memory_allocator cimport MemoryAllocator from sage.sets.disjoint_set cimport DisjointSet_of_hashables diff --git a/src/sage/graphs/traversals.pyx b/src/sage/graphs/traversals.pyx index 76d27a8b5c1..a9f52acf5e4 100644 --- a/src/sage/graphs/traversals.pyx +++ b/src/sage/graphs/traversals.pyx @@ -38,16 +38,16 @@ Methods from collections import deque from libc.string cimport memset -from sage.ext.memory_allocator cimport MemoryAllocator -from sage.graphs.base.static_sparse_graph cimport init_short_digraph -from sage.graphs.base.static_sparse_graph cimport free_short_digraph -from sage.graphs.base.static_sparse_graph cimport out_degree, has_edge from libc.stdint cimport uint32_t -from cysignals.signals cimport sig_on, sig_off, sig_check - from libcpp.queue cimport priority_queue from libcpp.pair cimport pair from libcpp.vector cimport vector +from cysignals.signals cimport sig_on, sig_off, sig_check +from memory_allocator cimport MemoryAllocator + +from sage.graphs.base.static_sparse_graph cimport init_short_digraph +from sage.graphs.base.static_sparse_graph cimport free_short_digraph +from sage.graphs.base.static_sparse_graph cimport out_degree, has_edge def _is_valid_lex_BFS_order(G, L): diff --git a/src/sage/graphs/weakly_chordal.pyx b/src/sage/graphs/weakly_chordal.pyx index fed5ced007d..3b8b0c6feb3 100644 --- a/src/sage/graphs/weakly_chordal.pyx +++ b/src/sage/graphs/weakly_chordal.pyx @@ -32,8 +32,8 @@ Methods # http://www.gnu.org/licenses/ ############################################################################## +from memory_allocator cimport MemoryAllocator from sage.data_structures.bitset_base cimport * -from sage.ext.memory_allocator cimport MemoryAllocator from sage.graphs.base.static_sparse_graph cimport short_digraph from sage.graphs.base.static_sparse_graph cimport init_short_digraph from sage.graphs.base.static_sparse_graph cimport free_short_digraph diff --git a/src/sage/groups/abelian_gps/abelian_group.py b/src/sage/groups/abelian_gps/abelian_group.py index ea5e8289e43..6567ac6c18b 100644 --- a/src/sage/groups/abelian_gps/abelian_group.py +++ b/src/sage/groups/abelian_gps/abelian_group.py @@ -507,7 +507,7 @@ class AbelianGroup_class(UniqueRepresentation, AbelianGroupBase): """ Element = AbelianGroupElement - def __init__(self, generator_orders, names): + def __init__(self, generator_orders, names, category=None): """ The Python constructor @@ -533,12 +533,13 @@ def __init__(self, generator_orders, names): n = len(generator_orders) names = normalize_names(n, names) self._assign_names(names) - cat = Groups().Commutative() + if category is None: + category = Groups().Commutative() if all(order > 0 for order in generator_orders): - cat = cat.Finite().Enumerated() + category = category.Finite().Enumerated() else: - cat = cat.Infinite() - AbelianGroupBase.__init__(self, category=cat) + category = category.Infinite() + AbelianGroupBase.__init__(self, category=category) def is_isomorphic(left, right): """ @@ -1225,7 +1226,7 @@ def subgroup(self, gensH, names="f"): for h in gensH: if h not in G: raise TypeError('Subgroup generators must belong to the given group.') - return AbelianGroup_subgroup(self, gensH, names) + return self.Subgroup(self, gensH, names) def list(self): """ @@ -1524,7 +1525,7 @@ def subgroup_reduced(self,elts, verbose=False): generated by {f0, f0*f1^2} sage: AbelianGroup([4,4]).subgroup_reduced( [ [1,0], [1,2] ]) Multiplicative Abelian subgroup isomorphic to C2 x C4 - generated by {f1^2, f0} + generated by {f0^2*f1^2, f0^3} """ from sage.matrix.constructor import matrix d = self.ngens() @@ -1578,7 +1579,7 @@ class AbelianGroup_subgroup(AbelianGroup_class): There should be a way to coerce an element of a subgroup into the ambient group. """ - def __init__(self, ambient, gens, names="f"): + def __init__(self, ambient, gens, names="f", category=None): """ EXAMPLES:: @@ -1704,7 +1705,9 @@ def __init__(self, ambient, gens, names="f"): for x in Hgens0: invs.append(0) invs = tuple(ZZ(i) for i in invs) - AbelianGroup_class.__init__(self, invs, names) + if category is None: + category = Groups().Commutative().Subobjects() + AbelianGroup_class.__init__(self, invs, names, category=category) def __contains__(self, x): """ @@ -1869,3 +1872,6 @@ def gen(self, n): """ return self._gens[n] +# We allow subclasses to override this, analogous to Element +AbelianGroup_class.Subgroup = AbelianGroup_subgroup + diff --git a/src/sage/groups/abelian_gps/abelian_group_gap.py b/src/sage/groups/abelian_gps/abelian_group_gap.py index 74bcc74de1e..1a6583dac03 100644 --- a/src/sage/groups/abelian_gps/abelian_group_gap.py +++ b/src/sage/groups/abelian_gps/abelian_group_gap.py @@ -364,8 +364,10 @@ def _element_constructor_(self, x, check=True): if len(exp) != len(gens_gap): raise ValueError("input does not match the number of generators") x = self.one() - for i in range(len(exp)): - x *= gens_gap[i]**(exp[i] % orders[i]) + for g, e, m in zip(gens_gap, exp, orders): + if m != 0: + e = e % m + x *= g**e x = x.gap() return self.element_class(self, x, check=check) @@ -792,8 +794,7 @@ def __init__(self, ambient, gens): category = category.Finite() else: category = category.Infinite() - # FIXME: Tell the category that it is a Subobjects() category - # category = category.Subobjects() + category = category.Subobjects() AbelianGroup_gap.__init__(self, G, ambient=ambient, category=category) def _repr_(self): @@ -834,6 +835,64 @@ def __reduce__(self): gens = tuple([amb(g) for g in self.gens()]) return amb.subgroup, (gens,) + def lift(self, x): + """ + Coerce to the ambient group. + + The terminology comes from the category framework and the more general notion of a subquotient. + + INPUT: + + - ``x`` -- an element of this subgroup + + OUTPUT: + + The corresponding element of the ambient group + + EXAMPLES:: + + sage: from sage.groups.abelian_gps.abelian_group_gap import AbelianGroupGap + sage: G = AbelianGroupGap([4]) + sage: g = G.gen(0) + sage: H = G.subgroup([g^2]) + sage: h = H.gen(0); h + f2 + sage: h.parent() + Subgroup of Abelian group with gap, generator orders (4,) generated by (f2,) + sage: H.lift(h) + f2 + sage: H.lift(h).parent() + Abelian group with gap, generator orders (4,) + """ + return self.ambient()(x) + + def retract(self, x): + """ + Convert an element of the ambient group into this subgroup. + + The terminology comes from the category framework and the more general notion of a subquotient. + + INPUT: + + - ``x`` -- an element of the ambient group that actually lies in this subgroup. + + OUTPUT: + + The corresponding element of this subgroup + + EXAMPLES:: + + sage: from sage.groups.abelian_gps.abelian_group_gap import AbelianGroupGap + sage: G = AbelianGroupGap([4]) + sage: g = G.gen(0) + sage: H = G.subgroup([g^2]) + sage: H.retract(g^2) + f2 + sage: H.retract(g^2).parent() + Subgroup of Abelian group with gap, generator orders (4,) generated by (f2,) + """ + return self(x) + class AbelianGroupQuotient_gap(AbelianGroup_gap): r""" Quotients of abelian groups by a subgroup. diff --git a/src/sage/groups/additive_abelian/additive_abelian_group.py b/src/sage/groups/additive_abelian/additive_abelian_group.py index 3dcedeb7e36..2c76b75e0cd 100644 --- a/src/sage/groups/additive_abelian/additive_abelian_group.py +++ b/src/sage/groups/additive_abelian/additive_abelian_group.py @@ -43,7 +43,7 @@ def AdditiveAbelianGroup(invs, remember_generators = True): sage: H = AdditiveAbelianGroup([0, 2, 3], remember_generators = False); H Additive abelian group isomorphic to Z/6 + Z sage: H.gens() - ((0, 1, 2), (1, 0, 0)) + ((0, 1, 1), (1, 0, 0)) There are several ways to create elements of an additive abelian group. Realize that there are two sets of generators: the "obvious" ones composed diff --git a/src/sage/groups/affine_gps/affine_group.py b/src/sage/groups/affine_gps/affine_group.py index 516cdb9cd13..6c1629f6dc8 100644 --- a/src/sage/groups/affine_gps/affine_group.py +++ b/src/sage/groups/affine_gps/affine_group.py @@ -18,6 +18,9 @@ from sage.groups.group import Group +from sage.categories.groups import Groups +from sage.groups.matrix_gps.linear import GL +from sage.categories.rings import Rings from sage.matrix.all import MatrixSpace from sage.modules.all import FreeModule from sage.structure.unique_representation import UniqueRepresentation @@ -202,9 +205,21 @@ def __init__(self, degree, ring): sage: G = AffineGroup(2, GF(5)); G Affine Group of degree 2 over Finite Field of size 5 sage: TestSuite(G).run() + sage: G.category() + Category of finite groups + + sage: Aff6 = AffineGroup(6, QQ) + sage: Aff6.category() + Category of infinite groups """ self._degree = degree - Group.__init__(self, base=ring) + cat = Groups() + if degree == 0 or ring in Rings().Finite(): + cat = cat.Finite() + elif ring in Rings().Infinite(): + cat = cat.Infinite() + self._GL = GL(degree, ring) + Group.__init__(self, base=ring, category=cat) Element = AffineGroupElement @@ -253,7 +268,8 @@ def _latex_(self): sage: latex(G) \mathrm{Aff}_{6}(\Bold{F}_{5}) """ - return "\\mathrm{Aff}_{%s}(%s)"%(self.degree(), self.base_ring()._latex_()) + return "\\mathrm{Aff}_{%s}(%s)" % (self.degree(), + self.base_ring()._latex_()) def _repr_(self): """ @@ -264,7 +280,22 @@ def _repr_(self): sage: AffineGroup(6, GF(5)) Affine Group of degree 6 over Finite Field of size 5 """ - return "Affine Group of degree %s over %s"%(self.degree(), self.base_ring()) + return "Affine Group of degree %s over %s" % (self.degree(), + self.base_ring()) + + def cardinality(self): + """ + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: AffineGroup(6, GF(5)).cardinality() + 172882428468750000000000000000 + sage: AffineGroup(6, ZZ).cardinality() + +Infinity + """ + card_GL = self._GL.cardinality() + return card_GL * self.base_ring().cardinality()**self.degree() def degree(self): """ @@ -448,9 +479,7 @@ def random_element(self): sage: G.random_element() in G True """ - A = self.matrix_space().random_element() - while not A.is_invertible(): # a generic matrix is invertible - A.randomize() + A = self._GL.random_element() b = self.vector_space().random_element() return self.element_class(self, A, b, check=False, convert=False) @@ -465,9 +494,38 @@ def _an_element_(self): sage: G.an_element() in G True """ - A = self.matrix_space().an_element() - while not A.is_invertible(): # a generic matrix is not always invertible - A.randomize() + A = self._GL.an_element() b = self.vector_space().an_element() return self.element_class(self, A, b, check=False, convert=False) + def some_elements(self): + """ + Return some elements. + + EXAMPLES:: + + sage: G = AffineGroup(4,5) + sage: G.some_elements() + [ [2 0 0 0] [1] + [0 1 0 0] [0] + x |-> [0 0 1 0] x + [0] + [0 0 0 1] [0], + [2 0 0 0] [0] + [0 1 0 0] [0] + x |-> [0 0 1 0] x + [0] + [0 0 0 1] [0], + [2 0 0 0] [0] + [0 1 0 0] [2] + x |-> [0 0 1 0] x + [0] + [0 0 0 1] [1]] + + sage: G = AffineGroup(2,QQ) + sage: G.some_elements() + [ [1 0] [1] + x |-> [0 1] x + [0], + ...] + """ + mats = self._GL.some_elements() + vecs = self.vector_space().some_elements() + return [self.element_class(self, A, b, check=False, convert=False) + for A in mats for b in vecs] diff --git a/src/sage/groups/fqf_orthogonal.py b/src/sage/groups/fqf_orthogonal.py index 3c5190589b5..ae9fc2fae2d 100644 --- a/src/sage/groups/fqf_orthogonal.py +++ b/src/sage/groups/fqf_orthogonal.py @@ -133,16 +133,16 @@ class FqfOrthogonalGroup(AbelianGroupAutomorphismGroup_subgroup): sage: T Finite quadratic module over Integer Ring with invariants (3, 3, 3) Gram matrix of the quadratic form with values in Q/2Z: - [4/3 0 0] + [2/3 0 0] [ 0 2/3 0] - [ 0 0 2/3] + [ 0 0 4/3] sage: T.orthogonal_group() Group of isometries of Finite quadratic module over Integer Ring with invariants (3, 3, 3) Gram matrix of the quadratic form with values in Q/2Z: - [4/3 0 0] + [2/3 0 0] [ 0 2/3 0] - [ 0 0 2/3] + [ 0 0 4/3] generated by 2 elements sage: q = matrix.diagonal(QQ, [3/2, 1/4, 1/4]) sage: T = TorsionQuadraticForm(q) @@ -158,7 +158,7 @@ class FqfOrthogonalGroup(AbelianGroupAutomorphismGroup_subgroup): sage: G = T.orthogonal_group() sage: g = G(matrix(ZZ, 2, [8, 0, 0, 1])) sage: Q.1 * g - (0, 2) + (0, 1) """ Element = FqfIsometry @@ -225,8 +225,8 @@ def _element_constructor_(self, x, check=True): sage: f = OL(f) sage: fbar = Oq(f) sage: fbar - [4 3] - [3 1] + [1 3] + [3 4] Note that the following does not work since it may lead to ambiguities, see :trac:`30669`:: @@ -307,12 +307,12 @@ def _get_action_(self, S, op, self_on_left): Right action by Group of isometries of Finite quadratic module over Integer Ring with invariants (3, 3) Gram matrix of the quadratic form with values in Q/2Z: - [4/3 0] - [ 0 2/3] + [2/3 0] + [ 0 4/3] generated by 2 elements on Finite quadratic module over Integer Ring with invariants (3, 3) Gram matrix of the quadratic form with values in Q/2Z: - [4/3 0] - [ 0 2/3] + [2/3 0] + [ 0 4/3] """ import operator if op == operator.mul and not self_on_left: @@ -450,19 +450,19 @@ def _act_(self, g, a): Right action by Group of isometries of Finite quadratic module over Integer Ring with invariants (3, 3) Gram matrix of the quadratic form with values in Q/2Z: - [4/3 0] - [ 0 2/3] + [2/3 0] + [ 0 4/3] generated by 2 elements on Finite quadratic module over Integer Ring with invariants (3, 3) Gram matrix of the quadratic form with values in Q/2Z: - [4/3 0] - [ 0 2/3] + [2/3 0] + [ 0 4/3] sage: x = q.an_element() sage: g = G.an_element() sage: A(x, g).parent() Finite quadratic module over Integer Ring with invariants (3, 3) Gram matrix of the quadratic form with values in Q/2Z: - [4/3 0] - [ 0 2/3] + [2/3 0] + [ 0 4/3] sage: q = TorsionQuadraticForm(matrix.diagonal([2/3, 2/3, 6/8, 1/4])) sage: G = q.orthogonal_group() sage: q2 = q.primary_part(2) diff --git a/src/sage/groups/galois_group.py b/src/sage/groups/galois_group.py index 218138e13de..694574a20b6 100644 --- a/src/sage/groups/galois_group.py +++ b/src/sage/groups/galois_group.py @@ -10,10 +10,11 @@ - David Roe (2019): initial version """ -from sage.groups.perm_gps.permgroup import PermutationGroup, PermutationGroup_generic -from sage.groups.abelian_gps.abelian_group import AbelianGroup_class +from sage.groups.perm_gps.permgroup import PermutationGroup, PermutationGroup_generic, PermutationGroup_subgroup +from sage.groups.abelian_gps.abelian_group import AbelianGroup_class, AbelianGroup_subgroup from sage.sets.finite_enumerated_set import FiniteEnumeratedSet from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method from sage.structure.category_object import normalize_names from sage.rings.integer_ring import ZZ @@ -39,26 +40,50 @@ def _alg_key(self, algorithm=None, recompute=False): algorithm = self._get_algorithm(algorithm) return algorithm -class _GaloisMixin: +class _GMixin: r""" This class provides some methods for Galois groups to be used for both permutation groups - and abelian groups. + and abelian groups, subgroups and full Galois groups. + + It is just intended to provide common functionality between various different Galois group classes. """ - def _repr_(self): + @lazy_attribute + def _default_algorithm(self): """ - String representation of this Galois group + A string, the default algorithm used for computing the Galois group EXAMPLES:: - sage: from sage.groups.galois_group import GaloisGroup_perm sage: R. = ZZ[] sage: K. = NumberField(x^3 + 2*x + 2) sage: G = K.galois_group() - sage: GaloisGroup_perm._repr_(G) - 'Galois group of x^3 + 2*x + 2' + sage: G._default_algorithm + 'pari' """ - f = self._field.defining_polynomial() - return "Galois group of %s" % f + return NotImplemented + + @lazy_attribute + def _gcdata(self): + """ + A pair: + + - the Galois closure of the top field in the ambient Galois group; + + - an embedding of the top field into the Galois closure. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 - 2) + sage: G = K.galois_group() + sage: G._gcdata + (Number Field in ac with defining polynomial x^6 + 108, + Ring morphism: + From: Number Field in a with defining polynomial x^3 - 2 + To: Number Field in ac with defining polynomial x^6 + 108 + Defn: a |--> -1/36*ac^4 - 1/2*ac) + """ + return NotImplemented def _get_algorithm(self, algorithm): r""" @@ -76,6 +101,89 @@ def _get_algorithm(self, algorithm): """ return self._default_algorithm if algorithm is None else algorithm + @lazy_attribute + def _galois_closure(self): + r""" + The Galois closure of the top field. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group(names='b') + sage: G._galois_closure + Number Field in b with defining polynomial x^6 + 12*x^4 + 36*x^2 + 140 + """ + return self._gcdata[0] + + def splitting_field(self): + r""" + The Galois closure of the top field. + + EXAMPLES:: + + sage: K = NumberField(x^3 - x + 1, 'a') + sage: K.galois_group(names='b').splitting_field() + Number Field in b with defining polynomial x^6 - 6*x^4 + 9*x^2 + 23 + sage: L = QuadraticField(-23, 'c'); L.galois_group().splitting_field() is L + True + """ + return self._galois_closure + + @lazy_attribute + def _gc_map(self): + r""" + The inclusion of the top field into the Galois closure. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group(names='b') + sage: G._gc_map + Ring morphism: + From: Number Field in a with defining polynomial x^3 + 2*x + 2 + To: Number Field in b with defining polynomial x^6 + 12*x^4 + 36*x^2 + 140 + Defn: a |--> 1/36*b^4 + 5/18*b^2 - 1/2*b + 4/9 + """ + return self._gcdata[1] + +class _GaloisMixin(_GMixin): + """ + This class provides methods for Galois groups, allowing concrete instances + to inherit from both permutation group and abelian group classes. + """ + @lazy_attribute + def _field(self): + """ + The top field, ie the field whose Galois closure elements of this group act upon. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group() + sage: G._field + Number Field in a with defining polynomial x^3 + 2*x + 2 + """ + return NotImplemented + + def _repr_(self): + """ + String representation of this Galois group + + EXAMPLES:: + + sage: from sage.groups.galois_group import GaloisGroup_perm + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group() + sage: GaloisGroup_perm._repr_(G) + 'Galois group of x^3 + 2*x + 2' + """ + f = self._field.defining_polynomial() + return "Galois group of %s" % f + def top_field(self): r""" Return the larger of the two fields in the extension defining this Galois group. @@ -162,52 +270,64 @@ def is_galois(self): """ return self.order() == self._field_degree +class _SubGaloisMixin(_GMixin): + """ + This class provides methods for subgroups of Galois groups, allowing concrete instances + to inherit from both permutation group and abelian group classes. + """ @lazy_attribute - def _galois_closure(self): - r""" - The Galois closure of the top field. + def _ambient_group(self): + """ + The ambient Galois group of which this is a subgroup. EXAMPLES:: - sage: R. = ZZ[] - sage: K. = NumberField(x^3 + 2*x + 2) - sage: G = K.galois_group(names='b') - sage: G._galois_closure - Number Field in b with defining polynomial x^6 + 12*x^4 + 36*x^2 + 140 + sage: L. = NumberField(x^4 + 1) + sage: G = L.galois_group() + sage: H = G.decomposition_group(L.primes_above(3)[0]) + sage: H._ambient_group is G + True """ - return self._gcdata[0] + return NotImplemented - def splitting_field(self): - r""" - The Galois closure of the top field. + @abstract_method(optional=True) + def fixed_field(self, name=None, polred=None, threshold=None): + """ + Return the fixed field of this subgroup (as a subfield of the Galois closure). + + INPUT: + + - ``name`` -- a variable name for the new field. + + - ``polred`` -- whether to optimize the generator of the newly created field + for a simpler polynomial, using pari's polredbest. + Defaults to ``True`` when the degree of the fixed field is at most 8. + + - ``threshold`` -- positive number; polred only performed if the cost is at most this threshold EXAMPLES:: - sage: K = NumberField(x^3 - x + 1, 'a') - sage: K.galois_group(names='b').splitting_field() - Number Field in b with defining polynomial x^6 - 6*x^4 + 9*x^2 + 23 - sage: L = QuadraticField(-23, 'c'); L.galois_group().splitting_field() is L - True + sage: k. = GF(3^12) + sage: g = k.galois_group()([8]) + sage: k0, embed = g.fixed_field() + sage: k0.cardinality() + 81 """ - return self._galois_closure @lazy_attribute - def _gc_map(self): - r""" - The inclusion of the top field into the Galois closure. + def _gcdata(self): + """ + The Galois closure data is just that of the ambient group. EXAMPLES:: - sage: R. = ZZ[] - sage: K. = NumberField(x^3 + 2*x + 2) - sage: G = K.galois_group(names='b') - sage: G._gc_map - Ring morphism: - From: Number Field in a with defining polynomial x^3 + 2*x + 2 - To: Number Field in b with defining polynomial x^6 + 12*x^4 + 36*x^2 + 140 - Defn: a |--> 1/36*b^4 + 5/18*b^2 - 1/2*b + 4/9 + sage: L. = NumberField(x^4 + 1) + sage: G = L.galois_group() + sage: H = G.decomposition_group(L.primes_above(3)[0]) + sage: H.splitting_field() # indirect doctest + Number Field in a with defining polynomial x^4 + 1 """ - return self._gcdata[1] + return self._ambient_group._gcdata class GaloisGroup_perm(_GaloisMixin, PermutationGroup_generic): r""" @@ -223,19 +343,37 @@ class GaloisGroup_perm(_GaloisMixin, PermutationGroup_generic): roots of the defining polynomial of the splitting field (versus the defining polynomial of the original extension). The default value may vary based on the type of field. """ - # Subclasses should implement the following methods and lazy attributes + @abstract_method + def transitive_number(self, algorithm=None, recompute=False): + """ + The transitive number (as in the GAP and Magma databases of transitive groups) + for the action on the roots of the defining polynomial of the top field. + + EXAMPLES:: + + sage: R. = ZZ[] + sage: K. = NumberField(x^3 + 2*x + 2) + sage: G = K.galois_group() + sage: G.transitive_number() + 2 + """ - # methods (taking algorithm and recompute as arguments): - # * transitive_number - # * order - # * _element_constructor_ -- for creating elements + @lazy_attribute + def _gens(self): + """ + The generators of this Galois group as permutations of the roots. It's important that this + be computed lazily, since it's often possible to compute other attributes (such as the order + or transitive number) more cheaply. - # lazy_attributes - # * _gcdata -- a pair, the Galois closure and an embedding of the top field into it - # * _gens -- the list of generators of this group, as elements. This is not computed during __init__ for speed - # * _elts -- the list of all elements of this group. + EXAMPLES:: - # * Element (for coercion) + sage: R. = ZZ[] + sage: K. = NumberField(x^5-2) + sage: G = K.galois_group(gc_numbering=False) + sage: G._gens + [(1,2,3,5), (1,4,3,2,5)] + """ + return NotImplemented def __init__(self, field, algorithm=None, names=None, gc_numbering=False): r""" @@ -462,3 +600,24 @@ def signature(self): 1 """ return ZZ(1) if (self._field.degree() % 2) else ZZ(-1) + +class GaloisSubgroup_perm(PermutationGroup_subgroup, _SubGaloisMixin): + """ + Subgroups of Galois groups (implemented as permutation groups), specified + by giving a list of generators. + + Unlike ambient Galois groups, where we use a lazy ``_gens`` attribute in order + to enable creation without determining a list of generators, + we require that generators for a subgroup be specified during initialization, + as specified in the ``__init__`` method of permutation subgroups. + """ + pass + +class GaloisSubgroup_ab(AbelianGroup_subgroup, _SubGaloisMixin): + """ + Subgroups of abelian Galois groups. + """ + pass + +GaloisGroup_perm.Subgroup = GaloisSubgroup_perm +GaloisGroup_ab.Subgroup = GaloisSubgroup_ab diff --git a/src/sage/groups/group.pyx b/src/sage/groups/group.pyx index 9590eba4e0f..d54fdc7b5f2 100644 --- a/src/sage/groups/group.pyx +++ b/src/sage/groups/group.pyx @@ -22,6 +22,7 @@ import random from sage.structure.parent cimport Parent from sage.rings.infinity import infinity from sage.rings.integer_ring import ZZ +from sage.misc.lazy_attribute import lazy_attribute def is_Group(x): diff --git a/src/sage/groups/perm_gps/cubegroup.py b/src/sage/groups/perm_gps/cubegroup.py index 8be5b5611b3..4ace0ce13e3 100644 --- a/src/sage/groups/perm_gps/cubegroup.py +++ b/src/sage/groups/perm_gps/cubegroup.py @@ -429,30 +429,54 @@ def cubie_colors(label, state0): # colors of the cubies on the F,U, R faces clr_any = named_colors['white'] state = inv_list(state0) - if label == 1: return [clr_any, named_colors[color_of_square(state[1-1])], clr_any] #ulb, - if label == 2: return [clr_any,named_colors[color_of_square(state[2-1])],clr_any] # ub, - if label == 3: return [clr_any, named_colors[color_of_square(state[3-1])], named_colors[color_of_square(state[27-1])]] # ubr, - if label == 4: return [clr_any, named_colors[color_of_square(state[4-1])], clr_any] # ul, - if label == 5: return [clr_any, named_colors[color_of_square(state[5-1])], named_colors[color_of_square(state[26-1])]] # ur, - if label == 6: return [named_colors[color_of_square(state[17-1])], named_colors[color_of_square(state[6-1])], clr_any] # ufl, - if label == 7: return [named_colors[color_of_square(state[18-1])], named_colors[color_of_square(state[7-1])], clr_any] # uf, - if label == 8: return [named_colors[color_of_square(state[19-1])], named_colors[color_of_square(state[8-1])], named_colors[color_of_square(state[25-1])]] # urf, - if label == 17: return [named_colors[color_of_square(state[17-1])], named_colors[color_of_square(state[6-1])], clr_any] # flu - if label == 18: return [named_colors[color_of_square(state[18-1])], named_colors[color_of_square(state[7-1])], clr_any] # fu - if label == 19: return [named_colors[color_of_square(state[19-1])], named_colors[color_of_square(state[8-1])], named_colors[color_of_square(state[25-1])]] # fur - if label == 20: return [named_colors[color_of_square(state[20-1])], clr_any, clr_any] # fl - if label == 21: return [named_colors[color_of_square(state[21-1])], clr_any, named_colors[color_of_square(state[28-1])]] # fr - if label == 22: return [named_colors[color_of_square(state[22-1])], clr_any, clr_any] # fdl - if label == 23: return [named_colors[color_of_square(state[23-1])], clr_any, clr_any] # fd - if label == 24: return [named_colors[color_of_square(state[24-1])], clr_any, named_colors[color_of_square(state[30-1])]] # frd - if label == 25: return [named_colors[color_of_square(state[19-1])],named_colors[color_of_square(state[8-1])],named_colors[color_of_square(state[25-1])]] #rfu, - if label == 26: return [clr_any,named_colors[color_of_square(state[5-1])],named_colors[color_of_square(state[26-1])]] # ru, - if label == 27: return [clr_any,named_colors[color_of_square(state[3-1])],named_colors[color_of_square(state[27-1])]] # rub, - if label == 28: return [named_colors[color_of_square(state[21-1])],clr_any,named_colors[color_of_square(state[28-1])]] # rf, - if label == 29: return [clr_any,clr_any,named_colors[color_of_square(state[29-1])]] # rb, - if label == 30: return [named_colors[color_of_square(state[24-1])],clr_any,named_colors[color_of_square(state[30-1])]] # rdf, - if label == 31: return [clr_any,clr_any,named_colors[color_of_square(state[31-1])]] # rd, - if label == 32: return [clr_any,clr_any,named_colors[color_of_square(state[32-1])]] #rbd, + if label == 1: + return [clr_any, named_colors[color_of_square(state[1-1])], clr_any] #ulb, + if label == 2: + return [clr_any,named_colors[color_of_square(state[2-1])],clr_any] # ub, + if label == 3: + return [clr_any, named_colors[color_of_square(state[3-1])], named_colors[color_of_square(state[27-1])]] # ubr, + if label == 4: + return [clr_any, named_colors[color_of_square(state[4-1])], clr_any] # ul, + if label == 5: + return [clr_any, named_colors[color_of_square(state[5-1])], named_colors[color_of_square(state[26-1])]] # ur, + if label == 6: + return [named_colors[color_of_square(state[17-1])], named_colors[color_of_square(state[6-1])], clr_any] # ufl, + if label == 7: + return [named_colors[color_of_square(state[18-1])], named_colors[color_of_square(state[7-1])], clr_any] # uf, + if label == 8: + return [named_colors[color_of_square(state[19-1])], named_colors[color_of_square(state[8-1])], named_colors[color_of_square(state[25-1])]] # urf, + if label == 17: + return [named_colors[color_of_square(state[17-1])], named_colors[color_of_square(state[6-1])], clr_any] # flu + if label == 18: + return [named_colors[color_of_square(state[18-1])], named_colors[color_of_square(state[7-1])], clr_any] # fu + if label == 19: + return [named_colors[color_of_square(state[19-1])], named_colors[color_of_square(state[8-1])], named_colors[color_of_square(state[25-1])]] # fur + if label == 20: + return [named_colors[color_of_square(state[20-1])], clr_any, clr_any] # fl + if label == 21: + return [named_colors[color_of_square(state[21-1])], clr_any, named_colors[color_of_square(state[28-1])]] # fr + if label == 22: + return [named_colors[color_of_square(state[22-1])], clr_any, clr_any] # fdl + if label == 23: + return [named_colors[color_of_square(state[23-1])], clr_any, clr_any] # fd + if label == 24: + return [named_colors[color_of_square(state[24-1])], clr_any, named_colors[color_of_square(state[30-1])]] # frd + if label == 25: + return [named_colors[color_of_square(state[19-1])],named_colors[color_of_square(state[8-1])],named_colors[color_of_square(state[25-1])]] #rfu, + if label == 26: + return [clr_any,named_colors[color_of_square(state[5-1])],named_colors[color_of_square(state[26-1])]] # ru, + if label == 27: + return [clr_any,named_colors[color_of_square(state[3-1])],named_colors[color_of_square(state[27-1])]] # rub, + if label == 28: + return [named_colors[color_of_square(state[21-1])],clr_any,named_colors[color_of_square(state[28-1])]] # rf, + if label == 29: + return [clr_any,clr_any,named_colors[color_of_square(state[29-1])]] # rb, + if label == 30: + return [named_colors[color_of_square(state[24-1])],clr_any,named_colors[color_of_square(state[30-1])]] # rdf, + if label == 31: + return [clr_any,clr_any,named_colors[color_of_square(state[31-1])]] # rd, + if label == 32: + return [clr_any,clr_any,named_colors[color_of_square(state[32-1])]] #rbd, def plot3d_cubie(cnt, clrs): diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index e07c2eefec6..e379fcf9d4a 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -2800,10 +2800,8 @@ def subgroup(self, gens=None, gap_group=None, domain=None, category=None, canoni sage: G.subgroup([g]) Subgroup generated by [(1,2,3)] of (Permutation Group with generators [(3,4,5), (1,2,3)]) """ - if gap_group is None: - gap_group = self.gap().Subgroup([self(g).gap() for g in gens]) - return PermutationGroup_subgroup(self, gens=gens, gap_group=gap_group, domain=None, - category=category, canonicalize=canonicalize, check=check) + return self.Subgroup(self, gens=gens, gap_group=gap_group, domain=None, + category=category, canonicalize=canonicalize, check=check) def _subgroup_constructor(self, libgap_group): """ @@ -4739,8 +4737,8 @@ def __init__(self, ambient, gens=None, gap_group=None, domain=None, - ``gens`` - the generators of the subgroup - - ``from_group`` - ``True``: subgroup is generated from a Gap - string representation of the generators (default: ``False``) + - ``gap_group`` - a GAP permutation group contained in the ambient group; + constructed from ``gens`` if not given. - ``check`` - ``True``: checks if ``gens`` are indeed elements of the ambient group @@ -4774,14 +4772,18 @@ def __init__(self, ambient, gens=None, gap_group=None, domain=None, sage: S = PermutationGroup_subgroup(G,list(gens)) Traceback (most recent call last): ... - TypeError: each generator must be in the ambient group + ValueError: permutation (1,2,3) not in Dihedral group of order 8 as a permutation group """ if not isinstance(ambient, PermutationGroup_generic): raise TypeError("ambient (=%s) must be perm group"%ambient) if domain is None: domain = ambient.domain() if category is None: - category = ambient.category() + category = ambient.category().Subobjects() + if gap_group is None: + gap_group = ambient.gap().Subgroup([ambient(g, check=check).gap() for g in gens]) + # the ambient element constructor checks if needed + check = False PermutationGroup_generic.__init__(self, gens=gens, gap_group=gap_group, domain=domain, category=category, @@ -4891,8 +4893,7 @@ def _repr_(self): sage: S._repr_() 'Subgroup generated by [(1,2,3,4)] of (Dihedral group of order 8 as a permutation group)' """ - s = "Subgroup generated by %s of (%s)"%(self.gens(), self.ambient_group()) - return s + return "Subgroup generated by %s of (%s)" % (self.gens(), self.ambient_group()) def _latex_(self): r""" @@ -4957,5 +4958,8 @@ def is_normal(self, other=None): other = self.ambient_group() return PermutationGroup_generic.is_normal(self, other) +# Allow for subclasses to use a different subgroup class +PermutationGroup_generic.Subgroup = PermutationGroup_subgroup + from sage.misc.rest_index_of_methods import gen_rest_table_index __doc__ = __doc__.format(METHODS_OF_PermutationGroup_generic=gen_rest_table_index(PermutationGroup_generic)) diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 0bc4baa4c36..753613884f8 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -2153,11 +2153,10 @@ class PrimitiveGroup(PermutationGroup_unique): Only primitive groups of "small" degree are available in GAP's database:: - sage: PrimitiveGroup(2500,1) + sage: PrimitiveGroup(2^12,1) Traceback (most recent call last): ... - NotImplementedError: Only the primitive groups of degree less - than 2500 are available in GAP's database + GAPError: Error, Primitive groups of degree 4096 are not known! """ def __init__(self, d, n): @@ -2252,14 +2251,12 @@ def PrimitiveGroups(d=None): sage: PrimitiveGroups() Primitive Groups - The database currently only contains primitive groups up to degree - 2499:: + The database is currently limited:: - sage: PrimitiveGroups(2500).cardinality() + sage: PrimitiveGroups(2^12).cardinality() Traceback (most recent call last): ... - NotImplementedError: Only the primitive groups of degree less - than 2500 are available in GAP's database + GAPError: Error, Primitive groups of degree 4096 are not known! .. TODO:: @@ -2423,10 +2420,7 @@ def __contains__(self, G): sage: 1 in PrimitiveGroups(4) False """ - if isinstance(G,PrimitiveGroup): - return G._d == self._degree - else: - False + return isinstance(G, PrimitiveGroup) and G._d == self._degree def __getitem__(self, n): r""" @@ -2486,35 +2480,29 @@ def cardinality(self): sage: [PrimitiveGroups(i).cardinality() for i in range(11)] [1, 1, 1, 2, 2, 5, 4, 7, 7, 11, 9] - GAP contains all primitive groups up to degree 2499:: - - sage: PrimitiveGroups(2500).cardinality() - Traceback (most recent call last): - ... - NotImplementedError: Only the primitive groups of degree less than - 2500 are available in GAP's database - TESTS:: sage: type(PrimitiveGroups(12).cardinality()) sage: type(PrimitiveGroups(0).cardinality()) + + Check for :trac:`31774`:: + + sage: PrimitiveGroups(2500).cardinality() + 34 + sage: PrimitiveGroups(2^12).cardinality() + Traceback (most recent call last): + ... + GAPError: Error, Primitive groups of degree 4096 are not known! """ - # gap.NrPrimitiveGroups(0) fails, so Sage needs to handle this - # While we are at it, and since Sage also handles the - # primitive group of degree 1, we may as well handle 1 if self._degree <= 1: + # gap.NrPrimitiveGroups(0) fails, so Sage needs to handle this + # While we are at it, and since Sage also handles the + # primitive group of degree 1, we may as well handle 1 return Integer(1) - elif self._degree >= 2500: - raise NotImplementedError("Only the primitive groups of degree less than 2500 are available in GAP's database") else: - try: - return Integer(libgap.NrPrimitiveGroups(self._degree)) - except RuntimeError: - from sage.misc.verbose import verbose - verbose("Error: PrimitiveGroups should be in GAP already.", level=0) - + return Integer(libgap.NrPrimitiveGroups(self._degree)) class PermutationGroup_plg(PermutationGroup_unique): def base_ring(self): diff --git a/src/sage/homology/algebraic_topological_model.py b/src/sage/homology/algebraic_topological_model.py index bfa6b18c80f..b14de9bdfa3 100644 --- a/src/sage/homology/algebraic_topological_model.py +++ b/src/sage/homology/algebraic_topological_model.py @@ -76,14 +76,14 @@ def algebraic_topological_model(K, base_ring=None): of `K`, as described in [GDR2003]_ and [PR2015]_. Implementation details: the cell complex `K` must have an - :meth:`~sage.homology.cell_complex.GenericCellComplex.n_cells` + :meth:`~sage.topology.cell_complex.GenericCellComplex.n_cells` method from which we can extract a list of cells in each dimension. Combining the lists in increasing order of dimension then defines a filtration of the complex: a list of cells in which the boundary of each cell consists of cells earlier in the list. This is required by Pilarczyk and Réal's algorithm. There must also be a - :meth:`~sage.homology.cell_complex.GenericCellComplex.chain_complex` + :meth:`~sage.topology.cell_complex.GenericCellComplex.chain_complex` method, to construct the chain complex `C` associated to this chain complex. diff --git a/src/sage/homology/all.py b/src/sage/homology/all.py index 7708a0cc57c..d9306c19daa 100644 --- a/src/sage/homology/all.py +++ b/src/sage/homology/all.py @@ -1,22 +1,6 @@ - from .chain_complex import ChainComplex from .chain_complex_morphism import ChainComplexMorphism -from .simplicial_complex import SimplicialComplex, Simplex - -from .simplicial_complex_morphism import SimplicialComplexMorphism - -from .delta_complex import DeltaComplex, delta_complexes - -from .cubical_complex import CubicalComplex, cubical_complexes - from sage.misc.lazy_import import lazy_import lazy_import('sage.homology.koszul_complex', 'KoszulComplex') -lazy_import('sage.homology', 'simplicial_complexes_catalog', 'simplicial_complexes') -lazy_import('sage.homology', 'simplicial_set_catalog', 'simplicial_sets') - - -# For taking care of old pickles -from sage.misc.persist import register_unpickle_override -register_unpickle_override('sage.homology.examples', 'SimplicialSurface', SimplicialComplex) diff --git a/src/sage/homology/cell_complex.py b/src/sage/homology/cell_complex.py index 01e85104c45..23292d902e3 100644 --- a/src/sage/homology/cell_complex.py +++ b/src/sage/homology/cell_complex.py @@ -1,1230 +1,12 @@ # -*- coding: utf-8 -*- r""" -Generic cell complexes +Generic cell complexes: deprecated -AUTHORS: - -- John H. Palmieri (2009-08) - -This module defines a class of abstract finite cell complexes. This -is meant as a base class from which other classes (like -:class:`~sage.homology.simplicial_complex.SimplicialComplex`, -:class:`~sage.homology.cubical_complex.CubicalComplex`, and -:class:`~sage.homology.delta_complex.DeltaComplex`) should derive. As -such, most of its properties are not implemented. It is meant for use -by developers producing new classes, not casual users. - -.. NOTE:: - - Keywords for :meth:`~GenericCellComplex.chain_complex`, - :meth:`~GenericCellComplex.homology`, etc.: any keywords given to - the :meth:`~GenericCellComplex.homology` method get passed on to - the :meth:`~GenericCellComplex.chain_complex` method and also to - the constructor for chain complexes in - :class:`sage.homology.chain_complex.ChainComplex_class `, - as well as its associated - :meth:`~sage.homology.chain_complex.ChainComplex_class.homology` method. - This means that those keywords should have consistent meaning in - all of those situations. It also means that it is easy to - implement new keywords: for example, if you implement a new - keyword for the - :meth:`sage.homology.chain_complex.ChainComplex_class.homology` method, - then it will be automatically accessible through the - :meth:`~GenericCellComplex.homology` method for cell complexes -- - just make sure it gets documented. +The current version is :mod:`sage.topology.cell_complexes`. """ -######################################################################## -# Copyright (C) 2009 John H. Palmieri -# -# 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 sage.structure.sage_object import SageObject -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.misc.abstract_method import abstract_method -from sage.homology.chains import Chains, Cochains - - -class GenericCellComplex(SageObject): - r""" - Class of abstract cell complexes. - - This is meant to be used by developers to produce new classes, not - by casual users. Classes which derive from this are - :class:`~sage.homology.simplicial_complex.SimplicialComplex`, - :class:`~sage.homology.delta_complex.DeltaComplex`, and - :class:`~sage.homology.cubical_complex.CubicalComplex`. - - Most of the methods here are not implemented, but probably should - be implemented in a derived class. Most of the other methods call - a non-implemented one; their docstrings contain examples from - derived classes in which the various methods have been defined. - For example, :meth:`homology` calls :meth:`chain_complex`; the - class :class:`~sage.homology.delta_complex.DeltaComplex` - implements - :meth:`~sage.homology.delta_complex.DeltaComplex.chain_complex`, - and so the :meth:`homology` method here is illustrated with - examples involving `\Delta`-complexes. - - EXAMPLES: - - It's hard to give informative examples of the base class, since - essentially nothing is implemented. :: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex() - """ - def __eq__(self,right): - """ - Comparisons of cell complexes are not implemented. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex(); B = GenericCellComplex() - sage: A == B # indirect doctest - Traceback (most recent call last): - ... - NotImplementedError - """ - raise NotImplementedError - - def __ne__(self,right): - """ - Comparisons of cell complexes are not implemented. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex(); B = GenericCellComplex() - sage: A != B # indirect doctest - Traceback (most recent call last): - ... - NotImplementedError - """ - raise NotImplementedError - - ############################################################ - # self.cells() and related methods - ############################################################ - - @abstract_method - def cells(self, subcomplex=None): - """ - The cells of this cell complex, in the form of a dictionary: - the keys are integers, representing dimension, and the value - associated to an integer `d` is the set of `d`-cells. If the - optional argument ``subcomplex`` is present, then return only - the cells which are *not* in the subcomplex. - - :param subcomplex: a subcomplex of this cell complex. Return - the cells which are not in this subcomplex. - :type subcomplex: optional, default None - - This is not implemented in general; it should be implemented - in any derived class. When implementing, see the warning in - the :meth:`dimension` method. - - This method is used by various other methods, such as - :meth:`n_cells` and :meth:`f_vector`. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex() - sage: A.cells() - Traceback (most recent call last): - ... - NotImplementedError: - """ - - def dimension(self): - """ - The dimension of this cell complex: the maximum - dimension of its cells. - - .. WARNING:: - - If the :meth:`cells` method calls :meth:`dimension`, - then you'll get an infinite loop. So either don't use - :meth:`dimension` or override :meth:`dimension`. - - EXAMPLES:: - - sage: simplicial_complexes.RandomComplex(d=5, n=8).dimension() - 5 - sage: delta_complexes.Sphere(3).dimension() - 3 - sage: T = cubical_complexes.Torus() - sage: T.product(T).dimension() - 4 - """ - try: - return max([x.dimension() for x in self._facets]) - except AttributeError: - if len(self.cells()) == 0: - # The empty cell complex has dimension -1. - return -1 - return max(self.cells()) - - def n_cells(self, n, subcomplex=None): - """ - List of cells of dimension ``n`` of this cell complex. - If the optional argument ``subcomplex`` is present, then - return the ``n``-dimensional cells which are *not* in the - subcomplex. - - :param n: the dimension - :type n: non-negative integer - :param subcomplex: a subcomplex of this cell complex. Return - the cells which are not in this subcomplex. - :type subcomplex: optional, default ``None`` - - .. NOTE:: - - The resulting list need not be sorted. If you want a sorted - list of `n`-cells, use :meth:`_n_cells_sorted`. - - EXAMPLES:: - - sage: delta_complexes.Torus().n_cells(1) - [(0, 0), (0, 0), (0, 0)] - sage: cubical_complexes.Cube(1).n_cells(0) - [[1,1], [0,0]] - """ - if n in self.cells(subcomplex): - return list(self.cells(subcomplex)[n]) - else: - # don't barf if someone asks for n_cells in a dimension where there are none - return [] - - def _n_cells_sorted(self, n, subcomplex=None): - """ - Sorted list of cells of dimension ``n`` of this cell complex. - If the optional argument ``subcomplex`` is present, then - return the ``n``-dimensional cells which are *not* in the - subcomplex. - - :param n: the dimension - :type n: non-negative integer - :param subcomplex: a subcomplex of this cell complex. Return - the cells which are not in this subcomplex. - :type subcomplex: optional, default ``None`` - - EXAMPLES:: - - sage: S = Set(range(1,5)) - sage: Z = SimplicialComplex(S.subsets()) - sage: Z - Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)} - sage: Z._n_cells_sorted(2) - [(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)] - sage: K = SimplicialComplex([[1,2,3], [2,3,4]]) - sage: Z._n_cells_sorted(2, subcomplex=K) - [(1, 2, 4), (1, 3, 4)] - sage: S = SimplicialComplex([[complex(i), complex(1)]]) - sage: S._n_cells_sorted(0) - [((1+0j),), (1j,)] - """ - n_cells = self.n_cells(n, subcomplex) - try: - return sorted(n_cells) - except TypeError: - return sorted(n_cells, key=str) - - def f_vector(self): - """ - The `f`-vector of this cell complex: a list whose `n^{th}` - item is the number of `(n-1)`-cells. Note that, like all - lists in Sage, this is indexed starting at 0: the 0th element - in this list is the number of `(-1)`-cells (which is 1: the - empty cell is the only `(-1)`-cell). - - EXAMPLES:: - - sage: simplicial_complexes.KleinBottle().f_vector() - [1, 8, 24, 16] - sage: delta_complexes.KleinBottle().f_vector() - [1, 1, 3, 2] - sage: cubical_complexes.KleinBottle().f_vector() - [1, 42, 84, 42] - """ - return [self._f_dict()[n] for n in range(-1, self.dimension()+1)] - - def _f_dict(self): - """ - The `f`-vector of this cell complex as a dictionary: the - item associated to an integer `n` is the number of the - `n`-cells. - - EXAMPLES:: - - sage: simplicial_complexes.KleinBottle()._f_dict()[1] - 24 - sage: delta_complexes.KleinBottle()._f_dict()[1] - 3 - """ - answer = {} - answer[-1] = 1 - for n in range(self.dimension() + 1): - answer[n] = len(self.cells()[n]) - return answer - - def euler_characteristic(self): - r""" - The Euler characteristic of this cell complex: the - alternating sum over `n \geq 0` of the number of - `n`-cells. - - EXAMPLES:: - - sage: simplicial_complexes.Simplex(5).euler_characteristic() - 1 - sage: delta_complexes.Sphere(6).euler_characteristic() - 2 - sage: cubical_complexes.KleinBottle().euler_characteristic() - 0 - """ - return sum([(-1)**n * self.f_vector()[n+1] for n in range(self.dimension() + 1)]) - - ############################################################ - # end of methods using self.cells() - ############################################################ - - @abstract_method - def product(self, right, rename_vertices=True): - """ - The (Cartesian) product of this cell complex with another one. - - Products are not implemented for general cell complexes. They - may be implemented in some derived classes (like simplicial - complexes). - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex(); B = GenericCellComplex() - sage: A.product(B) - Traceback (most recent call last): - ... - NotImplementedError: - """ - - @abstract_method - def disjoint_union(self, right): - """ - The disjoint union of this cell complex with another one. - - :param right: the other cell complex (the right-hand factor) - - Disjoint unions are not implemented for general cell complexes. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex(); B = GenericCellComplex() - sage: A.disjoint_union(B) - Traceback (most recent call last): - ... - NotImplementedError: - """ - - @abstract_method - def wedge(self, right): - """ - The wedge (one-point union) of this cell complex with - another one. - - :param right: the other cell complex (the right-hand factor) - - Wedges are not implemented for general cell complexes. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex(); B = GenericCellComplex() - sage: A.wedge(B) - Traceback (most recent call last): - ... - NotImplementedError: - """ - - ############################################################ - # self.join() and related methods - ############################################################ - - @abstract_method - def join(self, right): - """ - The join of this cell complex with another one. - - :param right: the other cell complex (the right-hand factor) - - Joins are not implemented for general cell complexes. They - may be implemented in some derived classes (like simplicial - complexes). - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex(); B = GenericCellComplex() - sage: A.join(B) - Traceback (most recent call last): - ... - NotImplementedError: - """ - - # for some classes, you may want * to mean join: - ### - # __mul__ = join - - # the cone on X is the join of X with a point. See - # simplicial_complex.py for one implementation. - ### - # def cone(self): - # return self.join(POINT) - - # the suspension of X is the join of X with the 0-sphere (two - # points). See simplicial_complex.py for one implementation. - ### - # def suspension(self, n=1): - # """ - # The suspension of this cell complex. - # - # INPUT: - # - # - ``n`` - positive integer (optional, default 1): suspend this - # many times. - # """ - # raise NotImplementedError - - ############################################################ - # end of methods using self.join() - ############################################################ - - ############################################################ - # chain complexes, homology - ############################################################ - - @abstract_method - def chain_complex(self, subcomplex=None, augmented=False, - verbose=False, check=True, dimensions=None, - base_ring=ZZ, cochain=False): - """ - This is not implemented for general cell complexes. - - Some keywords to possibly implement in a derived class: - - - ``subcomplex`` -- a subcomplex: compute the relative chain complex - - ``augmented`` -- a bool: whether to return the augmented complex - - ``verbose`` -- a bool: whether to print informational messages as - the chain complex is being computed - - ``check`` -- a bool: whether to check that the each - composite of two consecutive differentials is zero - - ``dimensions`` -- if ``None``, compute the chain complex in all - dimensions. If a list or tuple of integers, compute the - chain complex in those dimensions, setting the chain groups - in all other dimensions to zero. - - Definitely implement the following: - - - ``base_ring`` -- commutative ring (optional, default ZZ) - - ``cochain`` -- a bool: whether to return the cochain complex - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex() - sage: A.chain_complex() - Traceback (most recent call last): - ... - NotImplementedError: - """ - - def homology(self, dim=None, base_ring=ZZ, subcomplex=None, - generators=False, cohomology=False, algorithm='pari', - verbose=False, reduced=True, **kwds): - r""" - The (reduced) homology of this cell complex. - - :param dim: If None, then return the homology in every - dimension. If ``dim`` is an integer or list, return the - homology in the given dimensions. (Actually, if ``dim`` is - a list, return the homology in the range from ``min(dim)`` - to ``max(dim)``.) - :type dim: integer or list of integers or None; optional, - default None - :param base_ring: commutative ring, must be ZZ or a field. - :type base_ring: optional, default ZZ - :param subcomplex: a subcomplex of this simplicial complex. - Compute homology relative to this subcomplex. - :type subcomplex: optional, default empty - :param generators: If ``True``, return generators for the homology - groups along with the groups. NOTE: Since :trac:`6100`, the result - may not be what you expect when not using CHomP since its return - is in terms of the chain complex. - :type generators: boolean; optional, default False - :param cohomology: If True, compute cohomology rather than homology. - :type cohomology: boolean; optional, default False - :param algorithm: The options are 'auto', 'dhsw', 'pari' or 'no_chomp'. - See below for a description of what they mean. - :type algorithm: string; optional, default 'pari' - :param verbose: If True, print some messages as the homology is - computed. - :type verbose: boolean; optional, default False - :param reduced: If ``True``, return the reduced homology. - :type reduced: boolean; optional, default ``True`` - - ALGORITHM: - - If ``algorithm`` is set to 'auto', then use - CHomP if available. (CHomP is available at the web page - http://chomp.rutgers.edu/. It is also an optional package - for Sage.) - - CHomP computes homology, not cohomology, and only works over - the integers or finite prime fields. Therefore if any of - these conditions fails, or if CHomP is not present, or if - ``algorithm`` is set to 'no_chomp', go to plan B: if this complex - has a ``_homology`` method -- each simplicial complex has - this, for example -- then call that. Such a method implements - specialized algorithms for the particular type of cell - complex. - - Otherwise, move on to plan C: compute the chain complex of - this complex and compute its homology groups. To do this: over a - field, just compute ranks and nullities, thus obtaining - dimensions of the homology groups as vector spaces. Over the - integers, compute Smith normal form of the boundary matrices - defining the chain complex according to the value of - ``algorithm``. If ``algorithm`` is 'auto' or 'no_chomp', then - for each relatively small matrix, use the standard Sage - method, which calls the Pari package. For any large matrix, - reduce it using the Dumas, Heckenbach, Saunders, and Welker - elimination algorithm: see - :func:`sage.homology.matrix_utils.dhsw_snf` for details. - - Finally, ``algorithm`` may also be 'pari' or 'dhsw', which - forces the named algorithm to be used regardless of the size - of the matrices and regardless of whether CHomP is available. - - As of this writing, ``'pari'`` is the fastest standard option. - The optional CHomP package may be better still. - - EXAMPLES:: - - sage: P = delta_complexes.RealProjectivePlane() - sage: P.homology() - {0: 0, 1: C2, 2: 0} - sage: P.homology(reduced=False) - {0: Z, 1: C2, 2: 0} - sage: P.homology(base_ring=GF(2)) - {0: Vector space of dimension 0 over Finite Field of size 2, - 1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2} - sage: S7 = delta_complexes.Sphere(7) - sage: S7.homology(7) - Z - sage: cubical_complexes.KleinBottle().homology(1, base_ring=GF(2)) - Vector space of dimension 2 over Finite Field of size 2 - - Sage can compute generators of homology groups:: - - sage: S2 = simplicial_complexes.Sphere(2) - sage: S2.homology(dim=2, generators=True, base_ring=GF(2), algorithm='no_chomp') - [(Vector space of dimension 1 over Finite Field of size 2, (0, 1, 2) + (0, 1, 3) + (0, 2, 3) + (1, 2, 3))] - - Note: the answer may be formatted differently if the optional - package CHomP is installed. - - When generators are computed, Sage returns a pair for each - dimension: the group and the list of generators. For - simplicial complexes, each generator is represented as a - linear combination of simplices, as above, and for cubical - complexes, each generator is a linear combination of cubes:: - - sage: S2_cub = cubical_complexes.Sphere(2) - sage: S2_cub.homology(dim=2, generators=True, algorithm='no_chomp') - [(Z, - [0,0] x [0,1] x [0,1] - [0,1] x [0,0] x [0,1] + [0,1] x [0,1] x [0,0] - [0,1] x [0,1] x [1,1] + [0,1] x [1,1] x [0,1] - [1,1] x [0,1] x [0,1])] - - Similarly for simpicial sets:: - - sage: S = simplicial_sets.Sphere(2) - sage: S.homology(generators=True) - {0: [], 1: 0, 2: [(Z, sigma_2)]} - """ - from sage.interfaces.chomp import have_chomp, homcubes, homsimpl - from sage.homology.cubical_complex import CubicalComplex - from sage.homology.simplicial_complex import SimplicialComplex - from sage.modules.all import VectorSpace - from sage.homology.homology_group import HomologyGroup - - if dim is not None: - if isinstance(dim, (list, tuple, range)): - low = min(dim) - 1 - high = max(dim) + 2 - else: - low = dim - 1 - high = dim + 2 - dims = range(low, high) - else: - dims = None - - # try to use CHomP if computing homology (not cohomology) and - # working over Z or F_p, p a prime. - if (algorithm == 'auto' and cohomology is False - and (base_ring == ZZ or (base_ring.is_prime_field() - and base_ring != QQ))): - # homcubes, homsimpl seems fastest if all of homology is computed. - H = None - if isinstance(self, CubicalComplex): - if have_chomp('homcubes'): - H = homcubes(self, subcomplex, base_ring=base_ring, - verbose=verbose, generators=generators) - elif isinstance(self, SimplicialComplex): - if have_chomp('homsimpl'): - H = homsimpl(self, subcomplex, base_ring=base_ring, - verbose=verbose, generators=generators) - - # now pick off the requested dimensions - if H: - answer = {} - if not dims: - dims = range(self.dimension() + 1) - for d in dims: - answer[d] = H.get(d, HomologyGroup(0, base_ring)) - if dim is not None: - if not isinstance(dim, (list, tuple, range)): - answer = answer.get(dim, HomologyGroup(0, base_ring)) - return answer - - # Derived classes can implement specialized algorithms using a - # _homology_ method. See SimplicialComplex for one example. - # Those may allow for other arguments, so we pass **kwds. - if hasattr(self, '_homology_'): - return self._homology_(dim, subcomplex=subcomplex, - cohomology=cohomology, base_ring=base_ring, - verbose=verbose, algorithm=algorithm, - reduced=reduced, generators=generators, - **kwds) - - C = self.chain_complex(cochain=cohomology, augmented=reduced, - dimensions=dims, subcomplex=subcomplex, - base_ring=base_ring, verbose=verbose) - answer = C.homology(base_ring=base_ring, generators=generators, - verbose=verbose, algorithm=algorithm) - - if generators: - # Try to convert chain complex information to topological - # chain information. - for i in answer: - H_with_gens = answer[i] - if H_with_gens: - chains = self.n_chains(i, base_ring=base_ring) - new_H = [] - for (H, gen) in H_with_gens: - v = gen.vector(i) - new_gen = chains.zero() - for (coeff, chain) in zip(v, chains.gens()): - new_gen += coeff * chain - new_H.append((H, new_gen)) - answer[i] = new_H - - if dim is None: - dim = range(self.dimension() + 1) - zero = HomologyGroup(0, base_ring) - if isinstance(dim, (list, tuple, range)): - return dict([d, answer.get(d, zero)] for d in dim) - return answer.get(dim, zero) - - def cohomology(self, dim=None, base_ring=ZZ, subcomplex=None, - generators=False, algorithm='pari', - verbose=False, reduced=True): - r""" - The reduced cohomology of this cell complex. - - The arguments are the same as for the :meth:`homology` method, - except that :meth:`homology` accepts a ``cohomology`` key - word, while this function does not: ``cohomology`` is - automatically true here. Indeed, this function just calls - :meth:`homology` with ``cohomology`` set to ``True``. - - :param dim: - :param base_ring: - :param subcomplex: - :param algorithm: - :param verbose: - :param reduced: - - EXAMPLES:: - - sage: circle = SimplicialComplex([[0,1], [1,2], [0, 2]]) - sage: circle.cohomology(0) - 0 - sage: circle.cohomology(1) - Z - sage: P2 = SimplicialComplex([[0,1,2], [0,2,3], [0,1,5], [0,4,5], [0,3,4], [1,2,4], [1,3,4], [1,3,5], [2,3,5], [2,4,5]]) # projective plane - sage: P2.cohomology(2) - C2 - sage: P2.cohomology(2, base_ring=GF(2)) - Vector space of dimension 1 over Finite Field of size 2 - sage: P2.cohomology(2, base_ring=GF(3)) - Vector space of dimension 0 over Finite Field of size 3 - - sage: cubical_complexes.KleinBottle().cohomology(2) - C2 - - Relative cohomology:: - - sage: T = SimplicialComplex([[0,1]]) - sage: U = SimplicialComplex([[0], [1]]) - sage: T.cohomology(1, subcomplex=U) - Z - - A `\Delta`-complex example:: - - sage: s5 = delta_complexes.Sphere(5) - sage: s5.cohomology(base_ring=GF(7))[5] - Vector space of dimension 1 over Finite Field of size 7 - """ - return self.homology(dim=dim, cohomology=True, base_ring=base_ring, - subcomplex=subcomplex, generators=generators, - algorithm=algorithm, verbose=verbose, - reduced=reduced) - - def betti(self, dim=None, subcomplex=None): - r""" - The Betti numbers of this simplicial complex as a dictionary - (or a single Betti number, if only one dimension is given): - the ith Betti number is the rank of the ith homology group. - - :param dim: If ``None``, then return every Betti number, as - a dictionary with keys the non-negative integers. If - ``dim`` is an integer or list, return the Betti number for - each given dimension. (Actually, if ``dim`` is a list, - return the Betti numbers, as a dictionary, in the range - from ``min(dim)`` to ``max(dim)``. If ``dim`` is a number, - return the Betti number in that dimension.) - :type dim: integer or list of integers or ``None``; optional, - default ``None`` - :param subcomplex: a subcomplex of this cell complex. Compute - the Betti numbers of the homology relative to this subcomplex. - :type subcomplex: optional, default ``None`` - - EXAMPLES: - - Build the two-sphere as a three-fold join of a - two-point space with itself:: - - sage: S = SimplicialComplex([[0], [1]]) - sage: (S*S*S).betti() - {0: 1, 1: 0, 2: 1} - sage: (S*S*S).betti([1,2]) - {1: 0, 2: 1} - sage: (S*S*S).betti(2) - 1 - - Or build the two-sphere as a `\Delta`-complex:: - - sage: S2 = delta_complexes.Sphere(2) - sage: S2.betti([1,2]) - {1: 0, 2: 1} - - Or as a cubical complex:: - - sage: S2c = cubical_complexes.Sphere(2) - sage: S2c.betti(2) - 1 - """ - dict = {} - H = self.homology(dim, base_ring=QQ, subcomplex=subcomplex) - try: - for n in H.keys(): - dict[n] = H[n].dimension() - if n == 0: - dict[n] += 1 - return dict - except AttributeError: - return H.dimension() - - def is_acyclic(self, base_ring=ZZ): - """ - True if the reduced homology with coefficients in ``base_ring`` of - this cell complex is zero. - - INPUT: - - - ``base_ring`` -- optional, default ``ZZ``. Compute homology - with coefficients in this ring. - - EXAMPLES:: - - sage: RP2 = simplicial_complexes.RealProjectivePlane() - sage: RP2.is_acyclic() - False - sage: RP2.is_acyclic(QQ) - True - - This first computes the Euler characteristic: if it is not 1, - the complex cannot be acyclic. So this should return ``False`` - reasonably quickly on complexes with Euler characteristic not - equal to 1:: - - sage: K = cubical_complexes.KleinBottle() - sage: C = cubical_complexes.Cube(2) - sage: P = K.product(C) - sage: P - Cubical complex with 168 vertices and 1512 cubes - sage: P.euler_characteristic() - 0 - sage: P.is_acyclic() - False - """ - if self.euler_characteristic() != 1: - return False - H = self.homology(base_ring=base_ring) - if base_ring == ZZ: - return all(len(x.invariants()) == 0 for x in H.values()) - else: - # base_ring is a field. - return all(x.dimension() == 0 for x in H.values()) - - def n_chains(self, n, base_ring=ZZ, cochains=False): - r""" - Return the free module of chains in degree ``n`` over ``base_ring``. - - INPUT: - - - ``n`` -- integer - - ``base_ring`` -- ring (optional, default `\ZZ`) - - ``cochains`` -- boolean (optional, default ``False``); if - ``True``, return cochains instead - - The only difference between chains and cochains is - notation. In a simplicial complex, for example, a simplex - ``(0,1,2)`` is written as "(0,1,2)" in the group of chains but - as "\chi_(0,1,2)" in the group of cochains. - - EXAMPLES:: - - sage: S2 = simplicial_complexes.Sphere(2) - sage: S2.n_chains(1, QQ) - Free module generated by {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)} over Rational Field - sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=False).basis()) - [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] - sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=True).basis()) - [\chi_(0, 1), \chi_(0, 2), \chi_(0, 3), \chi_(1, 2), \chi_(1, 3), \chi_(2, 3)] - """ - n_cells = tuple(self._n_cells_sorted(n)) - if cochains: - return Cochains(self, n, n_cells, base_ring) - else: - return Chains(self, n, n_cells, base_ring) - - def algebraic_topological_model(self, base_ring=QQ): - r""" - Algebraic topological model for this cell complex with - coefficients in ``base_ring``. - - The term "algebraic topological model" is defined by Pilarczyk - and Réal [PR2015]_. - - This is not implemented for generic cell complexes. For any - classes deriving from this one, when this method is - implemented, it should essentially just call either - :func:`~sage.homology.algebraic_topological_model.algebraic_topological_model` - or - :func:`~sage.homology.algebraic_topological_model.algebraic_topological_model_delta_complex`. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex() - sage: A.algebraic_topological_model(QQ) - Traceback (most recent call last): - ... - NotImplementedError - """ - raise NotImplementedError - - def homology_with_basis(self, base_ring=QQ, cohomology=False): - r""" - Return the unreduced homology of this complex with - coefficients in ``base_ring`` with a chosen basis. - - This is implemented for simplicial, cubical, and - `\Delta`-complexes, not for arbitrary generic cell complexes. - - INPUT: - - - ``base_ring`` -- coefficient ring (optional, default - ``QQ``); must be a field - - ``cohomology`` -- boolean (optional, default ``False``); if - ``True``, return cohomology instead of homology - - Homology basis elements are named 'h_{dim,i}' where i ranges - between 0 and `r-1`, if `r` is the rank of the homology - group. Cohomology basis elements are denoted `h^{dim,i}` - instead. - - .. SEEALSO:: - - If ``cohomology`` is ``True``, this returns the cohomology - as a graded module. For the ring structure, use - :meth:`cohomology_ring`. - - EXAMPLES:: - - sage: K = simplicial_complexes.KleinBottle() - sage: H = K.homology_with_basis(QQ); H - Homology module of Minimal triangulation of the Klein bottle - over Rational Field - sage: sorted(H.basis(), key=str) - [h_{0,0}, h_{1,0}] - sage: H = K.homology_with_basis(GF(2)); H - Homology module of Minimal triangulation of the Klein bottle - over Finite Field of size 2 - sage: sorted(H.basis(), key=str) - [h_{0,0}, h_{1,0}, h_{1,1}, h_{2,0}] - - The homology is constructed as a graded object, so for - example, you can ask for the basis in a single degree:: - - sage: H.basis(1) - Finite family {(1, 0): h_{1,0}, (1, 1): h_{1,1}} - sage: S3 = delta_complexes.Sphere(3) - sage: H = S3.homology_with_basis(QQ, cohomology=True) - sage: list(H.basis(3)) - [h^{3,0}] - """ - from .homology_vector_space_with_basis import HomologyVectorSpaceWithBasis - return HomologyVectorSpaceWithBasis(base_ring, self, cohomology) - - def cohomology_ring(self, base_ring=QQ): - r""" - Return the unreduced cohomology with coefficients in - ``base_ring`` with a chosen basis. - - This is implemented for simplicial, cubical, and - `\Delta`-complexes, not for arbitrary generic cell complexes. - The resulting elements are suitable for computing cup - products. For simplicial complexes, they should be suitable - for computing cohomology operations; so far, only mod 2 - cohomology operations have been implemented. - - INPUT: - - - ``base_ring`` -- coefficient ring (optional, default - ``QQ``); must be a field - - The basis elements in dimension ``dim`` are named 'h^{dim,i}' - where `i` ranges between 0 and `r-1`, if `r` is the rank of - the cohomology group. - - .. NOTE:: - - For all but the smallest complexes, this is likely to be - slower than :meth:`cohomology` (with field coefficients), - possibly by several orders of magnitude. This and its - companion :meth:`homology_with_basis` carry extra - information which allows computation of cup products, for - example, but because of speed issues, you may only wish to - use these if you need that extra information. - - EXAMPLES:: - - sage: K = simplicial_complexes.KleinBottle() - sage: H = K.cohomology_ring(QQ); H - Cohomology ring of Minimal triangulation of the Klein bottle - over Rational Field - sage: sorted(H.basis(), key=str) - [h^{0,0}, h^{1,0}] - sage: H = K.cohomology_ring(GF(2)); H - Cohomology ring of Minimal triangulation of the Klein bottle - over Finite Field of size 2 - sage: sorted(H.basis(), key=str) - [h^{0,0}, h^{1,0}, h^{1,1}, h^{2,0}] - - sage: X = delta_complexes.SurfaceOfGenus(2) - sage: H = X.cohomology_ring(QQ); H - Cohomology ring of Delta complex with 3 vertices and 29 simplices - over Rational Field - sage: sorted(H.basis(1), key=str) - [h^{1,0}, h^{1,1}, h^{1,2}, h^{1,3}] - - sage: H = simplicial_complexes.Torus().cohomology_ring(QQ); H - Cohomology ring of Minimal triangulation of the torus - over Rational Field - sage: x = H.basis()[1,0]; x - h^{1,0} - sage: y = H.basis()[1,1]; y - h^{1,1} - - You can compute cup products of cohomology classes:: - - sage: x.cup_product(y) - -h^{2,0} - sage: x * y # alternate notation - -h^{2,0} - sage: y.cup_product(x) - h^{2,0} - sage: x.cup_product(x) - 0 - - Cohomology operations:: - - sage: RP2 = simplicial_complexes.RealProjectivePlane() - sage: K = RP2.suspension() - sage: K.set_immutable() - sage: y = K.cohomology_ring(GF(2)).basis()[2,0]; y - h^{2,0} - sage: y.Sq(1) - h^{3,0} - - To compute the cohomology ring, the complex must be - "immutable". This is only relevant for simplicial complexes, - and most simplicial complexes are immutable, but certain - constructions make them mutable. The suspension is one - example, and this is the reason for calling - ``K.set_immutable()`` above. Another example:: - - sage: S1 = simplicial_complexes.Sphere(1) - sage: T = S1.product(S1) - sage: T.is_immutable() - False - sage: T.cohomology_ring() - Traceback (most recent call last): - ... - ValueError: This simplicial complex must be immutable. Call set_immutable(). - sage: T.set_immutable() - sage: T.cohomology_ring() - Cohomology ring of Simplicial complex with 9 vertices and - 18 facets over Rational Field - """ - from .homology_vector_space_with_basis import CohomologyRing - return CohomologyRing(base_ring, self) - - @abstract_method - def alexander_whitney(self, cell, dim_left): - r""" - The decomposition of ``cell`` in this complex into left and right - factors, suitable for computing cup products. This should - provide a cellular approximation for the diagonal map `K \to K - \times K`. - - This method is not implemented for generic cell complexes, but - must be implemented for any derived class to make cup products - work in ``self.cohomology_ring()``. - - INPUT: - - - ``cell`` -- a cell in this complex - - ``dim_left`` -- the dimension of the left-hand factors in - the decomposition - - OUTPUT: a list containing triples ``(c, left, right)``. - ``left`` and ``right`` should be cells in this complex, and - ``c`` an integer. In the cellular approximation of the - diagonal map, the chain represented by ``cell`` should get - sent to the sum of terms `c (left \otimes right)` in the - tensor product `C(K) \otimes C(K)` of the chain complex for - this complex with itself. - - This gets used in the method - :meth:`~sage.homology.homology_vector_space_with_basis.CohomologyRing.product_on_basis` - for the class of cohomology rings. - - For simplicial and cubical complexes, the decomposition can be - done at the level of individual cells: see - :meth:`~sage.homology.simplicial_complex.Simplex.alexander_whitney` - and - :meth:`~sage.homology.cubical_complex.Cube.alexander_whitney`. Then - the method for simplicial complexes just calls the method for - individual simplices, and similarly for cubical complexes. For - `\Delta`-complexes and simplicial sets, the method is instead - defined at the level of the cell complex. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex() - sage: A.alexander_whitney(None, 2) - Traceback (most recent call last): - ... - NotImplementedError: - """ - - ############################################################ - # end of chain complexes, homology - ############################################################ - - def face_poset(self): - r""" - The face poset of this cell complex, the poset of - nonempty cells, ordered by inclusion. - - This uses the :meth:`cells` method, and also assumes that for - each cell ``f``, all of ``f.faces()``, ``tuple(f)``, and - ``f.dimension()`` make sense. (If this is not the case in - some derived class, as happens with `\Delta`-complexes, then - override this method.) - - EXAMPLES:: - - sage: P = SimplicialComplex([[0, 1], [1,2], [2,3]]).face_poset(); P - Finite poset containing 7 elements - sage: sorted(P.list()) - [(0,), (0, 1), (1,), (1, 2), (2,), (2, 3), (3,)] - - sage: S2 = cubical_complexes.Sphere(2) - sage: S2.face_poset() - Finite poset containing 26 elements - """ - from sage.combinat.posets.posets import Poset - from sage.misc.flatten import flatten - covers = {} - # The code for posets seems to work better if each cell is - # converted to a tuple. - all_cells = flatten([list(f) for f in self.cells().values()]) - - for C in all_cells: - if C.dimension() >= 0: - covers[tuple(C)] = [] - for C in all_cells: - for face in C.faces(): - if face.dimension() >= 0: - covers[tuple(face)].append(tuple(C)) - return Poset(covers) - - def graph(self): - """ - The 1-skeleton of this cell complex, as a graph. - - This is not implemented for general cell complexes. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex() - sage: A.graph() - Traceback (most recent call last): - ... - NotImplementedError - """ - raise NotImplementedError - - def is_connected(self): - """ - True if this cell complex is connected. - - EXAMPLES:: - - sage: V = SimplicialComplex([[0,1,2],[3]]) - sage: V - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(3,), (0, 1, 2)} - sage: V.is_connected() - False - sage: X = SimplicialComplex([[0,1,2]]) - sage: X.is_connected() - True - sage: U = simplicial_complexes.ChessboardComplex(3,3) - sage: U.is_connected() - True - sage: W = simplicial_complexes.Sphere(3) - sage: W.is_connected() - True - sage: S = SimplicialComplex([[0,1],[2,3]]) - sage: S.is_connected() - False - - sage: cubical_complexes.Sphere(0).is_connected() - False - sage: cubical_complexes.Sphere(2).is_connected() - True - """ - return self.graph().is_connected() - - @abstract_method - def n_skeleton(self, n): - """ - The `n`-skeleton of this cell complex: the cell - complex obtained by discarding all of the simplices in - dimensions larger than `n`. - - :param n: non-negative integer - - This is not implemented for general cell complexes. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: A = GenericCellComplex() - sage: A.n_skeleton(3) - Traceback (most recent call last): - ... - NotImplementedError: - """ - - def _string_constants(self): - """ - Tuple containing the name of the type of complex, and the - singular and plural of the name of the cells from which it is - built. This is used in constructing the string representation. - - :return: tuple of strings - - This returns ``('Cell', 'cell', 'cells')``, as in "Cell - complex", "1 cell", and "24 cells", but in other classes it - could be overridden, as for example with ``('Cubical', 'cube', - 'cubes')`` or ``('Delta', 'simplex', 'simplices')``. If for a - derived class, the basic form of the print representation is - acceptable, you can just modify these strings. - - EXAMPLES:: - - sage: from sage.homology.cell_complex import GenericCellComplex - sage: GenericCellComplex()._string_constants() - ('Cell', 'cell', 'cells') - sage: delta_complexes.Sphere(0)._string_constants() - ('Delta', 'simplex', 'simplices') - sage: cubical_complexes.Sphere(0)._string_constants() - ('Cubical', 'cube', 'cubes') - """ - return ('Cell', 'cell', 'cells') - - def _repr_(self): - """ - Print representation. - - :return: string - - EXAMPLES:: - - sage: delta_complexes.Sphere(7) # indirect doctest - Delta complex with 8 vertices and 257 simplices - sage: delta_complexes.Torus()._repr_() - 'Delta complex with 1 vertex and 7 simplices' - """ - vertices = len(self.n_cells(0)) - Name, cell_name, cells_name = self._string_constants() - if vertices != 1: - vertex_string = "with %s vertices" % vertices - else: - vertex_string = "with 1 vertex" - cells = 0 - for dim in self.cells(): - cells += len(self.cells()[dim]) - if cells != 1: - cells_string = " and %s %s" % (cells, cells_name) - else: - cells_string = " and 1 %s" % cell_name - return Name + " complex " + vertex_string + cells_string - +from sage.misc.superseded import deprecated_function_alias +import sage.topology.cell_complex +GenericCellComplex = deprecated_function_alias(31925, + sage.topology.cell_complex.GenericCellComplex) diff --git a/src/sage/homology/chain_complex.py b/src/sage/homology/chain_complex.py index 49aaff1a854..0f8492d140f 100644 --- a/src/sage/homology/chain_complex.py +++ b/src/sage/homology/chain_complex.py @@ -957,6 +957,7 @@ def differential(self, dim=None): except KeyError: pass # all differentials that are not 0x0 are in self._diff + # TODO: turn differentials into morphisms between free modules? return matrix(self.base_ring(), 0, 0) def dual(self): @@ -1270,7 +1271,7 @@ def homology(self, deg=None, base_ring=None, generators=False, sage: C_k = ChainComplex({0:d0, 1:d1, 2:d2}, degree=-1) sage: C_k.homology(generators=true, algorithm='no_chomp') {0: [(Z, Chain(0:(1)))], - 1: [(C2, Chain(1:(1, 0, 0))), (Z, Chain(1:(0, 0, 1)))], + 1: [(C2, Chain(1:(0, 1, -1))), (Z, Chain(1:(0, 1, 0)))], 2: []} From a torus using a field:: diff --git a/src/sage/homology/chains.py b/src/sage/homology/chains.py index 7d1ad790137..35df385f23d 100644 --- a/src/sage/homology/chains.py +++ b/src/sage/homology/chains.py @@ -4,7 +4,7 @@ This module implements formal linear combinations of cells of a given cell complex (:class:`Chains`) and their dual (:class:`Cochains`). It -is closely related to the :mod:`sage.homology.chain_complex` +is closely related to the :mod:`sage.topology.chain_complex` module. The main differences are that chains and cochains here are of homogeneous dimension only, and that they reference their cell complex. @@ -235,7 +235,7 @@ def to_complex(self): sage: square = cubical_complexes.Cube(2) sage: C1 = square.n_chains(1, QQ) - sage: from sage.homology.cubical_complex import Cube + sage: from sage.topology.cubical_complex import Cube sage: chain = C1(Cube([[1, 1], [0, 1]])) sage: chain.to_complex() Chain(1:(0, 0, 0, 1)) @@ -261,7 +261,7 @@ def boundary(self): sage: square = cubical_complexes.Cube(2) sage: C1 = square.n_chains(1, QQ) - sage: from sage.homology.cubical_complex import Cube + sage: from sage.topology.cubical_complex import Cube sage: chain = C1(Cube([[1, 1], [0, 1]])) - 2 * C1(Cube([[0, 1], [0, 0]])) sage: chain -2*[0,1] x [0,0] + [1,1] x [0,1] @@ -288,7 +288,7 @@ def is_cycle(self): sage: square = cubical_complexes.Cube(2) sage: C1 = square.n_chains(1, QQ) - sage: from sage.homology.cubical_complex import Cube + sage: from sage.topology.cubical_complex import Cube sage: chain = C1(Cube([[1, 1], [0, 1]])) - C1(Cube([[0, 1], [0, 0]])) sage: chain.is_cycle() False @@ -315,7 +315,7 @@ def is_boundary(self): sage: square = cubical_complexes.Cube(2) sage: C1 = square.n_chains(1, QQ) - sage: from sage.homology.cubical_complex import Cube + sage: from sage.topology.cubical_complex import Cube sage: chain = C1(Cube([[1, 1], [0, 1]])) - C1(Cube([[0, 1], [0, 0]])) sage: chain.is_boundary() False @@ -476,7 +476,7 @@ def to_complex(self): sage: square = cubical_complexes.Cube(2) sage: C1 = square.n_chains(1, QQ, cochains=True) - sage: from sage.homology.cubical_complex import Cube + sage: from sage.topology.cubical_complex import Cube sage: cochain = C1(Cube([[1, 1], [0, 1]])) sage: cochain.to_complex() Chain(1:(0, 0, 0, 1)) @@ -502,7 +502,7 @@ def coboundary(self): sage: square = cubical_complexes.Cube(2) sage: C1 = square.n_chains(1, QQ, cochains=True) - sage: from sage.homology.cubical_complex import Cube + sage: from sage.topology.cubical_complex import Cube sage: cochain = C1(Cube([[1, 1], [0, 1]])) - 2 * C1(Cube([[0, 1], [0, 0]])) sage: cochain -2*\chi_[0,1] x [0,0] + \chi_[1,1] x [0,1] @@ -529,7 +529,7 @@ def is_cocycle(self): sage: square = cubical_complexes.Cube(2) sage: C1 = square.n_chains(1, QQ, cochains=True) - sage: from sage.homology.cubical_complex import Cube + sage: from sage.topology.cubical_complex import Cube sage: cochain = C1(Cube([[1, 1], [0, 1]])) - C1(Cube([[0, 1], [0, 0]])) sage: cochain.is_cocycle() True @@ -556,7 +556,7 @@ def is_coboundary(self): sage: square = cubical_complexes.Cube(2) sage: C1 = square.n_chains(1, QQ, cochains=True) - sage: from sage.homology.cubical_complex import Cube + sage: from sage.topology.cubical_complex import Cube sage: cochain = C1(Cube([[1, 1], [0, 1]])) - C1(Cube([[0, 1], [0, 0]])) sage: cochain.is_coboundary() True diff --git a/src/sage/homology/cubical_complex.py b/src/sage/homology/cubical_complex.py index a1a1e32cc72..1149da028c0 100644 --- a/src/sage/homology/cubical_complex.py +++ b/src/sage/homology/cubical_complex.py @@ -1,1929 +1,17 @@ # -*- coding: utf-8 -*- r""" -Finite cubical complexes +Finite cubical complexes: deprecated -AUTHORS: - -- John H. Palmieri (2009-08) - -This module implements the basic structure of finite cubical -complexes. For full mathematical details, see Kaczynski, Mischaikow, -and Mrozek [KMM2004]_, for example. - -Cubical complexes are topological spaces built from gluing together -cubes of various dimensions; the collection of cubes must be closed -under taking faces, just as with a simplicial complex. In this -context, a "cube" means a product of intervals of length 1 or length 0 -(degenerate intervals), with integer endpoints, and its faces are -obtained by using the nondegenerate intervals: if `C` is a cube -- a -product of degenerate and nondegenerate intervals -- and if `[i,i+1]` -is the `k`-th nondegenerate factor, then `C` has two faces indexed by -`k`: the cubes obtained by replacing `[i, i+1]` with `[i, i]` or -`[i+1, i+1]`. - -So to construct a space homeomorphic to a circle as a cubical complex, -we could take for example the four line segments in the plane from -`(0,2)` to `(0,3)` to `(1,3)` to `(1,2)` to `(0,2)`. In Sage, this is -done with the following command:: - - sage: S1 = CubicalComplex([([0,0], [2,3]), ([0,1], [3,3]), ([0,1], [2,2]), ([1,1], [2,3])]); S1 - Cubical complex with 4 vertices and 8 cubes - -The argument to ``CubicalComplex`` is a list of the maximal "cubes" in -the complex. Each "cube" can be an instance of the class ``Cube`` or -a list (or tuple) of "intervals", and an "interval" is a pair of -integers, of one of the two forms `[i, i]` or `[i, i+1]`. So the -cubical complex ``S1`` above has four maximal cubes:: - - sage: len(S1.maximal_cells()) - 4 - sage: sorted(S1.maximal_cells()) - [[0,0] x [2,3], [0,1] x [2,2], [0,1] x [3,3], [1,1] x [2,3]] - -The first of these, for instance, is the product of the degenerate -interval `[0,0]` with the unit interval `[2,3]`: this is the line -segment in the plane from `(0,2)` to `(0,3)`. We could form a -topologically equivalent space by inserting some degenerate simplices:: - - sage: S1.homology() - {0: 0, 1: Z} - sage: X = CubicalComplex([([0,0], [2,3], [2]), ([0,1], [3,3], [2]), ([0,1], [2,2], [2]), ([1,1], [2,3], [2])]) - sage: X.homology() - {0: 0, 1: Z} - -Topologically, the cubical complex ``X`` consists of four edges of a -square in `\RR^3`: the same unit square as ``S1``, but embedded in -`\RR^3` with `z`-coordinate equal to 2. Thus ``X`` is homeomorphic to -``S1`` (in fact, they're "cubically equivalent"), and this is -reflected in the fact that they have isomorphic homology groups. - -.. note:: - - This class derives from - :class:`~sage.homology.cell_complex.GenericCellComplex`, and so - inherits its methods. Some of those methods are not listed here; - see the :mod:`Generic Cell Complex ` - page instead. +The current version is :mod:`sage.topology.cubical_complexes`. """ -from copy import copy -from sage.homology.cell_complex import GenericCellComplex -from sage.structure.sage_object import SageObject -from sage.rings.integer import Integer -from sage.sets.set import Set -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.matrix.constructor import matrix -from sage.homology.chain_complex import ChainComplex -from sage.graphs.graph import Graph -from sage.misc.cachefunc import cached_method -from functools import total_ordering - - -@total_ordering -class Cube(SageObject): - r""" - Define a cube for use in constructing a cubical complex. - - "Elementary cubes" are products of intervals with integer - endpoints, each of which is either a unit interval or a degenerate - (length 0) interval; for example, - - .. MATH:: - - [0,1] \times [3,4] \times [2,2] \times [1,2] - - is a 3-dimensional cube (since one of the intervals is degenerate) - embedded in `\RR^4`. - - :param data: list or tuple of terms of the form ``(i,i+1)`` or - ``(i,i)`` or ``(i,)`` -- the last two are degenerate intervals. - :return: an elementary cube - - Each cube is stored in a standard form: a tuple of tuples, with a - nondegenerate interval ``[j,j]`` represented by ``(j,j)``, not - ``(j,)``. (This is so that for any interval ``I``, ``I[1]`` will - produce a value, not an ``IndexError``.) - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]); C - [1,2] x [5,5] x [6,7] x [-1,0] - sage: C.dimension() # number of nondegenerate intervals - 3 - sage: C.nondegenerate_intervals() # indices of these intervals - [0, 2, 3] - sage: C.face(1, upper=False) - [1,2] x [5,5] x [6,6] x [-1,0] - sage: C.face(1, upper=True) - [1,2] x [5,5] x [7,7] x [-1,0] - sage: Cube(()).dimension() # empty cube has dimension -1 - -1 - """ - def __init__(self, data): - """ - Define a cube for use in constructing a cubical complex. - - See ``Cube`` for more information. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]); C # indirect doctest - [1,2] x [5,5] x [6,7] x [-1,0] - sage: C == loads(dumps(C)) - True - """ - if isinstance(data, Cube): - data = tuple(data) - new_data = [] - nondegenerate = [] - i = 0 - for x in data: - if len(x) == 2: - try: - Integer(x[0]) - except TypeError: - raise ValueError("The interval %s is not of the correct form" % x) - if x[0] + 1 == x[1]: - nondegenerate.append(i) - elif x[0] != x[1]: - raise ValueError("The interval %s is not of the correct form" % x) - new_data.append(tuple(x)) - elif len(x) == 1: - y = tuple(x) - new_data.append(y+y) - elif len(x) != 1: - raise ValueError("The interval %s is not of the correct form" % x) - i += 1 - self.__tuple = tuple(new_data) - self.__nondegenerate = nondegenerate - - def tuple(self): - """ - The tuple attached to this cube. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) - sage: C.tuple() - ((1, 2), (5, 5), (6, 7), (-1, 0)) - """ - return self.__tuple - - def is_face(self, other): - """ - Return True iff this cube is a face of other. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C1 = Cube([[1,2], [5,], [6,7], [-1, 0]]) - sage: C2 = Cube([[1,2], [5,], [6,], [-1, 0]]) - sage: C1.is_face(C2) - False - sage: C1.is_face(C1) - True - sage: C2.is_face(C1) - True - """ - def is_subinterval(i1, i2): - return ((i1[0] == i2[0] and i1[1] == i2[1]) or - (i1[0] == i2[0] and i1[1] == i2[0]) or - (i1[0] == i2[1] and i1[1] == i2[1])) - - t = self.tuple() - u = other.tuple() - if len(t) == len(u): - # these must be equal for self to be a face of other - return all(is_subinterval(ti, ui) for ti, ui in zip(t, u)) - else: - return False - - def _translate(self, vec): - """ - Translate ``self`` by ``vec``. - - :param vec: anything which can be converted to a tuple of integers - :return: the translation of ``self`` by ``vec`` - :rtype: Cube - - If ``vec`` is shorter than the list of intervals forming the - cube, pad with zeroes, and similarly if the cube's defining - tuple is too short. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) - sage: C._translate((-12,)) - [-11,-10] x [5,5] x [6,7] x [-1,0] - sage: C._translate((0, 0, 0, 0, 0, 5)) - [1,2] x [5,5] x [6,7] x [-1,0] x [0,0] x [5,5] - """ - t = self.__tuple - embed = max(len(t), len(vec)) - t = t + ((0,0),) * (embed-len(t)) - vec = tuple(vec) + (0,) * (embed-len(vec)) - new = [] - for (a, b) in zip(t, vec): - new.append([a[0] + b, a[1] + b]) - return Cube(new) - - def __getitem__(self, n): - """ - Return the nth interval in this cube. - - :param n: an integer - :return: tuple representing the `n`-th interval in the cube. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) - sage: C[2] - (6, 7) - sage: C[1] - (5, 5) - """ - return self.__tuple[n] - - def __iter__(self): - """ - Iterator for the intervals of this cube. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) - sage: [x[0] for x in C] - [1, 5, 6, -1] - """ - return iter(self.__tuple) - - def __add__(self, other): - """ - Cube obtained by concatenating the underlying tuples of the - two arguments. - - :param other: another cube - :return: the product of ``self`` and ``other``, as a Cube - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [3,]]) - sage: D = Cube([[4], [0,1]]) - sage: C.product(D) - [1,2] x [3,3] x [4,4] x [0,1] - - You can also use ``__add__`` or ``+`` or ``__mul__`` or ``*``:: - - sage: D * C - [4,4] x [0,1] x [1,2] x [3,3] - sage: D + C * C - [4,4] x [0,1] x [1,2] x [3,3] x [1,2] x [3,3] - """ - return Cube(self.__tuple + other.__tuple) - - # the __add__ operation actually produces the product of the two cubes - __mul__ = __add__ - product = __add__ - - def nondegenerate_intervals(self): - """ - The list of indices of nondegenerate intervals of this cube. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) - sage: C.nondegenerate_intervals() - [0, 2, 3] - sage: C = Cube([[1,], [5,], [6,], [-1,]]) - sage: C.nondegenerate_intervals() - [] - """ - return self.__nondegenerate - - def dimension(self): - """ - The dimension of this cube: the number of its nondegenerate intervals. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) - sage: C.dimension() - 3 - sage: C = Cube([[1,], [5,], [6,], [-1,]]) - sage: C.dimension() - 0 - sage: Cube([]).dimension() # empty cube has dimension -1 - -1 - """ - if len(self.__tuple) == 0: # empty cube - return -1 - return len(self.nondegenerate_intervals()) - - def face(self, n, upper=True): - """ - The nth primary face of this cube. - - :param n: an integer between 0 and one less than the dimension - of this cube - :param upper: if True, return the "upper" nth primary face; - otherwise, return the "lower" nth primary face. - :type upper: boolean; optional, default=True - :return: the cube obtained by replacing the nth non-degenerate - interval with either its upper or lower endpoint. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]); C - [1,2] x [5,5] x [6,7] x [-1,0] - sage: C.face(0) - [2,2] x [5,5] x [6,7] x [-1,0] - sage: C.face(0, upper=False) - [1,1] x [5,5] x [6,7] x [-1,0] - sage: C.face(1) - [1,2] x [5,5] x [7,7] x [-1,0] - sage: C.face(2, upper=False) - [1,2] x [5,5] x [6,7] x [-1,-1] - sage: C.face(3) - Traceback (most recent call last): - ... - ValueError: Can only compute the nth face if 0 <= n < dim. - """ - if n < 0 or n >= self.dimension(): - raise ValueError("Can only compute the nth face if 0 <= n < dim.") - idx = self.nondegenerate_intervals()[n] - t = self.__tuple - if upper: - new = t[idx][1] - else: - new = t[idx][0] - return Cube(t[0:idx] + ((new,new),) + t[idx+1:]) - - def faces(self): - """ - The list of faces (of codimension 1) of this cube. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [3,4]]) - sage: C.faces() - [[2,2] x [3,4], [1,2] x [4,4], [1,1] x [3,4], [1,2] x [3,3]] - """ - upper = [self.face(i,True) for i in range(self.dimension())] - lower = [self.face(i,False) for i in range(self.dimension())] - return upper + lower - - def faces_as_pairs(self): - """ - The list of faces (of codimension 1) of this cube, as pairs - (upper, lower). - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [3,4]]) - sage: C.faces_as_pairs() - [([2,2] x [3,4], [1,1] x [3,4]), ([1,2] x [4,4], [1,2] x [3,3])] - """ - upper = [self.face(i, True) for i in range(self.dimension())] - lower = [self.face(i, False) for i in range(self.dimension())] - return list(zip(upper, lower)) - - def _compare_for_gluing(self, other): - r""" - Given two cubes ``self`` and ``other``, describe how to - transform them so that they become equal. - - :param other: a cube of the same dimension as ``self`` - :return: a triple ``(insert_self, insert_other, translate)``. - ``insert_self`` is a tuple with entries ``(index, (list of - degenerate intervals))``. ``insert_other`` is similar. - ``translate`` is a tuple of integers, suitable as a second - argument for the ``_translate`` method. - - To do this, ``self`` and ``other`` must have the same - dimension; degenerate intervals from ``other`` are added to - ``self``, and vice versa. Intervals in ``other`` are - translated so that they coincide with the intervals in - ``self``. The output is a triple, as noted above: in the - tuple ``insert_self``, an entry like ``(3, (3, 4, 0))`` means - that in position 3 in ``self``, insert the degenerate - intervals ``[3,3]``, ``[4,4]``, and ``[0,0]``. The same goes - for ``insert_other``. After applying the translations to the - cube ``other``, call ``_translate`` with argument the tuple - ``translate``. - - This is used in forming connected sums of cubical complexes: - the two complexes are modified, via this method, so that they - have a cube which matches up, then those matching cubes are - removed. - - In the example below, this method is called with arguments - ``C1`` and ``C2``, where - - .. MATH:: - - C1 = [0,1] \times [3] \times [4] \times [6,7] \\ - C2 = [2] \times [7,8] \times [9] \times [1,2] \times [0] \times [5] - - To C1, we need to add [2] in position 0 and [0] and [5] in - position 5. To C2, we need to add [4] in position 3. Once - this has been done, we need to translate the new C2 by the - vector ``(0, -7, -6, 0, 5, 0, 0)``. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C1 = Cube([[0,1], [3], [4], [6,7]]) - sage: C2 = Cube([[2], [7,8], [9], [1,2], [0], [5]]) - sage: C1._compare_for_gluing(C2) - ([(0, ((2, 2),)), (5, ((0, 0), (5, 5)))], [(3, ((4, 4),))], [0, -7, -6, 0, 5, 0, 0]) - - sage: C1 = Cube([[1,1], [0,1]]) - sage: C2 = Cube([[2,3], [4,4], [5,5]]) - sage: C1._compare_for_gluing(C2) - ([(2, ((4, 4), (5, 5)))], [(0, ((1, 1),))], [0, -2, 0, 0]) - """ - d = self.dimension() - if d != other.dimension(): - raise ValueError("Cubes must be of the same dimension.") - insert_self = [] - insert_other = [] - translate = [] - self_tuple = self.tuple() - other_tuple = other.tuple() - nondegen = (list(zip(self.nondegenerate_intervals(), - other.nondegenerate_intervals())) - + [(len(self_tuple), len(other_tuple))]) - old = (-1, -1) - self_added = 0 - other_added = 0 - - for current in nondegen: - # number of positions between nondegenerate intervals: - self_diff = current[0] - old[0] - other_diff = current[1] - old[1] - diff = self_diff - other_diff - - if diff < 0: - insert_self.append((old[0] + self_diff + self_added, - other.tuple()[current[1]+diff:current[1]])) - common_terms = self_diff - diff = -diff - self_added += diff - elif diff > 0: - insert_other.append((old[1] + other_diff + other_added, - self.tuple()[current[0]-diff:current[0]])) - common_terms = other_diff - other_added += diff - else: - common_terms = other_diff - - if old[0] > -1: - translate.extend([self_tuple[old[0]+idx][0] - - other_tuple[old[1]+idx][0] for idx in - range(common_terms)]) - translate.extend(diff*[0]) - old = current - - return (insert_self, insert_other, translate) - - def _triangulation_(self): - r""" - Triangulate this cube by "pulling vertices," as described by - Hetyei. Return a list of simplices which triangulate - ``self``. - - ALGORITHM: - - If the cube is given by - - .. MATH:: - - C = [i_1, j_1] \times [i_2, j_2] \times ... \times [i_k, j_k] - - let `v_1` be the "upper" corner of `C`: `v` is the point - `(j_1, ..., j_k)`. Choose a coordinate `n` where the interval - `[i_n, j_n]` is non-degenerate and form `v_2` by replacing - `j_n` by `i_n`; repeat to define `v_3`, etc. The last vertex - so defined will be `(i_1, ..., i_k)`. These vertices define a - simplex, as do the vertices obtained by making different - choices at each stage. Return the list of such simplices; - thus if `C` is `n`-dimensional, then it is subdivided into - `n!` simplices. - - REFERENCES: - - - G. Hetyei, "On the Stanley ring of a cubical complex", - Discrete Comput. Geom. 14 (1995), 305-330. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C = Cube([[1,2], [3,4]]); C - [1,2] x [3,4] - sage: C._triangulation_() - [((1, 3), (1, 4), (2, 4)), ((1, 3), (2, 3), (2, 4))] - sage: C = Cube([[1,2], [3,4], [8,9]]) - sage: len(C._triangulation_()) - 6 - """ - from sage.homology.simplicial_complex import Simplex - if self.dimension() < 0: # the empty cube - return [Simplex(())] # the empty simplex - v = tuple([max(j) for j in self.tuple()]) - if self.dimension() == 0: # just v - return [Simplex((v,))] - simplices = [] - for i in range(self.dimension()): - for S in self.face(i, upper=False)._triangulation_(): - simplices.append(S.join(Simplex((v,)), rename_vertices=False)) - return simplices - - def alexander_whitney(self, dim): - r""" - Subdivide this cube into pairs of cubes. - - This provides a cubical approximation for the diagonal map - `K \to K \times K`. - - INPUT: - - - ``dim`` -- integer between 0 and one more than the - dimension of this cube - - OUTPUT: - - - a list containing triples ``(coeff, left, right)`` - - This uses the algorithm described by Pilarczyk and Réal [PR2015]_ - on p. 267; the formula is originally due to Serre. Calling - this method ``alexander_whitney`` is an abuse of notation, - since the actual Alexander-Whitney map goes from `C(K \times - L) \to C(K) \otimes C(L)`, where `C(-)` denotes the associated - chain complex, but this subdivision of cubes is at the heart - of it. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C1 = Cube([[0,1], [3,4]]) - sage: C1.alexander_whitney(0) - [(1, [0,0] x [3,3], [0,1] x [3,4])] - sage: C1.alexander_whitney(1) - [(1, [0,1] x [3,3], [1,1] x [3,4]), (-1, [0,0] x [3,4], [0,1] x [4,4])] - sage: C1.alexander_whitney(2) - [(1, [0,1] x [3,4], [1,1] x [4,4])] - """ - from sage.sets.set import Set - N = Set(self.nondegenerate_intervals()) - result = [] - for J in N.subsets(dim): - Jprime = N.difference(J) - nu = 0 - for i in J: - for j in Jprime: - if j= C2 - True - sage: C1 > C2 - False - sage: C3 <= C1 - True - sage: C1 > C3 - True - """ - return tuple(self) < tuple(other) - - def __hash__(self): - """ - Hash value for this cube. This computes the hash value of the - underlying tuple, since this is what's important when testing - equality. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C1 = Cube([[1,1], [2,3], [4,5]]) - sage: hash(C1) == hash(((1,1),(2,3),(4,5))) - True - """ - return hash(self.__tuple) - - def _repr_(self): - """ - Print representation of a cube. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C1 = Cube([[1,1], [2,3], [4,5]]) - sage: C1 - [1,1] x [2,3] x [4,5] - sage: C1._repr_() - '[1,1] x [2,3] x [4,5]' - """ - s = ["[%s,%s]"%(str(x), str(y)) for (x,y) in self.__tuple] - return " x ".join(s) - - def _latex_(self): - r""" - LaTeX representation of a cube.. - - EXAMPLES:: - - sage: from sage.homology.cubical_complex import Cube - sage: C1 = Cube([[1,1], [2,3], [4,5]]) - sage: latex(C1) - [1,1] \times [2,3] \times [4,5] - sage: C1._latex_() - '[1,1] \\times [2,3] \\times [4,5]' - """ - return self._repr_().replace('x', r'\times') - - -class CubicalComplex(GenericCellComplex): - r""" - Define a cubical complex. - - :param maximal_faces: set of maximal faces - :param maximality_check: see below - :type maximality_check: boolean; optional, default True - :return: a cubical complex - - ``maximal_faces`` should be a list or tuple or set (or anything - which may be converted to a set) of "cubes": instances of the - class :class:`Cube`, or lists or tuples suitable for conversion to - cubes. These cubes are the maximal cubes in the complex. - - In addition, ``maximal_faces`` may be a cubical complex, in which - case that complex is returned. Also, ``maximal_faces`` may - instead be any object which has a ``_cubical_`` method (e.g., a - simplicial complex); then that method is used to convert the - object to a cubical complex. - - If ``maximality_check`` is True, check that each maximal face is, - in fact, maximal. In this case, when producing the internal - representation of the cubical complex, omit those that are not. - It is highly recommended that this be True; various methods for - this class may fail if faces which are claimed to be maximal are - in fact not. - - EXAMPLES: - - The empty complex, consisting of one cube, the empty cube:: - - sage: CubicalComplex() - Cubical complex with 0 vertices and 1 cube - - A "circle" (four edges connecting the vertices (0,2), (0,3), - (1,2), and (1,3)):: - - sage: S1 = CubicalComplex([([0,0], [2,3]), ([0,1], [3,3]), ([0,1], [2,2]), ([1,1], [2,3])]) - sage: S1 - Cubical complex with 4 vertices and 8 cubes - sage: S1.homology() - {0: 0, 1: Z} - - A set of five points and its product with ``S1``:: - - sage: pts = CubicalComplex([([0],), ([3],), ([6],), ([-12],), ([5],)]) - sage: pts - Cubical complex with 5 vertices and 5 cubes - sage: pts.homology() - {0: Z x Z x Z x Z} - sage: X = S1.product(pts); X - Cubical complex with 20 vertices and 40 cubes - sage: X.homology() - {0: Z x Z x Z x Z, 1: Z^5} - - Converting a simplicial complex to a cubical complex:: - - sage: S2 = simplicial_complexes.Sphere(2) - sage: C2 = CubicalComplex(S2) - sage: all(C2.homology(n) == S2.homology(n) for n in range(3)) - True - - You can get the set of maximal cells or a dictionary of all cells:: - - sage: X.maximal_cells() # random: order may depend on the version of Python - {[0,0] x [2,3] x [-12,-12], [0,1] x [3,3] x [5,5], [0,1] x [2,2] x [3,3], [0,1] x [2,2] x [0,0], [0,1] x [3,3] x [6,6], [1,1] x [2,3] x [0,0], [0,1] x [2,2] x [-12,-12], [0,0] x [2,3] x [6,6], [1,1] x [2,3] x [-12,-12], [1,1] x [2,3] x [5,5], [0,1] x [2,2] x [5,5], [0,1] x [3,3] x [3,3], [1,1] x [2,3] x [3,3], [0,0] x [2,3] x [5,5], [0,1] x [3,3] x [0,0], [1,1] x [2,3] x [6,6], [0,1] x [2,2] x [6,6], [0,0] x [2,3] x [0,0], [0,0] x [2,3] x [3,3], [0,1] x [3,3] x [-12,-12]} - sage: sorted(X.maximal_cells()) - [[0,0] x [2,3] x [-12,-12], - [0,0] x [2,3] x [0,0], - [0,0] x [2,3] x [3,3], - [0,0] x [2,3] x [5,5], - [0,0] x [2,3] x [6,6], - [0,1] x [2,2] x [-12,-12], - [0,1] x [2,2] x [0,0], - [0,1] x [2,2] x [3,3], - [0,1] x [2,2] x [5,5], - [0,1] x [2,2] x [6,6], - [0,1] x [3,3] x [-12,-12], - [0,1] x [3,3] x [0,0], - [0,1] x [3,3] x [3,3], - [0,1] x [3,3] x [5,5], - [0,1] x [3,3] x [6,6], - [1,1] x [2,3] x [-12,-12], - [1,1] x [2,3] x [0,0], - [1,1] x [2,3] x [3,3], - [1,1] x [2,3] x [5,5], - [1,1] x [2,3] x [6,6]] - sage: S1.cells() - {-1: set(), - 0: {[0,0] x [2,2], [0,0] x [3,3], [1,1] x [2,2], [1,1] x [3,3]}, - 1: {[0,0] x [2,3], [0,1] x [2,2], [0,1] x [3,3], [1,1] x [2,3]}} - - Chain complexes, homology, and cohomology:: - - sage: T = S1.product(S1); T - Cubical complex with 16 vertices and 64 cubes - sage: T.chain_complex() - Chain complex with at most 3 nonzero terms over Integer Ring - sage: T.homology(base_ring=QQ) - {0: Vector space of dimension 0 over Rational Field, - 1: Vector space of dimension 2 over Rational Field, - 2: Vector space of dimension 1 over Rational Field} - sage: RP2 = cubical_complexes.RealProjectivePlane() - sage: RP2.cohomology(dim=[1, 2], base_ring=GF(2)) - {1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2} - - Joins are not implemented:: - - sage: S1.join(S1) - Traceback (most recent call last): - ... - NotImplementedError: Joins are not implemented for cubical complexes. - - Therefore, neither are cones or suspensions. - """ - def __init__(self, maximal_faces=None, maximality_check=True): - r""" - Define a cubical complex. See ``CubicalComplex`` for more - documentation. - - EXAMPLES:: - - sage: X = CubicalComplex([([0,0], [2,3]), ([0,1], [3,3]), ([0,1], [2,2]), ([1,1], [2,3])]); X - Cubical complex with 4 vertices and 8 cubes - sage: X == loads(dumps(X)) - True - """ - if maximal_faces is None: - maximal_faces = [] - C = None - if isinstance(maximal_faces, CubicalComplex): - C = maximal_faces - try: - C = maximal_faces._cubical_() - except AttributeError: - pass - if C is not None: - self._facets = copy(C._facets) - self._cells = copy(C._cells) - self._complex = copy(C._complex) - return - - good_faces = [] - maximal_cubes = [Cube(f) for f in maximal_faces] - for face in maximal_cubes: - # check whether each given face is actually maximal - face_is_maximal = True - if maximality_check: - faces_to_be_removed = [] - for other in good_faces: - if other.is_face(face): - faces_to_be_removed.append(other) - elif face_is_maximal: - face_is_maximal = not face.is_face(other) - for x in faces_to_be_removed: - good_faces.remove(x) - if face_is_maximal: - good_faces += [face] - # if no maximal faces, add the empty face as a facet - if len(maximal_cubes) == 0: - good_faces.append(Cube(())) - # self._facets: tuple of facets - self._facets = tuple(good_faces) - # self._cells: dictionary of dictionaries of faces. The main - # dictionary is keyed by subcomplexes, and each value is a - # dictionary keyed by dimension. This should be empty until - # needed -- that is, until the faces method is called - self._cells = {} - # self._complex: dictionary indexed by dimension d, base_ring, - # etc.: differential from dim d to dim d-1 in the associated - # chain complex. thus to get the differential in the cochain - # complex from dim d-1 to dim d, take the transpose of this - # one. - self._complex = {} - - def maximal_cells(self): - """ - The set of maximal cells (with respect to inclusion) of this - cubical complex. - - :return: Set of maximal cells - - This just returns the set of cubes used in defining the - cubical complex, so if the complex was defined with no - maximality checking, none is done here, either. - - EXAMPLES:: - - sage: interval = cubical_complexes.Cube(1) - sage: interval - Cubical complex with 2 vertices and 3 cubes - sage: interval.maximal_cells() - {[0,1]} - sage: interval.product(interval).maximal_cells() - {[0,1] x [0,1]} - """ - return Set(self._facets) - - def __eq__(self, other): - r""" - Return True if the set of maximal cells is the same for - ``self`` and ``other``. - - :param other: another cubical complex - :return: True if the set of maximal cells is the same for ``self`` and ``other`` - :rtype: bool - - EXAMPLES:: - - sage: I1 = cubical_complexes.Cube(1) - sage: I2 = cubical_complexes.Cube(1) - sage: I1.product(I2) == I2.product(I1) - True - sage: I1.product(I2.product(I2)) == I2.product(I1.product(I1)) - True - sage: S1 = cubical_complexes.Sphere(1) - sage: I1.product(S1) == S1.product(I1) - False - """ - return self.maximal_cells() == other.maximal_cells() - - def __ne__(self, other): - r""" - Return True if ``self`` and ``other`` are not equal. - - :param other: another cubical complex - :return: True if the complexes are not equal - :rtype: bool - - EXAMPLES:: - - sage: I1 = cubical_complexes.Cube(1) - sage: I2 = cubical_complexes.Cube(1) - sage: I1.product(I2) != I2.product(I1) - False - sage: I1.product(I2.product(I2)) != I2.product(I1.product(I1)) - False - sage: S1 = cubical_complexes.Sphere(1) - sage: I1.product(S1) != S1.product(I1) - True - """ - return not self.__eq__(other) - - def __hash__(self): - r""" - TESTS:: - - sage: I1 = cubical_complexes.Cube(1) - sage: I2 = cubical_complexes.Cube(1) - sage: hash(I1) == hash(I2) - True - sage: hash(I1.product(I1)) == hash(I2.product(I1)) - True - """ - return hash(frozenset(self._facets)) - - def is_subcomplex(self, other): - r""" - Return True if ``self`` is a subcomplex of ``other``. - - :param other: a cubical complex - - Each maximal cube of ``self`` must be a face of a maximal cube - of ``other`` for this to be True. - - EXAMPLES:: - - sage: S1 = cubical_complexes.Sphere(1) - sage: C0 = cubical_complexes.Cube(0) - sage: C1 = cubical_complexes.Cube(1) - sage: cyl = S1.product(C1) - sage: end = S1.product(C0) - sage: end.is_subcomplex(cyl) - True - sage: cyl.is_subcomplex(end) - False - - The embedding of the cubical complex is important here:: - - sage: C2 = cubical_complexes.Cube(2) - sage: C1.is_subcomplex(C2) - False - sage: C1.product(C0).is_subcomplex(C2) - True - - ``C1`` is not a subcomplex of ``C2`` because it's not embedded - in `\RR^2`. On the other hand, ``C1 x C0`` is a face of - ``C2``. Look at their maximal cells:: - - sage: C1.maximal_cells() - {[0,1]} - sage: C2.maximal_cells() - {[0,1] x [0,1]} - sage: C1.product(C0).maximal_cells() - {[0,1] x [0,0]} - """ - other_facets = other.maximal_cells() - return all(any(cube.is_face(other_cube) - for other_cube in other_facets) - for cube in self.maximal_cells()) - - def cells(self, subcomplex=None): - """ - The cells of this cubical complex, in the form of a dictionary: - the keys are integers, representing dimension, and the value - associated to an integer d is the list of d-cells. - - If the optional argument ``subcomplex`` is present, then - return only the faces which are *not* in the subcomplex. - - :param subcomplex: a subcomplex of this cubical complex - :type subcomplex: a cubical complex; optional, default None - :return: cells of this complex not contained in ``subcomplex`` - :rtype: dictionary - - EXAMPLES:: - - sage: S2 = cubical_complexes.Sphere(2) - sage: sorted(S2.cells()[2]) - [[0,0] x [0,1] x [0,1], - [0,1] x [0,0] x [0,1], - [0,1] x [0,1] x [0,0], - [0,1] x [0,1] x [1,1], - [0,1] x [1,1] x [0,1], - [1,1] x [0,1] x [0,1]] - """ - if subcomplex not in self._cells: - if subcomplex is not None and subcomplex.dimension() > -1: - if not subcomplex.is_subcomplex(self): - raise ValueError("The 'subcomplex' is not actually a subcomplex.") - # Cells is the dictionary of cells in self but not in - # subcomplex, indexed by dimension - Cells = {} - # sub_facets is the dictionary of facets in the subcomplex - sub_facets = {} - dimension = max([cube.dimension() for cube in self._facets]) - # initialize the lists: add each maximal cube to Cells and sub_facets - for i in range(-1,dimension+1): - Cells[i] = set([]) - sub_facets[i] = set([]) - for f in self._facets: - Cells[f.dimension()].add(f) - if subcomplex is not None: - for g in subcomplex._facets: - dim = g.dimension() - Cells[dim].discard(g) - sub_facets[dim].add(g) - # bad_faces is the set of faces in the subcomplex in the - # current dimension - bad_faces = sub_facets[dimension] - for dim in range(dimension, -1, -1): - # bad_bdries = boundaries of bad_faces: things to be - # discarded in dim-1 - bad_bdries = sub_facets[dim-1] - for f in bad_faces: - bad_bdries.update(f.faces()) - for f in Cells[dim]: - Cells[dim-1].update(set(f.faces()).difference(bad_bdries)) - bad_faces = bad_bdries - self._cells[subcomplex] = Cells - return self._cells[subcomplex] - - def n_cubes(self, n, subcomplex=None): - """ - The set of cubes of dimension n of this cubical complex. - If the optional argument ``subcomplex`` is present, then - return the ``n``-dimensional cubes which are *not* in the - subcomplex. - - :param n: dimension - :type n: integer - :param subcomplex: a subcomplex of this cubical complex - :type subcomplex: a cubical complex; optional, default None - :return: cells in dimension ``n`` - :rtype: set - - EXAMPLES:: - - sage: C = cubical_complexes.Cube(3) - sage: C.n_cubes(3) - {[0,1] x [0,1] x [0,1]} - sage: sorted(C.n_cubes(2)) - [[0,0] x [0,1] x [0,1], - [0,1] x [0,0] x [0,1], - [0,1] x [0,1] x [0,0], - [0,1] x [0,1] x [1,1], - [0,1] x [1,1] x [0,1], - [1,1] x [0,1] x [0,1]] - """ - return set(self.n_cells(n, subcomplex)) - - def chain_complex(self, subcomplex=None, augmented=False, - verbose=False, check=False, dimensions=None, - base_ring=ZZ, cochain=False): - r""" - The chain complex associated to this cubical complex. - - :param dimensions: if None, compute the chain complex in all - dimensions. If a list or tuple of integers, compute the - chain complex in those dimensions, setting the chain groups - in all other dimensions to zero. NOT IMPLEMENTED YET: this - function always returns the entire chain complex - :param base_ring: commutative ring - :type base_ring: optional, default ZZ - :param subcomplex: a subcomplex of this cubical complex. - Compute the chain complex relative to this subcomplex. - :type subcomplex: optional, default empty - :param augmented: If True, return the augmented chain complex - (that is, include a class in dimension `-1` corresponding - to the empty cell). This is ignored if ``dimensions`` is - specified. - :type augmented: boolean; optional, default False - :param cochain: If True, return the cochain complex (that is, - the dual of the chain complex). - :type cochain: boolean; optional, default False - :param verbose: If True, print some messages as the chain - complex is computed. - :type verbose: boolean; optional, default False - :param check: If True, make sure that the chain complex - is actually a chain complex: the differentials are - composable and their product is zero. - :type check: boolean; optional, default False - - .. note:: - - If subcomplex is nonempty, then the argument ``augmented`` - has no effect: the chain complex relative to a nonempty - subcomplex is zero in dimension `-1`. - - EXAMPLES:: - - sage: S2 = cubical_complexes.Sphere(2) - sage: S2.chain_complex() - Chain complex with at most 3 nonzero terms over Integer Ring - sage: Prod = S2.product(S2); Prod - Cubical complex with 64 vertices and 676 cubes - sage: Prod.chain_complex() - Chain complex with at most 5 nonzero terms over Integer Ring - sage: Prod.chain_complex(base_ring=QQ) - Chain complex with at most 5 nonzero terms over Rational Field - sage: C1 = cubical_complexes.Cube(1) - sage: S0 = cubical_complexes.Sphere(0) - sage: C1.chain_complex(subcomplex=S0) - Chain complex with at most 1 nonzero terms over Integer Ring - sage: C1.homology(subcomplex=S0) - {0: 0, 1: Z} - """ - # initialize subcomplex - if subcomplex is None: - subcomplex = CubicalComplex() - else: - # subcomplex is not empty, so don't augment the chain complex - augmented = False - differentials = {} - if augmented: - empty_cell = 1 # number of (-1)-dimensional cubes - else: - empty_cell = 0 - vertices = self._n_cells_sorted(0, subcomplex=subcomplex) - n = len(vertices) - mat = matrix(base_ring, empty_cell, n, n*empty_cell*[1]) - if cochain: - differentials[-1] = mat.transpose() - else: - differentials[0] = mat - current = vertices - # now loop from 1 to dimension of the complex - for dim in range(1,self.dimension()+1): - if verbose: - print(" starting dimension %s" % dim) - if (dim, subcomplex) in self._complex: - if cochain: - differentials[dim-1] = self._complex[(dim, subcomplex)].transpose().change_ring(base_ring) - mat = differentials[dim-1] - else: - differentials[dim] = self._complex[(dim, subcomplex)].change_ring(base_ring) - mat = differentials[dim] - if verbose: - print(" boundary matrix (cached): it's %s by %s." % (mat.nrows(), mat.ncols())) - else: - # 'current' is the list of cells in dimension n - # - # 'old' is a dictionary, with keys the cells in the - # previous dimension, values the integers 0, 1, 2, - # ... (the index of the face). finding an entry in a - # dictionary seems to be faster than finding the index - # of an entry in a list. - old = dict(zip(current, range(len(current)))) - current = self._n_cells_sorted(dim, subcomplex=subcomplex) - # construct matrix. it is easiest to construct it as - # a sparse matrix, specifying which entries are - # nonzero via a dictionary. - matrix_data = {} - col = 0 - if len(old) and len(current): - for cube in current: - faces = cube.faces_as_pairs() - sign = 1 - for (upper, lower) in faces: - try: - matrix_data[(old[upper], col)] = sign - sign *= -1 - matrix_data[(old[lower], col)] = sign - except KeyError: - pass - col += 1 - mat = matrix(ZZ, len(old), len(current), matrix_data) - self._complex[(dim, subcomplex)] = mat - if cochain: - differentials[dim-1] = mat.transpose().change_ring(base_ring) - else: - differentials[dim] = mat.change_ring(base_ring) - if verbose: - print(" boundary matrix computed: it's %s by %s." % (mat.nrows(), mat.ncols())) - # finally, return the chain complex - if cochain: - return ChainComplex(data=differentials, base_ring=base_ring, - degree=1, check=check) - else: - return ChainComplex(data=differentials, base_ring=base_ring, - degree=-1, check=check) - - def alexander_whitney(self, cube, dim_left): - r""" - Subdivide ``cube`` in this cubical complex into pairs of cubes. - - See :meth:`Cube.alexander_whitney` for more details. This - method just calls that one. - - INPUT: - - - ``cube`` -- a cube in this cubical complex - - ``dim`` -- integer between 0 and one more than the - dimension of this cube - - OUTPUT: a list containing triples ``(coeff, left, right)`` - - EXAMPLES:: - - sage: C = cubical_complexes.Cube(3) - sage: c = list(C.n_cubes(3))[0]; c - [0,1] x [0,1] x [0,1] - sage: C.alexander_whitney(c, 1) - [(1, [0,1] x [0,0] x [0,0], [1,1] x [0,1] x [0,1]), - (-1, [0,0] x [0,1] x [0,0], [0,1] x [1,1] x [0,1]), - (1, [0,0] x [0,0] x [0,1], [0,1] x [0,1] x [1,1])] - """ - return cube.alexander_whitney(dim_left) - - def n_skeleton(self, n): - r""" - The n-skeleton of this cubical complex. - - :param n: dimension - :type n: non-negative integer - :return: cubical complex - - EXAMPLES:: - - sage: S2 = cubical_complexes.Sphere(2) - sage: C3 = cubical_complexes.Cube(3) - sage: S2 == C3.n_skeleton(2) - True - """ - if n >= self.dimension(): - return self - else: - data = [] - for d in range(n+1): - data.extend(list(self.cells()[d])) - return CubicalComplex(data) - - def graph(self): - """ - The 1-skeleton of this cubical complex, as a graph. - - EXAMPLES:: - - sage: cubical_complexes.Sphere(2).graph() - Graph on 8 vertices - """ - data = {} - vertex_dict = {} - i = 0 - for vertex in self.n_cells(0): - vertex_dict[vertex] = i - data[i] = [] - i += 1 - for edge in self.n_cells(1): - start = edge.face(0, False) - end = edge.face(0, True) - data[vertex_dict[start]].append(vertex_dict[end]) - return Graph(data) - - def is_pure(self): - """ - True iff this cubical complex is pure: that is, - all of its maximal faces have the same dimension. - - .. warning:: - - This may give the wrong answer if the cubical complex - was constructed with ``maximality_check`` set to False. - - EXAMPLES:: - - sage: S4 = cubical_complexes.Sphere(4) - sage: S4.is_pure() - True - sage: C = CubicalComplex([([0,0], [3,3]), ([1,2], [4,5])]) - sage: C.is_pure() - False - """ - dims = [face.dimension() for face in self._facets] - return max(dims) == min(dims) - - def join(self, other): - r""" - The join of this cubical complex with another one. - - NOT IMPLEMENTED. - - :param other: another cubical complex - - EXAMPLES:: - - sage: C1 = cubical_complexes.Cube(1) - sage: C1.join(C1) - Traceback (most recent call last): - ... - NotImplementedError: Joins are not implemented for cubical complexes. - """ - raise NotImplementedError("Joins are not implemented for cubical complexes.") - - # Use * to mean 'join': - # __mul__ = join - - def cone(self): - r""" - The cone on this cubical complex. - - NOT IMPLEMENTED - - The cone is the complex formed by taking the join of the - original complex with a one-point complex (that is, a - 0-dimensional cube). Since joins are not implemented for - cubical complexes, neither are cones. - - EXAMPLES:: - - sage: C1 = cubical_complexes.Cube(1) - sage: C1.cone() - Traceback (most recent call last): - ... - NotImplementedError: Cones are not implemented for cubical complexes. - """ - #return self.join(cubical_complexes.Cube(0)) - raise NotImplementedError("Cones are not implemented for cubical complexes.") - - def suspension(self, n=1): - r""" - The suspension of this cubical complex. - - NOT IMPLEMENTED - - :param n: suspend this many times - :type n: positive integer; optional, default 1 - - The suspension is the complex formed by taking the join of the - original complex with a two-point complex (the 0-sphere). - Since joins are not implemented for cubical complexes, neither - are suspensions. - - EXAMPLES:: - - sage: C1 = cubical_complexes.Cube(1) - sage: C1.suspension() - Traceback (most recent call last): - ... - NotImplementedError: Suspensions are not implemented for cubical complexes. - """ -# if n<0: -# raise ValueError, "n must be non-negative." -# if n==0: -# return self -# if n==1: -# return self.join(cubical_complexes.Sphere(0)) -# return self.suspension().suspension(int(n-1)) - raise NotImplementedError("Suspensions are not implemented for cubical complexes.") - - def product(self, other): - r""" - The product of this cubical complex with another one. - - :param other: another cubical complex - - EXAMPLES:: - - sage: RP2 = cubical_complexes.RealProjectivePlane() - sage: S1 = cubical_complexes.Sphere(1) - sage: RP2.product(S1).homology()[1] # long time: 5 seconds - Z x C2 - """ - facets = [] - for f in self._facets: - for g in other._facets: - facets.append(f.product(g)) - return CubicalComplex(facets) - - def disjoint_union(self, other): - """ - The disjoint union of this cubical complex with another one. - - :param right: the other cubical complex (the right-hand factor) - - Algorithm: first embed both complexes in d-dimensional - Euclidean space. Then embed in (1+d)-dimensional space, - calling the new axis `x`, and putting the first complex at - `x=0`, the second at `x=1`. - - EXAMPLES:: - - sage: S1 = cubical_complexes.Sphere(1) - sage: S2 = cubical_complexes.Sphere(2) - sage: S1.disjoint_union(S2).homology() - {0: Z, 1: Z, 2: Z} - """ - embedded_left = len(tuple(self.maximal_cells()[0])) - embedded_right = len(tuple(other.maximal_cells()[0])) - zero = [0] * max(embedded_left, embedded_right) - facets = [] - for f in self.maximal_cells(): - facets.append(Cube([[0,0]]).product(f._translate(zero))) - for f in other.maximal_cells(): - facets.append(Cube([[1,1]]).product(f._translate(zero))) - return CubicalComplex(facets) - - def wedge(self, other): - """ - The wedge (one-point union) of this cubical complex with - another one. - - :param right: the other cubical complex (the right-hand factor) - - Algorithm: if ``self`` is embedded in `d` dimensions and - ``other`` in `n` dimensions, embed them in `d+n` dimensions: - ``self`` using the first `d` coordinates, ``other`` using the - last `n`, translating them so that they have the origin as a - common vertex. - - .. note:: - - This operation is not well-defined if ``self`` or - ``other`` is not path-connected. - - EXAMPLES:: - - sage: S1 = cubical_complexes.Sphere(1) - sage: S2 = cubical_complexes.Sphere(2) - sage: S1.wedge(S2).homology() - {0: 0, 1: Z, 2: Z} - """ - embedded_left = len(tuple(self.maximal_cells()[0])) - embedded_right = len(tuple(other.maximal_cells()[0])) - translate_left = [-a[0] for a in self.maximal_cells()[0]] + [0] * embedded_right - translate_right = [-a[0] for a in other.maximal_cells()[0]] - point_right = Cube([[0,0]] * embedded_left) - - facets = [] - for f in self.maximal_cells(): - facets.append(f._translate(translate_left)) - for f in other.maximal_cells(): - facets.append(point_right.product(f._translate(translate_right))) - return CubicalComplex(facets) - - def connected_sum(self, other): - """ - Return the connected sum of self with other. - - :param other: another cubical complex - :return: the connected sum ``self # other`` - - .. warning:: - - This does not check that self and other are manifolds, only - that their facets all have the same dimension. Since a - (more or less) random facet is chosen from each complex and - then glued together, this method may return random - results if applied to non-manifolds, depending on which - facet is chosen. - - EXAMPLES:: - - sage: T = cubical_complexes.Torus() - sage: S2 = cubical_complexes.Sphere(2) - sage: T.connected_sum(S2).cohomology() == T.cohomology() - True - sage: RP2 = cubical_complexes.RealProjectivePlane() - sage: T.connected_sum(RP2).homology(1) - Z x Z x C2 - sage: RP2.connected_sum(RP2).connected_sum(RP2).homology(1) - Z x Z x C2 - """ - # connected_sum: first check whether the complexes are pure - # and of the same dimension. Then insert degenerate intervals - # and translate them so that they have a common cube C. Add one - # more dimension, embedding the first complex as (..., 0) and - # the second as (..., 1). Keep all of the other facets, but remove - # C x 0 and C x 1, putting in its place (its boundary) x (0,1). - if not (self.is_pure() and other.is_pure() and - self.dimension() == other.dimension()): - raise ValueError("Complexes are not pure of the same dimension.") - - self_facets = list(self.maximal_cells()) - other_facets = list(other.maximal_cells()) - - C1 = self_facets.pop() - C2 = other_facets.pop() - (insert_self, insert_other, translate) = C1._compare_for_gluing(C2) - - CL = list(C1.tuple()) - for (idx, L) in insert_self: - CL[idx:idx] = L - removed = Cube(CL) - - # start assembling the facets in the connected sum: first, the - # cylinder on the removed face. - new_facets = [] - cylinder = removed.product(Cube([[0,1]])) - # don't want to include the ends of the cylinder, so don't - # include the last pair of faces. therefore, choose faces up - # to removed.dimension(), not cylinder.dimension(). - for n in range(removed.dimension()): - new_facets.append(cylinder.face(n, upper=False)) - new_facets.append(cylinder.face(n, upper=True)) - - for cube in self_facets: - CL = list(cube.tuple()) - for (idx, L) in insert_self: - CL[idx:idx] = L - CL.append((0,0)) - new_facets.append(Cube(CL)) - for cube in other_facets: - CL = list(cube.tuple()) - for (idx, L) in insert_other: - CL[idx:idx] = L - CL.append((1,1)) - new_facets.append(Cube(CL)._translate(translate)) - return CubicalComplex(new_facets) - - def _translate(self, vec): - """ - Translate ``self`` by ``vec``. - - :param vec: anything which can be converted to a tuple of integers - :return: the translation of ``self`` by ``vec`` - :rtype: cubical complex - - If ``vec`` is shorter than the list of intervals forming the - complex, pad with zeroes, and similarly if the complexes - defining tuples are too short. - - EXAMPLES:: - - sage: C1 = cubical_complexes.Cube(1) - sage: C1.maximal_cells() - {[0,1]} - sage: C1._translate([2,6]).maximal_cells() - {[2,3] x [6,6]} - """ - return CubicalComplex([f._translate(vec) for f in self.maximal_cells()]) - - # This is cached for speed reasons: it can be very slow to run - # this function. - @cached_method - def algebraic_topological_model(self, base_ring=None): - r""" - Algebraic topological model for this cubical complex with - coefficients in ``base_ring``. - - The term "algebraic topological model" is defined by Pilarczyk - and Réal [PR2015]_. - - INPUT: - - - ``base_ring`` - coefficient ring (optional, default - ``QQ``). Must be a field. - - Denote by `C` the chain complex associated to this cubical - complex. The algebraic topological model is a chain complex - `M` with zero differential, with the same homology as `C`, - along with chain maps `\pi: C \to M` and `\iota: M \to C` - satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic - to `1_C`. The chain homotopy `\phi` must satisfy - - - `\phi \phi = 0`, - - `\pi \phi = 0`, - - `\phi \iota = 0`. - - Such a chain homotopy is called a *chain contraction*. - - OUTPUT: a pair consisting of - - - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and - `\iota` - - the chain complex `M` - - Note that from the chain contraction ``phi``, one can recover the - chain maps `\pi` and `\iota` via ``phi.pi()`` and - ``phi.iota()``. Then one can recover `C` and `M` from, for - example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, - respectively. - - EXAMPLES:: - - sage: RP2 = cubical_complexes.RealProjectivePlane() - sage: phi, M = RP2.algebraic_topological_model(GF(2)) - sage: M.homology() - {0: Vector space of dimension 1 over Finite Field of size 2, - 1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2} - sage: T = cubical_complexes.Torus() - sage: phi, M = T.algebraic_topological_model(QQ) - sage: M.homology() - {0: Vector space of dimension 1 over Rational Field, - 1: Vector space of dimension 2 over Rational Field, - 2: Vector space of dimension 1 over Rational Field} - """ - from .algebraic_topological_model import algebraic_topological_model - if base_ring is None: - base_ring = QQ - return algebraic_topological_model(self, base_ring) - - def _chomp_repr_(self): - r""" - String representation of self suitable for use by the CHomP - program. This lists each maximal cube on its own line. - - EXAMPLES:: - - sage: C = cubical_complexes.Cube(0).product(cubical_complexes.Cube(2)) - sage: C.maximal_cells() - {[0,0] x [0,1] x [0,1]} - sage: C._chomp_repr_() - '[0,0] x [0,1] x [0,1]\n' - """ - s = "" - for c in self.maximal_cells(): - s += str(c) - s += "\n" - return s - - def _simplicial_(self): - r""" - Simplicial complex constructed from self. - - ALGORITHM: - - This is constructed as described by Hetyei: choose a total - ordering of the vertices of the cubical complex. Then for - each maximal face - - .. MATH:: - - C = [i_1, j_1] \times [i_2, j_2] \times ... \times [i_k, j_k] - - let `v_1` be the "upper" corner of `C`: `v` is the point - `(j_1, ..., j_k)`. Choose a coordinate `n` where the interval - `[i_n, j_n]` is non-degenerate and form `v_2` by replacing - `j_n` by `i_n`; repeat to define `v_3`, etc. The last vertex - so defined will be `(i_1, ..., i_k)`. These vertices define a - simplex, and do the vertices obtained by making different - choices at each stage. Thus each `n`-cube is subdivided into - `n!` simplices. - - REFERENCES: - - - G. Hetyei, "On the Stanley ring of a cubical complex", - Discrete Comput. Geom. 14 (1995), 305-330. - - EXAMPLES:: - - sage: T = cubical_complexes.Torus(); T - Cubical complex with 16 vertices and 64 cubes - sage: len(T.maximal_cells()) - 16 - - When this is triangulated, each maximal 2-dimensional cube - gets turned into a pair of triangles. Since there were 16 - maximal cubes, this results in 32 facets in the simplicial - complex:: - - sage: Ts = T._simplicial_(); Ts - Simplicial complex with 16 vertices and 32 facets - sage: T.homology() == Ts.homology() - True - - Each `n`-dimensional cube produces `n!` `n`-simplices:: - - sage: S4 = cubical_complexes.Sphere(4) - sage: len(S4.maximal_cells()) - 10 - sage: SimplicialComplex(S4) # calls S4._simplicial_() - Simplicial complex with 32 vertices and 240 facets - """ - from sage.homology.simplicial_complex import SimplicialComplex - simplices = [] - for C in self.maximal_cells(): - simplices.extend(C._triangulation_()) - return SimplicialComplex(simplices) - - def _string_constants(self): - """ - Tuple containing the name of the type of complex, and the - singular and plural of the name of the cells from which it is - built. This is used in constructing the string representation. - - EXAMPLES:: - - sage: S3 = cubical_complexes.Sphere(3) - sage: S3._string_constants() - ('Cubical', 'cube', 'cubes') - sage: S3._repr_() # indirect doctest - 'Cubical complex with 16 vertices and 80 cubes' - """ - return ('Cubical', 'cube', 'cubes') - - -class CubicalComplexExamples(): - r""" - Some examples of cubical complexes. - - Here are the available examples; you can also type - "cubical_complexes." and hit TAB to get a list:: - - Sphere - Torus - RealProjectivePlane - KleinBottle - SurfaceOfGenus - Cube - - EXAMPLES:: - - sage: cubical_complexes.Torus() # indirect doctest - Cubical complex with 16 vertices and 64 cubes - sage: cubical_complexes.Cube(7) - Cubical complex with 128 vertices and 2187 cubes - sage: cubical_complexes.Sphere(7) - Cubical complex with 256 vertices and 6560 cubes - """ - - def Sphere(self,n): - r""" - A cubical complex representation of the `n`-dimensional sphere, - formed by taking the boundary of an `(n+1)`-dimensional cube. - - :param n: the dimension of the sphere - :type n: non-negative integer - - EXAMPLES:: - - sage: cubical_complexes.Sphere(7) - Cubical complex with 256 vertices and 6560 cubes - """ - return CubicalComplex(Cube([[0,1]]*(n+1)).faces()) - - def Torus(self): - r""" - A cubical complex representation of the torus, obtained by - taking the product of the circle with itself. - - EXAMPLES:: - - sage: cubical_complexes.Torus() - Cubical complex with 16 vertices and 64 cubes - """ - S1 = cubical_complexes.Sphere(1) - return S1.product(S1) - - def RealProjectivePlane(self): - r""" - A cubical complex representation of the real projective plane. - This is taken from the examples from CHomP, the Computational - Homology Project: http://chomp.rutgers.edu/. - - EXAMPLES:: - - sage: cubical_complexes.RealProjectivePlane() - Cubical complex with 21 vertices and 81 cubes - """ - return CubicalComplex([ - ([0, 1], [0], [0], [0, 1], [0]), - ([0, 1], [0], [0], [0], [0, 1]), - ([0], [0, 1], [0, 1], [0], [0]), - ([0], [0, 1], [0], [0, 1], [0]), - ([0], [0], [0, 1], [0], [0, 1]), - ([0, 1], [0, 1], [1], [0], [0]), - ([0, 1], [1], [0, 1], [0], [0]), - ([1], [0, 1], [0, 1], [0], [0]), - ([0, 1], [0, 1], [0], [0], [1]), - ([0, 1], [1], [0], [0], [0, 1]), - ([1], [0, 1], [0], [0], [0, 1]), - ([0, 1], [0], [0, 1], [1], [0]), - ([0, 1], [0], [1], [0, 1], [0]), - ([1], [0], [0, 1], [0, 1], [0]), - ([0], [0, 1], [0], [0, 1], [1]), - ([0], [0, 1], [0], [1], [0, 1]), - ([0], [1], [0], [0, 1], [0, 1]), - ([0], [0], [0, 1], [0, 1], [1]), - ([0], [0], [0, 1], [1], [0, 1]), - ([0], [0], [1], [0, 1], [0, 1])]) - - def KleinBottle(self): - r""" - A cubical complex representation of the Klein bottle, formed - by taking the connected sum of the real projective plane with - itself. - - EXAMPLES:: - - sage: cubical_complexes.KleinBottle() - Cubical complex with 42 vertices and 168 cubes - """ - RP2 = cubical_complexes.RealProjectivePlane() - return RP2.connected_sum(RP2) - - def SurfaceOfGenus(self, g, orientable=True): - """ - A surface of genus g as a cubical complex. - - :param g: the genus - :type g: non-negative integer - :param orientable: whether the surface should be orientable - :type orientable: bool, optional, default True - - In the orientable case, return a sphere if `g` is zero, and - otherwise return a `g`-fold connected sum of a torus with - itself. - - In the non-orientable case, raise an error if `g` is zero. If - `g` is positive, return a `g`-fold connected sum of a - real projective plane with itself. - - EXAMPLES:: - - sage: cubical_complexes.SurfaceOfGenus(2) - Cubical complex with 32 vertices and 134 cubes - sage: cubical_complexes.SurfaceOfGenus(1, orientable=False) - Cubical complex with 21 vertices and 81 cubes - """ - try: - g = Integer(g) - except TypeError: - raise ValueError("genus must be a non-negative integer") - if g < 0: - raise ValueError("genus must be a non-negative integer") - if g == 0: - if not orientable: - raise ValueError("no non-orientable surface of genus zero") - else: - return cubical_complexes.Sphere(2) - if orientable: - T = cubical_complexes.Torus() - else: - T = cubical_complexes.RealProjectivePlane() - S = T - for i in range(g-1): - S = S.connected_sum(T) - return S - - def Cube(self, n): - r""" - A cubical complex representation of an `n`-dimensional cube. - - :param n: the dimension - :type n: non-negative integer - - EXAMPLES:: - - sage: cubical_complexes.Cube(0) - Cubical complex with 1 vertex and 1 cube - sage: cubical_complexes.Cube(3) - Cubical complex with 8 vertices and 27 cubes - """ - if n == 0: - return CubicalComplex([Cube([[0]])]) - else: - return CubicalComplex([Cube([[0,1]]*n)]) +from sage.misc.superseded import deprecated_function_alias +import sage.topology.cubical_complex -cubical_complexes = CubicalComplexExamples() +Cube = deprecated_function_alias(31925, sage.topology.cubical_complex.Cube) +CubicalComplex = deprecated_function_alias(31925, + sage.topology.cubical_complex.CubicalComplex) +CubicalComplexExamples = deprecated_function_alias(31925, + sage.topology.cubical_complex.CubicalComplexExamples) +cubical_complexes = deprecated_function_alias(31925, + sage.topology.cubical_complex.cubical_complexes) diff --git a/src/sage/homology/delta_complex.py b/src/sage/homology/delta_complex.py index 91c3bca27f0..cfd5fc6119f 100644 --- a/src/sage/homology/delta_complex.py +++ b/src/sage/homology/delta_complex.py @@ -1,1777 +1,15 @@ # -*- coding: utf-8 -*- r""" -Finite Delta-complexes +Finite Delta-complexes: deprecated -AUTHORS: - -- John H. Palmieri (2009-08) - -This module implements the basic structure of finite -`\Delta`-complexes. For full mathematical details, see Hatcher [Hat2002]_, -especially Section 2.1 and the Appendix on "Simplicial CW Structures". -As Hatcher points out, `\Delta`-complexes were first introduced by Eilenberg -and Zilber [EZ1950]_, although they called them "semi-simplicial complexes". - -A `\Delta`-complex is a generalization of a :mod:`simplicial complex -`; a `\Delta`-complex `X` consists -of sets `X_n` for each non-negative integer `n`, the elements of which -are called *n-simplices*, along with *face maps* between these sets of -simplices: for each `n` and for all `0 \leq i \leq n`, there are -functions `d_i` from `X_n` to `X_{n-1}`, with `d_i(s)` equal to the -`i`-th face of `s` for each simplex `s \in X_n`. These maps must -satisfy the *simplicial identity* - - .. MATH:: - - d_i d_j = d_{j-1} d_i \text{ for all } i` - page instead. +The current version is :mod:`sage.topology.delta_complexes`. """ - -from copy import copy -from sage.homology.cell_complex import GenericCellComplex -from sage.homology.chains import Chains, Cochains -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.rings.integer import Integer -from sage.matrix.constructor import matrix -from sage.homology.simplicial_complex import Simplex, lattice_paths, SimplicialComplex -from sage.homology.chain_complex import ChainComplex -from sage.graphs.graph import Graph -from sage.arith.all import binomial -from sage.misc.cachefunc import cached_method - - -class DeltaComplex(GenericCellComplex): - r""" - Define a `\Delta`-complex. - - :param data: see below for a description of the options - :param check_validity: If True, check that the simplicial identities hold. - :type check_validity: boolean; optional, default True - :return: a `\Delta`-complex - - Use ``data`` to define a `\Delta`-complex. It may be in any of - three forms: - - - ``data`` may be a dictionary indexed by simplices. The value - associated to a d-simplex `S` can be any of: - - - a list or tuple of (d-1)-simplices, where the ith entry is the - ith face of S, given as a simplex, - - - another d-simplex `T`, in which case the ith face of `S` is - declared to be the same as the ith face of `T`: `S` and `T` - are glued along their entire boundary, - - - None or True or False or anything other than the previous two - options, in which case the faces are just the ordinary faces - of `S`. - - For example, consider the following:: - - sage: n = 5 - sage: S5 = DeltaComplex({Simplex(n):True, Simplex(range(1,n+2)): Simplex(n)}) - sage: S5 - Delta complex with 6 vertices and 65 simplices - - The first entry in dictionary forming the argument to - ``DeltaComplex`` says that there is an `n`-dimensional simplex - with its ordinary boundary. The second entry says that there is - another simplex whose boundary is glued to that of the first - one. The resulting `\Delta`-complex is, of course, homeomorphic - to an `n`-sphere, or actually a 5-sphere, since we defined `n` - to be 5. (Note that the second simplex here can be any - `n`-dimensional simplex, as long as it is distinct from - ``Simplex(n)``.) - - Let's compute its homology, and also compare it to the simplicial version:: - - sage: S5.homology() - {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: Z} - sage: S5.f_vector() # number of simplices in each dimension - [1, 6, 15, 20, 15, 6, 2] - sage: simplicial_complexes.Sphere(5).f_vector() - [1, 7, 21, 35, 35, 21, 7] - - Both contain a single (-1)-simplex, the empty simplex; other - than that, the `\Delta`-complex version contains fewer simplices - than the simplicial one in each dimension. - - To construct a torus, use:: - - sage: torus_dict = {Simplex([0,1,2]): True, - ....: Simplex([3,4,5]): (Simplex([0,1]), Simplex([0,2]), Simplex([1,2])), - ....: Simplex([0,1]): (Simplex(0), Simplex(0)), - ....: Simplex([0,2]): (Simplex(0), Simplex(0)), - ....: Simplex([1,2]): (Simplex(0), Simplex(0)), - ....: Simplex(0): ()} - sage: T = DeltaComplex(torus_dict); T - Delta complex with 1 vertex and 7 simplices - sage: T.cohomology(base_ring=QQ) - {0: Vector space of dimension 0 over Rational Field, - 1: Vector space of dimension 2 over Rational Field, - 2: Vector space of dimension 1 over Rational Field} - - This `\Delta`-complex consists of two triangles (given by - ``Simplex([0,1,2])`` and ``Simplex([3,4,5])``); the boundary of - the first is just its usual boundary: the 0th face is obtained - by omitting the lowest numbered vertex, etc., and so the - boundary consists of the edges ``[1,2]``, ``[0,2]``, and - ``[0,1]``, in that order. The boundary of the second is, on the - one hand, computed the same way: the nth face is obtained by - omitting the nth vertex. On the other hand, the boundary is - explicitly declared to be edges ``[0,1]``, ``[0,2]``, and - ``[1,2]``, in that order. This glues the second triangle to the - first in the prescribed way. The three edges each start and end - at the single vertex, ``Simplex(0)``. - - .. image:: ../../media/torus_labelled.png - - - ``data`` may be nested lists or tuples. The nth entry in the - list is a list of the n-simplices in the complex, and each - n-simplex is encoded as a list, the ith entry of which is its - ith face. Each face is represented by an integer, giving its - index in the list of (n-1)-faces. For example, consider this:: - - sage: P = DeltaComplex( [ [(), ()], [(1,0), (1,0), (0,0)], - ....: [(1,0,2), (0, 1, 2)] ]) - - The 0th entry in the list is ``[(), ()]``: there are two - 0-simplices, and their boundaries are empty. - - The 1st entry in the list is ``[(1,0), (1,0), (0,0)]``: there - are three 1-simplices. Two of them have boundary ``(1,0)``, - which means that their 0th face is vertex 1 (in the list of - vertices), and their 1st face is vertex 0. The other edge has - boundary ``(0,0)``, so it starts and ends at vertex 0. - - The 2nd entry in the list is ``[(1,0,2), (0,1,2)]``: there are - two 2-simplices. The first 2-simplex has boundary ``(1,0,2)``, - meaning that its 0th face is edge 1 (in the list above), its 1st - face is edge 0, and its 2nd face is edge 2; similarly for the - 2nd 2-simplex. - - If one draws two triangles and identifies them according to this - description, the result is the real projective plane. - - .. image:: ../../media/rp2.png - - :: - - sage: P.homology(1) - C2 - sage: P.cohomology(2) - C2 - - Closely related to this form for ``data`` is ``X.cells()`` - for a `\Delta`-complex ``X``: this is a dictionary, indexed by - dimension ``d``, whose ``d``-th entry is a list of the - ``d``-simplices, as a list:: - - sage: P.cells() - {-1: ((),), - 0: ((), ()), - 1: ((1, 0), (1, 0), (0, 0)), - 2: ((1, 0, 2), (0, 1, 2))} - - - ``data`` may be a dictionary indexed by integers. For each - integer `n`, the entry with key `n` is the list of - `n`-simplices: this is the same format as is output by the - :meth:`cells` method. :: - - sage: P = DeltaComplex( [ [(), ()], [(1,0), (1,0), (0,0)], - ....: [(1,0,2), (0, 1, 2)] ]) - sage: cells_dict = P.cells() - sage: cells_dict - {-1: ((),), - 0: ((), ()), - 1: ((1, 0), (1, 0), (0, 0)), - 2: ((1, 0, 2), (0, 1, 2))} - sage: DeltaComplex(cells_dict) - Delta complex with 2 vertices and 8 simplices - sage: P == DeltaComplex(cells_dict) - True - - Since `\Delta`-complexes are generalizations of simplicial - complexes, any simplicial complex may be viewed as a - `\Delta`-complex:: - - sage: RP2 = simplicial_complexes.RealProjectivePlane() - sage: RP2_delta = RP2.delta_complex() - sage: RP2.f_vector() - [1, 6, 15, 10] - sage: RP2_delta.f_vector() - [1, 6, 15, 10] - - Finally, `\Delta`-complex constructions for several familiar - spaces are available as follows:: - - sage: delta_complexes.Sphere(4) # the 4-sphere - Delta complex with 5 vertices and 33 simplices - sage: delta_complexes.KleinBottle() - Delta complex with 1 vertex and 7 simplices - sage: delta_complexes.RealProjectivePlane() - Delta complex with 2 vertices and 8 simplices - - Type ``delta_complexes.`` and then hit the TAB key to get the - full list. - """ - def __init__(self, data=None, check_validity=True): - r""" - Define a `\Delta`-complex. See :class:`DeltaComplex` for more - documentation. - - EXAMPLES:: - - sage: X = DeltaComplex({Simplex(3):True, Simplex(range(1,5)): Simplex(3), Simplex(range(2,6)): Simplex(3)}); X # indirect doctest - Delta complex with 4 vertices and 18 simplices - sage: X.homology() - {0: 0, 1: 0, 2: 0, 3: Z x Z} - sage: X == loads(dumps(X)) - True - """ - def store_bdry(simplex, faces): - r""" - Given a simplex of dimension d and a list of boundaries - (as other simplices), this stores each boundary face in - new_data[d-1] if necessary, records the index of each - boundary face in bdry_list, represents the simplex as - bdry_list in new_data[d], and returns bdry_list. - - If the simplex is in the dictionary old_delayed, then it - is already stored, temporarily, in new_data[d], so replace - its temporary version with bdry_list. - """ - bdry_list = [] - d = simplex.dimension() - if d > 0: - for f in faces: - if f in new_data[d-1]: - bdry_list.append(new_data[d-1].index(f)) - else: - bdry_list.append(len(new_data[d-1])) - new_delayed[f] = len(new_data[d-1]) - new_data[d-1].append(f) - bdry_list = tuple(bdry_list) - else: - bdry_list = () - if simplex in old_delayed: - idx = old_delayed[simplex] - new_data[d][idx] = bdry_list - else: - new_data[d].append(bdry_list) - return bdry_list - - new_data = {-1: ((),)} # add the empty cell - if data is None: - pass - else: - if isinstance(data, (list, tuple)): - dim = 0 - for s in data: - new_data[dim] = s - dim += 1 - elif isinstance(data, dict): - if all(isinstance(a, (int, Integer)) for a in data): - # a dictionary indexed by integers - new_data = data - if -1 not in new_data: - new_data[-1] = ((),) # add the empty cell - else: - # else a dictionary indexed by simplices - dimension = max([f.dimension() for f in data]) - old_data_by_dim = {} - for dim in range(0, dimension+1): - old_data_by_dim[dim] = [] - new_data[dim] = [] - for x in data: - if not isinstance(x, Simplex): - raise TypeError("Each key in the data dictionary must be a simplex.") - old_data_by_dim[x.dimension()].append(x) - old_delayed = {} - for dim in range(dimension, -1, -1): - new_delayed = {} - current = {} - for x in old_data_by_dim[dim]: - if x in data: - bdry = data[x] - else: - bdry = True - if isinstance(bdry, Simplex): - # case 1 - # value is a simplex, so x is glued to the old - # one along its boundary. So the boundary of - # x is the boundary of the old simplex. - if bdry in current: - # if the old simplex is there, copy its boundary - if x in old_delayed: - idx = old_delayed[x] - new_data[dim][idx] = current[bdry] - else: - new_data[dim].append(current[bdry]) - elif bdry in data: - # the old simplex has not yet been added to - # new_data, but is in the data dictionary. So - # add it. - current[bdry] = store_bdry(bdry, bdry.faces()) - new_data[dim].append(current[bdry]) - else: - raise ValueError("In the data dictionary, there is a value which is a simplex not already in the dictionary. This is not allowed.") - elif isinstance(bdry, (list, tuple)): - # case 2 - # boundary is a list or tuple - current[x] = store_bdry(x, bdry) - else: - # case 3 - # no valid boundary specified, so the default - # boundary of x should be used - if x not in current: - # x hasn't already been added, in case 1 - current[x] = store_bdry(x, x.faces()) - old_delayed = new_delayed - if dim > 0: - old_data_by_dim[dim-1].extend(old_delayed.keys()) - else: - raise ValueError("data is not a list, tuple, or dictionary") - for n in new_data: - new_data[n] = tuple(new_data[n]) - # at this point, new_data is a dictionary indexed by - # dimension, with new_data[d] a list of "simplices" in - # dimension d - if check_validity: - dim = max(new_data) - for d in range(dim, 1, -1): - for s in new_data[d]: # s is a d-simplex - faces = new_data[d-1] - for j in range(d+1): - if not all(faces[s[j]][i] == faces[s[i]][j-1] for i in range(j)): - msg = "Simplicial identity d_i d_j = d_{j-1} d_i fails" - msg += " for j=%s, in dimension %s"%(j,d) - raise ValueError(msg) - # self._cells_dict: dictionary indexed by dimension d: for - # each d, have list or tuple of simplices, and for each - # simplex, have list or tuple with its boundary (as the index - # of an element in the list of (d-1)-simplices). - self._cells_dict = new_data - # self._is_subcomplex_of: if self is a subcomplex of another - # Delta complex, record that other complex here, along with - # data relating the cells in self to the cells in the - # containing complex: for each dimension, a list of indices - # specifying, for each cell in self, which cell it corresponds - # to in the containing complex. - self._is_subcomplex_of = None - # self._complex: dictionary indexed by dimension d, base_ring, - # etc.: differential from dim d to dim d-1 in the associated - # chain complex. thus to get the differential in the cochain - # complex from dim d-1 to dim d, take the transpose of this - # one. - # self._complex = {} - - def subcomplex(self, data): - r""" - Create a subcomplex. - - :param data: a dictionary indexed by dimension or a list (or - tuple); in either case, data[n] should be the list (or tuple - or set) of the indices of the simplices to be included in - the subcomplex. - - This automatically includes all faces of the simplices in - ``data``, so you only have to specify the simplices which are - maximal with respect to inclusion. - - EXAMPLES:: - - sage: X = delta_complexes.Torus() - sage: A = X.subcomplex({2: [0]}) # one of the triangles of X - sage: X.homology(subcomplex=A) - {0: 0, 1: 0, 2: Z} - - In the following, ``line`` is a line segment and ``ends`` is - the complex consisting of its two endpoints, so the relative - homology of the two is isomorphic to the homology of a circle:: - - sage: line = delta_complexes.Simplex(1) # an edge - sage: line.cells() - {-1: ((),), 0: ((), ()), 1: ((0, 1),)} - sage: ends = line.subcomplex({0: (0, 1)}) - sage: ends.cells() - {-1: ((),), 0: ((), ())} - sage: line.homology(subcomplex=ends) - {0: 0, 1: Z} - """ - if isinstance(data, (list, tuple)): - data = dict(zip(range(len(data)), data)) - - # new_dict: dictionary for constructing the subcomplex - new_dict = {} - # new_data: dictionary of all cells in the subcomplex: store - # this with the subcomplex to make it fast to list the cells - # in self which are not in the subcomplex. - new_data = {} - # max_dim: maximum dimension of cells being added - max_dim = max(data.keys()) - # cells_to_add: in each dimension, add these cells to - # new_dict. start with the cells given in new_data and add - # faces of cells one dimension higher. - cells_to_add = data[max_dim] - cells = self.cells() - for d in range(max_dim, -1, -1): - # cells_to_add is the set of indices of d-cells in self to - # add to new_dict. - cells_to_add = sorted(cells_to_add) - # we add only these cells, so we need to translate their - # indices from, for example, (0, 1, 4, 5) to (0, 1, 2, 3). - # That is, when they appear as boundaries of (d+1)-cells, - # we need to translate their indices in each (d+1)-cell. - # Here is the key for that translation: - translate = dict(zip(cells_to_add, range(len(cells_to_add)))) - new_dict[d] = [] - d_cells = cells_to_add - new_data[d] = cells_to_add - try: - cells_to_add = set(new_data[d-1]) # begin to populate the (d-1)-cells - except KeyError: - cells_to_add = set([]) - for x in d_cells: - if d+1 in new_dict: - old = new_dict[d+1] - new_dict[d+1] = [] - for f in old: - new_dict[d+1].append(tuple([translate[n] for n in f])) - new_dict[d].append(cells[d][x]) - cells_to_add.update(cells[d][x]) - new_cells = [new_dict[n] for n in range(0, max_dim+1)] - sub = DeltaComplex(new_cells) - sub._is_subcomplex_of = {self: new_data} - return sub - - def __hash__(self): - r""" - TESTS:: - - sage: hash(delta_complexes.Sphere(2)) == hash(delta_complexes.Sphere(2)) - True - sage: hash(delta_complexes.Sphere(4)) == hash(delta_complexes.Sphere(4)) - True - """ - return hash(frozenset(self._cells_dict.items())) - - def __eq__(self, right): - r""" - Two `\Delta`-complexes are equal, according to this, if they have - the same ``_cells_dict``. - - EXAMPLES:: - - sage: S4 = delta_complexes.Sphere(4) - sage: S2 = delta_complexes.Sphere(2) - sage: S4 == S2 - False - sage: newS2 = DeltaComplex({Simplex(2):True, Simplex([8,12,17]): Simplex(2)}) - sage: newS2 == S2 - True - """ - return self._cells_dict == right._cells_dict - - def __ne__(self, other): - r""" - Return ``True`` if ``self`` and ``other`` are not equal. - - EXAMPLES:: - - sage: S4 = delta_complexes.Sphere(4) - sage: S2 = delta_complexes.Sphere(2) - sage: S4 != S2 - True - sage: newS2 = DeltaComplex({Simplex(2):True, Simplex([8,12,17]): Simplex(2)}) - sage: newS2 != S2 - False - """ - return not self.__eq__(other) - - def cells(self, subcomplex=None): - r""" - The cells of this `\Delta`-complex. - - :param subcomplex: a subcomplex of this complex - :type subcomplex: optional, default None - - The cells of this `\Delta`-complex, in the form of a dictionary: - the keys are integers, representing dimension, and the value - associated to an integer d is the list of d-cells. Each - d-cell is further represented by a list, the ith entry of - which gives the index of its ith face in the list of - (d-1)-cells. - - If the optional argument ``subcomplex`` is present, then - "return only the faces which are *not* in the subcomplex". To - preserve the indexing, which is necessary to compute the - relative chain complex, this actually replaces the faces in - ``subcomplex`` with ``None``. - - EXAMPLES:: - - sage: S2 = delta_complexes.Sphere(2) - sage: S2.cells() - {-1: ((),), - 0: ((), (), ()), - 1: ((0, 1), (0, 2), (1, 2)), - 2: ((0, 1, 2), (0, 1, 2))} - sage: A = S2.subcomplex({1: [0,2]}) # one edge - sage: S2.cells(subcomplex=A) - {-1: (None,), - 0: (None, None, None), - 1: (None, (0, 2), None), - 2: ((0, 1, 2), (0, 1, 2))} - """ - cells = self._cells_dict.copy() - if subcomplex is None: - return cells - if subcomplex._is_subcomplex_of is None or self not in subcomplex._is_subcomplex_of: - if subcomplex == self: - for d in range(-1, max(cells.keys())+1): - l = len(cells[d]) - cells[d] = [None]*l # get rid of all cells - return cells - else: - raise ValueError("This is not a subcomplex of self.") - else: - subcomplex_cells = subcomplex._is_subcomplex_of[self] - for d in range(0, max(subcomplex_cells.keys())+1): - L = list(cells[d]) - for c in subcomplex_cells[d]: - L[c] = None - cells[d] = tuple(L) - cells[-1] = (None,) - return cells - - def chain_complex(self, subcomplex=None, augmented=False, - verbose=False, check=False, dimensions=None, - base_ring=ZZ, cochain=False): - r""" - The chain complex associated to this `\Delta`-complex. - - :param dimensions: if None, compute the chain complex in all - dimensions. If a list or tuple of integers, compute the - chain complex in those dimensions, setting the chain groups - in all other dimensions to zero. NOT IMPLEMENTED YET: this - function always returns the entire chain complex - :param base_ring: commutative ring - :type base_ring: optional, default ZZ - :param subcomplex: a subcomplex of this simplicial complex. - Compute the chain complex relative to this subcomplex. - :type subcomplex: optional, default empty - :param augmented: If True, return the augmented chain complex - (that is, include a class in dimension `-1` corresponding - to the empty cell). This is ignored if ``dimensions`` is - specified or if ``subcomplex`` is nonempty. - :type augmented: boolean; optional, default False - :param cochain: If True, return the cochain complex (that is, - the dual of the chain complex). - :type cochain: boolean; optional, default False - :param verbose: If True, print some messages as the chain - complex is computed. - :type verbose: boolean; optional, default False - :param check: If True, make sure that the chain complex - is actually a chain complex: the differentials are - composable and their product is zero. - :type check: boolean; optional, default False - - .. note:: - - If subcomplex is nonempty, then the argument ``augmented`` - has no effect: the chain complex relative to a nonempty - subcomplex is zero in dimension `-1`. - - EXAMPLES:: - - sage: circle = delta_complexes.Sphere(1) - sage: circle.chain_complex() - Chain complex with at most 2 nonzero terms over Integer Ring - sage: circle.chain_complex()._latex_() - '\\Bold{Z}^{1} \\xrightarrow{d_{1}} \\Bold{Z}^{1}' - sage: circle.chain_complex(base_ring=QQ, augmented=True) - Chain complex with at most 3 nonzero terms over Rational Field - sage: circle.homology(dim=1) - Z - sage: circle.cohomology(dim=1) - Z - sage: T = delta_complexes.Torus() - sage: T.chain_complex(subcomplex=T) - Trivial chain complex over Integer Ring - sage: T.homology(subcomplex=T, algorithm='no_chomp') - {0: 0, 1: 0, 2: 0} - sage: A = T.subcomplex({2: [1]}) # one of the two triangles forming T - sage: T.chain_complex(subcomplex=A) - Chain complex with at most 1 nonzero terms over Integer Ring - sage: T.homology(subcomplex=A) - {0: 0, 1: 0, 2: Z} - """ - if subcomplex is not None: - # relative chain complex, so don't augment the chain complex - augmented = False - - differentials = {} - if augmented: - empty_simplex = 1 # number of (-1)-dimensional simplices - else: - empty_simplex = 0 - vertices = self.n_cells(0, subcomplex=subcomplex) - old = vertices - old_real = [x for x in old if x is not None] # get rid of faces not in subcomplex - n = len(old_real) - differentials[0] = matrix(base_ring, empty_simplex, n, n*empty_simplex*[1]) - # current is list of simplices in dimension dim - # current_real is list of simplices in dimension dim, with None filtered out - # old is list of simplices in dimension dim-1 - # old_real is list of simplices in dimension dim-1, with None filtered out - for dim in range(1,self.dimension()+1): - current = list(self.n_cells(dim, subcomplex=subcomplex)) - current_real = [x for x in current if x is not None] - i = 0 - i_real = 0 - translate = {} - for s in old: - if s is not None: - translate[i] = i_real - i_real += 1 - i += 1 - mat_dict = {} - col = 0 - for s in current_real: - sign = 1 - for row in s: - if old[row] is not None: - actual_row = translate[row] - if (actual_row,col) in mat_dict: - mat_dict[(actual_row,col)] += sign - else: - mat_dict[(actual_row,col)] = sign - sign *= -1 - col += 1 - differentials[dim] = matrix(base_ring, len(old_real), len(current_real), mat_dict) - old = current - old_real = current_real - if cochain: - cochain_diffs = {} - for dim in differentials: - cochain_diffs[dim-1] = differentials[dim].transpose() - return ChainComplex(data=cochain_diffs, degree=1, - base_ring=base_ring, check=check) - else: - return ChainComplex(data=differentials, degree=-1, - base_ring=base_ring, check=check) - - def alexander_whitney(self, cell, dim_left): - r""" - Subdivide ``cell`` in this `\Delta`-complex into a pair of - simplices. - - For an abstract simplex with vertices `v_0`, `v_1`, ..., - `v_n`, then subdivide it into simplices `(v_0, v_1, ..., - v_{dim_left})` and `(v_{dim_left}, v_{dim_left + 1}, ..., - v_n)`. In a `\Delta`-complex, instead take iterated faces: - take top faces to get the left factor, take bottom faces to - get the right factor. - - INPUT: - - - ``cell`` -- a simplex in this complex, given as a pair - ``(idx, tuple)``, where ``idx`` is its index in the list of - cells in the given dimension, and ``tuple`` is the tuple of - its faces - - - ``dim_left`` -- integer between 0 and one more than the - dimension of this simplex - - OUTPUT: a list containing just the triple ``(1, left, - right)``, where ``left`` and ``right`` are the two cells - described above, each given as pairs ``(idx, tuple)``. - - EXAMPLES:: - - sage: X = delta_complexes.Torus() - sage: X.n_cells(2) - [(1, 2, 0), (0, 2, 1)] - sage: X.alexander_whitney((0, (1, 2, 0)), 1) - [(1, (0, (0, 0)), (1, (0, 0)))] - sage: X.alexander_whitney((0, (1, 2, 0)), 0) - [(1, (0, ()), (0, (1, 2, 0)))] - sage: X.alexander_whitney((1, (0, 2, 1)), 2) - [(1, (1, (0, 2, 1)), (0, ()))] - """ - dim = len(cell[1]) - 1 - left_cell = cell[1] - idx_l = cell[0] - for i in range(dim, dim_left, -1): - idx_l = left_cell[i] - left_cell = self.n_cells(i-1)[idx_l] - right_cell = cell[1] - idx_r = cell[0] - for i in range(dim, dim - dim_left, -1): - idx_r = right_cell[0] - right_cell = self.n_cells(i-1)[idx_r] - return [(ZZ.one(), (idx_l, left_cell), (idx_r, right_cell))] - - def n_skeleton(self, n): - r""" - The n-skeleton of this `\Delta`-complex. - - :param n: dimension - :type n: non-negative integer - - EXAMPLES:: - - sage: S3 = delta_complexes.Sphere(3) - sage: S3.n_skeleton(1) # 1-skeleton of a tetrahedron - Delta complex with 4 vertices and 11 simplices - sage: S3.n_skeleton(1).dimension() - 1 - sage: S3.n_skeleton(1).homology() - {0: 0, 1: Z x Z x Z} - """ - if n >= self.dimension(): - return self - else: - data = [] - for d in range(n+1): - data.append(self._cells_dict[d]) - return DeltaComplex(data) - - def graph(self): - r""" - The 1-skeleton of this `\Delta`-complex as a graph. - - EXAMPLES:: - - sage: T = delta_complexes.Torus() - sage: T.graph() - Looped multi-graph on 1 vertex - sage: S = delta_complexes.Sphere(2) - sage: S.graph() - Graph on 3 vertices - sage: delta_complexes.Simplex(4).graph() == graphs.CompleteGraph(5) - True - """ - data = {} - for vertex in range(len(self.n_cells(0))): - data[vertex] = [] - for edge in self.n_cells(1): - data[edge[0]].append(edge[1]) - return Graph(data) - - def join(self, other): - r""" - The join of this `\Delta`-complex with another one. - - :param other: another `\Delta`-complex (the right-hand - factor) - :return: the join ``self * other`` - - The join of two `\Delta`-complexes `S` and `T` is the - `\Delta`-complex `S*T` with simplices of the form `[v_0, ..., - v_k, w_0, ..., w_n]` for all simplices `[v_0, ..., v_k]` in - `S` and `[w_0, ..., w_n]` in `T`. The faces are computed - accordingly: the ith face of such a simplex is either `(d_i S) - * T` if `i \leq k`, or `S * (d_{i-k-1} T)` if `i > k`. - - EXAMPLES:: - - sage: T = delta_complexes.Torus() - sage: S0 = delta_complexes.Sphere(0) - sage: T.join(S0) # the suspension of T - Delta complex with 3 vertices and 21 simplices - - Compare to simplicial complexes:: - - sage: K = delta_complexes.KleinBottle() - sage: T_simp = simplicial_complexes.Torus() - sage: K_simp = simplicial_complexes.KleinBottle() - sage: T.join(K).homology()[3] == T_simp.join(K_simp).homology()[3] # long time (3 seconds) - True - - The notation '*' may be used, as well:: - - sage: S1 = delta_complexes.Sphere(1) - sage: X = S1 * S1 # X is a 3-sphere - sage: X.homology() - {0: 0, 1: 0, 2: 0, 3: Z} - """ - data = [] - # vertices of the join: the union of the vertices. put the - # vertices of self first, then the vertices of right. - data.append(self.n_cells(0) + other.n_cells(0)) - bdries = {} - for l_idx in range(len(self.n_cells(0))): - bdries[(0,l_idx,-1,0)] = l_idx - for r_idx in range(len(other.n_cells(0))): - bdries[(-1,0,0,r_idx)] = len(self.n_cells(0)) + r_idx - # dimension of the join: - maxdim = self.dimension() + other.dimension() + 1 - # now for the d-cells, d>0: - for d in range(1,maxdim+1): - d_cells = [] - positions = {} - new_idx = 0 - for k in range(-1,d+1): - n = d-1-k - # d=n+k. need a k-cell from self and an n-cell from other - if k == -1: - left = [()] - else: - left = self.n_cells(k) - l_idx = 0 - if n == -1: - right = [()] - else: - right = other.n_cells(n) - for l in left: - r_idx = 0 - for r in right: - # store index of the new simplex in positions - positions[(k, l_idx, n, r_idx)] = new_idx - # form boundary of l*r and store it in d_cells - bdry = [] - # first faces come from left-hand factor - if k == 0: - bdry.append(bdries[(-1, 0, n, r_idx)]) - else: - for i in range(k+1): - bdry.append(bdries[(k-1, l[i], n, r_idx)]) - # remaining faces come from right-hand factor - if n == 0: - bdry.append(bdries[(k, l_idx, -1, 0)]) - else: - for i in range(n+1): - bdry.append(bdries[(k, l_idx, n-1, r[i])]) - d_cells.append(tuple(bdry)) - r_idx += 1 - new_idx += 1 - l_idx += 1 - data.append(d_cells) - bdries = positions - return DeltaComplex(data) - - # Use * to mean 'join': - __mul__ = join - - def cone(self): - r""" - The cone on this `\Delta`-complex. - - The cone is the complex formed by adding a new vertex `C` and - simplices of the form `[C, v_0, ..., v_k]` for every simplex - `[v_0, ..., v_k]` in the original complex. That is, the cone - is the join of the original complex with a one-point complex. - - EXAMPLES:: - - sage: K = delta_complexes.KleinBottle() - sage: K.cone() - Delta complex with 2 vertices and 14 simplices - sage: K.cone().homology() - {0: 0, 1: 0, 2: 0, 3: 0} - """ - return self.join(delta_complexes.Simplex(0)) - - def suspension(self, n=1): - r""" - The suspension of this `\Delta`-complex. - - :param n: suspend this many times. - :type n: positive integer; optional, default 1 - - The suspension is the complex formed by adding two new - vertices `S_0` and `S_1` and simplices of the form `[S_0, v_0, - ..., v_k]` and `[S_1, v_0, ..., v_k]` for every simplex `[v_0, - ..., v_k]` in the original complex. That is, the suspension - is the join of the original complex with a two-point complex - (the 0-sphere). - - EXAMPLES:: - - sage: S = delta_complexes.Sphere(0) - sage: S3 = S.suspension(3) # the 3-sphere - sage: S3.homology() - {0: 0, 1: 0, 2: 0, 3: Z} - """ - if n<0: - raise ValueError("n must be non-negative.") - if n==0: - return self - if n==1: - return self.join(delta_complexes.Sphere(0)) - return self.suspension().suspension(int(n-1)) - - def product(self, other): - r""" - The product of this `\Delta`-complex with another one. - - :param other: another `\Delta`-complex (the right-hand - factor) - :return: the product ``self x other`` - - .. warning:: - - If ``X`` and ``Y`` are `\Delta`-complexes, then ``X*Y`` - returns their join, not their product. - - EXAMPLES:: - - sage: K = delta_complexes.KleinBottle() - sage: X = K.product(K) - sage: X.homology(1) - Z x Z x C2 x C2 - sage: X.homology(2) - Z x C2 x C2 x C2 - sage: X.homology(3) - C2 - sage: X.homology(4) - 0 - sage: X.homology(base_ring=GF(2)) - {0: Vector space of dimension 0 over Finite Field of size 2, - 1: Vector space of dimension 4 over Finite Field of size 2, - 2: Vector space of dimension 6 over Finite Field of size 2, - 3: Vector space of dimension 4 over Finite Field of size 2, - 4: Vector space of dimension 1 over Finite Field of size 2} - sage: S1 = delta_complexes.Sphere(1) - sage: K.product(S1).homology() == S1.product(K).homology() - True - sage: S1.product(S1) == delta_complexes.Torus() - True - """ - data = [] - bdries = {} - # vertices: the vertices in the product are of the form (v,w) - # for v a vertex in self, w a vertex in other - vertices = [] - l_idx = 0 - for v in self.n_cells(0): - r_idx = 0 - for w in other.n_cells(0): - # one vertex for each pair (v,w) - # store its indices in bdries; store its boundary in vertices - bdries[(0,l_idx,0,r_idx, ((0,0),))] = len(vertices) - vertices.append(()) # add new vertex (simplex with empty bdry) - r_idx += 1 - l_idx += 1 - data.append(tuple(vertices)) - # dim of the product: - maxdim = self.dimension() + other.dimension() - # d-cells, d>0: these are obtained by taking products of cells - # of dimensions k and n, where n+k >= d and n <= d, k <= d. - simplices = [] - new = {} - for d in range(1, maxdim+1): - for k in range(d+1): - for n in range(d-k,d+1): - k_idx = 0 - for k_cell in self.n_cells(k): - n_idx = 0 - for n_cell in other.n_cells(n): - # find d-dimensional faces in product of - # k_cell and n_cell. to avoid repetition, - # only look for faces which use all - # vertices of each factor: the 'path' - # corresponding to each d-cell must hit - # every row and every column in the - # lattice. (See the 'product' method for - # Simplex, as well as the function - # 'lattice_paths', in - # simplicial_complex.py.) - for path in lattice_paths(list(range(k + 1)), - list(range(n + 1)), - length=d+1): - path = tuple(path) - new[(k, k_idx, n, n_idx, path)] = len(simplices) - bdry_list = [] - for i in range(d+1): - face_path = path[:i] + path[i+1:] - if ((i0 and path[i][0] == path[i-1][0])): - # this k-simplex - k_face_idx = k_idx - k_face_dim = k - else: - # face of this k-simplex - k_face_idx = k_cell[path[i][0]] - k_face_dim = k-1 - tail = [] - for j in range(i,d): - tail.append((face_path[j][0]-1, - face_path[j][1])) - face_path = face_path[:i] + tuple(tail) - if ((i0 and path[i][1] == path[i-1][1])): - # this n-simplex - n_face_idx = n_idx - n_face_dim = n - else: - # face of this n-simplex - n_face_idx = n_cell[path[i][1]] - n_face_dim = n-1 - tail = [] - for j in range(i,d): - tail.append((face_path[j][0], - face_path[j][1]-1)) - face_path = face_path[:i] + tuple(tail) - bdry_list.append(bdries[(k_face_dim, k_face_idx, - n_face_dim, n_face_idx, - face_path)]) - simplices.append(tuple(bdry_list)) - n_idx += 1 - k_idx += 1 - # add d-simplices to data, store d-simplices in bdries, - # reset simplices - data.append(tuple(simplices)) - bdries = new - new = {} - simplices = [] - return DeltaComplex(data) - - def disjoint_union(self, right): - r""" - The disjoint union of this `\Delta`-complex with another one. - - :param right: the other `\Delta`-complex (the right-hand factor) - - EXAMPLES:: - - sage: S1 = delta_complexes.Sphere(1) - sage: S2 = delta_complexes.Sphere(2) - sage: S1.disjoint_union(S2).homology() - {0: Z, 1: Z, 2: Z} - """ - dim = max(self.dimension(), right.dimension()) - data = {} - # in dimension n, append simplices of self with simplices of - # right, but translate each entry of each right simplex: add - # len(self.n_cells(n-1)) to it - for n in range(dim, 0, -1): - data[n] = list(self.n_cells(n)) - translate = len(self.n_cells(n-1)) - for f in right.n_cells(n): - data[n].append(tuple([a+translate for a in f])) - data[0] = self.n_cells(0) + right.n_cells(0) - return DeltaComplex(data) - - def wedge(self, right): - r""" - The wedge (one-point union) of this `\Delta`-complex with - another one. - - :param right: the other `\Delta`-complex (the right-hand factor) - - .. note:: - - This operation is not well-defined if ``self`` or - ``other`` is not path-connected. - - EXAMPLES:: - - sage: S1 = delta_complexes.Sphere(1) - sage: S2 = delta_complexes.Sphere(2) - sage: S1.wedge(S2).homology() - {0: 0, 1: Z, 2: Z} - """ - data = self.disjoint_union(right).cells() - left_verts = len(self.n_cells(0)) - translate = {} - for i in range(left_verts): - translate[i] = i - translate[left_verts] = 0 - for i in range(left_verts + 1, left_verts + len(right.n_cells(0))): - translate[i] = i-1 - data[0] = data[0][:-1] - edges = [] - for e in data[1]: - edges.append([translate[a] for a in e]) - data[1] = edges - return DeltaComplex(data) - - def connected_sum(self, other): - r""" - Return the connected sum of self with other. - - :param other: another `\Delta`-complex - :return: the connected sum ``self # other`` - - .. warning:: - - This does not check that self and other are manifolds. It - doesn't even check that their facets all have the same - dimension. It just chooses top-dimensional simplices from - each complex, checks that they have the same dimension, - removes them, and glues the remaining pieces together. - Since a (more or less) random facet is chosen from each - complex, this method may return random results if applied - to non-manifolds, depending on which facet is chosen. - - ALGORITHM: - - Pick a top-dimensional simplex from each complex. Check to - see if there are any identifications on either simplex, using - the :meth:`_is_glued` method. If there are no - identifications, remove the simplices and glue the remaining - parts of complexes along their boundary. If there are - identifications on a simplex, subdivide it repeatedly (using - :meth:`elementary_subdivision`) until some piece has no - identifications. - - EXAMPLES:: - - sage: T = delta_complexes.Torus() - sage: S2 = delta_complexes.Sphere(2) - sage: T.connected_sum(S2).cohomology() == T.cohomology() - True - sage: RP2 = delta_complexes.RealProjectivePlane() - sage: T.connected_sum(RP2).homology(1) - Z x Z x C2 - sage: T.connected_sum(RP2).homology(2) - 0 - sage: RP2.connected_sum(RP2).connected_sum(RP2).homology(1) - Z x Z x C2 - """ - if not self.dimension() == other.dimension(): - raise ValueError("Complexes are not of the same dimension.") - dim = self.dimension() - # Look at the last simplex in the list of top-dimensional - # simplices for each complex. If there are identifications on - # either of these simplices, subdivide until there are no more - # identifications. - Left = self - while Left._is_glued(): - Left = Left.elementary_subdivision() - Right = other - while Right._is_glued(): - Right = Right.elementary_subdivision() - # remove last top-dimensional face from each one and glue. - data = {} - for n in Left.cells(): - data[n] = list(Left.cells()[n]) - right_cells = Right.cells() - data[dim] = data[dim][:-1] - left_simplex = Left.n_cells(dim)[-1] - right_simplex = Right.n_cells(dim)[-1] - # renaming: dictionary for translating all simplices of Right - renaming = dict(zip(right_simplex, left_simplex)) - # process_now: cells to be reindexed and added to data - process_now = right_cells[dim][:-1] - for n in range(dim, 0, -1): - # glued: dictionary of just the simplices being glued - glued = copy(renaming) - # process_later: cells one dim lower to be added to data - process_later = [] - old_idx = 0 - new_idx = len(data[n-1]) - # build 'renaming' - for s in right_cells[n-1]: - if old_idx not in renaming: - process_later.append(s) - renaming[old_idx] = new_idx - new_idx += 1 - old_idx += 1 - # reindex all simplices to be processed and add them to data - for s in process_now: - data[n].append(tuple([renaming[i] for i in s])) - # set up for next loop, one dimension down - renaming = {} - process_now = process_later - for f in glued: - renaming.update(dict(zip(right_cells[n-1][f], data[n-1][glued[f]]))) - # deal with vertices separately. we just need to add enough - # vertices: all the vertices from Right, minus the number - # being glued, which should be dim+1, the number of vertices - # in the simplex of dimension dim being glued. - for i in range(len(right_cells[0]) - dim - 1): - data[0].append(()) - return DeltaComplex(data) - - def elementary_subdivision(self, idx=-1): - r""" - Perform an "elementary subdivision" on a top-dimensional - simplex in this `\Delta`-complex. If the optional argument - ``idx`` is present, it specifies the index (in the list of - top-dimensional simplices) of the simplex to subdivide. If - not present, subdivide the last entry in this list. - - :param idx: index specifying which simplex to subdivide - :type idx: integer; optional, default -1 - :return: `\Delta`-complex with one simplex subdivided. - - *Elementary subdivision* of a simplex means replacing that - simplex with the cone on its boundary. That is, given a - `\Delta`-complex containing a `d`-simplex `S` with vertices - `v_0`, ..., `v_d`, form a new `\Delta`-complex by - - - removing `S` - - adding a vertex `w` (thought of as being in the interior of `S`) - - adding all simplices with vertices `v_{i_0}`, ..., - `v_{i_k}`, `w`, preserving any identifications present - along the boundary of `S` - - The algorithm for achieving this uses - :meth:`_epi_from_standard_simplex` to keep track of simplices - (with multiplicity) and what their faces are: this method - defines a surjection `\pi` from the standard `d`-simplex to - `S`. So first remove `S` and add a new vertex `w`, say at the - end of the old list of vertices. Then for each vertex `v` in - the standard `d`-simplex, add an edge from `\pi(v)` to `w`; - for each edge `(v_0, v_1)` in the standard `d`-simplex, add a - triangle `(\pi(v_0), \pi(v_1), w)`, etc. - - Note that given an `n`-simplex `(v_0, v_1, ..., v_n)` in the - standard `d`-simplex, the faces of the new `(n+1)`-simplex are - given by removing vertices, one at a time, from `(\pi(v_0), - ..., \pi(v_n), w)`. These are either the image of the old - `n`-simplex (if `w` is removed) or the various new - `n`-simplices added in the previous dimension. So keep track - of what's added in dimension `n` for use in computing the - faces in dimension `n+1`. - - In contrast with barycentric subdivision, note that only the - interior of `S` has been changed; this allows for subdivision - of a single top-dimensional simplex without subdividing every - simplex in the complex. - - The term "elementary subdivision" is taken from p. 112 in John - M. Lee's book [Lee2011]_. - - EXAMPLES:: - - sage: T = delta_complexes.Torus() - sage: T.n_cells(2) - [(1, 2, 0), (0, 2, 1)] - sage: T.elementary_subdivision(0) # subdivide first triangle - Delta complex with 2 vertices and 13 simplices - sage: X = T.elementary_subdivision(); X # subdivide last triangle - Delta complex with 2 vertices and 13 simplices - sage: X.elementary_subdivision() - Delta complex with 3 vertices and 19 simplices - sage: X.homology() == T.homology() - True - """ - pi = self._epi_from_standard_simplex(idx=idx) - cells_dict = {} - old_cells = self.cells() - for n in old_cells: - cells_dict[n] = list(old_cells[n]) - dim = self.dimension() - # cells of standard simplex of dimension dim - std_cells = SimplicialComplex([Simplex(dim)]).delta_complex(sort_simplices=True).cells() - # adjust zero-cells so they're distinct - std_cells[0] = tuple([[n] for n in range(dim + 1)]) - # remove the cell being subdivided - cells_dict[dim].pop(idx) - # add the new vertex "w" - cells_dict[0].append(()) - # added_cells: dict indexed by (n-1)-cells, with value the - # corresponding new n-cell. - added_cells = {(): len(cells_dict[0])-1} - for n in range(0, dim): - new_cells = {} - # for each n-cell in the standard simplex, add an - # (n+1)-cell to the subdivided complex. - try: - simplices = sorted(pi[n]) - except TypeError: - simplices = pi[n] - for simplex in simplices: - # compute the faces of the new (n+1)-cell. - cell = [] - for i in simplex: - if n > 0: - bdry = tuple(std_cells[n-1][i]) - else: - bdry = () - cell.append(added_cells[bdry]) - # last face is the image of the old simplex) - cell.append(pi[n][simplex]) - cell = tuple(cell) - cells_dict[n+1].append(cell) - new_cells[simplex] = len(cells_dict[n+1])-1 - added_cells = new_cells - return DeltaComplex(cells_dict) - - def _epi_from_standard_simplex(self, idx=-1, dim=None): - r""" - Construct an epimorphism from a standard simplex to a - top-dimensional simplex in this `\Delta`-complex. - - If the optional argument ``dim`` is not ``None``, then - construct the map to a simplex with this dimension. If the - optional argument ``idx`` is present, it specifies which - simplex to use by giving its index in the list of simplices of - the appropriate dimension; if not present, use the last - simplex in this list. - - This is used by :meth:`elementary_subdivision`. - - :param idx: index specifying which simplex to examine - :type idx: integer; optional, default -1 - :return: boolean, True if the boundary of the simplex has any - identifications - :param dim: dimension of simplex to consider - :type dim: integer; optional, default = dim of complex - - Suppose that the dimension is `d`. The map is given by a - dictionary indexed by dimension: in dimension `i`, its value - is a dictionary specifying, for each `i`-simplex in the - domain, the corresponding `i`-simplex in the codomain. The - vertices are specified as their indices in the lists of - simplices in each complex; the same goes for all of the - simplices in the codomain. The simplices of dimension 1 or - higher in the domain are listed explicitly (in the form of - entries from the output of :meth:`cells`). - - In this function, the "standard simplex" is defined to be - ``simplicial_complexes.Simplex(d).delta_complex(sort_simplices=True)``. - - EXAMPLES: - - The `\Delta`-complex model for a torus has two triangles and - three edges, but only one vertex. So a surjection from the - standard 2-simplex to either of the triangles is a bijection - in dimension 1, but in dimension 0, sends all three vertices - to the same place:: - - sage: T = delta_complexes.Torus() - sage: sorted(T._epi_from_standard_simplex()[1].items()) - [((1, 0), 1), ((2, 0), 2), ((2, 1), 0)] - sage: sorted(T._epi_from_standard_simplex()[0].items()) - [((0,), 0), ((1,), 0), ((2,), 0)] - """ - if dim is None: - dim = self.dimension() - # the output is easier to read if the entries are non-negative. - if idx == -1: - idx = len(self.n_cells(dim)) - 1 - simplex = SimplicialComplex([Simplex(dim)]).delta_complex(sort_simplices=True) - simplex_cells = simplex.cells() - self_cells = self.cells() - if dim > 0: - map = {dim: {tuple(simplex_cells[dim][0]): idx}} - else: - map = {dim: {(0,): idx}} - faces_dict = map[dim] - for n in range(dim, 0, -1): - n_cells = faces_dict - faces_dict = {} - for cell in n_cells: - if n > 1: - faces = [tuple(simplex_cells[n-1][cell[j]]) for j in range(0,n+1)] - one_cell = dict(zip(faces, self_cells[n][n_cells[cell]])) - else: - temp = dict(zip(cell, self_cells[n][n_cells[cell]])) - one_cell = {} - for j in temp: - one_cell[(j,)] = temp[j] - for j in one_cell: - if j not in faces_dict: - faces_dict[j] = one_cell[j] - map[n-1] = faces_dict - return map - - def _is_glued(self, idx=-1, dim=None): - r""" - ``True`` if there is any gluing along the boundary of a - top-dimensional simplex in this `\Delta`-complex. - - If the optional argument ``idx`` is present, it specifies - which simplex to consider by giving its index in the list of - top-dimensional simplices; if not present, look at the last - simplex in this list. If the optional argument ``dim`` is - present, it specifies the dimension of the simplex to - consider; if not present, look at a top-dimensional simplex. - - This is used by :meth:`connected_sum`. - - :param idx: index specifying which simplex to examine - :type idx: integer; optional, default -1 - :return: boolean, True if the boundary of the simplex has any - identifications - :param dim: dimension of simplex to consider - :type dim: integer; optional, default = dim of complex - - EXAMPLES:: - - sage: T = delta_complexes.Torus() - sage: T._is_glued() - True - sage: S = delta_complexes.Simplex(3) - sage: S._is_glued() - False - """ - if dim is None: - dim = self.dimension() - - simplex = self.n_cells(dim)[idx] - i = self.dimension() - 1 - i_faces = set(simplex) - # if there are enough i_faces, then no gluing is evident so far - not_glued = (len(i_faces) == binomial(dim+1, i+1)) - while not_glued and i > 0: - # count the (i-1) cells and compare to (n+1) choose i. - old_faces = i_faces - i_faces = set([]) - all_cells = self.n_cells(i) - for face in old_faces: - i_faces.update(all_cells[face]) - not_glued = (len(i_faces) == binomial(dim+1, i)) - i = i-1 - return not not_glued - - def face_poset(self): - r""" - The face poset of this `\Delta`-complex, the poset of - nonempty cells, ordered by inclusion. - - EXAMPLES:: - - sage: T = delta_complexes.Torus() - sage: T.face_poset() - Finite poset containing 6 elements - """ - from sage.combinat.posets.posets import Poset - # given the structure of self.cells(), it's easier to compute - # the dual poset, then reverse it at the end. - dim = self.dimension() - covers = {} - # store each n-simplex as a pair (n, idx). - for n in range(dim, 0, -1): - idx = 0 - for s in self.n_cells(n): - covers[(n, idx)] = list(set([(n-1, i) for i in s])) - idx += 1 - # deal with vertices separately: they have no covers (in the - # dual poset). - idx = 0 - for s in self.n_cells(0): - covers[(0, idx)] = [] - idx += 1 - return Poset(Poset(covers).hasse_diagram().reverse()) - - # implement using the definition? the simplices are obtained by - # taking chains of inclusions of simplices, etc. have to work out - # the faces and identifications. - def barycentric_subdivision(self): - r""" - Not implemented. - - EXAMPLES:: - - sage: K = delta_complexes.KleinBottle() - sage: K.barycentric_subdivision() - Traceback (most recent call last): - ... - NotImplementedError: Barycentric subdivisions are not implemented for Delta complexes. - """ - raise NotImplementedError("Barycentric subdivisions are not implemented for Delta complexes.") - - def n_chains(self, n, base_ring=None, cochains=False): - r""" - Return the free module of chains in degree ``n`` over ``base_ring``. - - INPUT: - - - ``n`` -- integer - - ``base_ring`` -- ring (optional, default `\ZZ`) - - ``cochains`` -- boolean (optional, default ``False``); if - ``True``, return cochains instead - - Since the list of `n`-cells for a `\Delta`-complex may have - some ambiguity -- for example, the list of edges may look like - ``[(0, 0), (0, 0), (0, 0)]`` if each edge starts and ends at - vertex 0 -- we record the indices of the cells along with - their tuples. So the basis of chains in such a case would look - like ``[(0, (0, 0)), (1, (0, 0)), (2, (0, 0))]``. - - The only difference between chains and cochains is notation: - the dual cochain to the chain basis element ``b`` is written - as ``\chi_b``. - - EXAMPLES:: - - sage: T = delta_complexes.Torus() - sage: T.n_chains(1, QQ) - Free module generated by {(0, (0, 0)), (1, (0, 0)), (2, (0, 0))} over Rational Field - sage: list(T.n_chains(1, QQ, cochains=False).basis()) - [(0, (0, 0)), (1, (0, 0)), (2, (0, 0))] - sage: list(T.n_chains(1, QQ, cochains=True).basis()) - [\chi_(0, (0, 0)), \chi_(1, (0, 0)), \chi_(2, (0, 0))] - """ - n_cells = tuple(enumerate(self.n_cells(n))) - if cochains: - return Cochains(self, n, n_cells, base_ring) - else: - return Chains(self, n, n_cells, base_ring) - - # the second barycentric subdivision is a simplicial complex. implement this somehow? -# def simplicial_complex(self): -# X = self.barycentric_subdivision().barycentric_subdivision() -# find facets of X and return SimplicialComplex(facets) - - # This is cached for speed reasons: it can be very slow to run - # this function. - @cached_method - def algebraic_topological_model(self, base_ring=None): - r""" - Algebraic topological model for this `\Delta`-complex with - coefficients in ``base_ring``. - - The term "algebraic topological model" is defined by Pilarczyk - and Réal [PR2015]_. - - INPUT: - - - ``base_ring`` - coefficient ring (optional, default - ``QQ``). Must be a field. - - Denote by `C` the chain complex associated to this - `\Delta`-complex. The algebraic topological model is a chain complex - `M` with zero differential, with the same homology as `C`, - along with chain maps `\pi: C \to M` and `\iota: M \to C` - satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic - to `1_C`. The chain homotopy `\phi` must satisfy - - - `\phi \phi = 0`, - - `\pi \phi = 0`, - - `\phi \iota = 0`. - - Such a chain homotopy is called a *chain contraction*. - - OUTPUT: a pair consisting of - - - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and - `\iota` - - the chain complex `M` - - Note that from the chain contraction ``phi``, one can recover the - chain maps `\pi` and `\iota` via ``phi.pi()`` and - ``phi.iota()``. Then one can recover `C` and `M` from, for - example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, - respectively. - - EXAMPLES:: - - sage: RP2 = delta_complexes.RealProjectivePlane() - sage: phi, M = RP2.algebraic_topological_model(GF(2)) - sage: M.homology() - {0: Vector space of dimension 1 over Finite Field of size 2, - 1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2} - sage: T = delta_complexes.Torus() - sage: phi, M = T.algebraic_topological_model(QQ) - sage: M.homology() - {0: Vector space of dimension 1 over Rational Field, - 1: Vector space of dimension 2 over Rational Field, - 2: Vector space of dimension 1 over Rational Field} - """ - from .algebraic_topological_model import algebraic_topological_model_delta_complex - if base_ring is None: - base_ring = QQ - return algebraic_topological_model_delta_complex(self, base_ring) - - def _string_constants(self): - r""" - Tuple containing the name of the type of complex, and the - singular and plural of the name of the cells from which it is - built. This is used in constructing the string representation. - - EXAMPLES:: - - sage: T = delta_complexes.Torus() - sage: T._string_constants() - ('Delta', 'simplex', 'simplices') - """ - return ('Delta', 'simplex', 'simplices') - - -class DeltaComplexExamples(): - r""" - Some examples of `\Delta`-complexes. - - Here are the available examples; you can also type - ``delta_complexes.`` and hit TAB to get a list:: - - Sphere - Torus - RealProjectivePlane - KleinBottle - Simplex - SurfaceOfGenus - - EXAMPLES:: - - sage: S = delta_complexes.Sphere(6) # the 6-sphere - sage: S.dimension() - 6 - sage: S.cohomology(6) - Z - sage: delta_complexes.Torus() == delta_complexes.Sphere(3) - False - """ - - def Sphere(self,n): - r""" - A `\Delta`-complex representation of the `n`-dimensional sphere, - formed by gluing two `n`-simplices along their boundary, - except in dimension 1, in which case it is a single 1-simplex - starting and ending at the same vertex. - - :param n: dimension of the sphere - - EXAMPLES:: - - sage: delta_complexes.Sphere(4).cohomology(4, base_ring=GF(3)) - Vector space of dimension 1 over Finite Field of size 3 - """ - if n == 1: - return DeltaComplex([ [()], [(0, 0)] ]) - else: - return DeltaComplex({Simplex(n): True, Simplex(range(1,n+2)): Simplex(n)}) - - def Torus(self): - r""" - A `\Delta`-complex representation of the torus, consisting of one - vertex, three edges, and two triangles. - - .. image:: ../../media/torus.png - - EXAMPLES:: - - sage: delta_complexes.Torus().homology(1) - Z x Z - """ - return DeltaComplex(( ((),), ((0,0), (0,0), (0,0)), ((1,2,0), (0, 2, 1)))) - - def RealProjectivePlane(self): - r""" - A `\Delta`-complex representation of the real projective plane, - consisting of two vertices, three edges, and two triangles. - - .. image:: ../../media/rp2.png - - EXAMPLES:: - - sage: P = delta_complexes.RealProjectivePlane() - sage: P.cohomology(1) - 0 - sage: P.cohomology(2) - C2 - sage: P.cohomology(dim=1, base_ring=GF(2)) - Vector space of dimension 1 over Finite Field of size 2 - sage: P.cohomology(dim=2, base_ring=GF(2)) - Vector space of dimension 1 over Finite Field of size 2 - """ - return DeltaComplex(( ((), ()), ((1,0), (1,0), (0,0)), ((1,0,2), (0,1,2)))) - - def KleinBottle(self): - r""" - A `\Delta`-complex representation of the Klein bottle, consisting - of one vertex, three edges, and two triangles. - - .. image:: ../../media/klein.png - - EXAMPLES:: - - sage: delta_complexes.KleinBottle() - Delta complex with 1 vertex and 7 simplices - """ - return DeltaComplex(( ((),), ((0,0), (0,0), (0,0)), ((1,2,0), (0, 1, 2)))) - - def Simplex(self, n): - r""" - A `\Delta`-complex representation of an `n`-simplex, - consisting of a single `n`-simplex and its faces. (This is - the same as the simplicial complex representation available by - using ``simplicial_complexes.Simplex(n)``.) - - EXAMPLES:: - - sage: delta_complexes.Simplex(3) - Delta complex with 4 vertices and 16 simplices - """ - return DeltaComplex({Simplex(n): True}) - - def SurfaceOfGenus(self, g, orientable=True): - r""" - A surface of genus g as a `\Delta`-complex. - - :param g: the genus - :type g: non-negative integer - :param orientable: whether the surface should be orientable - :type orientable: bool, optional, default ``True`` - - In the orientable case, return a sphere if `g` is zero, and - otherwise return a `g`-fold connected sum of a torus with - itself. - - In the non-orientable case, raise an error if `g` is zero. If - `g` is positive, return a `g`-fold connected sum of a - real projective plane with itself. - - EXAMPLES:: - - sage: delta_complexes.SurfaceOfGenus(1, orientable=False) - Delta complex with 2 vertices and 8 simplices - sage: delta_complexes.SurfaceOfGenus(3, orientable=False).homology(1) - Z x Z x C2 - sage: delta_complexes.SurfaceOfGenus(3, orientable=False).homology(2) - 0 - - Compare to simplicial complexes:: - - sage: delta_g4 = delta_complexes.SurfaceOfGenus(4) - sage: delta_g4.f_vector() - [1, 3, 27, 18] - sage: simpl_g4 = simplicial_complexes.SurfaceOfGenus(4) - sage: simpl_g4.f_vector() - [1, 19, 75, 50] - sage: delta_g4.homology() == simpl_g4.homology() - True - """ - try: - g = Integer(g) - except TypeError: - raise ValueError("genus must be a non-negative integer") - if g < 0: - raise ValueError("genus must be a non-negative integer") - if g == 0: - if not orientable: - raise ValueError("no non-orientable surface of genus zero") - else: - return delta_complexes.Sphere(2) - if orientable: - X = delta_complexes.Torus() - else: - X = delta_complexes.RealProjectivePlane() - S = X - for i in range(g-1): - S = S.connected_sum(X) - return S - -delta_complexes = DeltaComplexExamples() +from sage.misc.superseded import deprecated_function_alias +import sage.topology.delta_complex + +DeltaComplex = deprecated_function_alias(31925, + sage.topology.delta_complex.DeltaComplex) +DeltaComplexExamples = deprecated_function_alias(31925, + sage.topology.delta_complex.DeltaComplexExamples) +delta_complexes = deprecated_function_alias(31925, + sage.topology.delta_complex.delta_complexes) diff --git a/src/sage/homology/examples.py b/src/sage/homology/examples.py index 9f542ce8fe3..704b1219189 100644 --- a/src/sage/homology/examples.py +++ b/src/sage/homology/examples.py @@ -1,1619 +1,41 @@ # -*- coding: utf-8 -*- """ -Examples of simplicial complexes +Examples of simplicial complexes: deprecated -There are two main types: manifolds and examples related to graph -theory. - -For manifolds, there are functions defining the `n`-sphere for any -`n`, the torus, `n`-dimensional real projective space for any `n`, the -complex projective plane, surfaces of arbitrary genus, and some other -manifolds, all as simplicial complexes. - -Aside from surfaces, this file also provides functions for -constructing some other simplicial complexes: the simplicial complex -of not-`i`-connected graphs on `n` vertices, the matching complex on n -vertices, the chessboard complex for an `n` by `i` chessboard, and -others. These provide examples of large simplicial complexes; for -example, ``simplicial_complexes.NotIConnectedGraphs(7, 2)`` has over a -million simplices. - -All of these examples are accessible by typing -``simplicial_complexes.NAME``, where ``NAME`` is the name of the example. - -- :func:`BarnetteSphere` -- :func:`BrucknerGrunbaumSphere` -- :func:`ChessboardComplex` -- :func:`ComplexProjectivePlane` -- :func:`DunceHat` -- :func:`FareyMap` -- :func:`K3Surface` -- :func:`KleinBottle` -- :func:`MatchingComplex` -- :func:`MooreSpace` -- :func:`NotIConnectedGraphs` -- :func:`PoincareHomologyThreeSphere` -- :func:`PseudoQuaternionicProjectivePlane` -- :func:`RandomComplex` -- :func:`RandomTwoSphere` -- :func:`RealProjectivePlane` -- :func:`RealProjectiveSpace` -- :func:`RudinBall` -- :func:`ShiftedComplex` -- :func:`Simplex` -- :func:`Sphere` -- :func:`SumComplex` -- :func:`SurfaceOfGenus` -- :func:`Torus` -- :func:`ZieglerBall` - -You can also get a list by typing ``simplicial_complexes.`` and hitting the -TAB key. - -EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(2) # the 2-sphere - sage: S.homology() - {0: 0, 1: 0, 2: Z} - sage: simplicial_complexes.SurfaceOfGenus(3) - Triangulation of an orientable surface of genus 3 - sage: M4 = simplicial_complexes.MooreSpace(4) - sage: M4.homology() - {0: 0, 1: C4, 2: 0} - sage: simplicial_complexes.MatchingComplex(6).homology() - {0: 0, 1: Z^16, 2: 0} +The current version is :mod:`sage.topology.simplicial_complex_examples`. """ -from sage.homology.simplicial_complex import SimplicialComplex -from sage.structure.unique_representation import UniqueRepresentation -# Below we define a function Simplex to construct a simplex as a -# simplicial complex. We also need to use actual simplices as -# simplices, hence: -from sage.homology.simplicial_complex import Simplex as TrueSimplex -from sage.sets.set import Set -from sage.misc.functional import is_even -from sage.combinat.subset import Subsets -import sage.misc.prandom as random - -# Miscellaneous utility functions. - -# The following two functions can be used to generate the facets for -# the corresponding examples in sage.homology.examples. These take a -# few seconds to run, so the actual examples have the facets -# hard-coded. Thus the following functions are not currently used in -# the Sage library. - -def facets_for_RP4(): - """ - Return the list of facets for a minimal triangulation of 4-dimensional - real projective space. - - We use vertices numbered 1 through 16, define two facets, and define - a certain subgroup `G` of the symmetric group `S_{16}`. Then the set - of all facets is the `G`-orbit of the two given facets. - - See the description in Example 3.12 in Datta [Dat2007]_. - - EXAMPLES:: - - sage: from sage.homology.examples import facets_for_RP4 - sage: A = facets_for_RP4() # long time (1 or 2 seconds) - sage: SimplicialComplex(A) == simplicial_complexes.RealProjectiveSpace(4) # long time - True - """ - # Define the group: - from sage.groups.perm_gps.permgroup import PermutationGroup - g1 = '(2, 7)(4, 10)(5, 6)(11, 12)' - g2 = '(1, 2, 3, 4, 5, 10)(6, 8, 9)(11, 12, 13, 14, 15, 16)' - G = PermutationGroup([g1, g2]) - # Define the two simplices: - t1 = (1, 2, 4, 5, 11) - t2 = (1, 2, 4, 11, 13) - # Apply the group elements to the simplices: - facets = [] - for g in G: - d = g.dict() - for t in [t1, t2]: - new = tuple([d[j] for j in t]) - if new not in facets: - facets.append(new) - return facets - - -def facets_for_K3(): - """ - Return the facets for a minimal triangulation of the K3 surface. - - This is a pure simplicial complex of dimension 4 with 16 - vertices and 288 facets. The facets are obtained by constructing a - few facets and a permutation group `G`, and then computing the - `G`-orbit of those facets. - - See Casella and Kühnel in [CK2001]_ and Spreer and Kühnel [SK2011]_; - the construction here uses the labeling from Spreer and Kühnel. - - EXAMPLES:: - - sage: from sage.homology.examples import facets_for_K3 - sage: A = facets_for_K3() # long time (a few seconds) - sage: SimplicialComplex(A) == simplicial_complexes.K3Surface() # long time - True - """ - from sage.groups.perm_gps.permgroup import PermutationGroup - G = PermutationGroup([[(1, 3, 8, 4, 9, 16, 15, 2, 14, 12, 6, 7, 13, 5, 10)], - [(1, 11, 16), (2, 10, 14), (3, 12, 13), (4, 9, 15), (5, 7, 8)]]) - return ([tuple([g(i) for i in (1, 2, 3, 8, 12)]) for g in G] + - [tuple([g(i) for i in (1, 2, 5, 8, 14)]) for g in G]) - -def matching(A, B): - r""" - List of maximal matchings between the sets ``A`` and ``B``. - - A matching is a set of pairs `(a, b) \in A \times B` where each `a` and - `b` appears in at most one pair. A maximal matching is one which is - maximal with respect to inclusion of subsets of `A \times B`. - - INPUT: - - - ``A``, ``B`` -- list, tuple, or indeed anything which can be - converted to a set. - - EXAMPLES:: - - sage: from sage.homology.examples import matching - sage: matching([1, 2], [3, 4]) - [{(1, 3), (2, 4)}, {(1, 4), (2, 3)}] - sage: matching([0, 2], [0]) - [{(0, 0)}, {(2, 0)}] - """ - answer = [] - if len(A) == 0 or len(B) == 0: - return [set([])] - for v in A: - for w in B: - for M in matching(set(A).difference([v]), set(B).difference([w])): - new = M.union([(v, w)]) - if new not in answer: - answer.append(new) - return answer - - -class UniqueSimplicialComplex(SimplicialComplex, UniqueRepresentation): - """ - This combines :class:`SimplicialComplex` and - :class:`UniqueRepresentation`. It is intended to be used to make - standard examples of simplicial complexes unique. See :trac:`13566`. - - INPUT: - - - the inputs are the same as for a :class:`SimplicialComplex`, - with one addition and two exceptions. The exceptions are that - ``is_mutable`` and ``is_immutable`` are ignored: all instances - of this class are immutable. The addition: - - - ``name`` -- string (optional), the string representation for this complex. - - EXAMPLES:: - - sage: from sage.homology.examples import UniqueSimplicialComplex - sage: SimplicialComplex([[0, 1]]) is SimplicialComplex([[0, 1]]) - False - sage: UniqueSimplicialComplex([[0, 1]]) is UniqueSimplicialComplex([[0, 1]]) - True - sage: UniqueSimplicialComplex([[0, 1]]) - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - sage: UniqueSimplicialComplex([[0, 1]], name='The 1-simplex') - The 1-simplex - """ - @staticmethod - def __classcall__(self, maximal_faces=None, name=None, **kwds): - """ - TESTS:: - - sage: from sage.homology.examples import UniqueSimplicialComplex - sage: UniqueSimplicialComplex([[1, 2, 3], [0, 1, 3]]) is UniqueSimplicialComplex([(1, 2, 3), (0, 1, 3)]) - True - sage: X = UniqueSimplicialComplex([[1, 2, 3], [0, 1, 3]]) - sage: X is UniqueSimplicialComplex(X) - True - - Testing ``from_characteristic_function``:: - - sage: UniqueSimplicialComplex(from_characteristic_function=(lambda x:sum(x)<=4, range(5))) - Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 4), (0, 1, 2), (0, 1, 3)} - """ - char_fcn = kwds.get('from_characteristic_function', None) - if char_fcn: - kwds['from_characteristic_function'] = (char_fcn[0], tuple(char_fcn[1])) - if maximal_faces: - # Test to see if maximal_faces is a cell complex or another - # object which can be converted to a simplicial complex: - C = None - if isinstance(maximal_faces, SimplicialComplex): - C = maximal_faces - else: - try: - C = maximal_faces._simplicial_() - except AttributeError: - if not isinstance(maximal_faces, (list, tuple, Simplex)): - # Convert it into a list (in case it is an iterable) - maximal_faces = list(maximal_faces) - if C is not None: - maximal_faces = C.facets() - # Now convert maximal_faces to a tuple of tuples, so that it is hashable. - maximal_faces = tuple(tuple(mf) for mf in maximal_faces) - return super(UniqueSimplicialComplex, self).__classcall__(self, maximal_faces, - name=name, - **kwds) - - def __init__(self, maximal_faces=None, name=None, **kwds): - """ - TESTS:: - - sage: from sage.homology.examples import UniqueSimplicialComplex - sage: UniqueSimplicialComplex([[1, 2, 3], [0, 1, 3]], is_mutable=True).is_mutable() - False - """ - if 'is_mutable' in kwds: - del kwds['is_mutable'] - if 'is_immutable' in kwds: - del kwds['is_immutable'] - self._name = name - SimplicialComplex.__init__(self, maximal_faces=maximal_faces, is_mutable=False, **kwds) - - def _repr_(self): - """ - Print representation - - If the argument ``name`` was specified when defining the - complex, use that. Otherwise, use the print representation - from the class :class:`SimplicialComplex`. - - TESTS:: - - sage: from sage.homology.examples import UniqueSimplicialComplex - sage: UniqueSimplicialComplex([[0, 1]]) - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - sage: UniqueSimplicialComplex([[0, 1]], name='Joe') - Joe - """ - if self._name: - return self._name - return SimplicialComplex._repr_(self) - -# Now the functions that produce the actual examples... - -def Sphere(n): - """ - A minimal triangulation of the `n`-dimensional sphere. - - INPUT: - - - ``n`` -- positive integer - - EXAMPLES:: - - sage: simplicial_complexes.Sphere(2) - Minimal triangulation of the 2-sphere - sage: simplicial_complexes.Sphere(5).homology() - {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: Z} - sage: [simplicial_complexes.Sphere(n).euler_characteristic() for n in range(6)] - [2, 0, 2, 0, 2, 0] - sage: [simplicial_complexes.Sphere(n).f_vector() for n in range(6)] - [[1, 2], - [1, 3, 3], - [1, 4, 6, 4], - [1, 5, 10, 10, 5], - [1, 6, 15, 20, 15, 6], - [1, 7, 21, 35, 35, 21, 7]] - """ - S = TrueSimplex(n+1) - facets = tuple(S.faces()) - return UniqueSimplicialComplex(facets, - name='Minimal triangulation of the {}-sphere'.format(n)) - -def Simplex(n): - """ - An `n`-dimensional simplex, as a simplicial complex. - - INPUT: - - - ``n`` -- a non-negative integer - - OUTPUT: the simplicial complex consisting of the `n`-simplex - on vertices `(0, 1, ..., n)` and all of its faces. - - EXAMPLES:: - - sage: simplicial_complexes.Simplex(3) - The 3-simplex - sage: simplicial_complexes.Simplex(5).euler_characteristic() - 1 - """ - return UniqueSimplicialComplex([TrueSimplex(n)], - name='The {}-simplex'.format(n)) - -def Torus(): - r""" - A minimal triangulation of the torus. - - This is a simplicial complex with 7 vertices, 21 edges and 14 - faces. It is the unique triangulation of the torus with 7 - vertices, and has been found by Möbius in 1861. - - This is also the combinatorial structure of the Császár - polyhedron (see :wikipedia:`Császár_polyhedron`). - - EXAMPLES:: - - sage: T = simplicial_complexes.Torus(); T.homology(1) - Z x Z - sage: T.f_vector() - [1, 7, 21, 14] - - TESTS:: - - sage: T.flip_graph().is_isomorphic(graphs.HeawoodGraph()) - True - - REFERENCES: - - - [Lut2002]_ - """ - return UniqueSimplicialComplex([[0, 1, 2], [1, 2, 4], [1, 3, 4], [1, 3, 6], - [0, 1, 5], [1, 5, 6], [2, 3, 5], [2, 4, 5], - [2, 3, 6], [0, 2, 6], [0, 3, 4], [0, 3, 5], - [4, 5, 6], [0, 4, 6]], - name='Minimal triangulation of the torus') - -def RealProjectivePlane(): - """ - A minimal triangulation of the real projective plane. - - EXAMPLES:: - - sage: P = simplicial_complexes.RealProjectivePlane() - sage: Q = simplicial_complexes.ProjectivePlane() - sage: P == Q - True - sage: P.cohomology(1) - 0 - sage: P.cohomology(2) - C2 - sage: P.cohomology(1, base_ring=GF(2)) - Vector space of dimension 1 over Finite Field of size 2 - sage: P.cohomology(2, base_ring=GF(2)) - Vector space of dimension 1 over Finite Field of size 2 - """ - return UniqueSimplicialComplex([[0, 1, 2], [0, 2, 3], [0, 1, 5], [0, 4, 5], - [0, 3, 4], [1, 2, 4], [1, 3, 4], [1, 3, 5], - [2, 3, 5], [2, 4, 5]], - name='Minimal triangulation of the real projective plane') - -ProjectivePlane = RealProjectivePlane - -def KleinBottle(): - """ - A minimal triangulation of the Klein bottle, as presented for example - in Davide Cervone's thesis [Cer1994]_. - - EXAMPLES:: - - sage: simplicial_complexes.KleinBottle() - Minimal triangulation of the Klein bottle - """ - return UniqueSimplicialComplex([[2, 3, 7], [1, 2, 3], [1, 3, 5], [1, 5, 7], - [1, 4, 7], [2, 4, 6], [1, 2, 6], [1, 6, 0], - [1, 4, 0], [2, 4, 0], [3, 4, 7], [3, 4, 6], - [3, 5, 6], [5, 6, 0], [2, 5, 0], [2, 5, 7]], - name='Minimal triangulation of the Klein bottle') - -def SurfaceOfGenus(g, orientable=True): - """ - A surface of genus `g`. - - INPUT: - - - ``g`` -- a non-negative integer. The desired genus - - - ``orientable`` -- boolean (optional, default ``True``). If - ``True``, return an orientable surface, and if ``False``, - return a non-orientable surface. - - In the orientable case, return a sphere if `g` is zero, and - otherwise return a `g`-fold connected sum of a torus with itself. - - In the non-orientable case, raise an error if `g` is zero. If - `g` is positive, return a `g`-fold connected sum of a - real projective plane with itself. - - EXAMPLES:: - - sage: simplicial_complexes.SurfaceOfGenus(2) - Triangulation of an orientable surface of genus 2 - sage: simplicial_complexes.SurfaceOfGenus(1, orientable=False) - Triangulation of a non-orientable surface of genus 1 - """ - if g == 0: - if not orientable: - raise ValueError("No non-orientable surface of genus zero.") - else: - return Sphere(2) - if orientable: - T = Torus() - else: - T = RealProjectivePlane() - S = T - for i in range(g-1): - S = S.connected_sum(T) - if orientable: - orient_str = 'n orientable' - else: - orient_str = ' non-orientable' - return UniqueSimplicialComplex(S, - name='Triangulation of a{} surface of genus {}'.format(orient_str, g)) - -def MooreSpace(q): - """ - Triangulation of the mod `q` Moore space. - - INPUT: - - - ``q`` -0 integer, at least 2 - - This is a simplicial complex with simplices of dimension 0, 1, - and 2, such that its reduced homology is isomorphic to - `\\ZZ/q\\ZZ` in dimension 1, zero otherwise. - - If `q=2`, this is the real projective plane. If `q>2`, then - construct it as follows: start with a triangle with vertices - 1, 2, 3. We take a `3q`-gon forming a `q`-fold cover of the - triangle, and we form the resulting complex as an - identification space of the `3q`-gon. To triangulate this - identification space, put `q` vertices `A_0`, ..., `A_{q-1}`, - in the interior, each of which is connected to 1, 2, 3 (two - facets each: `[1, 2, A_i]`, `[2, 3, A_i]`). Put `q` more - vertices in the interior: `B_0`, ..., `B_{q-1}`, with facets - `[3, 1, B_i]`, `[3, B_i, A_i]`, `[1, B_i, A_{i+1}]`, `[B_i, - A_i, A_{i+1}]`. Then triangulate the interior polygon with - vertices `A_0`, `A_1`, ..., `A_{q-1}`. - - EXAMPLES:: - - sage: simplicial_complexes.MooreSpace(2) - Minimal triangulation of the real projective plane - sage: simplicial_complexes.MooreSpace(3).homology()[1] - C3 - sage: simplicial_complexes.MooreSpace(4).suspension().homology()[2] - C4 - sage: simplicial_complexes.MooreSpace(8) - Triangulation of the mod 8 Moore space - """ - if q <= 1: - raise ValueError("The mod q Moore space is only defined if q is at least 2") - if q == 2: - return RealProjectivePlane() - facets = [] - for i in range(q): - Ai = "A" + str(i) - Aiplus = "A" + str((i+1) % q) - Bi = "B" + str(i) - facets.append([1, 2, Ai]) - facets.append([2, 3, Ai]) - facets.append([3, 1, Bi]) - facets.append([3, Bi, Ai]) - facets.append([1, Bi, Aiplus]) - facets.append([Bi, Ai, Aiplus]) - for i in range(1, q-1): - Ai = "A" + str(i) - Aiplus = "A" + str((i+1) % q) - facets.append(["A0", Ai, Aiplus]) - return UniqueSimplicialComplex(facets, - name='Triangulation of the mod {} Moore space'.format(q)) - -def ComplexProjectivePlane(): - """ - A minimal triangulation of the complex projective plane. - - This was constructed by Kühnel and Banchoff [KB1983]_. - - EXAMPLES:: - - sage: C = simplicial_complexes.ComplexProjectivePlane() - sage: C.f_vector() - [1, 9, 36, 84, 90, 36] - sage: C.homology(2) - Z - sage: C.homology(4) - Z - """ - return UniqueSimplicialComplex( - [[1, 2, 4, 5, 6], [2, 3, 5, 6, 4], [3, 1, 6, 4, 5], - [1, 2, 4, 5, 9], [2, 3, 5, 6, 7], [3, 1, 6, 4, 8], - [2, 3, 6, 4, 9], [3, 1, 4, 5, 7], [1, 2, 5, 6, 8], - [3, 1, 5, 6, 9], [1, 2, 6, 4, 7], [2, 3, 4, 5, 8], - [4, 5, 7, 8, 9], [5, 6, 8, 9, 7], [6, 4, 9, 7, 8], - [4, 5, 7, 8, 3], [5, 6, 8, 9, 1], [6, 4, 9, 7, 2], - [5, 6, 9, 7, 3], [6, 4, 7, 8, 1], [4, 5, 8, 9, 2], - [6, 4, 8, 9, 3], [4, 5, 9, 7, 1], [5, 6, 7, 8, 2], - [7, 8, 1, 2, 3], [8, 9, 2, 3, 1], [9, 7, 3, 1, 2], - [7, 8, 1, 2, 6], [8, 9, 2, 3, 4], [9, 7, 3, 1, 5], - [8, 9, 3, 1, 6], [9, 7, 1, 2, 4], [7, 8, 2, 3, 5], - [9, 7, 2, 3, 6], [7, 8, 3, 1, 4], [8, 9, 1, 2, 5]], - name='Minimal triangulation of the complex projective plane') - - -def PseudoQuaternionicProjectivePlane(): - r""" - Return a pure simplicial complex of dimension 8 with 490 facets. - - .. WARNING:: - - This is expected to be a triangulation of the projective plane - `HP^2` over the ring of quaternions, but this has not been - proved yet. - - This simplicial complex has the same homology as `HP^2`. Its - automorphism group is isomorphic to the alternating group `A_5` - and acts transitively on vertices. - - This is defined here using the description in [BK1992]_. This - article deals with three different triangulations. This procedure - returns the only one which has a transitive group of - automorphisms. - - EXAMPLES:: - - sage: HP2 = simplicial_complexes.PseudoQuaternionicProjectivePlane() ; HP2 - Simplicial complex with 15 vertices and 490 facets - sage: HP2.f_vector() - [1, 15, 105, 455, 1365, 3003, 4515, 4230, 2205, 490] - - Checking its automorphism group:: - - sage: HP2.automorphism_group().is_isomorphic(AlternatingGroup(5)) - True - """ - from sage.groups.perm_gps.permgroup import PermutationGroup - P = [(1, 2, 3, 4, 5), (6, 7, 8, 9, 10), (11, 12, 13, 14, 15)] - S = [(1, 6, 11), (2, 15, 14), (3, 13, 8), (4, 7, 5), (9, 12, 10)] - start_list = [ - (1, 2, 3, 6, 8, 11, 13, 14, 15), # A - (1, 3, 6, 8, 9, 10, 11, 12, 13), # B - (1, 2, 6, 9, 10, 11, 12, 14, 15), # C - (1, 2, 3, 4, 7, 9, 12, 14, 15), # D - (1, 2, 4, 7, 9, 10, 12, 13, 14), # E - (1, 2, 6, 8, 9, 10, 11, 14, 15), # F - (1, 2, 3, 4, 5, 6, 9, 11, 13), # G - (1, 3, 5, 6, 8, 9, 10, 11, 12), # H - (1, 3, 5, 6, 7, 8, 9, 10, 11), # I - (1, 2, 3, 4, 5, 7, 10, 12, 15), # J - (1, 2, 3, 7, 8, 10, 12, 13, 14), # K - (2, 5, 6, 7, 8, 9, 10, 13, 14), # M - - (3, 4, 6, 7, 11, 12, 13, 14, 15), # L - (3, 4, 6, 7, 10, 12, 13, 14, 15)] # N - return UniqueSimplicialComplex([[g(index) for index in tuple] - for tuple in start_list - for g in PermutationGroup([P, S])]) - -def PoincareHomologyThreeSphere(): - """ - A triangulation of the Poincaré homology 3-sphere. - - This is a manifold whose integral homology is identical to the - ordinary 3-sphere, but it is not simply connected. In particular, - its fundamental group is the binary icosahedral group, which has - order 120. The triangulation given here has 16 vertices and is - due to Björner and Lutz [BL2000]_. - - EXAMPLES:: - - sage: S3 = simplicial_complexes.Sphere(3) - sage: Sigma3 = simplicial_complexes.PoincareHomologyThreeSphere() - sage: S3.homology() == Sigma3.homology() - True - sage: Sigma3.fundamental_group().cardinality() # long time - 120 - """ - return UniqueSimplicialComplex( - [[1, 2, 4, 9], [1, 2, 4, 15], [1, 2, 6, 14], [1, 2, 6, 15], - [1, 2, 9, 14], [1, 3, 4, 12], [1, 3, 4, 15], [1, 3, 7, 10], - [1, 3, 7, 12], [1, 3, 10, 15], [1, 4, 9, 12], [1, 5, 6, 13], - [1, 5, 6, 14], [1, 5, 8, 11], [1, 5, 8, 13], [1, 5, 11, 14], - [1, 6, 13, 15], [1, 7, 8, 10], [1, 7, 8, 11], [1, 7, 11, 12], - [1, 8, 10, 13], [1, 9, 11, 12], [1, 9, 11, 14], [1, 10, 13, 15], - [2, 3, 5, 10], [2, 3, 5, 11], [2, 3, 7, 10], [2, 3, 7, 13], - [2, 3, 11, 13], [2, 4, 9, 13], [2, 4, 11, 13], [2, 4, 11, 15], - [2, 5, 8, 11], [2, 5, 8, 12], [2, 5, 10, 12], [2, 6, 10, 12], - [2, 6, 10, 14], [2, 6, 12, 15], [2, 7, 9, 13], [2, 7, 9, 14], - [2, 7, 10, 14], [2, 8, 11, 15], [2, 8, 12, 15], [3, 4, 5, 14], - [3, 4, 5, 15], [3, 4, 12, 14], [3, 5, 10, 15], [3, 5, 11, 14], - [3, 7, 12, 13], [3, 11, 13, 14], [3, 12, 13, 14], [4, 5, 6, 7], - [4, 5, 6, 14], [4, 5, 7, 15], [4, 6, 7, 11], [4, 6, 10, 11], - [4, 6, 10, 14], [4, 7, 11, 15], [4, 8, 9, 12], [4, 8, 9, 13], - [4, 8, 10, 13], [4, 8, 10, 14], [4, 8, 12, 14], [4, 10, 11, 13], - [5, 6, 7, 13], [5, 7, 9, 13], [5, 7, 9, 15], [5, 8, 9, 12], - [5, 8, 9, 13], [5, 9, 10, 12], [5, 9, 10, 15], [6, 7, 11, 12], - [6, 7, 12, 13], [6, 10, 11, 12], [6, 12, 13, 15], [7, 8, 10, 14], - [7, 8, 11, 15], [7, 8, 14, 15], [7, 9, 14, 15], [8, 12, 14, 15], - [9, 10, 11, 12], [9, 10, 11, 16], [9, 10, 15, 16], [9, 11, 14, 16], - [9, 14, 15, 16], [10, 11, 13, 16], [10, 13, 15, 16], - [11, 13, 14, 16], [12, 13, 14, 15], [13, 14, 15, 16]], - name='Triangulation of the Poincare homology 3-sphere') - -def RealProjectiveSpace(n): - r""" - A triangulation of `\Bold{R}P^n` for any `n \geq 0`. - - INPUT: - - - ``n`` -- integer, the dimension of the real projective space - to construct - - The first few cases are pretty trivial: - - - `\Bold{R}P^0` is a point. - - - `\Bold{R}P^1` is a circle, triangulated as the boundary of a - single 2-simplex. - - - `\Bold{R}P^2` is the real projective plane, here given its - minimal triangulation with 6 vertices, 15 edges, and 10 - triangles. - - - `\Bold{R}P^3`: any triangulation has at least 11 vertices by - a result of Walkup [Wal1970]_; this function returns a - triangulation with 11 vertices, as given by Lutz [Lut2005]_. - - - `\Bold{R}P^4`: any triangulation has at least 16 vertices by - a result of Walkup; this function returns a triangulation - with 16 vertices as given by Lutz; see also Datta [Dat2007]_, - Example 3.12. - - - `\Bold{R}P^n`: Lutz has found a triangulation of - `\Bold{R}P^5` with 24 vertices, but it does not seem to have - been published. Kühnel [Kuh1987]_ has described a triangulation of - `\Bold{R}P^n`, in general, with `2^{n+1}-1` vertices; see - also Datta, Example 3.21. This triangulation is presumably - not minimal, but it seems to be the best in the published - literature as of this writing. So this function returns it - when `n > 4`. - - ALGORITHM: For `n < 4`, these are constructed explicitly by - listing the facets. For `n = 4`, this is constructed by - specifying 16 vertices, two facets, and a certain subgroup `G` - of the symmetric group `S_{16}`. Then the set of all facets - is the `G`-orbit of the two given facets. This is implemented - here by explicitly listing all of the facets; the facets - can be computed by the function :func:`~sage.homology.simplicial_complex.facets_for_RP4`, but - running the function takes a few seconds. - - For `n > 4`, the construction is as follows: let `S` denote - the simplicial complex structure on the `n`-sphere given by - the first barycentric subdivision of the boundary of an - `(n+1)`-simplex. This has a simplicial antipodal action: if - `V` denotes the vertices in the boundary of the simplex, then - the vertices in its barycentric subdivision `S` correspond to - nonempty proper subsets `U` of `V`, and the antipodal action - sends any subset `U` to its complement. One can show that - modding out by this action results in a triangulation for - `\Bold{R}P^n`. To find the facets in this triangulation, find - the facets in `S`. These are identified in pairs to form - `\Bold{R}P^n`, so choose a representative from each pair: for - each facet in `S`, replace any vertex in `S` containing 0 with - its complement. - - Of course these complexes increase in size pretty quickly as - `n` increases. - - EXAMPLES:: - - sage: P3 = simplicial_complexes.RealProjectiveSpace(3) - sage: P3.f_vector() - [1, 11, 51, 80, 40] - sage: P3.homology() - {0: 0, 1: C2, 2: 0, 3: Z} - sage: P4 = simplicial_complexes.RealProjectiveSpace(4) - sage: P4.f_vector() - [1, 16, 120, 330, 375, 150] - sage: P4.homology() # long time - {0: 0, 1: C2, 2: 0, 3: C2, 4: 0} - sage: P5 = simplicial_complexes.RealProjectiveSpace(5) # long time (44s on sage.math, 2012) - sage: P5.f_vector() # long time - [1, 63, 903, 4200, 8400, 7560, 2520] - - The following computation can take a long time -- over half an - hour -- with Sage's default computation of homology groups, - but if you have CHomP installed, Sage will use that and the - computation should only take a second or two. (You can - download CHomP from http://chomp.rutgers.edu/, or you can - install it as a Sage package using ``sage -i chomp``). :: - - sage: P5.homology() # long time # optional - CHomP - {0: 0, 1: C2, 2: 0, 3: C2, 4: 0, 5: Z} - sage: simplicial_complexes.RealProjectiveSpace(2).dimension() - 2 - sage: P3.dimension() - 3 - sage: P4.dimension() # long time - 4 - sage: P5.dimension() # long time - 5 - """ - if n == 0: - return Simplex(0) - if n == 1: - return Sphere(1) - if n == 2: - return RealProjectivePlane() - if n == 3: - # Minimal triangulation found by Walkup and given - # explicitly by Lutz - return UniqueSimplicialComplex( - [[1, 2, 3, 7], [1, 4, 7, 9], [2, 3, 4, 8], [2, 5, 8, 10], - [3, 6, 7, 10], [1, 2, 3, 11], [1, 4, 7, 10], [2, 3, 4, 11], - [2, 5, 9, 10], [3, 6, 8, 9], [1, 2, 6, 9], [1, 4, 8, 9], - [2, 3, 7, 8], [2, 6, 9, 10], [3, 6, 9, 10], [1, 2, 6, 11], - [1, 4, 8, 10], [2, 4, 6, 10], [3, 4, 5, 9], [4, 5, 6, 7], - [1, 2, 7, 9], [1, 5, 6, 8], [2, 4, 6, 11], [3, 4, 5, 11], - [4, 5, 6, 11], [1, 3, 5, 10], [1, 5, 6, 11], [2, 4, 8, 10], - [3, 4, 8, 9], [4, 5, 7, 9], [1, 3, 5, 11], [1, 5, 8, 10], - [2, 5, 7, 8], [3, 5, 9, 10], [4, 6, 7, 10], [1, 3, 7, 10], - [1, 6, 8, 9], [2, 5, 7, 9], [3, 6, 7, 8], [5, 6, 7, 8]], - name='Minimal triangulation of RP^3') - if n == 4: - return UniqueSimplicialComplex( - [(1, 3, 8, 12, 13), (2, 7, 8, 13, 16), (4, 8, 9, 12, 14), - (2, 6, 10, 12, 16), (5, 7, 9, 10, 13), (1, 2, 7, 8, 15), - (1, 3, 9, 11, 16), (5, 6, 8, 13, 16), (1, 3, 8, 11, 13), - (3, 4, 10, 13, 15), (4, 6, 9, 12, 15), (2, 4, 6, 11, 13), - (2, 3, 9, 12, 16), (1, 6, 9, 12, 15), (2, 5, 10, 11, 12), - (1, 7, 8, 12, 15), (2, 6, 9, 13, 16), (1, 5, 9, 11, 15), - (4, 9, 10, 13, 14), (2, 7, 8, 15, 16), (2, 3, 9, 12, 14), - (1, 6, 7, 10, 14), (2, 5, 10, 11, 15), (1, 2, 4, 13, 14), - (1, 6, 10, 14, 16), (2, 6, 9, 12, 16), (1, 3, 9, 12, 16), - (4, 5, 7, 11, 16), (5, 9, 10, 11, 15), (3, 5, 8, 12, 14), - (5, 6, 9, 13, 16), (5, 6, 9, 13, 15), (1, 3, 4, 10, 16), - (1, 6, 10, 12, 16), (2, 4, 6, 9, 13), (2, 4, 6, 9, 12), - (1, 2, 4, 11, 13), (7, 9, 10, 13, 14), (1, 7, 8, 12, 13), - (4, 6, 7, 11, 12), (3, 4, 6, 11, 13), (1, 5, 6, 9, 15), - (1, 6, 7, 14, 15), (2, 3, 7, 14, 15), (2, 6, 10, 11, 12), - (5, 7, 9, 10, 11), (1, 2, 4, 5, 14), (3, 5, 10, 13, 15), - (3, 8, 9, 12, 14), (5, 9, 10, 13, 15), (2, 6, 8, 13, 16), - (1, 2, 7, 13, 14), (1, 7, 10, 12, 13), (3, 4, 6, 13, 15), - (4, 9, 10, 13, 15), (2, 3, 10, 12, 16), (1, 2, 5, 14, 15), - (2, 6, 8, 10, 11), (1, 3, 10, 12, 13), (4, 8, 9, 12, 15), - (1, 3, 8, 9, 11), (4, 6, 7, 12, 15), (1, 8, 9, 11, 15), - (4, 5, 8, 14, 16), (1, 2, 8, 11, 13), (3, 6, 8, 11, 13), - (3, 6, 8, 11, 14), (3, 5, 8, 12, 13), (3, 7, 9, 11, 14), - (4, 6, 9, 13, 15), (2, 3, 5, 10, 12), (4, 7, 8, 15, 16), - (1, 2, 7, 14, 15), (3, 7, 9, 11, 16), (3, 6, 7, 14, 15), - (2, 6, 8, 11, 13), (4, 8, 9, 10, 14), (1, 4, 10, 13, 14), - (4, 8, 9, 10, 15), (2, 7, 9, 13, 16), (1, 6, 9, 12, 16), - (2, 3, 7, 9, 14), (4, 8, 10, 15, 16), (1, 5, 9, 11, 16), - (1, 5, 6, 14, 15), (5, 7, 9, 11, 16), (4, 5, 7, 11, 12), - (5, 7, 10, 11, 12), (2, 3, 10, 15, 16), (1, 2, 7, 8, 13), - (1, 6, 7, 10, 12), (1, 3, 10, 12, 16), (7, 9, 10, 11, 14), - (1, 7, 10, 13, 14), (1, 2, 4, 5, 11), (3, 4, 6, 7, 11), - (1, 6, 7, 12, 15), (1, 3, 4, 10, 13), (1, 4, 10, 14, 16), - (2, 4, 6, 11, 12), (5, 6, 8, 14, 16), (3, 5, 6, 8, 13), - (3, 5, 6, 8, 14), (1, 2, 8, 11, 15), (1, 4, 5, 14, 16), - (2, 3, 7, 15, 16), (8, 9, 10, 11, 14), (1, 3, 4, 11, 16), - (6, 8, 10, 14, 16), (8, 9, 10, 11, 15), (1, 3, 4, 11, 13), - (2, 4, 5, 12, 14), (2, 4, 9, 13, 14), (3, 4, 7, 11, 16), - (3, 6, 7, 11, 14), (3, 8, 9, 11, 14), (2, 8, 10, 11, 15), - (1, 3, 8, 9, 12), (4, 5, 7, 8, 16), (4, 5, 8, 12, 14), - (2, 4, 9, 12, 14), (6, 8, 10, 11, 14), (3, 5, 6, 13, 15), - (1, 4, 5, 11, 16), (3, 5, 6, 14, 15), (2, 4, 5, 11, 12), - (4, 5, 7, 8, 12), (1, 8, 9, 12, 15), (5, 7, 8, 13, 16), - (2, 3, 5, 12, 14), (3, 5, 10, 12, 13), (6, 7, 10, 11, 12), - (5, 7, 9, 13, 16), (6, 7, 10, 11, 14), (5, 7, 10, 12, 13), - (1, 2, 5, 11, 15), (1, 5, 6, 9, 16), (5, 7, 8, 12, 13), - (4, 7, 8, 12, 15), (2, 3, 5, 10, 15), (2, 6, 8, 10, 16), - (3, 4, 10, 15, 16), (1, 5, 6, 14, 16), (2, 3, 5, 14, 15), - (2, 3, 7, 9, 16), (2, 7, 9, 13, 14), (3, 4, 6, 7, 15), - (4, 8, 10, 14, 16), (3, 4, 7, 15, 16), (2, 8, 10, 15, 16)], - name='Minimal triangulation of RP^4') - if n >= 5: - # Use the construction given by Datta in Example 3.21. - V = set(range(0, n+2)) - S = Sphere(n).barycentric_subdivision() - X = S.facets() - facets = set([]) - for f in X: - new = [] - for v in f: - if 0 in v: - new.append(tuple(V.difference(v))) - else: - new.append(v) - facets.add(tuple(new)) - return UniqueSimplicialComplex(list(facets), - name='Triangulation of RP^{}'.format(n)) - - -def K3Surface(): - """ - Return a minimal triangulation of the K3 surface. - - This is a pure simplicial complex of dimension 4 with 16 vertices - and 288 facets. It was constructed by Casella and Kühnel - in [CK2001]_. The construction here uses the labeling from - Spreer and Kühnel [SK2011]_. - - EXAMPLES:: - - sage: K3=simplicial_complexes.K3Surface() ; K3 - Minimal triangulation of the K3 surface - sage: K3.f_vector() - [1, 16, 120, 560, 720, 288] - - This simplicial complex is implemented just by listing all 288 - facets. The list of facets can be computed by the function - :func:`~sage.homology.simplicial_complex.facets_for_K3`, but running the function takes a few - seconds. - """ - return UniqueSimplicialComplex( - [(2, 10, 13, 15, 16), (2, 8, 11, 15, 16), (2, 5, 7, 8, 10), - (1, 9, 11, 13, 14), (1, 2, 8, 10, 12), (1, 3, 5, 6, 11), - (1, 5, 6, 9, 12), (1, 2, 6, 13, 16), (1, 4, 10, 13, 14), - (1, 9, 10, 14, 15), (2, 4, 7, 8, 12), (3, 4, 6, 10, 12), - (1, 6, 7, 8, 9), (3, 4, 5, 7, 15), (1, 7, 12, 15, 16), - (4, 5, 7, 13, 16), (5, 8, 11, 12, 15), (2, 4, 7, 12, 14), - (1, 4, 5, 14, 16), (2, 5, 6, 10, 11), (1, 6, 8, 12, 14), - (5, 8, 9, 14, 16), (5, 10, 11, 12, 13), (2, 4, 8, 9, 12), - (7, 9, 12, 15, 16), (1, 2, 6, 9, 15), (1, 5, 14, 15, 16), - (2, 3, 4, 5, 9), (6, 8, 10, 11, 15), (1, 5, 8, 10, 12), - (1, 3, 7, 9, 10), (6, 7, 8, 9, 13), (1, 2, 9, 11, 15), - (2, 8, 11, 14, 16), (2, 4, 5, 13, 16), (1, 4, 8, 13, 15), - (4, 7, 8, 10, 11), (2, 3, 9, 11, 14), (2, 3, 4, 9, 13), - (2, 8, 10, 12, 13), (1, 2, 4, 11, 15), (2, 3, 9, 11, 15), - (3, 5, 10, 13, 15), (3, 4, 5, 9, 11), (6, 10, 13, 15, 16), - (8, 10, 11, 15, 16), (6, 7, 11, 13, 15), (1, 5, 7, 15, 16), - (4, 5, 7, 9, 15), (3, 4, 6, 7, 16), (2, 3, 11, 14, 16), - (3, 4, 9, 11, 13), (1, 2, 5, 14, 15), (2, 3, 9, 13, 14), - (1, 2, 5, 13, 16), (2, 3, 7, 8, 12), (2, 9, 11, 12, 14), - (1, 9, 11, 15, 16), (4, 6, 9, 14, 16), (1, 4, 9, 13, 14), - (1, 2, 3, 12, 16), (8, 11, 12, 14, 15), (2, 4, 11, 12, 14), - (1, 4, 10, 12, 13), (1, 2, 6, 7, 13), (1, 3, 6, 10, 11), - (1, 6, 8, 9, 12), (1, 4, 5, 6, 14), (3, 9, 10, 12, 15), - (5, 8, 11, 12, 16), (5, 9, 10, 14, 15), (3, 9, 12, 15, 16), - (3, 6, 8, 14, 15), (2, 4, 9, 10, 16), (5, 8, 9, 13, 15), - (2, 3, 6, 9, 15), (6, 11, 12, 14, 16), (2, 3, 10, 13, 15), - (2, 8, 9, 10, 13), (3, 4, 8, 11, 13), (3, 4, 5, 7, 13), - (5, 7, 8, 10, 14), (4, 12, 13, 14, 15), (6, 7, 10, 14, 16), - (5, 10, 11, 13, 14), (3, 4, 7, 13, 16), (6, 8, 9, 12, 13), - (1, 3, 4, 10, 14), (2, 4, 6, 11, 12), (1, 7, 9, 10, 14), - (4, 6, 8, 13, 14), (4, 9, 10, 11, 16), (3, 7, 8, 10, 16), - (5, 7, 9, 15, 16), (1, 7, 9, 11, 14), (6, 8, 10, 15, 16), - (5, 8, 9, 10, 14), (7, 8, 10, 14, 16), (2, 6, 7, 9, 11), - (7, 9, 10, 13, 15), (3, 6, 7, 10, 12), (2, 4, 6, 10, 11), - (4, 5, 8, 9, 11), (1, 2, 3, 8, 16), (3, 7, 9, 10, 12), - (1, 2, 6, 8, 14), (3, 5, 6, 13, 15), (1, 5, 6, 12, 14), - (2, 5, 7, 14, 15), (1, 5, 10, 11, 12), (3, 7, 8, 10, 11), - (1, 2, 6, 14, 15), (1, 2, 6, 8, 16), (7, 9, 10, 12, 15), - (3, 4, 6, 8, 14), (3, 7, 13, 14, 16), (2, 5, 7, 8, 14), - (6, 7, 9, 10, 14), (2, 3, 7, 12, 14), (4, 10, 12, 13, 14), - (2, 5, 6, 11, 13), (4, 5, 6, 7, 16), (1, 3, 12, 13, 16), - (1, 4, 11, 15, 16), (1, 3, 4, 6, 10), (1, 10, 11, 12, 13), - (6, 9, 11, 12, 14), (1, 4, 7, 8, 15), (5, 8, 9, 10, 13), - (1, 2, 5, 7, 15), (1, 7, 12, 13, 16), (3, 11, 13, 14, 16), - (1, 2, 5, 7, 13), (4, 7, 8, 9, 15), (1, 5, 6, 10, 11), - (6, 7, 10, 13, 15), (3, 4, 7, 14, 15), (7, 11, 13, 14, 16), - (3, 4, 10, 12, 14), (3, 6, 8, 10, 16), (2, 7, 8, 14, 16), - (2, 3, 4, 5, 13), (5, 8, 12, 13, 15), (4, 6, 9, 13, 14), - (2, 4, 5, 6, 12), (1, 3, 7, 8, 9), (8, 11, 12, 14, 16), - (1, 7, 12, 13, 15), (8, 12, 13, 14, 15), (2, 8, 9, 12, 13), - (4, 6, 10, 12, 15), (2, 8, 11, 14, 15), (2, 6, 9, 11, 12), - (8, 9, 10, 11, 16), (2, 3, 6, 13, 15), (2, 3, 12, 15, 16), - (1, 3, 5, 9, 12), (2, 5, 6, 9, 12), (2, 10, 12, 13, 14), - (2, 6, 13, 15, 16), (2, 3, 11, 15, 16), (3, 5, 6, 8, 15), - (2, 4, 5, 9, 12), (5, 6, 8, 11, 15), (6, 8, 12, 13, 14), - (1, 2, 3, 8, 12), (1, 4, 7, 8, 11), (3, 5, 7, 14, 15), - (3, 5, 7, 13, 14), (1, 7, 10, 11, 14), (6, 7, 11, 12, 15), - (3, 4, 6, 7, 12), (1, 2, 4, 7, 11), (6, 9, 10, 14, 16), - (4, 10, 12, 15, 16), (5, 6, 7, 12, 16), (3, 9, 11, 13, 14), - (5, 9, 14, 15, 16), (4, 5, 6, 7, 12), (1, 3, 9, 10, 15), - (4, 7, 8, 9, 12), (5, 9, 10, 13, 15), (1, 3, 8, 13, 16), - (2, 9, 12, 13, 14), (6, 7, 10, 12, 15), (2, 6, 8, 14, 15), - (3, 5, 6, 8, 11), (3, 4, 7, 12, 14), (1, 3, 10, 14, 15), - (7, 11, 12, 13, 16), (3, 11, 12, 13, 16), (3, 4, 5, 8, 15), - (2, 4, 7, 8, 10), (2, 4, 7, 14, 15), (1, 2, 10, 12, 16), - (1, 6, 8, 13, 16), (1, 7, 8, 13, 15), (3, 9, 11, 15, 16), - (4, 6, 10, 11, 15), (2, 4, 11, 14, 15), (1, 3, 8, 9, 12), - (1, 3, 6, 14, 15), (2, 4, 5, 6, 10), (1, 4, 9, 14, 16), - (5, 7, 9, 12, 16), (1, 3, 7, 10, 11), (7, 8, 9, 13, 15), - (3, 5, 10, 14, 15), (1, 4, 10, 12, 16), (3, 4, 5, 8, 11), - (1, 2, 6, 7, 9), (1, 3, 11, 12, 13), (1, 5, 7, 13, 16), - (5, 7, 10, 11, 14), (2, 10, 12, 15, 16), (3, 6, 7, 10, 16), - (1, 2, 5, 8, 10), (4, 10, 11, 15, 16), (5, 8, 10, 12, 13), - (3, 6, 8, 10, 11), (4, 5, 7, 9, 12), (6, 7, 11, 12, 16), - (3, 5, 9, 11, 16), (8, 9, 10, 14, 16), (3, 4, 6, 8, 16), - (1, 10, 11, 13, 14), (2, 9, 10, 13, 16), (1, 2, 5, 8, 14), - (2, 4, 5, 10, 16), (1, 2, 7, 9, 11), (1, 3, 5, 6, 9), - (5, 7, 11, 13, 14), (3, 5, 10, 13, 14), (2, 4, 8, 9, 10), - (4, 11, 12, 14, 15), (2, 3, 7, 14, 16), (3, 4, 8, 13, 16), - (6, 7, 9, 11, 14), (5, 6, 11, 13, 15), (4, 5, 6, 14, 16), - (3, 4, 8, 14, 15), (4, 5, 8, 9, 15), (1, 4, 8, 11, 13), - (5, 6, 12, 14, 16), (2, 3, 10, 12, 14), (1, 2, 5, 10, 16), - (2, 5, 7, 10, 11), (2, 6, 7, 11, 13), (1, 4, 5, 10, 16), - (2, 6, 8, 15, 16), (2, 3, 10, 12, 15), (7, 11, 12, 13, 15), - (1, 3, 8, 11, 13), (4, 8, 9, 10, 11), (1, 9, 14, 15, 16), - (1, 3, 6, 9, 15), (6, 9, 12, 13, 14), (2, 3, 10, 13, 14), - (2, 5, 7, 11, 13), (2, 3, 5, 6, 13), (4, 6, 8, 13, 16), - (6, 7, 9, 10, 13), (5, 8, 12, 14, 16), (4, 6, 9, 13, 16), - (5, 8, 9, 11, 16), (2, 3, 5, 6, 9), (1, 3, 5, 11, 12), - (3, 7, 8, 9, 12), (4, 6, 11, 12, 15), (3, 5, 9, 12, 16), - (5, 11, 12, 13, 15), (1, 3, 4, 6, 14), (3, 5, 11, 12, 16), - (1, 5, 8, 12, 14), (4, 8, 13, 14, 15), (1, 3, 7, 8, 11), - (6, 9, 10, 13, 16), (2, 4, 9, 13, 16), (1, 6, 7, 8, 13), - (1, 4, 12, 13, 15), (2, 4, 7, 10, 11), (1, 4, 9, 11, 13), - (6, 7, 11, 14, 16), (1, 4, 9, 11, 16), (1, 4, 12, 15, 16), - (1, 2, 4, 7, 15), (2, 3, 7, 8, 16), (1, 4, 5, 6, 10)], - name='Minimal triangulation of the K3 surface') - - -def BarnetteSphere(): - r""" - Return Barnette's triangulation of the 3-sphere. - - This is a pure simplicial complex of dimension 3 with 8 - vertices and 19 facets, which is a non-polytopal triangulation - of the 3-sphere. It was constructed by Barnette in - [Bar1970]_. The construction here uses the labeling from De - Loera, Rambau and Santos [DLRS2010]_. Another reference is chapter - III.4 of Ewald [Ewa1996]_. - - EXAMPLES:: - - sage: BS = simplicial_complexes.BarnetteSphere() ; BS - Barnette's triangulation of the 3-sphere - sage: BS.f_vector() - [1, 8, 27, 38, 19] - - TESTS: - - Checks that this is indeed the same Barnette Sphere as the one - given on page 87 of [Ewa1996]_.:: - - sage: BS2 = SimplicialComplex([[1, 2, 3, 4], [3, 4, 5, 6], [1, 2, 5, 6], - ....: [1, 2, 4, 7], [1, 3, 4, 7], [3, 4, 6, 7], - ....: [3, 5, 6, 7], [1, 2, 5, 7], [2, 5, 6, 7], - ....: [2, 4, 6, 7], [1, 2, 3, 8], [2, 3, 4, 8], - ....: [3, 4, 5, 8], [4, 5, 6, 8], [1, 2, 6, 8], - ....: [1, 5, 6, 8], [1, 3, 5, 8], [2, 4, 6, 8], - ....: [1, 3, 5, 7]]) - sage: BS.is_isomorphic(BS2) - True - """ - return UniqueSimplicialComplex([ - (1, 2, 4, 5), (2, 3, 5, 6), (1, 3, 4, 6), (1, 2, 3, 7), (4, 5, 6, 7), (1, 2, 4, 7), - (2, 4, 5, 7), (2, 3, 5, 7), (3, 5, 6, 7), (3, 1, 6, 7), (1, 6, 4, 7), (1, 2, 3, 8), - (4, 5, 6, 8), (1, 2, 5, 8), (1, 4, 5, 8), (2, 3, 6, 8), (2, 5, 6, 8), (3, 1, 4, 8), - (3, 6, 4, 8)], - name="Barnette's triangulation of the 3-sphere") - - -def BrucknerGrunbaumSphere(): - r""" - Return Bruckner and Grunbaum's triangulation of the 3-sphere. - - This is a pure simplicial complex of dimension 3 with 8 - vertices and 20 facets, which is a non-polytopal triangulation - of the 3-sphere. It appeared first in [Br1910]_ and was studied in - [GrS1967]_. - - It is defined here as the link of any vertex in the unique minimal - triangulation of the complex projective plane, see chapter 4 of - [Kuh1995]_. - - EXAMPLES:: - - sage: BGS = simplicial_complexes.BrucknerGrunbaumSphere() ; BGS - Bruckner and Grunbaum's triangulation of the 3-sphere - sage: BGS.f_vector() - [1, 8, 28, 40, 20] - """ - # X = ComplexProjectivePlane().link([9]) - # return UniqueSimplicialComplex(X.facets(), - # name="Bruckner and Grunbaum's triangulation of the 3-sphere") - return UniqueSimplicialComplex(ComplexProjectivePlane().link([9]), - name="Bruckner and Grunbaum's triangulation of the 3-sphere") - -############################################################### -# examples from graph theory: - -def NotIConnectedGraphs(n, i): - """ - The simplicial complex of all graphs on `n` vertices which are - not `i`-connected. - - Fix an integer `n>0` and consider the set of graphs on `n` - vertices. View each graph as its set of edges, so it is a - subset of a set of size `n` choose 2. A graph is - `i`-connected if, for any `j 0 and len(B) < len(G_minus_A): - C = G_minus_A.difference(B) - facet = E - for v in B: - for w in C: - bad_edge = (min(v, w), max(v, w)) - facet = facet.difference(Set([bad_edge])) - facets.append(facet) - return UniqueSimplicialComplex(facets, name='Simplicial complex of not {}-connected graphs on {} vertices'.format(i, n)) - -def MatchingComplex(n): - """ - The matching complex of graphs on `n` vertices. - - Fix an integer `n>0` and consider a set `V` of `n` vertices. - A 'partial matching' on `V` is a graph formed by edges so that - each vertex is in at most one edge. If `G` is a partial - matching, then so is any graph obtained by deleting edges from - `G`. Thus the set of all partial matchings on `n` vertices, - viewed as a set of subsets of the `n` choose 2 possible edges, - is closed under taking subsets, and thus forms a simplicial - complex called the 'matching complex'. This function produces - that simplicial complex. - - INPUT: - - - ``n`` -- positive integer. - - See Dumas et al. [DHSW2003]_ for information on computing its homology - by computer, and see Wachs [Wac2003]_ for an expository article about - the theory. For example, the homology of these complexes seems to - have only mod 3 torsion, and this has been proved for the - bottom non-vanishing homology group for the matching complex `M_n`. - - EXAMPLES:: - - sage: M = simplicial_complexes.MatchingComplex(7) - sage: H = M.homology() - sage: H - {0: 0, 1: C3, 2: Z^20} - sage: H[2].ngens() - 20 - sage: simplicial_complexes.MatchingComplex(8).homology(2) # long time (6s on sage.math, 2012) - Z^132 - """ - G_vertices = Set(range(1, n+1)) - facets = [] - if is_even(n): - half = int(n/2) - half_n_sets = list(G_vertices.subsets(size=half)) - else: - half = int((n-1)/2) - half_n_sets = list(G_vertices.subsets(size=half)) - for X in half_n_sets: - Xcomp = G_vertices.difference(X) - if is_even(n): - if 1 in X: - A = X - B = Xcomp - else: - A = Xcomp - B = X - for M in matching(A, B): - facet = [] - for pair in M: - facet.append(tuple(sorted(pair))) - facets.append(facet) - else: - for w in Xcomp: - if 1 in X or (w == 1 and 2 in X): - A = X - B = Xcomp.difference([w]) - else: - B = X - A = Xcomp.difference([w]) - for M in matching(A, B): - facet = [] - for pair in M: - facet.append(tuple(sorted(pair))) - facets.append(facet) - return UniqueSimplicialComplex(facets, name='Matching complex on {} vertices'.format(n)) - -def ChessboardComplex(n, i): - r""" - The chessboard complex for an `n \times i` chessboard. - - Fix integers `n, i > 0` and consider sets `V` of `n` vertices - and `W` of `i` vertices. A 'partial matching' between `V` and - `W` is a graph formed by edges `(v, w)` with `v \in V` and `w - \in W` so that each vertex is in at most one edge. If `G` is - a partial matching, then so is any graph obtained by deleting - edges from `G`. Thus the set of all partial matchings on `V` - and `W`, viewed as a set of subsets of the `n+i` choose 2 - possible edges, is closed under taking subsets, and thus forms - a simplicial complex called the 'chessboard complex'. This - function produces that simplicial complex. (It is called the - chessboard complex because such graphs also correspond to ways - of placing rooks on an `n` by `i` chessboard so that none of - them are attacking each other.) - - INPUT: - - - ``n, i`` -- positive integers. - - See Dumas et al. [DHSW2003]_ for information on computing its homology - by computer, and see Wachs [Wac2003]_ for an expository article about - the theory. - - EXAMPLES:: - - sage: C = simplicial_complexes.ChessboardComplex(5, 5) - sage: C.f_vector() - [1, 25, 200, 600, 600, 120] - sage: simplicial_complexes.ChessboardComplex(3, 3).homology() - {0: 0, 1: Z x Z x Z x Z, 2: 0} - """ - A = range(n) - B = range(i) - E_dict = {} - index = 0 - for v in A: - for w in B: - E_dict[(v, w)] = index - index += 1 - facets = [] - for M in matching(A, B): - facet = [] - for pair in M: - facet.append(E_dict[pair]) - facets.append(facet) - return UniqueSimplicialComplex(facets, name='Chessboard complex for an {}x{} chessboard'.format(n, i)) - -def RandomComplex(n, d, p=0.5): - """ - A random ``d``-dimensional simplicial complex on ``n`` vertices. - - INPUT: - - - ``n`` -- number of vertices - - - ``d`` -- dimension of the complex - - - ``p`` -- floating point number between 0 and 1 - (optional, default 0.5) - - A random `d`-dimensional simplicial complex on `n` vertices, - as defined for example by Meshulam and Wallach [MW2009]_, is - constructed as follows: take `n` vertices and include all of - the simplices of dimension strictly less than `d`, and then for each - possible simplex of dimension `d`, include it with probability `p`. - - EXAMPLES:: - - sage: X = simplicial_complexes.RandomComplex(6, 2); X - Random 2-dimensional simplicial complex on 6 vertices - sage: len(list(X.vertices())) - 6 - - If `d` is too large (if `d+1 > n`, so that there are no - `d`-dimensional simplices), then return the simplicial complex - with a single `(n+1)`-dimensional simplex:: - - sage: simplicial_complexes.RandomComplex(6, 12) - The 5-simplex - """ - if d+1 > n: - return Simplex(n-1) - else: - vertices = range(n) - facets = Subsets(vertices, d).list() - maybe = Subsets(vertices, d+1) - facets.extend([f for f in maybe if random.random() <= p]) - return UniqueSimplicialComplex(facets, - name='Random {}-dimensional simplicial complex on {} vertices'.format(d, n)) - -def SumComplex(n, A): - r""" - The sum complexes of Linial, Meshulam, and Rosenthal [LMR2010]_. - - If `k+1` is the cardinality of `A`, then this returns a - `k`-dimensional simplicial complex `X_A` with vertices - `\ZZ/(n)`, and facets given by all `k+1`-tuples `(x_0, x_1, - ..., x_k)` such that the sum `\sum x_i` is in `A`. See the - paper by Linial, Meshulam, and Rosenthal [LMR2010]_, in which - they prove various results about these complexes; for example, - if `n` is prime, then `X_A` is rationally acyclic, and if in - addition `A` forms an arithmetic progression in `\ZZ/(n)`, - then `X_A` is `\ZZ`-acyclic. Throughout their paper, they - assume that `n` and `k` are relatively prime, but the - construction makes sense in general. - - In addition to the results from the cited paper, these - complexes can have large torsion, given the number of - vertices; for example, if `n=10`, and `A=\{0, 1, 2, 3, 6\}`, then - `H_3(X_A)` is cyclic of order 2728, and there is a - 4-dimensional complex on 13 vertices with `H_3` having a - cyclic summand of order - - .. MATH:: - - 706565607945 = 3 \cdot 5 \cdot 53 \cdot 79 \cdot 131 - \cdot 157 \cdot 547. - - See the examples. - - INPUT: - - - ``n`` -- a positive integer - - - ``A`` -- a subset of `\ZZ/(n)` - - EXAMPLES:: - - sage: S = simplicial_complexes.SumComplex(10, [0, 1, 2, 3, 6]); S - Sum complex on vertices Z/10Z associated to {0, 1, 2, 3, 6} - sage: S.homology() - {0: 0, 1: 0, 2: 0, 3: C2728, 4: 0} - sage: factor(2728) - 2^3 * 11 * 31 - - sage: S = simplicial_complexes.SumComplex(11, [0, 1, 3]); S - Sum complex on vertices Z/11Z associated to {0, 1, 3} - sage: S.homology(1) - C23 - sage: S = simplicial_complexes.SumComplex(11, [0, 1, 2, 3, 4, 7]); S - Sum complex on vertices Z/11Z associated to {0, 1, 2, 3, 4, 7} - sage: S.homology(algorithm='no_chomp') # long time - {0: 0, 1: 0, 2: 0, 3: 0, 4: C645679, 5: 0} - sage: factor(645679) - 23 * 67 * 419 - - sage: S = simplicial_complexes.SumComplex(13, [0, 1, 3]); S - Sum complex on vertices Z/13Z associated to {0, 1, 3} - sage: S.homology(1) - C159 - sage: factor(159) - 3 * 53 - sage: S = simplicial_complexes.SumComplex(13, [0, 1, 2, 5]); S - Sum complex on vertices Z/13Z associated to {0, 1, 2, 5} - sage: S.homology(algorithm='no_chomp') # long time - {0: 0, 1: 0, 2: C146989209, 3: 0} - sage: factor(1648910295) - 3^2 * 5 * 53 * 521 * 1327 - sage: S = simplicial_complexes.SumComplex(13, [0, 1, 2, 3, 5]); S - Sum complex on vertices Z/13Z associated to {0, 1, 2, 3, 5} - sage: S.homology(algorithm='no_chomp') # long time - {0: 0, 1: 0, 2: 0, 3: C3 x C237 x C706565607945, 4: 0} - sage: factor(706565607945) - 3 * 5 * 53 * 79 * 131 * 157 * 547 - - sage: S = simplicial_complexes.SumComplex(17, [0, 1, 4]); S - Sum complex on vertices Z/17Z associated to {0, 1, 4} - sage: S.homology(1, algorithm='no_chomp') - C140183 - sage: factor(140183) - 103 * 1361 - sage: S = simplicial_complexes.SumComplex(19, [0, 1, 4]); S - Sum complex on vertices Z/19Z associated to {0, 1, 4} - sage: S.homology(1, algorithm='no_chomp') - C5670599 - sage: factor(5670599) - 11 * 191 * 2699 - sage: S = simplicial_complexes.SumComplex(31, [0, 1, 4]); S - Sum complex on vertices Z/31Z associated to {0, 1, 4} - sage: S.homology(1, algorithm='no_chomp') # long time - C5 x C5 x C5 x C5 x C26951480558170926865 - sage: factor(26951480558170926865) - 5 * 311 * 683 * 1117 * 11657 * 1948909 - """ - from sage.rings.all import Integers - Zn = Integers(n) - A = frozenset([Zn(x) for x in A]) - facets = [] - for f in Set(Zn).subsets(len(A)): - if sum(f) in A: - facets.append(tuple(f)) - return UniqueSimplicialComplex(facets, name='Sum complex on vertices Z/{}Z associated to {}'.format(n, Set(A))) - - -def RandomTwoSphere(n): - r""" - Return a random triangulation of the 2-dimensional sphere with `n` - vertices. - - INPUT: - - `n` -- an integer - - OUTPUT: - - A random triangulation of the sphere chosen uniformly among - the *rooted* triangulations on `n` vertices. Because some - triangulations have nontrivial automorphism groups, this may - not be equal to the uniform distribution among unrooted - triangulations. - - ALGORITHM: - - The algorithm is taken from [PS2006]_, section 2.1. - - Starting from a planar tree (represented by its contour as a - sequence of vertices), one first performs local closures, until no - one is possible. A local closure amounts to replace in the cyclic - contour word a sequence ``in1, in2, in3, lf, in3`` by - ``in1, in3``. After all local closures are done, one has reached - the partial closure, as in [PS2006]_, figure 5 (a). - - Then one has to perform complete closure by adding two more - vertices, in order to reach the situation of [PS2006]_, figure 5 - (b). For this, it is necessary to find inside the final contour - one of the two subsequences ``lf, in, lf``. - - At every step of the algorithm, newly created triangles are added - in a simplicial complex. - - This algorithm is implemented in - :meth:`~sage.graphs.generators.random.RandomTriangulation`, which - creates an embedded graph. The triangles of the simplicial - complex are recovered from this embedded graph. - - EXAMPLES:: - - sage: G = simplicial_complexes.RandomTwoSphere(6); G - Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and 8 facets - sage: G.homology() - {0: 0, 1: 0, 2: Z} - sage: G.is_pure() - True - sage: fg = G.flip_graph(); fg - Graph on 8 vertices - sage: fg.is_planar() and fg.is_regular(3) - True - """ - from sage.graphs.generators.random import RandomTriangulation - - graph = RandomTriangulation(n) - - graph = graph.relabel(inplace=False) - triangles = [(u, v, w) for u, L in graph._embedding.items() - for v, w in zip(L, L[1:] + [L[0]]) if u < v and u < w] - - return SimplicialComplex(triangles, maximality_check=False) - -def ShiftedComplex(generators): - r""" - Return the smallest shifted simplicial complex containing ``generators`` - as faces. - - Let `V` be a set of vertices equipped with a total order. The - 'componentwise partial ordering' on k-subsets of `V` is defined as - follows: if `A = \{a_1 < \cdots < a_k\}` and `B = \{b_1 < \cdots < b_k\}`, - then `A \leq_C B` iff `a_i \leq b_i` for all `i`. A simplicial complex - `X` on vertex set `[n]` is *shifted* if its faces form an order ideal - under the componentwise partial ordering, i.e., if `B \in X` and - `A \leq_C B` then `A \in X`. Shifted complexes of dimension 1 are also - known as threshold graphs. - - .. NOTE:: - - This method assumes that `V` consists of positive integers - with the natural ordering. - - INPUT: - - - ``generators`` -- a list of generators of the order ideal, which may - be lists, tuples or simplices - - EXAMPLES:: - - sage: X = simplicial_complexes.ShiftedComplex([ Simplex([1, 6]), (2, 4), [8] ]) - sage: sorted(X.facets()) - [(1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (2, 3), (2, 4), (7,), (8,)] - sage: X = simplicial_complexes.ShiftedComplex([ [2, 3, 5] ]) - sage: sorted(X.facets()) - [(1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (2, 3, 4), (2, 3, 5)] - sage: X = simplicial_complexes.ShiftedComplex([ [1, 3, 5], [2, 6] ]) - sage: sorted(X.facets()) - [(1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 6), (2, 6)] - """ - from sage.combinat.partition import Partitions - Facets = [] - for G in generators: - G = list(reversed(sorted(G))) - L = len(G) - for k in range(L * (L+1) // 2, sum(G) + 1): - for P in Partitions(k, length=L, max_slope=-1, outer=G): - Facets.append(list(reversed(P))) - return SimplicialComplex(Facets) - -def RudinBall(): - r""" - Return the non-shellable ball constructed by Rudin. - - This complex is a non-shellable triangulation of the 3-ball - with 14 vertices and 41 facets, constructed by Rudin in - [Rud1958]_. - - EXAMPLES:: - - sage: R = simplicial_complexes.RudinBall(); R - Rudin ball - sage: R.f_vector() - [1, 14, 66, 94, 41] - sage: R.homology() - {0: 0, 1: 0, 2: 0, 3: 0} - sage: R.is_cohen_macaulay() - True - """ - return UniqueSimplicialComplex( - [[1, 9, 2, 5], [1, 10, 2, 5], [1, 10, 5, 11], [1, 10, 7, 11], [1, 13, 5, 11], - [1, 13, 7, 11], [2, 10, 3, 6], [2, 11, 3, 6], [2, 11, 6, 12], [2, 11, 8, 12], - [2, 14, 6, 12], [2, 14, 8, 12], [3, 11, 4, 7], [3, 12, 4, 7], [3, 12, 5, 9], - [3, 12, 7, 9], [3, 13, 5, 9], [3, 13, 7, 9], [4, 9, 1, 8], [4, 9, 6, 10], - [4, 9, 8, 10], [4, 12, 1, 8], [4, 14, 6, 10], [4, 14, 8, 10], [9, 10, 2, 5], - [9, 10, 2, 6], [9, 10, 5, 11], [9, 10, 11, 12], [9, 13, 5, 11], [10, 11, 3, 6], - [10, 11, 3, 7], [10, 11, 6, 12], [10, 14, 6, 12], [11, 12, 4, 7], [11, 12, 4, 8], - [11, 12, 7, 9], [11, 13, 7, 9], [12, 9, 1, 5], [12, 9, 1, 8], [12, 9, 8, 10], - [12, 14, 8, 10]], - name="Rudin ball" - ) - -def ZieglerBall(): - r""" - Return the non-shellable ball constructed by Ziegler. - - This complex is a non-shellable triangulation of the 3-ball - with 10 vertices and 21 facets, constructed by Ziegler in - [Zie1998]_ and the smallest such complex known. - - EXAMPLES:: - - sage: Z = simplicial_complexes.ZieglerBall(); Z - Ziegler ball - sage: Z.f_vector() - [1, 10, 38, 50, 21] - sage: Z.homology() - {0: 0, 1: 0, 2: 0, 3: 0} - sage: Z.is_cohen_macaulay() - True - """ - - return UniqueSimplicialComplex( - [[1, 2, 3, 4], [1, 2, 5, 6], [1, 5, 6, 9], [2, 5, 6, 0], [3, 6, 7, 8], [4, 5, 7, 8], - [2, 3, 6, 7], [1, 6, 2, 9], [2, 6, 7, 0], [3, 2, 4, 8], [4, 1, 3, 7], [3, 4, 7, 8], - [1, 2, 4, 9], [2, 7, 3, 0], [3, 2, 6, 8], [4, 1, 5, 7], [4, 1, 8, 5], [1, 4, 8, 9], - [2, 3, 1, 0], [1, 8, 5, 9], [2, 1, 5, 0]], - name="Ziegler ball" - ) - -def DunceHat(): - r""" - Return the minimal triangulation of the dunce hat given by Hachimori - [Hac2016]_. - - This is a standard example of a space that is contractible - but not collapsible. - - EXAMPLES:: - - sage: D = simplicial_complexes.DunceHat(); D - Minimal triangulation of the dunce hat - sage: D.f_vector() - [1, 8, 24, 17] - sage: D.homology() - {0: 0, 1: 0, 2: 0} - sage: D.is_cohen_macaulay() - True - """ - return UniqueSimplicialComplex( - [[1, 3, 5], [2, 3, 5], [2, 4, 5], [1, 2, 4], [1, 3, 4], [3, 4, 8], - [1, 2, 8], [1, 7, 8], [1, 2, 7], [2, 3, 7], [3, 6, 7], [1, 3, 6], - [1, 5, 6], [4, 5, 6], [4, 6, 8], [6, 7, 8], [2, 3, 8]], - name="Minimal triangulation of the dunce hat" - ) - - -def FareyMap(p): - r""" - Return a discrete surface associated with `PSL(2, \GF(p))`. - - INPUT: - - - `p` -- a prime number - - The vertices are the non-zero pairs `(x,y)` in `\GF(p)^2` modulo - the identification of `(-x, -y)` with `(x,y)`. - - The triangles are the images of the base triangle ((1,0),(0,1),(1,1)) - under the action of `PSL(2, \GF(p))`. - - For `p = 3`, the result is a tetrahedron, for `p = 5` an icosahedron, - and for `p = 7` a triangulation of the Klein quartic of genus `3`. - - As a Riemann surface, this is the quotient of the upper half plane - by the principal congruence subgroup `\Gamma(p)`. - - EXAMPLES:: - - sage: S5 = simplicial_complexes.FareyMap(5); S5 - Simplicial complex with 12 vertices and 20 facets - sage: S5.automorphism_group().cardinality() - 120 - - sage: S7 = simplicial_complexes.FareyMap(7); S7 - Simplicial complex with 24 vertices and 56 facets - sage: S7.f_vector() - [1, 24, 84, 56] - - REFERENCES: - - - [ISS2019] Ioannis Ivrissimtzis, David Singerman and James Strudwick, - *From Farey Fractions to the Klein Quartic and Beyond*. - :arxiv:`1909.08568` - """ - from sage.combinat.permutation import Permutation - from sage.groups.perm_gps.permgroup import PermutationGroup - from sage.matrix.constructor import matrix - from sage.modules.free_module_element import vector - from sage.rings.finite_rings.finite_field_constructor import GF - from sage.libs.gap.libgap import libgap - - def normalise(pair): - x, y = pair - if x != 0 and p - x < x: - return ((-x) % p, (-y) % p) - elif x == 0 and p - y < y: - return (0, (-y) % p) - return (x, y) - - points = [(x, y) for x in range(p) for y in range(p) - if (x, y) != (0, 0) and - (x != 0 and p - x >= x or (x == 0 and p - y >= y))] - convert = {pt: i + 1 for i, pt in enumerate(points)} - - F = GF(p) - S = matrix(F, 2, 2, [0, -1, 1, 0]) - T = matrix(F, 2, 2, [1, 1, 0, 1]) - perm_S = Permutation([convert[normalise(S * vector(pt))] - for pt in points]) - perm_T = Permutation([convert[normalise(T * vector(pt))] - for pt in points]) - group = PermutationGroup([perm_S, perm_T]) - triangle = [convert[normalise(pt)] for pt in [(1, 0), (0, 1), (1, 1)]] - triangle = libgap.Set(triangle) - triangles = libgap.Orbit(group, triangle, libgap.OnSets).sage() - return SimplicialComplex(triangles) +from sage.misc.superseded import deprecated_function_alias +import sage.topology.simplicial_complex_examples + +for f in ['facets_for_RP4', + 'facets_for_K3', + 'matching', + 'UniqueSimplicialComplex', + 'Sphere', + 'Simplex', + 'Torus', + 'RealProjectivePlane', + 'ProjectivePlane', + 'KleinBottle', + 'SurfaceOfGenus', + 'MooreSpace', + 'ComplexProjectivePlane', + 'PseudoQuaternionicProjectivePlane', + 'PoincareHomologyThreeSphere', + 'RealProjectiveSpace', + 'K3Surface', + 'BarnetteSphere', + 'BrucknerGrunbaumSphere', + 'NotIConnectedGraphs', + 'MatchingComplex', + 'ChessboardComplex', + 'RandomComplex', + 'SumComplex', + 'RandomTwoSphere', + 'ShiftedComplex', + 'RudinBall', + 'ZieglerBall', + 'DunceHat', + 'FareyMap']: + exec('{} = deprecated_function_alias(31925, sage.topology.simplicial_complex_examples.{})'.format(f, f)) diff --git a/src/sage/homology/hochschild_complex.py b/src/sage/homology/hochschild_complex.py index 78f38739dc5..478118b4274 100644 --- a/src/sage/homology/hochschild_complex.py +++ b/src/sage/homology/hochschild_complex.py @@ -282,6 +282,8 @@ def on_basis(k): return ret return Fd1.module_morphism(on_basis, codomain=Fd) + differential = boundary + def coboundary(self, d): """ Return the coboundary morphism of degree ``d``. diff --git a/src/sage/homology/homology_morphism.py b/src/sage/homology/homology_morphism.py index eb3b8a08ff5..7dfd5fc8b08 100644 --- a/src/sage/homology/homology_morphism.py +++ b/src/sage/homology/homology_morphism.py @@ -32,7 +32,7 @@ from sage.categories.morphism import Morphism from sage.categories.homset import Hom from sage.rings.rational_field import QQ -from sage.homology.simplicial_complex import SimplicialComplex +from sage.topology.simplicial_complex import SimplicialComplex class InducedHomologyMorphism(Morphism): r""" @@ -52,7 +52,7 @@ class InducedHomologyMorphism(Morphism): This is not intended to be used directly by the user, but instead via the method - :meth:`~sage.homology.simplicial_complex_morphism.SimplicialComplexMorphism.induced_homology_morphism`. + :meth:`~sage.topology.simplicial_complex_morphism.SimplicialComplexMorphism.induced_homology_morphism`. EXAMPLES:: diff --git a/src/sage/homology/homology_vector_space_with_basis.py b/src/sage/homology/homology_vector_space_with_basis.py index 4282e88d273..8861ee20ad9 100644 --- a/src/sage/homology/homology_vector_space_with_basis.py +++ b/src/sage/homology/homology_vector_space_with_basis.py @@ -31,8 +31,8 @@ from sage.categories.modules import Modules from sage.combinat.free_module import CombinatorialFreeModule from sage.sets.family import Family -from .simplicial_complex import SimplicialComplex -from .simplicial_set import SimplicialSet_arbitrary +from sage.topology.simplicial_complex import SimplicialComplex +from sage.topology.simplicial_set import SimplicialSet_arbitrary class HomologyVectorSpaceWithBasis(CombinatorialFreeModule): r""" @@ -49,10 +49,10 @@ class HomologyVectorSpaceWithBasis(CombinatorialFreeModule): This is not intended to be created directly by the user, but instead via the methods - :meth:`~sage.homology.cell_complex.GenericCellComplex.homology_with_basis` and - :meth:`~sage.homology.cell_complex.GenericCellComplex.cohomology_ring` + :meth:`~sage.topology.cell_complex.GenericCellComplex.homology_with_basis` and + :meth:`~sage.topology.cell_complex.GenericCellComplex.cohomology_ring` for the class of :class:`cell - complexes`. + complexes`. INPUT: @@ -414,9 +414,9 @@ class CohomologyRing(HomologyVectorSpaceWithBasis): This is not intended to be created directly by the user, but instead via the - :meth:`cohomology ring` + :meth:`cohomology ring` of a :class:`cell - complex`. + complex`. INPUT: @@ -570,7 +570,7 @@ def product_on_basis(self, li, ri): and simplicial sets:: - sage: from sage.homology.simplicial_set_examples import RealProjectiveSpace + sage: from sage.topology.simplicial_set_examples import RealProjectiveSpace sage: RP5 = RealProjectiveSpace(5) sage: x = RP5.cohomology_ring(GF(2)).basis()[1,0] sage: x**4 diff --git a/src/sage/homology/simplicial_complex.py b/src/sage/homology/simplicial_complex.py index 8db479fd549..8c8399d3b24 100644 --- a/src/sage/homology/simplicial_complex.py +++ b/src/sage/homology/simplicial_complex.py @@ -1,4787 +1,16 @@ # -*- coding: utf-8 -*- r""" -Finite simplicial complexes +Finite simplicial complexes: deprecated -AUTHORS: - -- John H. Palmieri (2009-04) - -- D. Benjamin Antieau (2009-06): added is_connected, generated_subcomplex, - remove_facet, and is_flag_complex methods; - cached the output of the graph() method. - -- Travis Scrimshaw (2012-08-17): Made :class:`SimplicialComplex` have an - immutable option, and added ``__hash__()`` function which checks to make - sure it is immutable. Made :meth:`SimplicialComplex.remove_face()` into a - mutator. Deprecated the ``vertex_set`` parameter. - -- Christian Stump (2011-06): implementation of is_cohen_macaulay - -- Travis Scrimshaw (2013-02-16): Allowed :class:`SimplicialComplex` to make - mutable copies. - -- Simon King (2014-05-02): Let simplicial complexes be objects of the - category of simplicial complexes. - -- Jeremy Martin (2016-06-02): added cone_vertices, decone, is_balanced, - is_partitionable, intersection methods - -This module implements the basic structure of finite simplicial -complexes. Given a set `V` of "vertices", a simplicial complex on `V` -is a collection `K` of subsets of `V` satisfying the condition that if -`S` is one of the subsets in `K`, then so is every subset of `S`. The -subsets `S` are called the 'simplices' of `K`. - -.. NOTE:: - - In Sage, the elements of the vertex set are determined - automatically: `V` is defined to be the union of the sets in - `K`. So in Sage's implementation of simplicial complexes, every - vertex is included in some face. - -A simplicial complex `K` can be viewed as a purely combinatorial -object, as described above, but it also gives rise to a topological -space `|K|` (its *geometric realization*) as follows: first, the -points of `V` should be in general position in euclidean space. Next, -if `\{v\}` is in `K`, then the vertex `v` is in `|K|`. If `\{v, w\}` -is in `K`, then the line segment from `v` to `w` is in `|K|`. If `\{u, -v, w\}` is in `K`, then the triangle with vertices `u`, `v`, and `w` -is in `|K|`. In general, `|K|` is the union of the convex hulls of -simplices of `K`. Frequently, one abuses notation and uses `K` to -denote both the simplicial complex and the associated topological -space. - -.. image:: ../../media/simplices.png - -For any simplicial complex `K` and any commutative ring `R` there is -an associated chain complex, with differential of degree `-1`. The -`n^{th}` term is the free `R`-module with basis given by the -`n`-simplices of `K`. The differential is determined by its value on -any simplex: on the `n`-simplex with vertices `(v_0, v_1, ..., v_n)`, -the differential is the alternating sum with `i^{th}` summand `(-1)^i` -multiplied by the `(n-1)`-simplex obtained by omitting vertex `v_i`. - -In the implementation here, the vertex set must be finite. To define a -simplicial complex, specify its *facets*: the maximal subsets (with -respect to inclusion) of the vertex set belonging to `K`. Each facet -can be specified as a list, a tuple, or a set. - -.. NOTE:: - - This class derives from - :class:`~sage.homology.cell_complex.GenericCellComplex`, and so - inherits its methods. Some of those methods are not listed here; - see the :mod:`Generic Cell Complex ` - page instead. - -EXAMPLES:: - - sage: SimplicialComplex([[1], [3, 7]]) - Simplicial complex with vertex set (1, 3, 7) and facets {(1,), (3, 7)} - sage: SimplicialComplex() # the empty simplicial complex - Simplicial complex with vertex set () and facets {()} - sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [3,0]]) - sage: X - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 3), (1, 2), (2, 3)} - -Sage can perform a number of operations on simplicial complexes, such -as the join and the product, and it can also compute homology:: - - sage: S = SimplicialComplex([[0,1], [1,2], [0,2]]) # circle - sage: T = S.product(S) # torus - sage: T - Simplicial complex with 9 vertices and 18 facets - sage: T.homology() # this computes reduced homology - {0: 0, 1: Z x Z, 2: Z} - sage: T.euler_characteristic() - 0 - -Sage knows about some basic combinatorial data associated to a -simplicial complex:: - - sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [0,3]]) - sage: X.f_vector() - [1, 4, 4] - sage: X.face_poset() - Finite poset containing 8 elements - sage: x0, x1, x2, x3 = X.stanley_reisner_ring().gens() - sage: x0*x2 == x1*x3 == 0 - True - sage: X.is_pure() - True - -Mutability (see :trac:`12587`):: - - sage: S = SimplicialComplex([[1,4], [2,4]]) - sage: S.add_face([1,3]) - sage: S.remove_face([1,3]); S - Simplicial complex with vertex set (1, 2, 3, 4) and facets {(3,), (1, 4), (2, 4)} - sage: hash(S) - Traceback (most recent call last): - ... - ValueError: This simplicial complex must be immutable. Call set_immutable(). - sage: S = SimplicialComplex([[1,4], [2,4]]) - sage: S.set_immutable() - sage: S.add_face([1,3]) - Traceback (most recent call last): - ... - ValueError: This simplicial complex is not mutable - sage: S.remove_face([1,3]) - Traceback (most recent call last): - ... - ValueError: This simplicial complex is not mutable - sage: hash(S) == hash(S) - True - - sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) - sage: hash(S2) == hash(S) - True - -We can also make mutable copies of an immutable simplicial complex -(see :trac:`14142`):: - - sage: S = SimplicialComplex([[1,4], [2,4]]) - sage: S.set_immutable() - sage: T = copy(S) - sage: T.is_mutable() - True - sage: S == T - True +The current version is :mod:`sage.topology.simplicial_complexes`. """ -from operator import index as PyNumber_Index - -# possible future directions for SimplicialComplex: -# -# make compatible with GAP (see http://linalg.org/gap.html) -# compare to and make compatible with polymake -# see Macaulay: http://www.math.uiuc.edu/Macaulay2/doc/Macaulay2-1.1/share/doc/Macaulay2/SimplicialComplexes/html/___Simplicial__Complex.html; compare performance and make compatible -# should + have any meaning? -# cohomology: compute cup products (and Massey products?) - -from copy import copy -from sage.misc.lazy_import import lazy_import -from sage.misc.cachefunc import cached_method -from sage.homology.cell_complex import GenericCellComplex -from sage.structure.sage_object import SageObject -from sage.structure.parent import Parent -from sage.rings.integer import Integer -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.sets.set import Set -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.structure.category_object import normalize_names -from sage.misc.latex import latex -from sage.misc.misc import union -from sage.matrix.constructor import matrix -from sage.homology.chain_complex import ChainComplex -from sage.graphs.graph import Graph -from functools import reduce, total_ordering -from itertools import combinations -lazy_import('sage.categories.simplicial_complexes', 'SimplicialComplexes') - - -def lattice_paths(t1, t2, length=None): - r""" - Given lists (or tuples or ...) ``t1`` and ``t2``, think of them as - labelings for vertices: ``t1`` labeling points on the x-axis, - ``t2`` labeling points on the y-axis, both increasing. Return the - list of rectilinear paths along the grid defined by these points - in the plane, starting from ``(t1[0], t2[0])``, ending at - ``(t1[last], t2[last])``, and at each grid point, going either - right or up. See the examples. - - :param t1: labeling for vertices - :param t2: labeling for vertices - :param length: if not ``None``, then an integer, the length of the desired - path. - :type length: integer or ``None``; optional, default ``None`` - :type t1: list, other iterable - :type t2: list, other iterable - :return: list of lists of vertices making up the paths as described above - :rtype: list of lists - - This is used when triangulating the product of simplices. The - optional argument ``length`` is used for `\Delta`-complexes, to - specify all simplices in a product: in the triangulation of a - product of two simplices, there is a `d`-simplex for every path of - length `d+1` in the lattice. The path must start at the bottom - left and end at the upper right, and it must use at least one - point in each row and in each column, so if ``length`` is too - small, there will be no paths. - - EXAMPLES:: - - sage: from sage.homology.simplicial_complex import lattice_paths - sage: lattice_paths([0,1,2], [0,1,2]) - [[(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)], - [(0, 0), (0, 1), (1, 1), (1, 2), (2, 2)], - [(0, 0), (1, 0), (1, 1), (1, 2), (2, 2)], - [(0, 0), (0, 1), (1, 1), (2, 1), (2, 2)], - [(0, 0), (1, 0), (1, 1), (2, 1), (2, 2)], - [(0, 0), (1, 0), (2, 0), (2, 1), (2, 2)]] - sage: lattice_paths(('a', 'b', 'c'), (0, 3, 5)) - [[('a', 0), ('a', 3), ('a', 5), ('b', 5), ('c', 5)], - [('a', 0), ('a', 3), ('b', 3), ('b', 5), ('c', 5)], - [('a', 0), ('b', 0), ('b', 3), ('b', 5), ('c', 5)], - [('a', 0), ('a', 3), ('b', 3), ('c', 3), ('c', 5)], - [('a', 0), ('b', 0), ('b', 3), ('c', 3), ('c', 5)], - [('a', 0), ('b', 0), ('c', 0), ('c', 3), ('c', 5)]] - sage: lattice_paths(range(3), range(3), length=2) - [] - sage: lattice_paths(range(3), range(3), length=3) - [[(0, 0), (1, 1), (2, 2)]] - sage: lattice_paths(range(3), range(3), length=4) - [[(0, 0), (1, 1), (1, 2), (2, 2)], - [(0, 0), (0, 1), (1, 2), (2, 2)], - [(0, 0), (1, 1), (2, 1), (2, 2)], - [(0, 0), (1, 0), (2, 1), (2, 2)], - [(0, 0), (0, 1), (1, 1), (2, 2)], - [(0, 0), (1, 0), (1, 1), (2, 2)]] - """ - # Convert t1, t2 to tuples, in case they are (for example) Python 3 ranges. - t1 = tuple(t1) - t2 = tuple(t2) - if length is None: - # 0 x n (or k x 0) rectangle: - if len(t1) == 0 or len(t2) == 0: - return [[]] - # 1 x n (or k x 1) rectangle: - elif len(t1) == 1: - return [[(t1[0], w) for w in t2]] - elif len(t2) == 1: - return [[(v, t2[0]) for v in t1]] - else: - # recursive: paths in rectangle with either one fewer row - # or column, plus the upper right corner - return ([path + [(t1[-1], t2[-1])] for path - in lattice_paths(t1[:-1], t2)] + - [path + [(t1[-1], t2[-1])] for path - in lattice_paths(t1, t2[:-1])]) - else: - if length > len(t1) + len(t2) - 1: - return [] - # as above, except make sure that lengths are correct. if - # not, return an empty list. - # - # 0 x n (or k x 0) rectangle: - elif len(t1) == 0 or len(t2) == 0: - if length == 0: - return [[]] - else: - return [] - # 1 x n (or k x 1) rectangle: - elif len(t1) == 1: - if length == len(t2): - return [[(t1[0], w) for w in t2]] - else: - return [] - elif len(t2) == 1: - if length == len(t1): - return [[(v, t2[0]) for v in t1]] - else: - return [] - else: - # recursive: paths of length one fewer in rectangle with - # either one fewer row, one fewer column, or one fewer of - # each, and then plus the upper right corner - return ([path + [(t1[-1], t2[-1])] for path - in lattice_paths(t1[:-1], t2, length=length-1)] + - [path + [(t1[-1], t2[-1])] for path - in lattice_paths(t1, t2[:-1], length=length-1)] + - [path + [(t1[-1], t2[-1])] for path - in lattice_paths(t1[:-1], t2[:-1], length=length-1)]) - -def rename_vertex(n, keep, left=True): - """ - Rename a vertex: the vertices from the list ``keep`` get - relabeled 0, 1, 2, ..., in order. Any other vertex (e.g. 4) gets - renamed to by prepending an 'L' or an 'R' (thus to either 'L4' or - 'R4'), depending on whether the argument left is ``True`` or ``False``. - - :param n: a 'vertex': either an integer or a string - :param keep: a list of three vertices - :param left: if ``True``, rename for use in left factor - :type left: boolean; optional, default ``True`` - - This is used by the :meth:`~SimplicialComplex.connected_sum` method for - simplicial complexes. - - EXAMPLES:: - - sage: from sage.homology.simplicial_complex import rename_vertex - sage: rename_vertex(6, [5, 6, 7]) - 1 - sage: rename_vertex(3, [5, 6, 7, 8, 9]) - 'L3' - sage: rename_vertex(3, [5, 6, 7], left=False) - 'R3' - """ - lookup = {i:v for v,i in enumerate(keep)} - try: - return lookup[n] - except KeyError: - if left: - return "L" + str(n) - else: - return "R" + str(n) - -@total_ordering -class Simplex(SageObject): - """ - Define a simplex. - - Topologically, a simplex is the convex hull of a collection of - vertices in general position. Combinatorially, it is defined just - by specifying a set of vertices. It is represented in Sage by the - tuple of the vertices. - - :param X: set of vertices - :type X: integer, list, other iterable - :return: simplex with those vertices - - ``X`` may be a non-negative integer `n`, in which case the - simplicial complex will have `n+1` vertices `(0, 1, ..., n)`, or - it may be anything which may be converted to a tuple, in which - case the vertices will be that tuple. In the second case, each - vertex must be hashable, so it should be a number, a string, or a - tuple, for instance, but not a list. - - .. WARNING:: - - The vertices should be distinct, and no error checking is done - to make sure this is the case. - - EXAMPLES:: - - sage: Simplex(4) - (0, 1, 2, 3, 4) - sage: Simplex([3, 4, 1]) - (3, 4, 1) - sage: X = Simplex((3, 'a', 'vertex')); X - (3, 'a', 'vertex') - sage: X == loads(dumps(X)) - True - - Vertices may be tuples but not lists:: - - sage: Simplex([(1,2), (3,4)]) - ((1, 2), (3, 4)) - sage: Simplex([[1,2], [3,4]]) - Traceback (most recent call last): - ... - TypeError: unhashable type: 'list' - """ - - def __init__(self, X): - """ - Define a simplex. See :class:`Simplex` for full documentation. - - EXAMPLES:: - - sage: Simplex(2) - (0, 1, 2) - sage: Simplex(('a', 'b', 'c')) - ('a', 'b', 'c') - sage: Simplex(-1) - () - sage: Simplex(-3) - Traceback (most recent call last): - ... - ValueError: the n-simplex is only defined if n > -2 - """ - try: - N = int(X) + 1 - if N < 0: - raise ValueError('the n-simplex is only defined if n > -2') - self.__tuple = tuple(range(N)) - except TypeError: - self.__tuple = tuple(X) - self.__set = frozenset(self.__tuple) - - def tuple(self): - """ - The tuple attached to this simplex. - - EXAMPLES:: - - sage: Simplex(3).tuple() - (0, 1, 2, 3) - - Although simplices are printed as if they were tuples, they - are not the same type:: - - sage: type(Simplex(3).tuple()) - <... 'tuple'> - sage: type(Simplex(3)) - - """ - return self.__tuple - - def set(self): - """ - The frozenset attached to this simplex. - - EXAMPLES:: - - sage: Simplex(3).set() - frozenset({0, 1, 2, 3}) - """ - return self.__set - - def is_face(self, other): - """ - Return ``True`` iff this simplex is a face of other. - - EXAMPLES:: - - sage: Simplex(3).is_face(Simplex(5)) - True - sage: Simplex(5).is_face(Simplex(2)) - False - sage: Simplex(['a', 'b', 'c']).is_face(Simplex(8)) - False - """ - return self.__set.issubset(other.__set) - - def __contains__(self, x): - """ - Return ``True`` iff ``x`` is a vertex of this simplex. - - EXAMPLES:: - - sage: 3 in Simplex(5) - True - sage: 3 in Simplex(2) - False - """ - return x in self.__set - - def __getitem__(self, n): - """ - Return the `n`-th vertex in this simplex. - - EXAMPLES:: - - sage: Simplex(5)[2] - 2 - sage: Simplex(['a', 'b', 'c'])[1] - 'b' - """ - return self.__tuple[n] - - def __iter__(self): - """ - Iterator for the vertices of this simplex. - - EXAMPLES:: - - sage: [v**2 for v in Simplex(3)] - [0, 1, 4, 9] - """ - return iter(self.__tuple) - - def __add__(self, other): - """ - Simplex obtained by concatenating the underlying tuples of the - two arguments. - - :param other: another simplex - - EXAMPLES:: - - sage: Simplex((1,2,3)) + Simplex((5,6)) - (1, 2, 3, 5, 6) - """ - return Simplex(self.__tuple + other.__tuple) - - def face(self, n): - """ - The `n`-th face of this simplex. - - :param n: an integer between 0 and the dimension of this simplex - :type n: integer - :return: the simplex obtained by removing the `n`-th vertex from this - simplex - - EXAMPLES:: - - sage: S = Simplex(4) - sage: S.face(0) - (1, 2, 3, 4) - sage: S.face(3) - (0, 1, 2, 4) - """ - if n >= 0 and n <= self.dimension(): - return Simplex(self.__tuple[:n] + self.__tuple[n+1:]) - else: - raise IndexError("{} does not have an nth face for n={}".format(self, n)) - - def faces(self): - """ - The list of faces (of codimension 1) of this simplex. - - EXAMPLES:: - - sage: S = Simplex(4) - sage: S.faces() - [(1, 2, 3, 4), (0, 2, 3, 4), (0, 1, 3, 4), (0, 1, 2, 4), (0, 1, 2, 3)] - sage: len(Simplex(10).faces()) - 11 - """ - return [self.face(i) for i in range(self.dimension() + 1)] - - def dimension(self): - """ - The dimension of this simplex. - - The dimension of a simplex is the number of vertices minus 1. - - EXAMPLES:: - - sage: Simplex(5).dimension() == 5 - True - sage: Simplex(5).face(1).dimension() - 4 - """ - return len(self.__tuple) - 1 - - def is_empty(self): - """ - Return ``True`` iff this simplex is the empty simplex. - - EXAMPLES:: - - sage: [Simplex(n).is_empty() for n in range(-1,4)] - [True, False, False, False, False] - """ - return self.dimension() < 0 - - def join(self, right, rename_vertices=True): - """ - The join of this simplex with another one. - - The join of two simplices `[v_0, ..., v_k]` and `[w_0, ..., - w_n]` is the simplex `[v_0, ..., v_k, w_0, ..., w_n]`. - - :param right: the other simplex (the right-hand factor) - - :param rename_vertices: If this is ``True``, the vertices in the - join will be renamed by this formula: vertex "v" in the - left-hand factor --> vertex "Lv" in the join, vertex "w" - in the right-hand factor --> vertex "Rw" in the join. If - this is false, this tries to construct the join without - renaming the vertices; this may cause problems if the two - factors have any vertices with names in common. - - :type rename_vertices: boolean; optional, default ``True`` - - EXAMPLES:: - - sage: Simplex(2).join(Simplex(3)) - ('L0', 'L1', 'L2', 'R0', 'R1', 'R2', 'R3') - sage: Simplex(['a', 'b']).join(Simplex(['x', 'y', 'z'])) - ('La', 'Lb', 'Rx', 'Ry', 'Rz') - sage: Simplex(['a', 'b']).join(Simplex(['x', 'y', 'z']), rename_vertices=False) - ('a', 'b', 'x', 'y', 'z') - """ - if rename_vertices: - vertex_set = (["L" + str(v) for v in self] - + ["R" + str(w) for w in right]) - else: - vertex_set = self.__tuple + right.__tuple - return Simplex(vertex_set) - - def product(self, other, rename_vertices=True): - r""" - The product of this simplex with another one, as a list of simplices. - - :param other: the other simplex - - :param rename_vertices: If this is ``False``, then the vertices in - the product are the set of ordered pairs `(v,w)` where `v` - is a vertex in the left-hand factor (``self``) and `w` is - a vertex in the right-hand factor (``other``). If this is - ``True``, then the vertices are renamed as "LvRw" (e.g., the - vertex (1,2) would become "L1R2"). This is useful if you - want to define the Stanley-Reisner ring of the complex: - vertex names like (0,1) are not suitable for that, while - vertex names like "L0R1" are. - - :type rename_vertices: boolean; optional, default ``True`` - - Algorithm: see Hatcher, p. 277-278 [Hat2002]_ (who in turn refers to - Eilenberg-Steenrod, p. 68): given ``S = Simplex(m)`` and - ``T = Simplex(n)``, then `S \times T` can be - triangulated as follows: for each path `f` from `(0,0)` to - `(m,n)` along the integer grid in the plane, going up or right - at each lattice point, associate an `(m+n)`-simplex with - vertices `v_0`, `v_1`, ..., where `v_k` is the `k^{th}` vertex - in the path `f`. - - Note that there are `m+n` choose `n` such paths. Note also - that each vertex in the product is a pair of vertices `(v,w)` - where `v` is a vertex in the left-hand factor and `w` - is a vertex in the right-hand factor. - - .. NOTE:: - - This produces a list of simplices -- not a :class:`Simplex`, not - a :class:`SimplicialComplex`. - - EXAMPLES:: - - sage: len(Simplex(2).product(Simplex(2))) - 6 - sage: Simplex(1).product(Simplex(1)) - [('L0R0', 'L0R1', 'L1R1'), ('L0R0', 'L1R0', 'L1R1')] - sage: Simplex(1).product(Simplex(1), rename_vertices=False) - [((0, 0), (0, 1), (1, 1)), ((0, 0), (1, 0), (1, 1))] - """ - if not rename_vertices: - return [Simplex(x) for x in lattice_paths(self.tuple(), other.tuple())] - - answer = [] - for x in lattice_paths(self.tuple(), other.tuple()): - new = tuple(["L" + str(v) + "R" + str(w) for (v, w) in x]) - answer.append(Simplex(new)) - return answer - - def alexander_whitney(self, dim): - r""" - Subdivide this simplex into a pair of simplices. - - If this simplex has vertices `v_0`, `v_1`, ..., `v_n`, then - subdivide it into simplices `(v_0, v_1, ..., v_{dim})` and - `(v_{dim}, v_{dim + 1}, ..., v_n)`. - - INPUT: - - - ``dim`` -- integer between 0 and one more than the - dimension of this simplex - - OUTPUT: - - - a list containing just the triple ``(1, left, right)``, - where ``left`` and ``right`` are the two simplices described - above. - - This method allows one to construct a coproduct from the - `p+q`-chains to the tensor product of the `p`-chains and the - `q`-chains. The number 1 (a Sage integer) is the coefficient - of ``left tensor right`` in this coproduct. (The corresponding - formula is more complicated for the cubes that make up a - cubical complex, and the output format is intended to be - consistent for both cubes and simplices.) - - Calling this method ``alexander_whitney`` is an abuse of - notation, since the actual Alexander-Whitney map goes from - `C(X \times Y) \to C(X) \otimes C(Y)`, where `C(-)` denotes - the chain complex of singular chains, but this subdivision of - simplices is at the heart of it. - - EXAMPLES:: - - sage: s = Simplex((0,1,3,4)) - sage: s.alexander_whitney(0) - [(1, (0,), (0, 1, 3, 4))] - sage: s.alexander_whitney(2) - [(1, (0, 1, 3), (3, 4))] - """ - return [(ZZ.one(), Simplex(self.tuple()[:dim+1]), - Simplex(self.tuple()[dim:]))] - - def __eq__(self, other): - """ - Return ``True`` iff this simplex is the same as ``other``: that - is, if the vertices of the two are the same, even with a - different ordering - - :param other: the other simplex - - EXAMPLES:: - - sage: Simplex([0,1,2]) == Simplex([0,2,1]) - True - sage: Simplex([0,1,2]) == Simplex(['a','b','c']) - False - sage: Simplex([1]) < Simplex([2]) - True - sage: Simplex([1]) > Simplex([2]) - False - """ - if not isinstance(other, Simplex): - return False - return set(self) == set(other) - - def __ne__(self, other): - """ - Return ``True`` iff this simplex is not equal to ``other``. - - :param other: the other simplex - - EXAMPLES:: - - sage: Simplex([0,1,2]) != Simplex([0,2,1]) - False - sage: Simplex([0,1,2]) != Simplex(['a','b','c']) - True - """ - return not self == other - - def __lt__(self, other): - """ - Return ``True`` iff the sorted tuple for this simplex is less than - that for ``other``. - - :param other: the other simplex - - EXAMPLES:: - - sage: Simplex([1]) < Simplex([2]) - True - sage: Simplex([2,3]) < Simplex([1]) - False - sage: Simplex([0,1,2]) < Simplex([0,2,1]) - False - - Test ``@total_ordering`` by testing other comparisons:: - - sage: Simplex([0,1,2]) <= Simplex([0,2,1]) - True - sage: Simplex([1]) <= Simplex([2]) - True - sage: Simplex([2]) <= Simplex([1]) - False - sage: Simplex([0,1,2]) > Simplex([0,2,1]) - False - sage: Simplex([1]) > Simplex([2]) - False - sage: Simplex([2]) > Simplex([1]) - True - sage: Simplex([0,1,2]) > Simplex([0,2,1]) - False - sage: Simplex([0,1,2]) >= Simplex([0,2,1]) - True - sage: Simplex([1]) >= Simplex([2]) - False - sage: Simplex([2]) >= Simplex([1]) - True - """ - if not isinstance(other, Simplex): - return False - try: - return sorted(self) < sorted(other) - except TypeError: - return sorted(map(str, self)) < sorted(map(str, other)) - - def __hash__(self): - """ - Hash value for this simplex. This computes the hash value of - the Python frozenset of the underlying tuple, since this is - what's important when testing equality. - - EXAMPLES:: - - sage: Simplex([1,2,0]).__hash__() == Simplex(2).__hash__() - True - sage: Simplex([1,2,0,1,1,2]).__hash__() == Simplex(2).__hash__() - True - """ - return hash(self.__set) - - def _repr_(self): - """ - Print representation. - - EXAMPLES:: - - sage: S = Simplex(5) - sage: S._repr_() - '(0, 1, 2, 3, 4, 5)' - """ - return repr(self.__tuple) - - def _latex_(self): - r""" - LaTeX representation. - - EXAMPLES:: - - sage: Simplex(3)._latex_() - \left(0, - 1, - 2, - 3\right) - """ - return latex(self.__tuple) - -class SimplicialComplex(Parent, GenericCellComplex): - r""" - Define a simplicial complex. - - :param maximal_faces: set of maximal faces - :param from_characteristic_function: see below - :param maximality_check: see below - :type maximality_check: boolean; optional, default ``True`` - :param sort_facets: see below - :type sort_facets: dict - :param name_check: see below - :type name_check: boolean; optional, default ``False`` - :param is_mutable: Set to ``False`` to make this immutable - :type is_mutable: boolean; optional, default ``True`` - :param category: the category of the simplicial complex - :type category: category; optional, default finite simplicial complexes - :return: a simplicial complex - - ``maximal_faces`` should be a list or tuple or set (indeed, - anything which may be converted to a set) whose elements are lists - (or tuples, etc.) of vertices. Maximal faces are also known as - 'facets'. ``maximal_faces`` can also be a list containing a single - non-negative integer `n`, in which case this constructs the - simplicial complex with a single `n`-simplex as the only facet. - - Alternatively, the maximal faces can be defined from a monotone boolean - function on the subsets of a set `X`. While defining ``maximal_faces=None``, - you can thus set ``from_characteristic_function=(f,X)`` where ``X`` is the - set of points and ``f`` a boolean monotone hereditary function that accepts - a list of elements from ``X`` as input (see - :func:`~sage.combinat.subsets_hereditary.subsets_with_hereditary_property` - for more information). - - If ``maximality_check`` is ``True``, check that each maximal face is, - in fact, maximal. In this case, when producing the internal - representation of the simplicial complex, omit those that are not. - It is highly recommended that this be ``True``; various methods for - this class may fail if faces which are claimed to be maximal are - in fact not. - - ``sort_facets``: if not set to ``None``, the default, this should - be a dictionary, used for sorting the vertices in each facet. The - keys must be the vertices for the simplicial complex, and the - values should be distinct sortable objects, for example - integers. This should not need to be specified except in very - special circumstances; currently the only use in the Sage library - is when defining the product of a simplicial complex with itself: - in this case, the vertices in the product must be sorted - compatibly with the vertices in each factor so that the diagonal - map is properly defined. - - If ``name_check`` is ``True``, check the names of the vertices to see - if they can be easily converted to generators of a polynomial ring - -- use this if you plan to use the Stanley-Reisner ring for the - simplicial complex. - - EXAMPLES:: - - sage: SimplicialComplex([[1,2], [1,4]]) - Simplicial complex with vertex set (1, 2, 4) and facets {(1, 2), (1, 4)} - sage: SimplicialComplex([[0,2], [0,3], [0]]) - Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2), (0, 3)} - sage: SimplicialComplex([[0,2], [0,3], [0]], maximality_check=False) - Simplicial complex with vertex set (0, 2, 3) and facets {(0,), (0, 2), (0, 3)} - - Finally, if the first argument is a simplicial complex, return - that complex. If it is an object with a built-in conversion to - simplicial complexes (via a ``_simplicial_`` method), then the - resulting simplicial complex is returned:: - - sage: S = SimplicialComplex([[0,2], [0,3], [0,6]]) - sage: SimplicialComplex(S) == S - True - sage: Tc = cubical_complexes.Torus(); Tc - Cubical complex with 16 vertices and 64 cubes - sage: Ts = SimplicialComplex(Tc); Ts - Simplicial complex with 16 vertices and 32 facets - sage: Ts.homology() - {0: 0, 1: Z x Z, 2: Z} - - In the situation where the first argument is a simplicial complex - or another object with a built-in conversion, most of the other - arguments are ignored. The only exception is ``is_mutable``:: - - sage: S.is_mutable() - True - sage: SimplicialComplex(S, is_mutable=False).is_mutable() - False - - From a characteristic monotone boolean function, e.g. the simplicial complex - of all subsets `S\subseteq \{0,1,2,3,4\}` such that `sum(S)\leq 4`:: - - sage: SimplicialComplex(from_characteristic_function=(lambda x:sum(x)<=4, range(5))) - Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 4), (0, 1, 2), (0, 1, 3)} - - or e.g. the simplicial complex of all 168 hyperovals of the projective plane of order 4:: - - sage: l = designs.ProjectiveGeometryDesign(2,1,GF(4,name='a')) - sage: f = lambda S: not any(len(set(S).intersection(x))>2 for x in l) - sage: SimplicialComplex(from_characteristic_function=(f, l.ground_set())) - Simplicial complex with 21 vertices and 168 facets - - TESTS: - - Check that we can make mutable copies (see :trac:`14142`):: - - sage: S = SimplicialComplex([[0,2], [0,3]], is_mutable=False) - sage: S.is_mutable() - False - sage: C = copy(S) - sage: C.is_mutable() - True - sage: SimplicialComplex(S, is_mutable=True).is_mutable() - True - sage: SimplicialComplex(S, is_immutable=False).is_mutable() - True - - .. WARNING:: - - Simplicial complexes are not proper parents as they do - not possess element classes. In particular, parents are assumed - to be hashable (and hence immutable) by the coercion framework. - However this is close enough to being a parent with elements - being the faces of ``self`` that we currently allow this abuse. - """ - - def __init__(self, - maximal_faces=None, - from_characteristic_function=None, - maximality_check=True, - sort_facets=None, - name_check=False, - is_mutable=True, - is_immutable=False, - category=None): - """ - Define a simplicial complex. See ``SimplicialComplex`` for more - documentation. - - EXAMPLES:: - - sage: SimplicialComplex([[0,2], [0,3], [0]]) - Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2), (0, 3)} - sage: SimplicialComplex((('a', 'b'), ['a', 'c'], ('b', 'c'))) == SimplicialComplex((('a', 'b'), ('b', 'c'), ('a', 'c'))) - True - - TESTS:: - - sage: S = SimplicialComplex([[1,4], [2,4]]) - sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) - sage: S == S2 - True - sage: S3 = SimplicialComplex(maximal_faces=[[1,4], [2,4]]) - sage: S == S3 - True - - Test that we have fixed a problem revealed in :trac:`20718`; - see also :trac:`20720`:: - - sage: SimplicialComplex([2]) - Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} - - sage: S = SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c'))) - sage: S == loads(dumps(S)) - True - - sage: TestSuite(S).run() - sage: TestSuite(S3).run() - - Test ``sort_facets``:: - - sage: S = SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c')), sort_facets=True) - Traceback (most recent call last): - ... - TypeError: sort_facets must be a dict - sage: S = SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c')), sort_facets={'a': 1, 6: 3, 'c': 2}) - Traceback (most recent call last): - ... - ValueError: the set of keys of sort_facets must equal the set of vertices - sage: S = SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c')), sort_facets={'a': 1, 'b': 3, 'c': 2}) - sage: S._vertex_to_index['b'] - 3 - """ - if (maximal_faces is not None and - from_characteristic_function is not None): - raise ValueError("maximal_faces and from_characteristic_function cannot be both defined") - category = SimplicialComplexes().Finite().or_subcategory(category) - Parent.__init__(self, category=category) - - C = None - vertex_set = () - if from_characteristic_function is not None: - from sage.combinat.subsets_hereditary import subsets_with_hereditary_property - f, X = from_characteristic_function - maximal_faces = subsets_with_hereditary_property(f, X) - - if maximal_faces is None: - maximal_faces = [] - elif isinstance(maximal_faces, SimplicialComplex): - C = maximal_faces - else: - try: - C = maximal_faces._simplicial_() - except AttributeError: - if not isinstance(maximal_faces, (list, tuple, Simplex)): - # Convert it into a list (in case it is an iterable) - maximal_faces = list(maximal_faces) - if maximal_faces: - vertex_set = reduce(union, maximal_faces) - if C is not None: - self._facets = list(C.facets()) - self._faces = copy(C._faces) - self._gen_dict = copy(C._gen_dict) - self._complex = copy(C._complex) - self.__contractible = copy(C.__contractible) - self.__enlarged = copy(C.__enlarged) - self._graph = copy(C._graph) - self._vertex_to_index = copy(C._vertex_to_index) - self._is_immutable = False - if not is_mutable or is_immutable: - self.set_immutable() - return - - try: - # Check whether vertex_set is an integer - n = PyNumber_Index(vertex_set) - except TypeError: - pass - else: - vertex_set = range(n + 1) - - vertices = tuple(vertex_set) - - gen_dict = {} - for v in vertices: - if name_check: - try: - if int(v) < 0: - raise ValueError("the vertex %s does not have an appropriate name" % v) - except ValueError: # v is not an integer - try: - normalize_names(1, v) - except ValueError: - raise ValueError("the vertex %s does not have an appropriate name"%v) - # build dictionary of generator names - try: - gen_dict[v] = 'x%s' % int(v) - except Exception: - gen_dict[v] = v - # build set of facets - good_faces = [] - maximal_simplices = [Simplex(f) for f in maximal_faces] - - if maximality_check: # Sorting is useful to filter maximal faces - maximal_simplices.sort(key=lambda x: x.dimension(), reverse=True) - # Translate vertices to numbers, for use in sorting - # facets. Having a consistent ordering for the vertices in - # each facet is necessary for homology computations. - if sort_facets: - if not isinstance(sort_facets, dict): - raise TypeError("sort_facets must be a dict") - if set(sort_facets.keys()) != set(vertices): - raise ValueError("the set of keys of sort_facets must equal the set of vertices") - vertex_to_index = sort_facets - else: - vertex_to_index = {v: i for i, v in enumerate(vertices)} - - for face in maximal_simplices: - # check whether each given face is actually maximal - if (maximality_check and - any(face.is_face(other) for other in good_faces)): - continue - # This sorting is crucial for homology computations: - face = Simplex(sorted(face.tuple(), key=vertex_to_index.__getitem__)) - good_faces.append(face) - - # if no maximal faces, add the empty face as a facet - if len(maximal_simplices) == 0: - good_faces.append(Simplex(-1)) - # now record the attributes for self - # self._vertex_to_index: dictionary to convert vertices to integers - self._vertex_to_index = vertex_to_index - # self._facets: unsorted list of facets - self._facets = good_faces - # self._faces: dictionary of dictionaries of faces. The main - # dictionary is keyed by subcomplexes, and each value is a - # dictionary keyed by dimension. This should be empty until - # needed -- that is, until the faces method is called - self._faces = {} - # self._gen_dict: dictionary of names for the polynomial - # generators of the Stanley-Reisner ring - self._gen_dict = gen_dict - # self._complex: dictionary indexed by dimension d, subcomplex, - # etc.: differential from dim d to dim d-1 in the associated - # chain complex. thus to get the differential in the cochain - # complex from dim d-1 to dim d, take the transpose of this - # one. - self._complex = {} - # self.__contractible: if not None, a contractible subcomplex - # of self, as found by the _contractible_subcomplex method. - self.__contractible = None - # self.__enlarged: dictionary of enlarged subcomplexes, - # indexed by subcomplexes. For use in the _enlarge_subcomplex - # method. - self.__enlarged = {} - # initialize self._graph to None. - self._graph = None - - # Handle mutability keywords - self._is_immutable = False - if not is_mutable or is_immutable: - self.set_immutable() - - def __hash__(self): - """ - Compute the hash value of ``self``. - - If this simplicial complex is immutable, it computes the hash value - based upon the facets. Otherwise it raises a ``ValueError``. - - EXAMPLES:: - - sage: S = SimplicialComplex([[1,4], [2,4]]) - sage: hash(S) - Traceback (most recent call last): - ... - ValueError: This simplicial complex must be immutable. Call set_immutable(). - sage: S.set_immutable() - sage: hash(S) == hash(S) - True - sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) - sage: S == S2 - True - sage: hash(S) == hash(S2) - True - """ - if not self._is_immutable: - raise ValueError("This simplicial complex must be immutable. Call set_immutable().") - return hash(frozenset(self._facets)) - - def __eq__(self, right): - """ - Two simplicial complexes are equal iff their vertex sets are - equal and their sets of facets are equal. - - EXAMPLES:: - - sage: SimplicialComplex([[1,2], [2,3], [4]]) == SimplicialComplex([[4], [2,3], [3], [2,1]]) - True - sage: X = SimplicialComplex() - sage: X.add_face([1,3]) - sage: X == SimplicialComplex([[1,3]]) - True - """ - return isinstance(right, SimplicialComplex) and set(self._facets) == set(right._facets) - - def __ne__(self, right): - """ - Return ``True`` if ``self`` and ``right`` are not equal. - - EXAMPLES:: - - sage: SimplicialComplex([[1,2], [2,3], [4]]) != SimplicialComplex([[4], [2,3], [3], [2,1]]) - False - sage: X = SimplicialComplex() - sage: X.add_face([1,3]) - sage: X != SimplicialComplex([[1,3]]) - False - """ - return not self.__eq__(right) - - def __copy__(self): - """ - Return a mutable copy of ``self``. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,2], [0,3]], is_mutable=False) - sage: S.is_mutable() - False - sage: C = copy(S) - sage: C.is_mutable() - True - sage: C == S - True - sage: S.is_mutable() - False - sage: T = copy(C) - sage: T == C - True - """ - return SimplicialComplex(self, is_mutable=True) - - def vertices(self): - """ - The vertex set, as a tuple, of this simplicial complex. - - EXAMPLES:: - - sage: S = SimplicialComplex([[i] for i in range(16)] + [[0,1], [1,2]]) - sage: S - Simplicial complex with 16 vertices and 15 facets - sage: sorted(S.vertices()) - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - """ - return tuple(self._vertex_to_index) - - def _an_element_(self): - """ - The first facet of this complex. - - EXAMPLES:: - - sage: SimplicialComplex()._an_element_() - () - sage: simplicial_complexes.Sphere(3)._an_element_() - (0, 1, 2, 3) - """ - try: - return sorted(self.facets())[0] - except TypeError: - return self.facets()[0] - - def __contains__(self, x): - """ - True if ``x`` is a simplex which is contained in this complex. - - EXAMPLES:: - - sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) - sage: Simplex((0,2)) in K - True - sage: Simplex((1,3)) in K - False - sage: 0 in K # not a simplex - False - """ - if not isinstance(x, Simplex): - return False - dim = x.dimension() - return dim in self.faces() and x in self.faces()[dim] - - def __call__(self, simplex): - """ - If ``simplex`` is a simplex in this complex, return it. - Otherwise, raise a ``ValueError``. - - EXAMPLES:: - - sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) - sage: K(Simplex((1,2))) - (1, 2) - sage: K(Simplex((0,1,3))) - Traceback (most recent call last): - ... - ValueError: the simplex is not in this complex - """ - if simplex not in self: - raise ValueError('the simplex is not in this complex') - return simplex - - def maximal_faces(self): - """ - The maximal faces (a.k.a. facets) of this simplicial complex. - - This just returns the set of facets used in defining the - simplicial complex, so if the simplicial complex was defined - with no maximality checking, none is done here, either. - - EXAMPLES:: - - sage: Y = SimplicialComplex([[0,2], [1,4]]) - sage: sorted(Y.maximal_faces()) - [(0, 2), (1, 4)] - - ``facets`` is a synonym for ``maximal_faces``:: - - sage: S = SimplicialComplex([[0,1], [0,1,2]]) - sage: S.facets() - {(0, 1, 2)} - """ - return Set(self._facets) - - facets = maximal_faces - - def faces(self, subcomplex=None): - """ - The faces of this simplicial complex, in the form of a - dictionary of sets keyed by dimension. If the optional - argument ``subcomplex`` is present, then return only the - faces which are *not* in the subcomplex. - - :param subcomplex: a subcomplex of this simplicial complex. - Return faces which are not in this subcomplex. - - :type subcomplex: optional, default ``None`` - - EXAMPLES:: - - sage: Y = SimplicialComplex([[1,2], [1,4]]) - sage: Y.faces() - {-1: {()}, 0: {(1,), (2,), (4,)}, 1: {(1, 2), (1, 4)}} - sage: L = SimplicialComplex([[1,2]]) - sage: Y.faces(subcomplex=L) - {-1: set(), 0: {(4,)}, 1: {(1, 4)}} - """ - # Make the subcomplex immutable if it is not - if subcomplex is not None and not subcomplex._is_immutable: - subcomplex = SimplicialComplex(subcomplex._facets, maximality_check=False, - is_mutable=False) - - if subcomplex not in self._faces: - # Faces is the dictionary of faces in self but not in - # subcomplex, indexed by dimension - Faces = {} - # sub_facets is the dictionary of facets in the subcomplex - sub_facets = {} - dimension = max([face.dimension() for face in self._facets]) - for i in range(-1, dimension + 1): - Faces[i] = set([]) - sub_facets[i] = set([]) - for f in self._facets: - dim = f.dimension() - Faces[dim].add(f) - if subcomplex is not None: - for g in subcomplex._facets: - dim = g.dimension() - Faces[dim].discard(g) - sub_facets[dim].add(g) - # bad_faces is the set of faces in the subcomplex in the - # current dimension - bad_faces = sub_facets[dimension] - for dim in range(dimension, -1, -1): - # bad_bdries = boundaries of bad_faces: things to be - # discarded in dim-1 - bad_bdries = sub_facets[dim-1] - for f in bad_faces: - bad_bdries.update(f.faces()) - for f in Faces[dim]: - Faces[dim-1].update(set(f.faces()).difference(bad_bdries)) - bad_faces = bad_bdries - self._faces[subcomplex] = Faces - return self._faces[subcomplex] - - def face_iterator(self, increasing=True): - """ - An iterator for the faces in this simplicial complex. - - INPUT: - - - ``increasing`` -- (optional, default ``True``) if ``True``, return - faces in increasing order of dimension, thus starting with - the empty face. Otherwise it returns faces in decreasing order of - dimension. - - .. NOTE:: - - Among the faces of a fixed dimension, there is no sorting. - - EXAMPLES:: - - sage: S1 = simplicial_complexes.Sphere(1) - sage: sorted(S1.face_iterator()) - [(), (0,), (0, 1), (0, 2), (1,), (1, 2), (2,)] - """ - Fs = self.faces() - dim_index = range(-1, self.dimension() + 1) - if not increasing: - dim_index = reversed(dim_index) - for i in dim_index: - for F in Fs[i]: - yield F - - cells = faces - - n_faces = GenericCellComplex.n_cells - - def is_pure(self): - """ - Return ``True`` iff this simplicial complex is pure. - - A simplicial complex is pure if and only if all of its maximal faces - have the same dimension. - - .. WARNING:: - - This may give the wrong answer if the simplicial complex - was constructed with ``maximality_check`` set to ``False``. - - EXAMPLES:: - - sage: U = SimplicialComplex([[1,2], [1, 3, 4]]) - sage: U.is_pure() - False - sage: X = SimplicialComplex([[0,1], [0,2], [1,2]]) - sage: X.is_pure() - True - - Demonstration of the warning:: - - sage: S = SimplicialComplex([[0,1], [0]], maximality_check=False) - sage: S.is_pure() - False - """ - dims = [face.dimension() for face in self._facets] - return max(dims) == min(dims) - - def h_vector(self): - r""" - The `h`-vector of this simplicial complex. - - If the complex has dimension `d` and `(f_{-1}, f_0, f_1, ..., - f_d)` is its `f`-vector (with `f_{-1} = 1`, representing the - empty simplex), then the `h`-vector `(h_0, h_1, ..., h_d, - h_{d+1})` is defined by - - .. MATH:: - - \sum_{i=0}^{d+1} h_i x^{d+1-i} = \sum_{i=0}^{d+1} f_{i-1} (x-1)^{d+1-i}. - - Alternatively, - - .. MATH:: - - h_j = \sum_{i=-1}^{j-1} (-1)^{j-i-1} \binom{d-i}{j-i-1} f_i. - - EXAMPLES: - - The `f`- and `h`-vectors of the boundary of an octahedron are - computed in :wikipedia:`Simplicial_complex`:: - - sage: square = SimplicialComplex([[0,1], [1,2], [2,3], [0,3]]) - sage: S0 = SimplicialComplex([[0], [1]]) - sage: octa = square.join(S0) # boundary of an octahedron - sage: octa.f_vector() - [1, 6, 12, 8] - sage: octa.h_vector() - [1, 3, 3, 1] - """ - from sage.arith.all import binomial - d = self.dimension() - f = self.f_vector() # indexed starting at 0, since it's a Python list - h = [] - for j in range(0, d + 2): - s = 0 - for i in range(-1, j): - s += (-1)**(j-i-1) * binomial(d-i, j-i-1) * f[i+1] - h.append(s) - return h - - def g_vector(self): - r""" - The `g`-vector of this simplicial complex. - - If the `h`-vector of the complex is `(h_0, h_1, ..., h_d, - h_{d+1})` -- see :meth:`h_vector` -- then its `g`-vector - `(g_0, g_1, ..., g_{[(d+1)/2]})` is defined by `g_0 = 1` and - `g_i = h_i - h_{i-1}` for `i > 0`. - - EXAMPLES:: - - sage: S3 = simplicial_complexes.Sphere(3).barycentric_subdivision() - sage: S3.f_vector() - [1, 30, 150, 240, 120] - sage: S3.h_vector() - [1, 26, 66, 26, 1] - sage: S3.g_vector() - [1, 25, 40] - """ - d = self.dimension() - h = self.h_vector() - g = [1] - for i in range(1, (d + 1) // 2 + 1): - g.append(h[i] - h[i-1]) - return g - - def face(self, simplex, i): - """ - The `i`-th face of ``simplex`` in this simplicial complex - - INPUT: - - - ``simplex`` -- a simplex in this simplicial complex - - ``i`` -- integer - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1,4], [0,1,2]]) - sage: S.face(Simplex((0,2)), 0) - (2,) - - sage: S.face(Simplex((0,3)), 0) - Traceback (most recent call last): - ... - ValueError: this simplex is not in this simplicial complex - """ - d = simplex.dimension() - if d in self.faces() and simplex in self.faces()[d]: - return simplex.face(i) - else: - raise ValueError('this simplex is not in this simplicial complex') - - def f_triangle(self): - r""" - Compute the `f`-triangle of ``self``. - - The `f`-triangle is given by `f_{i,j}` being the number of - faces `F` of size `j` such that `i = \max_{G \subseteq F} |G|`. - - EXAMPLES:: - - sage: X = SimplicialComplex([[1,2,3], [3,4,5], [1,4], [1,5], [2,4], [2,5]]) - sage: X.f_triangle() ## this complex is not pure - [[0], - [0, 0], - [0, 0, 4], - [1, 5, 6, 2]] - - A complex is pure if and only if the last row is nonzero:: - - sage: X = SimplicialComplex([[1,2,3], [3,4,5], [1,4,5]]) - sage: X.f_triangle() - [[0], [0, 0], [0, 0, 0], [1, 5, 8, 3]] - """ - ret = [[0]*(i+1) for i in range(self.dimension() + 2)] - facets = [set(F) for F in self.facets()] - faces = self.faces() - for d in faces: - for f in faces[d]: - f = set(f) - L = [len(F) for F in facets if f.issubset(F)] - i = max(L) - ret[i][len(f)] += 1 - return ret - - def h_triangle(self): - r""" - Compute the `h`-triangle of ``self``. - - The `h`-triangle of a simplicial complex `\Delta` is given by - - .. MATH:: - - h_{i,j} = \sum_{k=0}^j (-1)^{j-k} \binom{i-k}{j-k} f_{i,k}, - - where `f_{i,k}` is the `f`-triangle of `\Delta`. - - EXAMPLES:: - - sage: X = SimplicialComplex([[1,2,3], [3,4,5], [1,4], [1,5], [2,4], [2,5]]) - sage: X.h_triangle() - [[0], - [0, 0], - [0, 0, 4], - [1, 2, -1, 0]] - """ - from sage.arith.all import binomial - ret = [[0]*(i+1) for i in range(self.dimension() + 2)] - f = self.f_triangle() - for i, row in enumerate(ret): - for j in range(i+1): - row[j] = sum((-1)**(j-k) * binomial(i-k, j-k) * f[i][k] - for k in range(j+1)) - return ret - - def flip_graph(self): - """ - If ``self`` is pure, then it returns the flip graph of ``self``, - otherwise, it returns ``None``. - - The flip graph of a pure simplicial complex is the (undirected) graph - with vertices being the facets, such that two facets are joined by - an edge if they meet in a codimension `1` face. - - The flip graph is used to detect if ``self`` is a pseudomanifold. - - EXAMPLES:: - - sage: S0 = simplicial_complexes.Sphere(0) - sage: G = S0.flip_graph() - sage: G.vertices(); G.edges(labels=False) - [(0,), (1,)] - [((0,), (1,))] - - sage: G = (S0.wedge(S0)).flip_graph() - sage: G.vertices(); G.edges(labels=False) - [(0,), ('L1',), ('R1',)] - [((0,), ('L1',)), ((0,), ('R1',)), (('L1',), ('R1',))] - - sage: S1 = simplicial_complexes.Sphere(1) - sage: S2 = simplicial_complexes.Sphere(2) - sage: G = (S1.wedge(S1)).flip_graph() - sage: len(G.vertices()) - 6 - sage: len(G.edges()) - 10 - - sage: (S1.wedge(S2)).flip_graph() is None - True - - sage: G = S2.flip_graph() - sage: G.vertices(); G.edges(labels=False) - [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)] - [((0, 1, 2), (0, 1, 3)), - ((0, 1, 2), (0, 2, 3)), - ((0, 1, 2), (1, 2, 3)), - ((0, 1, 3), (0, 2, 3)), - ((0, 1, 3), (1, 2, 3)), - ((0, 2, 3), (1, 2, 3))] - - sage: T = simplicial_complexes.Torus() - sage: G = T.suspension(4).flip_graph() - sage: len(G.vertices()); len(G.edges(labels=False)) - 46 - 161 - """ - from collections import defaultdict - if not self.is_pure(): - return None - d = self.dimension() - Fs = self.facets() - flipG = Graph() - flipG.add_vertices(Fs) - edges = defaultdict(list) - # go through all codim 1 faces to build the edge - for F in Fs: - try: - F_tuple = sorted(F._Simplex__set) - except TypeError: - F_tuple = tuple(F._Simplex__set) - for i in range(d+1): - coF = tuple(F_tuple[:i]+F_tuple[i+1:]) - if coF in edges: - for G in edges[coF]: - flipG.add_edge((F, G)) - edges[coF].append(F) - return flipG - - def is_pseudomanifold(self): - """ - Return True if self is a pseudomanifold. - - A pseudomanifold is a simplicial complex with the following properties: - - - it is pure of some dimension `d` (all of its facets are `d`-dimensional) - - every `(d-1)`-dimensional simplex is the face of exactly two facets - - for every two facets `S` and `T`, there is a sequence of - facets - - .. MATH:: - - S = f_0, f_1, ..., f_n = T - - such that for each `i`, `f_i` and `f_{i-1}` intersect in a - `(d-1)`-simplex. - - By convention, `S^0` is the only 0-dimensional pseudomanifold. - - EXAMPLES:: - - sage: S0 = simplicial_complexes.Sphere(0) - sage: S0.is_pseudomanifold() - True - sage: (S0.wedge(S0)).is_pseudomanifold() - False - sage: S1 = simplicial_complexes.Sphere(1) - sage: S2 = simplicial_complexes.Sphere(2) - sage: (S1.wedge(S1)).is_pseudomanifold() - False - sage: (S1.wedge(S2)).is_pseudomanifold() - False - sage: S2.is_pseudomanifold() - True - sage: T = simplicial_complexes.Torus() - sage: T.suspension(4).is_pseudomanifold() - True - """ - if not self.is_pure(): - return False - d = self.dimension() - if d == 0: - return len(self.facets()) == 2 - F = self.facets() - X = self.faces()[d-1] - # is each (d-1)-simplex is the face of exactly two facets? - for s in X: - if len([a for a in [s.is_face(f) for f in F] if a]) != 2: - return False - # construct a graph with one vertex for each facet, one edge - # when two facets intersect in a (d-1)-simplex, and see - # whether that graph is connected. - return self.flip_graph().is_connected() - - def product(self, right, rename_vertices=True, is_mutable=True): - """ - The product of this simplicial complex with another one. - - :param right: the other simplicial complex (the right-hand - factor) - - :param rename_vertices: If this is False, then the vertices in - the product are the set of ordered pairs `(v,w)` where `v` - is a vertex in ``self`` and `w` is a vertex in - ``right``. If this is ``True``, then the vertices are renamed - as "LvRw" (e.g., the vertex (1,2) would become "L1R2"). - This is useful if you want to define the Stanley-Reisner - ring of the complex: vertex names like (0,1) are not - suitable for that, while vertex names like "L0R1" are. - - :type rename_vertices: boolean; optional, default ``True`` - - :param is_mutable: Determines if the output is mutable - :type is_mutable: boolean; optional, default ``True`` - - The vertices in the product will be the set of ordered pairs - `(v,w)` where `v` is a vertex in self and `w` is a vertex in - right. - - .. WARNING:: - - If ``X`` and ``Y`` are simplicial complexes, then ``X*Y`` - returns their join, not their product. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1], [1,2], [0,2]]) # circle - sage: K = SimplicialComplex([[0,1]]) # edge - sage: Cyl = S.product(K) # cylinder - sage: sorted(Cyl.vertices()) - ['L0R0', 'L0R1', 'L1R0', 'L1R1', 'L2R0', 'L2R1'] - sage: Cyl2 = S.product(K, rename_vertices=False) - sage: sorted(Cyl2.vertices()) - [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)] - sage: T = S.product(S) # torus - sage: T - Simplicial complex with 9 vertices and 18 facets - sage: T.homology() - {0: 0, 1: Z x Z, 2: Z} - - These can get large pretty quickly:: - - sage: T = simplicial_complexes.Torus(); T - Minimal triangulation of the torus - sage: K = simplicial_complexes.KleinBottle(); K - Minimal triangulation of the Klein bottle - sage: T.product(K) # long time: 5 or 6 seconds - Simplicial complex with 56 vertices and 1344 facets - """ - facets = [] - for f in self._facets: - for g in right._facets: - facets.extend(f.product(g, rename_vertices)) - if self != right: - return SimplicialComplex(facets, is_mutable=is_mutable) - else: - # Need to sort the vertices compatibly with the sorting in - # self, so that the diagonal map is defined properly. - V = self._vertex_to_index - L = len(V) - d = {} - for v in V.keys(): - for w in V.keys(): - if rename_vertices: - d['L' + str(v) + 'R' + str(w)] = V[v] * L + V[w] - else: - d[(v,w)] = V[v] * L + V[w] - return SimplicialComplex(facets, is_mutable=is_mutable, sort_facets=d) - - def join(self, right, rename_vertices=True, is_mutable=True): - """ - The join of this simplicial complex with another one. - - The join of two simplicial complexes `S` and `T` is the - simplicial complex `S*T` with simplices of the form `[v_0, - ..., v_k, w_0, ..., w_n]` for all simplices `[v_0, ..., v_k]` in - `S` and `[w_0, ..., w_n]` in `T`. - - :param right: the other simplicial complex (the right-hand factor) - - :param rename_vertices: If this is True, the vertices in the - join will be renamed by the formula: vertex "v" in the - left-hand factor --> vertex "Lv" in the join, vertex "w" in - the right-hand factor --> vertex "Rw" in the join. If this - is false, this tries to construct the join without renaming - the vertices; this will cause problems if the two factors - have any vertices with names in common. - - :type rename_vertices: boolean; optional, default ``True`` - - :param is_mutable: Determines if the output is mutable - :type is_mutable: boolean; optional, default ``True`` - - EXAMPLES:: - - sage: S = SimplicialComplex([[0], [1]]) - sage: T = SimplicialComplex([[2], [3]]) - sage: S.join(T) - Simplicial complex with vertex set ('L0', 'L1', 'R2', 'R3') and 4 facets - sage: S.join(T, rename_vertices=False) - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2), (0, 3), (1, 2), (1, 3)} - - The notation '*' may be used, as well:: - - sage: S * S - Simplicial complex with vertex set ('L0', 'L1', 'R0', 'R1') and 4 facets - sage: S * S * S * S * S * S * S * S - Simplicial complex with 16 vertices and 256 facets - """ - facets = [] - for f in self._facets: - for g in right._facets: - facets.append(f.join(g, rename_vertices)) - return SimplicialComplex(facets, is_mutable=is_mutable) - - # Use * to mean 'join': - __mul__ = join - - def cone(self, is_mutable=True): - """ - The cone on this simplicial complex. - - :param is_mutable: Determines if the output is mutable - :type is_mutable: boolean; optional, default ``True`` - - The cone is the simplicial complex formed by adding a new - vertex `C` and simplices of the form `[C, v_0, ..., v_k]` for - every simplex `[v_0, ..., v_k]` in the original simplicial - complex. That is, the cone is the join of the original - complex with a one-point simplicial complex. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0], [1]]) - sage: CS = S.cone() - sage: sorted(CS.vertices()) - ['L0', 'L1', 'R0'] - sage: len(CS.facets()) - 2 - sage: CS.facets() == set([Simplex(['L0', 'R0']), Simplex(['L1', 'R0'])]) - True - """ - return self.join(SimplicialComplex([["0"]], is_mutable=is_mutable), - rename_vertices = True) - - def suspension(self, n=1, is_mutable=True): - r""" - The suspension of this simplicial complex. - - :param n: positive integer -- suspend this many times. - - :type n: optional, default 1 - - :param is_mutable: Determines if the output is mutable - :type is_mutable: boolean; optional, default ``True`` - - The suspension is the simplicial complex formed by adding two - new vertices `S_0` and `S_1` and simplices of the form `[S_0, - v_0, ..., v_k]` and `[S_1, v_0, ..., v_k]` for every simplex - `[v_0, ..., v_k]` in the original simplicial complex. That - is, the suspension is the join of the original complex with a - two-point simplicial complex. - - If the simplicial complex `M` happens to be a pseudomanifold - (see :meth:`is_pseudomanifold`), then this instead constructs - Datta's one-point suspension (see [Dat2007]_, p. 434): - choose a vertex `u` in `M` and choose a new vertex - `w` to add. Denote the join of simplices by "`*`". The - facets in the one-point suspension are of the two forms - - - `u * \alpha` where `\alpha` is a facet of `M` not containing - `u` - - - `w * \beta` where `\beta` is any facet of `M`. - - EXAMPLES:: - - sage: S0 = SimplicialComplex([[0], [1]]) - sage: S0.suspension() == simplicial_complexes.Sphere(1) - True - sage: S3 = S0.suspension(3) # the 3-sphere - sage: S3.homology() - {0: 0, 1: 0, 2: 0, 3: Z} - - For pseudomanifolds, the complex constructed here will be - smaller than that obtained by taking the join with the - 0-sphere: the join adds two vertices, while this construction - only adds one. :: - - sage: T = simplicial_complexes.Torus() - sage: sorted(T.join(S0).vertices()) # 9 vertices - ['L0', 'L1', 'L2', 'L3', 'L4', 'L5', 'L6', 'R0', 'R1'] - sage: T.suspension().vertices() # 8 vertices - (0, 1, 2, 3, 4, 5, 6, 7) - """ - if n < 0: - raise ValueError("n must be non-negative.") - if n == 0: - return self - if n == 1: - if self.is_pseudomanifold(): - # Use one-point compactification of Datta. The - # construction is a bit slower, but the resulting - # complex is smaller. - V = self.vertices() - u = V[0] - w = 0 - while w in V: - w += 1 - w = Simplex([w]) - new_facets = [] - for f in self.facets(): - if u not in f: - new_facets.append(f.join(Simplex([u]), rename_vertices=False)) - new_facets.append(f.join(w, rename_vertices=False)) - return SimplicialComplex(new_facets) - else: - return self.join(SimplicialComplex([["0"], ["1"]], is_mutable=is_mutable), - rename_vertices = True) - return self.suspension(1, is_mutable).suspension(int(n-1), is_mutable) - - def disjoint_union(self, right, rename_vertices=True, is_mutable=True): - """ - The disjoint union of this simplicial complex with another one. - - :param right: the other simplicial complex (the right-hand factor) - - :param rename_vertices: If this is True, the vertices in the - disjoint union will be renamed by the formula: vertex "v" - in the left-hand factor --> vertex "Lv" in the disjoint - union, vertex "w" in the right-hand factor --> vertex "Rw" - in the disjoint union. If this is false, this tries to - construct the disjoint union without renaming the vertices; - this will cause problems if the two factors have any - vertices with names in common. - - :type rename_vertices: boolean; optional, default True - - EXAMPLES:: - - sage: S1 = simplicial_complexes.Sphere(1) - sage: S2 = simplicial_complexes.Sphere(2) - sage: S1.disjoint_union(S2).homology() - {0: Z, 1: Z, 2: Z} - """ - facets = [] - for f in self._facets: - facets.append(tuple(["L" + str(v) for v in f])) - for f in right._facets: - facets.append(tuple(["R" + str(v) for v in f])) - return SimplicialComplex(facets, is_mutable=is_mutable) - - def wedge(self, right, rename_vertices=True, is_mutable=True): - """ - The wedge (one-point union) of this simplicial complex with - another one. - - :param right: the other simplicial complex (the right-hand factor) - - :param rename_vertices: If this is ``True``, the vertices in the - wedge will be renamed by the formula: first vertex in each - are glued together and called "0". Otherwise, each vertex - "v" in the left-hand factor --> vertex "Lv" in the wedge, - vertex "w" in the right-hand factor --> vertex "Rw" in the - wedge. If this is ``False``, this tries to construct the wedge - without renaming the vertices; this will cause problems if - the two factors have any vertices with names in common. - - :type rename_vertices: boolean; optional, default ``True`` - - :param is_mutable: Determines if the output is mutable - :type is_mutable: boolean; optional, default ``True`` - - .. NOTE:: - - This operation is not well-defined if ``self`` or - ``other`` is not path-connected. - - EXAMPLES:: - - sage: S1 = simplicial_complexes.Sphere(1) - sage: S2 = simplicial_complexes.Sphere(2) - sage: S1.wedge(S2).homology() - {0: 0, 1: Z, 2: Z} - """ - left_vertices = list(self.vertices()) - left_0 = left_vertices.pop(0) - right_vertices = list(right.vertices()) - right_0 = right_vertices.pop(0) - left_dict = {left_0: 0} - right_dict = {right_0: 0} - if rename_vertices: - facets = [] - for v in left_vertices: - left_dict[v] = "L" + str(v) - for v in right_vertices: - right_dict[v] = "R" + str(v) - - for f in self._facets: - facets.append(tuple([left_dict[v] for v in f])) - for f in right._facets: - facets.append(tuple([right_dict[v] for v in f])) - else: - facets = self._facets + right._facets - return SimplicialComplex(facets, is_mutable=is_mutable) - - def chain_complex(self, subcomplex=None, augmented=False, - verbose=False, check=False, dimensions=None, - base_ring=ZZ, cochain=False): - """ - The chain complex associated to this simplicial complex. - - :param dimensions: if ``None``, compute the chain complex in all - dimensions. If a list or tuple of integers, compute the - chain complex in those dimensions, setting the chain groups - in all other dimensions to zero. - :param base_ring: commutative ring - :type base_ring: optional, default ``ZZ`` - :param subcomplex: a subcomplex of this simplicial complex. - Compute the chain complex relative to this subcomplex. - :type subcomplex: optional, default empty - :param augmented: If ``True``, return the augmented chain complex - (that is, include a class in dimension `-1` corresponding - to the empty cell). This is ignored if ``dimensions`` is - specified. - :type augmented: boolean; optional, default ``False`` - :param cochain: If ``True``, return the cochain complex (that is, - the dual of the chain complex). - :type cochain: boolean; optional, default ``False`` - :param verbose: If ``True``, print some messages as the chain - complex is computed. - :type verbose: boolean; optional, default ``False`` - :param check: If ``True``, make sure that the chain complex - is actually a chain complex: the differentials are - composable and their product is zero. - :type check: boolean; optional, default ``False`` - - .. NOTE:: - - If subcomplex is nonempty, then the argument ``augmented`` - has no effect: the chain complex relative to a nonempty - subcomplex is zero in dimension `-1`. - - The rows and columns of the boundary matrices are indexed by - the lists given by the :meth:`_n_cells_sorted` method, which by - default are sorted. - - EXAMPLES:: - - sage: circle = SimplicialComplex([[0,1], [1,2], [0, 2]]) - sage: circle.chain_complex() - Chain complex with at most 2 nonzero terms over Integer Ring - sage: circle.chain_complex()._latex_() - '\\Bold{Z}^{3} \\xrightarrow{d_{1}} \\Bold{Z}^{3}' - sage: circle.chain_complex(base_ring=QQ, augmented=True) - Chain complex with at most 3 nonzero terms over Rational Field - """ - # initialize subcomplex - if subcomplex is None: - subcomplex = SimplicialComplex(is_mutable=False) - else: - # subcomplex is not empty, so don't augment the chain complex - augmented = False - # Use an immutable copy of the subcomplex - if subcomplex._is_immutable: - subcomplex = SimplicialComplex(subcomplex._facets, maximality_check=False, - is_mutable=False) - # now construct the range of dimensions in which to compute - if dimensions is None: - dimensions = range(self.dimension() + 1) - first = 0 - else: - augmented = False - first = dimensions[0] - dimensions = list(dimensions) - differentials = {} - # in the chain complex, compute the first dimension by hand, - # and don't cache it: it may be differ from situation to - # situation because of boundary effects. - current = None - current_dim = None - if augmented: # then first == 0 - current = self._n_cells_sorted(0, subcomplex=subcomplex) - current_dim = 0 - if cochain: - differentials[-1] = matrix(base_ring, len(current), 1, - [1]*len(current)) - else: - differentials[0] = matrix(base_ring, 1, len(current), - [1]*len(current)) - elif first == 0 and not augmented: - current = self._n_cells_sorted(0, subcomplex=subcomplex) - current_dim = 0 - if not cochain: - differentials[0] = matrix(base_ring, 0, len(current)) - else: # first > 0 - current = self._n_cells_sorted(first, subcomplex=subcomplex) - current_dim = first - if not cochain: - differentials[first] = matrix(base_ring, 0, len(current)) - for n in dimensions[1:]: - if verbose: - print(" starting dimension %s" % n) - if (n, subcomplex) in self._complex: - if cochain: - differentials[n-1] = self._complex[(n, subcomplex)].transpose().change_ring(base_ring) - mat = differentials[n-1] - else: - differentials[n] = self._complex[(n, subcomplex)].change_ring(base_ring) - mat = differentials[n] - if verbose: - print(" boundary matrix (cached): it's %s by %s." % (mat.nrows(), mat.ncols())) - else: - # 'current' is the list of faces in dimension n - # - # 'old' is a dictionary, with keys the faces in the - # previous dimension (dim n-1 for the chain complex, - # n+1 for the cochain complex), values the integers 0, - # 1, 2, ... (the index of the face). finding an entry - # in a dictionary seems to be faster than finding the - # index of an entry in a list. - if current_dim == n-1: - old = dict(zip(current, range(len(current)))) - else: - set_of_faces = self._n_cells_sorted(n-1, subcomplex=subcomplex) - old = dict(zip(set_of_faces, range(len(set_of_faces)))) - current = self._n_cells_sorted(n, subcomplex=subcomplex) - current_dim = n - # construct matrix. it is easiest to construct it as - # a sparse matrix, specifying which entries are - # nonzero via a dictionary. - matrix_data = {} - col = 0 - if len(old) and len(current): - for simplex in current: - for i in range(n + 1): - face_i = simplex.face(i) - try: - matrix_data[(old[face_i], col)] = (-1)**i - except KeyError: - pass - col += 1 - mat = matrix(ZZ, len(old), len(current), matrix_data) - if cochain: - self._complex[(n, subcomplex)] = mat - differentials[n-1] = mat.transpose().change_ring(base_ring) - else: - self._complex[(n, subcomplex)] = mat - differentials[n] = mat.change_ring(base_ring) - if verbose: - print(" boundary matrix computed: it's %s by %s." % (mat.nrows(), mat.ncols())) - # now for the cochain complex, compute the last dimension by - # hand, and don't cache it. - if cochain: - n = dimensions[-1] + 1 - if current_dim != n-1: - current = self._n_cells_sorted(n-1, subcomplex=subcomplex) - differentials[n-1] = matrix(base_ring, 0, len(current)) - # finally, return the chain complex - if cochain: - return ChainComplex(data=differentials, degree=1, - base_ring=base_ring, check=check) - else: - return ChainComplex(data=differentials, degree=-1, - base_ring=base_ring, check=check) - - def _homology_(self, dim=None, base_ring=ZZ, subcomplex=None, - cohomology=False, enlarge=True, algorithm='pari', - verbose=False, reduced=True, generators=False): - """ - The (reduced) homology of this simplicial complex. - - :param dim: If ``None``, then return the homology in every - dimension. If ``dim`` is an integer or list, return the - homology in the given dimensions. (Actually, if ``dim`` is - a list, return the homology in the range from ``min(dim)`` - to ``max(dim)``.) - - :type dim: integer or list of integers or ``None``; optional, - default ``None`` - - :param base_ring: commutative ring. Must be ``ZZ`` or a field. - - :type base_ring: optional, default ``ZZ`` - - :param subcomplex: a subcomplex of this simplicial complex. - Compute homology relative to this subcomplex. - - :type subcomplex: optional, default ``None`` - - :param cohomology: If ``True``, compute cohomology rather than - homology. - - :type cohomology: boolean; optional, default ``False`` - - :param enlarge: If ``True``, find a new subcomplex homotopy - equivalent to, and probably larger than, the given one. - - :type enlarge: boolean; optional, default ``True`` - - :param algorithm: The options are ``'auto'``, ``'dhsw'``, - ``'pari'`` or ``'no_chomp'``. If ``'auto'``, first try CHomP, - then use the Dumas, Heckenbach, Saunders, and Welker elimination - algorithm for large matrices, Pari for small ones. If - ``'no_chomp'``, then don't try CHomP, but behave the same - otherwise. If ``'pari'``, then compute elementary divisors - using Pari. If ``'dhsw'``, then use the DHSW algorithm to - compute elementary divisors. (As of this writing, ``'pari'`` - is the fastest standard option. The optional CHomP package - may be better still.) - - :type algorithm: string; optional, default ``'pari'`` - - :param verbose: If ``True``, print some messages as the homology - is computed. - - :type verbose: boolean; optional, default ``False`` - - :param reduced: If ``True``, return the reduced homology. - - :type reduced: boolean; optional, default ``True`` - - :param generators: If ``True``, return the homology groups and - also generators for them. - - :type reduced: boolean; optional, default ``False`` - - Algorithm: if ``generators`` is ``True``, directly compute the - chain complex, compute its homology along with its generators, - and then convert the chain complex generators to chains in the - simplicial complex. - - Otherwise: if ``subcomplex`` is ``None``, replace it with a - facet -- a contractible subcomplex of the original complex. - Then as long as ``enlarge`` is ``True``, no matter what - ``subcomplex`` is, replace it with a subcomplex `L` which is - homotopy equivalent and as large as possible. Compute the - homology of the original complex relative to `L`: if `L` is - large, then the relative chain complex will be small enough to - speed up computations considerably. - - EXAMPLES:: - - sage: circle = SimplicialComplex([[0,1], [1,2], [0, 2]]) - sage: circle._homology_() - {0: 0, 1: Z} - sage: sphere = SimplicialComplex([[0,1,2,3]]) - sage: sphere.remove_face([0,1,2,3]) - sage: sphere - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)} - sage: sphere._homology_() - {0: 0, 1: 0, 2: Z} - sage: sphere._homology_(reduced=False) - {0: Z, 1: 0, 2: Z} - sage: sphere._homology_(base_ring=GF(2), reduced=False) - {0: Vector space of dimension 1 over Finite Field of size 2, - 1: Vector space of dimension 0 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2} - - We need an immutable complex to compute homology generators:: - - sage: sphere.set_immutable() - sage: sphere._homology_(generators=True, algorithm='no_chomp') - {0: [], 1: [], 2: [(Z, (0, 1, 2) - (0, 1, 3) + (0, 2, 3) - (1, 2, 3))]} - - Note that the answer may be formatted differently if the - optional package CHomP is installed. - - Another way to get a two-sphere: take a two-point space and take its - three-fold join with itself:: - - sage: S = SimplicialComplex([[0], [1]]) - sage: (S*S*S)._homology_(dim=2, cohomology=True) - Z - - The same computation, done without finding a contractible subcomplex:: - - sage: (S*S*S)._homology_(dim=2, cohomology=True, enlarge=False) - Z - - Relative homology:: - - sage: T = SimplicialComplex([[0,1,2]]) - sage: U = SimplicialComplex([[0,1], [1,2], [0,2]]) - sage: T._homology_(subcomplex=U) - {0: 0, 1: 0, 2: Z} - - Generators:: - - sage: simplicial_complexes.Torus().homology(generators=True, algorithm='no_chomp') - {0: [], - 1: [(Z, (1, 2) - (1, 6) + (2, 6)), (Z, (3, 4) - (3, 6) + (4, 6))], - 2: [(Z, - (0, 1, 2) - (0, 1, 5) + (0, 2, 6) - (0, 3, 4) + (0, 3, 5) - (0, 4, 6) - (1, 2, 4) + (1, 3, 4) - (1, 3, 6) + (1, 5, 6) - (2, 3, 5) + (2, 3, 6) + (2, 4, 5) - (4, 5, 6))]} - """ - from sage.homology.homology_group import HomologyGroup - - if dim is not None: - if isinstance(dim, (list, tuple, range)): - low = min(dim) - 1 - high = max(dim) + 2 - else: - low = dim - 1 - high = dim + 2 - dims = range(low, high) - else: - dims = None - - if generators: - enlarge = False - - if verbose: - print("starting calculation of the homology of this") - print("%s-dimensional simplicial complex" % self.dimension()) - if subcomplex is None: - if enlarge: - if verbose: - print("Constructing contractible subcomplex...") - L = self._contractible_subcomplex(verbose=verbose) - if verbose: - print("Done finding contractible subcomplex.") - vec = [len(self.faces(subcomplex=L)[n-1]) for n in range(self.dimension()+2)] - print("The difference between the f-vectors is:") - print(" %s" % vec) - else: - L = SimplicialComplex([[self.vertices()[0]]]) - else: - if enlarge: - if verbose: - print("Enlarging subcomplex...") - L = self._enlarge_subcomplex(subcomplex, verbose=verbose) - if verbose: - print("Done enlarging subcomplex:") - else: - L = subcomplex - L.set_immutable() - - if verbose: - print("Computing the chain complex...") - C = self.chain_complex(dimensions=dims, augmented=reduced, - cochain=cohomology, base_ring=base_ring, - subcomplex=L, verbose=verbose) - if verbose: - print(" Done computing the chain complex. ") - print("Now computing homology...") - answer = C.homology(base_ring=base_ring, verbose=verbose, - algorithm=algorithm, generators=generators) - - if generators: - # Convert chain complex information to simplicial complex - # information. - for i in answer: - H_with_gens = answer[i] - if H_with_gens: - chains = self.n_chains(i, base_ring=base_ring) - new_H = [] - for (H, gen) in H_with_gens: - v = gen.vector(i) - new_gen = chains.zero() - for (coeff, chain) in zip(v, chains.gens()): - new_gen += coeff * chain - new_H.append((H, new_gen)) - answer[i] = new_H - - if dim is None: - dim = range(self.dimension() + 1) - zero = HomologyGroup(0, base_ring) - if isinstance(dim, (list, tuple, range)): - # Fix non-reduced answer. - if subcomplex is None and not reduced and 0 in dim: - try: - if base_ring.is_field(): - rank = answer[0].dimension() - else: - rank = len(answer[0].invariants()) - except KeyError: - rank = 0 - answer[0] = HomologyGroup(rank + 1, base_ring) - return dict([d, answer.get(d, zero)] for d in dim) - return answer.get(dim, zero) - - # This is cached for speed reasons: it can be very slow to run - # this function. - @cached_method - def algebraic_topological_model(self, base_ring=None): - r""" - Algebraic topological model for this simplicial complex with - coefficients in ``base_ring``. - - The term "algebraic topological model" is defined by Pilarczyk - and Réal [PR2015]_. - - INPUT: - - - ``base_ring`` - coefficient ring (optional, default - ``QQ``). Must be a field. - - Denote by `C` the chain complex associated to this simplicial - complex. The algebraic topological model is a chain complex - `M` with zero differential, with the same homology as `C`, - along with chain maps `\pi: C \to M` and `\iota: M \to C` - satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic - to `1_C`. The chain homotopy `\phi` must satisfy - - - `\phi \phi = 0`, - - `\pi \phi = 0`, - - `\phi \iota = 0`. - - Such a chain homotopy is called a *chain contraction*. - - OUTPUT: a pair consisting of - - - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and - `\iota` - - the chain complex `M` - - Note that from the chain contraction ``phi``, one can recover the - chain maps `\pi` and `\iota` via ``phi.pi()`` and - ``phi.iota()``. Then one can recover `C` and `M` from, for - example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, - respectively. - - EXAMPLES:: - - sage: RP2 = simplicial_complexes.RealProjectivePlane() - sage: phi, M = RP2.algebraic_topological_model(GF(2)) - sage: M.homology() - {0: Vector space of dimension 1 over Finite Field of size 2, - 1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2} - sage: T = simplicial_complexes.Torus() - sage: phi, M = T.algebraic_topological_model(QQ) - sage: M.homology() - {0: Vector space of dimension 1 over Rational Field, - 1: Vector space of dimension 2 over Rational Field, - 2: Vector space of dimension 1 over Rational Field} - """ - from .algebraic_topological_model import algebraic_topological_model - if base_ring is None: - base_ring = QQ - return algebraic_topological_model(self, base_ring) - - def alexander_whitney(self, simplex, dim_left): - r""" - Subdivide this simplex into a pair of simplices. - - If this simplex has vertices `v_0`, `v_1`, ..., `v_n`, then - subdivide it into simplices `(v_0, v_1, ..., v_{dim})` and - `(v_{dim}, v_{dim + 1}, ..., v_n)`. - - See :meth:`Simplex.alexander_whitney` for more details. This - method just calls that one. - - INPUT: - - - ``simplex`` -- a simplex in this complex - - ``dim`` -- integer between 0 and one more than the - dimension of this simplex - - OUTPUT: a list containing just the triple ``(1, left, - right)``, where ``left`` and ``right`` are the two simplices - described above. - - EXAMPLES:: - - sage: s = Simplex((0,1,3,4)) - sage: X = SimplicialComplex([s]) - sage: X.alexander_whitney(s, 0) - [(1, (0,), (0, 1, 3, 4))] - sage: X.alexander_whitney(s, 2) - [(1, (0, 1, 3), (3, 4))] - """ - return simplex.alexander_whitney(dim_left) - - def add_face(self, face): - """ - Add a face to this simplicial complex. - - :param face: a subset of the vertex set - - This *changes* the simplicial complex, adding a new face and all - of its subfaces. - - EXAMPLES:: - - sage: X = SimplicialComplex([[0,1], [0,2]]) - sage: X.add_face([0,1,2,]); X - Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} - sage: Y = SimplicialComplex(); Y - Simplicial complex with vertex set () and facets {()} - sage: Y.add_face([0,1]) - sage: Y.add_face([1,2,3]) - sage: Y - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (1, 2, 3)} - - If you add a face which is already present, there is no effect:: - - sage: Y.add_face([1,3]); Y - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (1, 2, 3)} - - TESTS: - - Check that the bug reported at :trac:`14354` has been fixed:: - - sage: T = SimplicialComplex([range(1,5)]).n_skeleton(1) - sage: T.homology(algorithm='no_chomp') - {0: 0, 1: Z x Z x Z} - sage: T.add_face([1,2,3]) - sage: T.homology(algorithm='no_chomp') - {0: 0, 1: Z x Z, 2: 0} - - Check that the ``_faces`` cache is treated correctly - (:trac:`20758`):: - - sage: T = SimplicialComplex([range(1,5)]).n_skeleton(1) - sage: _ = T.faces() # populate the _faces attribute - sage: _ = T.homology() # add more to _faces - sage: T.add_face((1,2,3)) - sage: all(Simplex((1,2,3)) in T._faces[L][2] for L in T._faces) - True - - Check that the ``__enlarged`` cache is treated correctly - (:trac:`20758`):: - - sage: T = SimplicialComplex([range(1,5)]).n_skeleton(1) - sage: T.homology(algorithm='no_chomp') # to populate the __enlarged attribute - {0: 0, 1: Z x Z x Z} - sage: T.add_face([1,2,3]) - sage: len(T._SimplicialComplex__enlarged) > 0 - True - - Check we've fixed the bug reported at :trac:`14578`:: - - sage: t0 = SimplicialComplex() - sage: t0.add_face(('a', 'b')) - sage: t0.add_face(('c', 'd', 'e')) - sage: t0.add_face(('e', 'f', 'c')) - sage: t0.homology() - {0: Z, 1: 0, 2: 0} - - Check that we've fixed the bug reported at :trac:`22880`:: - - sage: X = SimplicialComplex([[0], [1]]) - sage: temp = X.faces(SimplicialComplex(())) - sage: X.add_face([0,1]) - """ - if self._is_immutable: - raise ValueError("This simplicial complex is not mutable") - - vertex_to_index = self._translation_to_numeric() - - # Update vertex_to_index by giving each new vertex a larger - # entry than the existing ones. - if vertex_to_index: - idx = max(vertex_to_index.values()) + 1 - else: - idx = 0 - new_vertices = [] - for v in face: - if v not in self.vertices(): - new_vertices.append(v) - vertex_to_index[v] = idx - idx += 1 - - new_face = Simplex(sorted(face, key=vertex_to_index.__getitem__)) - - face_is_maximal = True - for other in self._facets: - if face_is_maximal: - face_is_maximal = not new_face.is_face(other) - if face_is_maximal: - # remove any old facets which are no longer maximal - Facets = list(self._facets) - for old_face in self._facets: - if old_face.is_face(new_face): - Facets.remove(old_face) - # add new_face to facet list - Facets.append(new_face) - self._facets = Facets - - # Update the vertex set - self._vertex_to_index = vertex_to_index - - # Update self._faces. - all_new_faces = SimplicialComplex([new_face]).faces() - for L in self._faces: - L_complex = self._faces[L] - for dim in range(new_face.dimension()+1): - if dim in L_complex: - if L is None: - new_faces = all_new_faces[dim] - else: - new_faces = all_new_faces[dim].difference(L.n_cells(dim)) - L_complex[dim] = L_complex[dim].union(new_faces) - else: - L_complex[dim] = all_new_faces[dim] - # update self._graph if necessary - if self._graph is not None: - d = new_face.dimension()+1 - for i in range(d): - for j in range(i + 1, d): - self._graph.add_edge(new_face[i], new_face[j]) - self._complex = {} - self.__contractible = None - - def remove_face(self, face, check=False): - """ - Remove a face from this simplicial complex. - - :param face: a face of the simplicial complex - - :param check: boolean; optional, default ``False``. If - ``True``, raise an error if ``face`` is not a - face of this simplicial complex - - This does not return anything; instead, it *changes* the - simplicial complex. - - ALGORITHM: - - The facets of the new simplicial complex are - the facets of the original complex not containing ``face``, - together with those of ``link(face)*boundary(face)``. - - EXAMPLES:: - - sage: S = range(1,5) - sage: Z = SimplicialComplex([S]); Z - Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)} - sage: Z.remove_face([1,2]) - sage: Z - Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 3, 4), (2, 3, 4)} - - sage: S = SimplicialComplex([[0,1,2],[2,3]]) - sage: S - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(2, 3), (0, 1, 2)} - sage: S.remove_face([0,1,2]) - sage: S - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 2), (1, 2), (2, 3)} - - TESTS: - - Check that the ``_faces`` cache is treated properly: see - :trac:`20758`:: - - sage: T = SimplicialComplex([range(1,5)]).n_skeleton(1) - sage: _ = T.faces() # populate the _faces attribute - sage: _ = T.homology(algorithm='no_chomp') # add more to _faces - sage: T.add_face((1,2,3)) - sage: T.remove_face((1,2,3)) - sage: len(T._faces) - 2 - sage: T.remove_face((1,2)) - sage: len(T._faces) - 1 - - Check that the face to be removed can be given with a - different vertex ordering:: - - sage: S = SimplicialComplex([[1,2], [1,3]]) - sage: S.remove_face([3,1]) - sage: S - Simplicial complex with vertex set (1, 2, 3) and facets {(3,), (1, 2)} - """ - if self._is_immutable: - raise ValueError("This simplicial complex is not mutable") - - getindex = self._translation_to_numeric().__getitem__ - simplex = Simplex(sorted(face, key=getindex)) - facets = self.facets() - if all(not simplex.is_face(F) for F in facets): - # face is not in self - if check: - raise ValueError('trying to remove a face which is not in the simplicial complex') - return - link = self.link(simplex) - join_facets = [] - for f in simplex.faces(): - for g in link.facets(): - join_facets.append(f.join(g, rename_vertices=False)) - # join_facets is the list of facets in the join bdry(face) * link(face) - remaining = join_facets + [elem for elem in facets if not simplex.is_face(elem)] - - # Check to see if there are any non-maximal faces - # build set of facets - self._facets = [] - for f in remaining: - face2 = Simplex(f) - face_is_maximal = True - faces_to_be_removed = [] - for other in self._facets: - if other.is_face(face2): - faces_to_be_removed.append(other) - elif face_is_maximal: - face_is_maximal = not face2.is_face(other) - for x in faces_to_be_removed: - self._facets.remove(x) - face2 = Simplex(sorted(face2.tuple())) - if face_is_maximal: - self._facets.append(face2) - # if no maximal faces, add the empty face as a facet - if len(remaining) == 0: - self._facets.append(Simplex(-1)) - - # Recreate the vertex set - from sage.misc.misc import union - vertices = tuple(reduce(union, self._facets)) - for v in self.vertices(): - if v not in vertices: - del self._vertex_to_index[v] - - # Update self._faces. - # Note: can't iterate over self._faces, because the dictionary - # size may change during iteration. - for L in list(self._faces): - del self._faces[L] - if L is None or Simplex(face) not in L: - self.faces(L) - # Update self._graph if necessary. - if self._graph is not None: - # Only if removing a 1 or 2 dim face will the graph be affected - if len(face) == 1: - self._graph.delete_vertex(face[0]) - self._graph.add_vertex(face[0]) - elif len(face) == 2: - self._graph.delete_edge(face[0], face[1]) - self._complex = {} - self.__contractible = None - self.__enlarged = {} - - def remove_faces(self, faces, check=False): - """ - Remove a collection of faces from this simplicial complex. - - :param faces: a list (or any iterable) of faces of the - simplicial complex - - :param check: boolean; optional, default ``False``. If - ``True``, raise an error if any element of ``faces`` is not a - face of this simplicial complex - - This does not return anything; instead, it *changes* the - simplicial complex. - - ALGORITHM: - - Run ``self.remove_face(f)`` repeatedly, for ``f`` in ``faces``. - - EXAMPLES:: - - sage: S = range(1,5) - sage: Z = SimplicialComplex([S]); Z - Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)} - sage: Z.remove_faces([[1,2]]) - sage: Z - Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 3, 4), (2, 3, 4)} - - sage: Z = SimplicialComplex([S]); Z - Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)} - sage: Z.remove_faces([[1,2], [2,3]]) - sage: Z - Simplicial complex with vertex set (1, 2, 3, 4) and facets {(2, 4), (1, 3, 4)} - - TESTS: - - Check the ``check`` argument:: - - sage: Z = SimplicialComplex([[1,2,3,4]]) - sage: Z.remove_faces([[1,2], [3,4]]) - sage: Z.remove_faces([[1,2]]) - sage: Z.remove_faces([[1,2]], check=True) - Traceback (most recent call last): - ... - ValueError: trying to remove a face which is not in the simplicial complex - """ - for f in faces: - self.remove_face(f, check=check) - - def connected_sum(self, other, is_mutable=True): - """ - The connected sum of this simplicial complex with another one. - - :param other: another simplicial complex - :param is_mutable: Determines if the output is mutable - :type is_mutable: boolean; optional, default ``True`` - :return: the connected sum ``self # other`` - - .. WARNING:: - - This does not check that ``self`` and ``other`` are manifolds, - only that their facets all have the same dimension. Since a - (more or less) random facet is chosen from each complex and - then glued together, this method may return random - results if applied to non-manifolds, depending on which - facet is chosen. - - Algorithm: a facet is chosen from each surface, and removed. - The vertices of these two facets are relabeled to - ``(0,1,...,dim)``. Of the remaining vertices, the ones from - the left-hand factor are renamed by prepending an "L", and - similarly the remaining vertices in the right-hand factor are - renamed by prepending an "R". - - EXAMPLES:: - - sage: S1 = simplicial_complexes.Sphere(1) - sage: S1.connected_sum(S1.connected_sum(S1)).homology() - {0: 0, 1: Z} - sage: P = simplicial_complexes.RealProjectivePlane(); P - Minimal triangulation of the real projective plane - sage: P.connected_sum(P) # the Klein bottle - Simplicial complex with 9 vertices and 18 facets - - The notation '+' may be used for connected sum, also:: - - sage: P + P # the Klein bottle - Simplicial complex with 9 vertices and 18 facets - sage: (P + P).homology()[1] - Z x C2 - """ - if not (self.is_pure() and other.is_pure() and - self.dimension() == other.dimension()): - raise ValueError("complexes are not pure of the same dimension") - # first find a top-dimensional simplex to remove from each surface - keep_left = self._facets[0] - keep_right = other._facets[0] - # construct the set of facets: - left = set(self._facets).difference(set([keep_left])) - right = set(other._facets).difference(set([keep_right])) - facet_set = ([[rename_vertex(v, keep=list(keep_left)) - for v in face] for face in left] - + [[rename_vertex(v, keep=list(keep_right), left=False) - for v in face] for face in right]) - # return the new surface - return SimplicialComplex(facet_set, is_mutable=is_mutable) - - __add__ = connected_sum - - def link(self, simplex, is_mutable=True): - r""" - The link of a simplex in this simplicial complex. - - The link of a simplex `F` is the simplicial complex formed by - all simplices `G` which are disjoint from `F` but for which `F - \cup G` is a simplex. - - :param simplex: a simplex in this simplicial complex. - :param is_mutable: Determines if the output is mutable - :type is_mutable: boolean; optional, default ``True`` - - EXAMPLES:: - - sage: X = SimplicialComplex([[0,1,2], [1,2,3]]) - sage: X.link(Simplex([0])) - Simplicial complex with vertex set (1, 2) and facets {(1, 2)} - sage: X.link([1,2]) - Simplicial complex with vertex set (0, 3) and facets {(0,), (3,)} - sage: Y = SimplicialComplex([[0,1,2,3]]) - sage: Y.link([1]) - Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2, 3)} - """ - faces = [] - s = Simplex(simplex) - for f in self._facets: - if s.is_face(f): - faces.append(Simplex(f.set().difference(s.set()))) - return SimplicialComplex(faces, is_mutable=is_mutable) - - def star(self, simplex, is_mutable=True): - """ - Return the star of a simplex in this simplicial complex. - - The star of ``simplex`` is the simplicial complex formed by - all simplices which contain ``simplex``. - - INPUT: - - - ``simplex`` -- a simplex in this simplicial complex - - ``is_mutable`` -- (default: ``True``) boolean; determines if the output - is mutable - - EXAMPLES:: - - sage: X = SimplicialComplex([[0,1,2], [1,2,3]]) - sage: X.star(Simplex([0])) - Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} - sage: X.star(Simplex([1])) - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (1, 2, 3)} - sage: X.star(Simplex([1,2])) - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (1, 2, 3)} - sage: X.star(Simplex([])) - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (1, 2, 3)} - """ - faces = [] - s = Simplex(simplex) - for f in self._facets: - if s.is_face(f): - faces.append(f) - return SimplicialComplex(faces, is_mutable=is_mutable) - - def is_cohen_macaulay(self, base_ring=QQ, ncpus=0): - r""" - Return ``True`` if ``self`` is Cohen-Macaulay. - - A simplicial complex `\Delta` is Cohen-Macaulay over `R` iff - `\tilde{H}_i(\mathrm{lk}_\Delta(F);R) = 0` for all - `F \in \Delta` and `i < \dim\mathrm{lk}_\Delta(F)`. - Here, `\Delta` is ``self`` and `R` is ``base_ring``, and - `\mathrm{lk}` denotes the link operator on ``self``. - - INPUT: - - - ``base_ring`` -- (default: ``QQ``) the base ring. - - - ``ncpus`` -- (default: 0) number of cpus used for the - computation. If this is 0, determine the number of cpus - automatically based on the hardware being used. - - For finite simplicial complexes, this is equivalent to the - statement that the Stanley-Reisner ring of ``self`` is - Cohen-Macaulay. - - EXAMPLES: - - Spheres are Cohen-Macaulay:: - - sage: S = SimplicialComplex([[1,2],[2,3],[3,1]]) - sage: S.is_cohen_macaulay(ncpus=3) - True - - The following example is taken from Bruns, Herzog - Cohen-Macaulay - rings, Figure 5.3:: - - sage: S = SimplicialComplex([[1,2,3],[1,4,5]]) - sage: S.is_cohen_macaulay(ncpus=3) - False - - The choice of base ring can matter. The real projective plane `\RR P^2` - has `H_1(\RR P^2) = \ZZ/2`, hence is CM over `\QQ` but not over `\ZZ`. :: - - sage: X = simplicial_complexes.RealProjectivePlane() - sage: X.is_cohen_macaulay() - True - sage: X.is_cohen_macaulay(ZZ) - False - """ - from sage.parallel.decorate import parallel - - if not ncpus: - from sage.parallel.ncpus import ncpus as get_ncpus - ncpus = get_ncpus() - - facs = [ x for x in self.face_iterator() ] - n = len(facs) - facs_divided = [ [] for i in range(ncpus) ] - for i in range(n): - facs_divided[i % ncpus].append(facs[i]) - - def all_homologies_vanish(F): - S = self.link(F) - H = S.homology(base_ring=base_ring) - if base_ring.is_field(): - return all( H[j].dimension() == 0 for j in range(S.dimension()) ) - else: - return not any( H[j].invariants() for j in range(S.dimension()) ) - - @parallel(ncpus=ncpus) - def all_homologies_in_list_vanish(Fs): - return all( all_homologies_vanish(F) for F in Fs ) - - return all( answer[1] for answer in all_homologies_in_list_vanish(facs_divided) ) - - def generated_subcomplex(self, sub_vertex_set, is_mutable=True): - """ - Return the largest sub-simplicial complex of ``self`` containing - exactly ``sub_vertex_set`` as vertices. - - :param sub_vertex_set: The sub-vertex set. - :param is_mutable: Determines if the output is mutable - :type is_mutable: boolean; optional, default ``True`` - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(2) - sage: S - Minimal triangulation of the 2-sphere - sage: S.generated_subcomplex([0,1,2]) - Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} - - """ - if not set(self.vertices()).issuperset(sub_vertex_set): - raise ValueError("input must be a subset of the vertex set") - faces = [] - for i in range(self.dimension() + 1): - for j in self.faces()[i]: - if j.set().issubset(sub_vertex_set): - faces.append(j) - return SimplicialComplex(faces, maximality_check=True, - is_mutable=is_mutable) - - def is_shelling_order(self, shelling_order, certificate=False): - r""" - Return if the order of the facets given by ``shelling_order`` - is a shelling order for ``self``. - - A sequence of facets `(F_i)_{i=1}^N` of a simplicial - complex of dimension `d` is a *shelling order* if for all - `i = 2, 3, 4, \ldots`, the complex - - .. MATH:: - - X_i = \left( \bigcup_{j=1}^{i-1} F_j \right) \cap F_i - - is pure and of dimension `\dim F_i - 1`. - - INPUT: - - - ``shelling_order`` -- an ordering of the facets of ``self`` - - ``certificate`` -- (default: ``False``) if ``True`` then returns - the index of the first facet that violate the condition - - .. SEEALSO:: - - :meth:`is_shellable` - - EXAMPLES:: - - sage: facets = [[1,2,5],[2,3,5],[3,4,5],[1,4,5]] - sage: X = SimplicialComplex(facets) - sage: X.is_shelling_order(facets) - True - - sage: b = [[1,2,5], [3,4,5], [2,3,5], [1,4,5]] - sage: X.is_shelling_order(b) - False - sage: X.is_shelling_order(b, True) - (False, 1) - - A non-pure example:: - - sage: facets = [[1,2,3], [3,4], [4,5], [5,6], [4,6]] - sage: X = SimplicialComplex(facets) - sage: X.is_shelling_order(facets) - True - - REFERENCES: - - - [BW1996]_ - """ - # Quick check by Lemma 2.2 in [BW1996] - if self.dimension() != len(list(shelling_order[0])) - 1: - return False - - cur_complex = SimplicialComplex([]) - for i, F in enumerate(shelling_order): - if i > 0: - # The shelling condition is precisely that intersection is - # a pure complex of one dimension less and stop if this fails - common = set(F).intersection(set(cur_complex.vertices())) - intersection = cur_complex.generated_subcomplex(list(common)) - - dim = len(list(F)) - 1 - if not intersection.is_pure() or dim - 1 != intersection.dimension(): - if certificate: - return (False, i) - return False - cur_complex.add_face(F) - return True - - @cached_method - def is_shellable(self, certificate=False): - r""" - Return if ``self`` is shellable. - - A simplicial complex is shellable if there exists a shelling - order. - - .. NOTE:: - - 1. This method can check all orderings of the facets by brute - force, hence can be very slow. - - 2. This is shellability in the general (nonpure) sense of - Bjorner and Wachs [BW1996]_. This method does not check purity. - - .. SEEALSO:: - - :meth:`is_shelling_order` - - INPUT: - - - ``certificate`` -- (default: ``False``) if ``True`` then - returns the shelling order (if it exists) - - EXAMPLES:: - - sage: X = SimplicialComplex([[1,2,5], [2,3,5], [3,4,5], [1,4,5]]) - sage: X.is_shellable() - True - sage: order = X.is_shellable(True); order - ((1, 2, 5), (2, 3, 5), (1, 4, 5), (3, 4, 5)) - sage: X.is_shelling_order(order) - True - - sage: X = SimplicialComplex([[1,2,3], [3,4,5]]) - sage: X.is_shellable() - False - - Examples from Figure 1 in [BW1996]_:: - - sage: X = SimplicialComplex([[1,2,3], [3,4], [4,5], [5,6], [4,6]]) - sage: X.is_shellable() - True - - sage: X = SimplicialComplex([[1,2,3], [3,4], [4,5,6]]) - sage: X.is_shellable() - False - - REFERENCES: - - - :wikipedia:`Shelling_(topology)` - """ - if not certificate: - return bool(self.is_shellable(certificate=True)) - - if self.is_pure(): - if any(x < 0 for x in self.h_vector()): - return False - else: # Non-pure complex - if any(x < 0 for row in self.h_triangle() for x in row): - return False - - facets = set(self.facets()) - cur_order = [] - # For consistency when using different Python versions, for example, sort 'faces'. - it = [iter(sorted(facets, key=str))] - cur_complex = SimplicialComplex([]) - while facets: - try: - F = next(it[-1]) - except StopIteration: - # Backtrace - if not cur_order: - return False - it.pop() - facets.add(cur_order.pop()) - cur_complex = SimplicialComplex(cur_order) - continue - - # First facet must be top dimensional - if not cur_order: - if self.dimension() == F.dimension(): - cur_complex.add_face(F) - cur_order.append(F) - facets.remove(F) - it.append(iter(set(facets))) - continue - - - # The shelling condition is precisely that intersection is - # a pure complex of one dimension less and stop if this fails - common = set(F).intersection(set(cur_complex.vertices())) - intersection = cur_complex.generated_subcomplex(list(common)) - - if (not intersection.is_pure() - or F.dimension() - 1 != intersection.dimension()): - continue - cur_complex.add_face(F) - cur_order.append(F) - facets.remove(F) - it.append(iter(set(facets))) # Iterate over a copy of the current facets - - return tuple(cur_order) - - def restriction_sets(self, order): - """ - Return the restriction sets of the facets according to ``order``. - - A restriction set of a shelling order is the sequence of - smallest new faces that are created during the shelling order. - - .. SEEALSO:: - - :meth:`is_shelling_order` - - EXAMPLES:: - - sage: facets = [[1,2,5], [2,3,5], [3,4,5], [1,4,5]] - sage: X = SimplicialComplex(facets) - sage: X.restriction_sets(facets) - [(), (3,), (4,), (1, 4)] - - sage: b = [[1,2,5], [3,4,5], [2,3,5], [1,4,5]] - sage: X.restriction_sets(b) - Traceback (most recent call last): - ... - ValueError: not a shelling order - """ - # It starts with the first empty - restrictions = [()] - - # Each time we hit a facet, the complement goes to the restriction - cur_complex = SimplicialComplex([]) - for i, F in enumerate(order): - if i > 0: - # The shelling condition is precisely that intersection is - # a pure complex of one dimension less and stop if this fails - common = set(F).intersection(set(cur_complex.vertices())) - intersection = cur_complex.generated_subcomplex(list(common)) - - if not intersection.is_pure() or self.dimension() - 1 > intersection.dimension(): - raise ValueError("not a shelling order") - faces = SimplicialComplex([F]).faces() - for k, v in intersection.faces().items(): - faces[k] = faces[k].difference(v) - for k in sorted(faces.keys()): - if faces[k]: - restrictions.append(faces[k].pop()) - break - cur_complex.add_face(F) - - return restrictions - - def _complement(self, simplex): - """ - Return the complement of a simplex in the vertex set of this - simplicial complex. - - :param simplex: a simplex (need not be in the simplicial complex) - - OUTPUT: its complement: the simplex formed by the vertices not - contained in ``simplex``. - - Note that this only depends on the vertex set of the - simplicial complex, not on its simplices. - - EXAMPLES:: - - sage: X = SimplicialComplex([[0,1,2,3,4,5]]) - sage: X._complement([1,2,3]) - (0, 4, 5) - sage: X._complement([0,1,3,4]) - (2, 5) - sage: X._complement([0,4,1,3]) - (2, 5) - """ - return Simplex(set(self.vertices()).difference(simplex)) - - def _transpose_simplices(self, *simplices): - """ - Given tuple ``L`` of simplices, returns new list, where each - simplex is formed by taking a vertex from each simplex from - ``L``. - - :param simplices: a bunch of simplices - - If ``simplices`` consists of `(f_0, f_1, f_2, ...)`, then the - output consists of all possible simplices of the form `(v_0, - v_1, v_2, ...)`, where `v_i` is a vertex of `f_i`. If a - vertex appears more than once in such a simplex, remove all - but one of its appearances. If such a simplex contains others - already produced, then ignore that larger simplex -- the - output should be a list of minimal simplices constructed in - this way. - - This is used in computing the minimal nonfaces and hence the - Stanley-Reisner ring. - - Note that this only depends on the vertex set of the - simplicial complex, not on its simplices. - - I don't know if there is a standard name for this, but it - looked sort of like the transpose of a matrix; hence the name - for this method. - - EXAMPLES:: - - sage: X = SimplicialComplex() - sage: X._transpose_simplices([1,2]) - [(1,), (2,)] - sage: X._transpose_simplices([1,2], [3,4]) - [(1, 3), (1, 4), (2, 3), (2, 4)] - - In the following example, one can construct the simplices - ``(1,2)`` and ``(1,3)``, but you can also construct ``(1,1) = (1,)``, - which is a face of both of the others. So the answer omits - ``(1,2)`` and ``(1,3)``:: - - sage: X._transpose_simplices([1,2], [1,3]) - [(1,), (2, 3)] - """ - answer = [] - if len(simplices) == 1: - answer = [Simplex((v,)) for v in simplices[0]] - elif len(simplices) > 1: - face = simplices[0] - rest = simplices[1:] - for v in face: - for partial in self._transpose_simplices(*rest): - if v not in partial: - L = sorted([v] + list(partial)) - simplex = Simplex(L) - else: - simplex = partial - add_simplex = True - simplices_to_delete = [] - for already in answer: - if add_simplex: - if already.is_face(simplex): - add_simplex = False - if add_simplex and simplex.is_face(already): - simplices_to_delete.append(already) - if add_simplex: - answer.append(simplex) - for x in simplices_to_delete: - answer.remove(x) - return answer - - def minimal_nonfaces(self): - """ - Set consisting of the minimal subsets of the vertex set of - this simplicial complex which do not form faces. - - Algorithm: Proceeds through the faces of the complex increasing the - dimension, starting from dimension 0, and add the faces that are not - contained in the complex and that are not already contained in a - previously seen minimal non-face. - - This is used in computing the - :meth:`Stanley-Reisner ring` and the - :meth:`Alexander dual`. - - EXAMPLES:: - - sage: X = SimplicialComplex([[1,3],[1,2]]) - sage: X.minimal_nonfaces() - {(2, 3)} - sage: Y = SimplicialComplex([[0,1], [1,2], [2,3], [3,0]]) - sage: sorted(Y.minimal_nonfaces()) - [(0, 2), (1, 3)] - - TESTS:: - - sage: SC = SimplicialComplex([(0,1,2),(0,2,3),(2,3,4),(1,2,4), \ - (1,4,5),(0,3,6),(3,6,7),(4,5,7)]) - - This was taking a long time before :trac:`20078`:: - - sage: sorted(SC.minimal_nonfaces()) - [(0, 4), - (0, 5), - (0, 7), - (1, 3), - (1, 6), - (1, 7), - (2, 5), - (2, 6), - (2, 7), - (3, 4, 7), - (3, 5), - (4, 6), - (5, 6)] - """ - face_dict = self.faces() - vertices = self.vertices() - dimension = self.dimension() - set_mnf = set() - - for dim in range(dimension + 1): - face_sets = frozenset(f.set() for f in face_dict[dim]) - for candidate in combinations(vertices, dim + 1): - set_candidate = frozenset(candidate) - if set_candidate not in face_sets: - new = not any(set_candidate.issuperset(mnf) for mnf in set_mnf) - if new: - set_mnf.add(set_candidate) - - for candidate in combinations(vertices, dimension+2): # Checks for minimal nonfaces in the remaining dimension - set_candidate = frozenset(candidate) - new = not any(set_candidate.issuperset(mnf) for mnf in set_mnf) - if new: - set_mnf.add(set_candidate) - - min_non_faces = Set([Simplex(mnf) for mnf in set_mnf]) - - return min_non_faces - - def _stanley_reisner_base_ring(self, base_ring=ZZ): - """ - The polynomial algebra of which the Stanley-Reisner ring is a - quotient. - - :param base_ring: a commutative ring - :type base_ring: optional, default ``ZZ`` - :return: a polynomial algebra with coefficients in base_ring, - with one generator for each vertex in the simplicial complex. - - See the documentation for :meth:`stanley_reisner_ring` for a - warning about the names of the vertices. - - EXAMPLES:: - - sage: X = SimplicialComplex([[1,2], [0], [3]]) - sage: X._stanley_reisner_base_ring() - Multivariate Polynomial Ring in x0, x1, x2, x3 over Integer Ring - sage: Y = SimplicialComplex([['a', 'b', 'c']]) - sage: Y._stanley_reisner_base_ring(base_ring=QQ) - Multivariate Polynomial Ring in a, b, c over Rational Field - """ - verts = self._gen_dict.values() - try: - verts = sorted(verts) - except TypeError: - verts = sorted(verts, key=str) - return PolynomialRing(base_ring, verts) - - def stanley_reisner_ring(self, base_ring=ZZ): - """ - The Stanley-Reisner ring of this simplicial complex. - - :param base_ring: a commutative ring - :type base_ring: optional, default ``ZZ`` - :return: a quotient of a polynomial algebra with coefficients - in ``base_ring``, with one generator for each vertex in the - simplicial complex, by the ideal generated by the products - of those vertices which do not form faces in it. - - Thus the ideal is generated by the products corresponding to - the minimal nonfaces of the simplicial complex. - - .. WARNING:: - - This may be quite slow! - - Also, this may behave badly if the vertices have the - 'wrong' names. To avoid this, define the simplicial complex - at the start with the flag ``name_check`` set to ``True``. - - More precisely, this is a quotient of a polynomial ring - with one generator for each vertex. If the name of a - vertex is a non-negative integer, then the corresponding - polynomial generator is named ``'x'`` followed by that integer - (e.g., ``'x2'``, ``'x3'``, ``'x5'``, ...). Otherwise, the - polynomial generators are given the same names as the vertices. - Thus if the vertex set is ``(2, 'x2')``, there will be problems. - - EXAMPLES:: - - sage: X = SimplicialComplex([[0,1,2], [0,2,3]]) - sage: X.stanley_reisner_ring() - Quotient of Multivariate Polynomial Ring in x0, x1, x2, x3 over Integer Ring by the ideal (x1*x3) - sage: Y = SimplicialComplex([[0,1,2,3,4]]); Y - Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 2, 3, 4)} - sage: Y.add_face([0,1,2,3,4]) - sage: Y.stanley_reisner_ring(base_ring=QQ) - Multivariate Polynomial Ring in x0, x1, x2, x3, x4 over Rational Field - """ - R = self._stanley_reisner_base_ring(base_ring) - products = [] - for f in self.minimal_nonfaces(): - prod = 1 - for v in f: - prod *= R(self._gen_dict[v]) - products.append(prod) - return R.quotient(products) - - def alexander_dual(self, is_mutable=True): - """ - The Alexander dual of this simplicial complex: according to - the Macaulay2 documentation, this is the simplicial complex - whose faces are the complements of its nonfaces. - - Thus find the minimal nonfaces and take their complements to - find the facets in the Alexander dual. - - :param is_mutable: Determines if the output is mutable - :type is_mutable: boolean; optional, default ``True`` - - EXAMPLES:: - - sage: Y = SimplicialComplex([[i] for i in range(5)]); Y - Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0,), (1,), (2,), (3,), (4,)} - sage: Y.alexander_dual() - Simplicial complex with vertex set (0, 1, 2, 3, 4) and 10 facets - sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [3,0]]) - sage: X.alexander_dual() - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2), (1, 3)} - """ - nonfaces = self.minimal_nonfaces() - return SimplicialComplex([self._complement(f) for f in nonfaces], is_mutable=is_mutable) - - def barycentric_subdivision(self): - """ - The barycentric subdivision of this simplicial complex. - - See :wikipedia:`Barycentric_subdivision` for a - definition. - - EXAMPLES:: - - sage: triangle = SimplicialComplex([[0,1], [1,2], [0, 2]]) - sage: hexagon = triangle.barycentric_subdivision() - sage: hexagon - Simplicial complex with 6 vertices and 6 facets - sage: hexagon.homology(1) == triangle.homology(1) - True - - Barycentric subdivisions can get quite large, since each - `n`-dimensional facet in the original complex produces - `(n+1)!` facets in the subdivision:: - - sage: S4 = simplicial_complexes.Sphere(4) - sage: S4 - Minimal triangulation of the 4-sphere - sage: S4.barycentric_subdivision() - Simplicial complex with 62 vertices and 720 facets - """ - return self.face_poset().order_complex() - - def stellar_subdivision(self, simplex, inplace=False, is_mutable=True): - """ - Return the stellar subdivision of a simplex in this simplicial complex. - - The stellar subdivision of a face is obtained by adding a new vertex to the - simplicial complex ``self`` joined to the star of the face and then - deleting the face ``simplex`` to the result. - - INPUT: - - - ``simplex`` -- a simplex face of ``self`` - - ``inplace`` -- (default: ``False``) boolean; determines if the - operation is done on ``self`` or on a copy - - ``is_mutable`` -- (default: ``True``) boolean; determines if the - output is mutable - - OUTPUT: - - - A simplicial complex obtained by the stellar subdivision of the face - ``simplex`` - - EXAMPLES:: - - sage: SC = SimplicialComplex([[0,1,2],[1,2,3]]) - sage: F1 = Simplex([1,2]) - sage: F2 = Simplex([1,3]) - sage: F3 = Simplex([1,2,3]) - sage: SC.stellar_subdivision(F1) - Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 4), (0, 2, 4), (1, 3, 4), (2, 3, 4)} - sage: SC.stellar_subdivision(F2) - Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 2), (1, 2, 4), (2, 3, 4)} - sage: SC.stellar_subdivision(F3) - Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 2), (1, 2, 4), (1, 3, 4), (2, 3, 4)} - sage: SC.stellar_subdivision(F3, inplace=True);SC - Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 2), (1, 2, 4), (1, 3, 4), (2, 3, 4)} - - The simplex to subdivide should be a face of self:: - - sage: SC = SimplicialComplex([[0,1,2],[1,2,3]]) - sage: F4 = Simplex([3,4]) - sage: SC.stellar_subdivision(F4) - Traceback (most recent call last): - ... - ValueError: the face to subdivide is not a face of self - - One can not modify an immutable simplicial complex:: - - sage: SC = SimplicialComplex([[0,1,2],[1,2,3]], is_mutable=False) - sage: SC.stellar_subdivision(F1, inplace=True) - Traceback (most recent call last): - ... - ValueError: this simplicial complex is not mutable - """ - - if inplace and self._is_immutable: - raise ValueError("this simplicial complex is not mutable") - - if not Simplex(simplex) in self: - raise ValueError("the face to subdivide is not a face of self") - - if inplace: - working_complex = self - else: - working_complex = copy(self) - - vertices = working_complex.vertices() - not_found = True - vertex_label = 0 - while not_found: - if vertex_label not in vertices: - not_found = False - else: - vertex_label += 1 - new_vertex = SimplicialComplex([[vertex_label]]) - new_faces = new_vertex.join(working_complex.star(simplex), rename_vertices=False) - for face in new_faces.facets(): - working_complex.add_face(face) - - working_complex.remove_face(simplex) - - if not is_mutable: - working_complex.set_immutable() - - if not inplace: - return working_complex - - def graph(self): - """ - The 1-skeleton of this simplicial complex, as a graph. - - .. WARNING:: - - This may give the wrong answer if the simplicial complex - was constructed with ``maximality_check`` set to ``False``. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1,2,3]]) - sage: G = S.graph(); G - Graph on 4 vertices - sage: G.edges() - [(0, 1, None), (0, 2, None), (0, 3, None), (1, 2, None), (1, 3, None), (2, 3, None)] - """ - if self._graph is None: - edges = self.n_cells(1) - vertices = [min(f) for f in self._facets if f.dimension() == 0] - used_vertices = [] # vertices which are in an edge - d = {} - for e in edges: - try: - v = min(e) - max_e = max(e) - except TypeError: - v = min(e, key=str) - max_e = max(e, key=str) - if v in d: - d[v].append(max_e) - else: - d[v] = [max_e] - used_vertices.extend(list(e)) - for v in vertices: - if v not in used_vertices: - d[v] = [] - self._graph = Graph(d) - return self._graph - - def delta_complex(self, sort_simplices=False): - r""" - Return ``self`` as a `\Delta`-complex. - - The `\Delta`-complex is essentially identical to the - simplicial complex: it has same simplices with the same - boundaries. - - :param sort_simplices: if ``True``, sort the list of simplices in - each dimension - :type sort_simplices: boolean; optional, default ``False`` - - EXAMPLES:: - - sage: T = simplicial_complexes.Torus() - sage: Td = T.delta_complex() - sage: Td - Delta complex with 7 vertices and 43 simplices - sage: T.homology() == Td.homology() - True - """ - from .delta_complex import DeltaComplex - data = {} - dim = self.dimension() - n_cells = self._n_cells_sorted(dim) - if sort_simplices: - n_cells.sort() - for n in range(dim, -1, -1): - bdries = self._n_cells_sorted(n-1) - if sort_simplices: - bdries.sort() - data[n] = [] - for f in n_cells: - data[n].append([bdries.index(f.face(i)) for i in range(n+1)]) - n_cells = bdries - return DeltaComplex(data) - - def is_flag_complex(self): - """ - Return ``True`` if and only if ``self`` is a flag complex. - - A flag complex is a simplicial complex that is the largest simplicial - complex on its 1-skeleton. Thus a flag complex is the clique complex - of its graph. - - EXAMPLES:: - - sage: h = Graph({0:[1,2,3,4],1:[2,3,4],2:[3]}) - sage: x = h.clique_complex() - sage: x - Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 4), (0, 1, 2, 3)} - sage: x.is_flag_complex() - True - - sage: X = simplicial_complexes.ChessboardComplex(3,3) - sage: X.is_flag_complex() - True - """ - return self == self.graph().clique_complex() - - def n_skeleton(self, n): - """ - The `n`-skeleton of this simplicial complex. - - The `n`-skeleton of a simplicial complex is obtained by discarding - all of the simplices in dimensions larger than `n`. - - :param n: non-negative integer - - EXAMPLES:: - - sage: X = SimplicialComplex([[0,1], [1,2,3], [0,2,3]]) - sage: X.n_skeleton(1) - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)} - sage: X.set_immutable() - sage: X.n_skeleton(2) - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 2, 3), (1, 2, 3)} - sage: X.n_skeleton(4) - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 2, 3), (1, 2, 3)} - """ - if n >= self.dimension(): - return self - # make sure it's a list (it will be a tuple if immutable) - facets = [f for f in self._facets if f.dimension() < n] - facets.extend(self.faces()[n]) - return SimplicialComplex(facets, is_immutable=self._is_immutable) - - def _contractible_subcomplex(self, verbose=False): - """ - Find a contractible subcomplex `L` of this simplicial complex, - preferably one which is as large as possible. - - :param verbose: If ``True``, print some messages as the simplicial - complex is computed. - :type verbose: boolean; optional, default ``False`` - - Motivation: if `K` is the original complex and if `L` is - contractible, then the relative homology `H_*(K,L)` is - isomorphic to the reduced homology of `K`. If `L` is large, - then the relative chain complex will be a good deal smaller - than the augmented chain complex for `K`, and this leads to a - speed improvement for computing the homology of `K`. - - This just passes an immutable subcomplex consisting of a facet to the - method ``_enlarge_subcomplex``. - - .. NOTE:: - - Thus when the simplicial complex is empty, so is the - resulting 'contractible subcomplex', which is therefore not - technically contractible. In this case, that doesn't - matter because the homology is computed correctly anyway. - - EXAMPLES:: - - sage: sphere = SimplicialComplex([[0,1,2,3]]) - sage: sphere.remove_face([0,1,2,3]) - sage: sphere - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)} - sage: L = sphere._contractible_subcomplex(); L - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (0, 1, 3), (0, 2, 3)} - sage: L.homology() - {0: 0, 1: 0, 2: 0} - """ - facets = [sorted(self._facets, key=str)[0]] - return self._enlarge_subcomplex(SimplicialComplex(facets, is_mutable=False), verbose=verbose) - - def _enlarge_subcomplex(self, subcomplex, verbose=False): - """ - Given a subcomplex `S` of this simplicial complex `K`, find a - subcomplex `L`, as large as possible, containing `S` which is - homotopy equivalent to `S` (so that `H_{*}(K,S)` is isomorphic - to `H_{*}(K,L)`). This way, the chain complex for computing - `H_{*}(K,L)` will be smaller than that for computing - `H_{*}(K,S)`, so the computations should be faster. - - :param subcomplex: a subcomplex of this simplicial complex - :param verbose: If ``True``, print some messages as the simplicial - complex is computed. - :type verbose: boolean; optional, default ``False`` - :return: a complex `L` containing ``subcomplex`` and contained - in ``self``, homotopy equivalent to ``subcomplex``. - - Algorithm: start with the subcomplex `S` and loop through the - facets of `K` which are not in `S`. For each one, see whether - its intersection with `S` is contractible, and if so, add it. - This is recursive: testing for contractibility calls this - routine again, via ``_contractible_subcomplex``. - - EXAMPLES:: - - sage: T = simplicial_complexes.Torus(); T - Minimal triangulation of the torus - - Inside the torus, define a subcomplex consisting of a loop:: - - sage: S = SimplicialComplex([[0,1], [1,2], [0,2]], is_mutable=False) - sage: S.homology() - {0: 0, 1: Z} - sage: L = T._enlarge_subcomplex(S) - sage: L - Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 8 facets - sage: sorted(L.facets()) - [(0, 1), (0, 1, 5), (0, 2), (0, 2, 6), (0, 3, 4), (0, 3, 5), (0, 4, 6), (1, 2)] - sage: L.homology()[1] - Z - """ - # Make the subcomplex immutable if not - if subcomplex is not None and not subcomplex._is_immutable: - subcomplex = SimplicialComplex(subcomplex._facets, - maximality_check=False, - is_mutable=False) - - if subcomplex in self.__enlarged: - return self.__enlarged[subcomplex] - faces = [x for x in list(self._facets) if x not in subcomplex._facets] - # For consistency when using different Python versions, for example, sort 'faces'. - faces = sorted(faces, key=str) - done = False - new_facets = sorted(subcomplex._facets, key=str) - while not done: - done = True - remove_these = [] - if verbose: - print(" looping through %s facets" % len(faces)) - for f in faces: - f_set = f.set() - int_facets = set( a.set().intersection(f_set) for a in new_facets ) - intersection = SimplicialComplex(int_facets) - if not intersection._facets[0].is_empty(): - if (len(intersection._facets) == 1 or - intersection == intersection._contractible_subcomplex()): - new_facets.append(f) - remove_these.append(f) - done = False - if verbose and not done: - print(" added %s facets" % len(remove_these)) - for f in remove_these: - faces.remove(f) - if verbose: - print(" now constructing a simplicial complex with %s vertices and %s facets" % (len(self.vertices()), len(new_facets))) - L = SimplicialComplex(new_facets, maximality_check=False, - is_immutable=self._is_immutable) - self.__enlarged[subcomplex] = L - # Use the same sorting on the vertices in L as in the ambient complex. - L._vertex_to_index = self._vertex_to_index - return L - - def _cubical_(self): - r""" - Cubical complex constructed from ``self``. - - ALGORITHM: - - The algorithm comes from a paper by Shtan'ko and Shtogrin, as - reported by Bukhshtaber and Panov. Let `I^m` denote the unit - `m`-cube, viewed as a cubical complex. Let `[m] = \{1, 2, - ..., m\}`; then each face of `I^m` has the following form, for - subsets `I \subset J \subset [m]`: - - .. MATH:: - - F_{I \subset J} = \{ (y_1,...,y_m) \in I^m \,:\, y_i =0 \text{ - for } i \in I, y_j = 1 \text{ for } j \not \in J\}. - - If `K` is a simplicial complex on vertex set `[m]` and if `I - \subset [m]`, write `I \in K` if `I` is a simplex of `K`. - Then we associate to `K` the cubical subcomplex of `I^m` with - faces - - .. MATH:: - - \{F_{I \subset J} \,:\, J \in K, I \neq \emptyset \} - - The geometric realization of this cubical complex is - homeomorphic to the geometric realization of the original - simplicial complex. - - REFERENCES: - - - [BP2000]_ - - [SS1992]_ - - EXAMPLES:: - - sage: T = simplicial_complexes.Torus() - sage: T.homology() - {0: 0, 1: Z x Z, 2: Z} - sage: Tc = T._cubical_() - sage: Tc - Cubical complex with 42 vertices and 168 cubes - sage: Tc.homology() - {0: 0, 1: Z x Z, 2: Z} - """ - from sage.homology.cubical_complex import CubicalComplex - V = self.vertices() - embed = len(V) - # dictionary to translate vertices to the numbers 1, ..., embed - vd = dict(zip(V, range(1, embed + 1))) - cubes = [] - for JJ in self.facets(): - J = [vd[i] for i in JJ] - for i in J: - # loop over indices from 1 to embed. if equal to i, - # set to 0. if not in J, set to 1. Otherwise, range - # from 0 to 1 - cube = [] - for n in range(1, embed+1): - if n == i: - cube.append([0]) - elif n not in J: - cube.append([1]) - else: - cube.append([0, 1]) - cubes.append(cube) - return CubicalComplex(cubes) - - def connected_component(self, simplex=None): - """ - Return the connected component of this simplicial complex - containing ``simplex``. If ``simplex`` is omitted, then return - the connected component containing the zeroth vertex in the - vertex list. (If the simplicial complex is empty, raise an - error.) - - EXAMPLES:: - - sage: S1 = simplicial_complexes.Sphere(1) - sage: S1 == S1.connected_component() - True - sage: X = S1.disjoint_union(S1) - sage: X == X.connected_component() - False - sage: X.connected_component(Simplex(['L0'])) == X.connected_component(Simplex(['R0'])) - False - - sage: S0 = simplicial_complexes.Sphere(0) - sage: S0.vertices() - (0, 1) - sage: S0.connected_component() - Simplicial complex with vertex set (0,) and facets {(0,)} - sage: S0.connected_component(Simplex((1,))) - Simplicial complex with vertex set (1,) and facets {(1,)} - - sage: SimplicialComplex([[]]).connected_component() - Traceback (most recent call last): - ... - ValueError: the empty simplicial complex has no connected components - """ - if self.dimension() == -1: - raise ValueError("the empty simplicial complex has no connected components") - if simplex is None: - v = self.vertices()[0] - else: - v = simplex[0] - vertices = self.graph().connected_component_containing_vertex(v) - facets = [f for f in self.facets() if f.is_face(Simplex(vertices))] - return SimplicialComplex(facets) - - def fundamental_group(self, base_point=None, simplify=True): - r""" - Return the fundamental group of this simplicial complex. - - INPUT: - - - ``base_point`` (optional, default None) -- if this complex is - not path-connected, then specify a vertex; the fundamental - group is computed with that vertex as a base point. If the - complex is path-connected, then you may specify a vertex or - leave this as its default setting of ``None``. (If this - complex is path-connected, then this argument is ignored.) - - - ``simplify`` (bool, optional True) -- if False, then return a - presentation of the group in terms of generators and - relations. If True, the default, simplify as much as GAP is - able to. - - Algorithm: we compute the edge-path group -- see - :wikipedia:`Fundamental_group`. Choose a spanning tree for the - 1-skeleton, and then the group's generators are given by the - edges in the 1-skeleton; there are two types of relations: - `e=1` if `e` is in the spanning tree, and for every 2-simplex, - if its edges are `e_0`, `e_1`, and `e_2`, then we impose the - relation `e_0 e_1^{-1} e_2 = 1`. - - EXAMPLES:: - - sage: S1 = simplicial_complexes.Sphere(1) - sage: S1.fundamental_group() - Finitely presented group < e | > - - If we pass the argument ``simplify=False``, we get generators and - relations in a form which is not usually very helpful. Here is the - cyclic group of order 2, for instance:: - - sage: RP2 = simplicial_complexes.RealProjectiveSpace(2) - sage: C2 = RP2.fundamental_group(simplify=False) - sage: C2 - Finitely presented group < e0, e1, e2, e3, e4, e5, e6, e7, e8, e9 | e0, e3, e4, e7, e9, e5*e2^-1*e0, e7*e2^-1*e1, e8*e3^-1*e1, e8*e6^-1*e4, e9*e6^-1*e5 > - sage: C2.simplified() - Finitely presented group < e1 | e1^2 > - - This is the same answer given if the argument ``simplify`` is True - (the default):: - - sage: RP2.fundamental_group() - Finitely presented group < e1 | e1^2 > - - You must specify a base point to compute the fundamental group - of a non-connected complex:: - - sage: K = S1.disjoint_union(RP2) - sage: K.fundamental_group() - Traceback (most recent call last): - ... - ValueError: this complex is not connected, so you must specify a base point - sage: K.fundamental_group(base_point='L0') - Finitely presented group < e | > - sage: K.fundamental_group(base_point='R0').order() - 2 - - Some other examples:: - - sage: S1.wedge(S1).fundamental_group() - Finitely presented group < e0, e1 | > - sage: simplicial_complexes.Torus().fundamental_group() - Finitely presented group < e1, e4 | e4^-1*e1^-1*e4*e1 > - - sage: G = simplicial_complexes.MooreSpace(5).fundamental_group() - sage: G.ngens() - 1 - sage: x = G.gen(0) - sage: [(x**n).is_one() for n in range(1,6)] - [False, False, False, False, True] - """ - if not self.is_connected(): - if base_point is None: - raise ValueError("this complex is not connected, so you must specify a base point") - return self.connected_component(Simplex([base_point])).fundamental_group(simplify=simplify) - - from sage.groups.free_group import FreeGroup - from sage.interfaces.gap import gap - G = self.graph() - # If the vertices and edges of G are not sortable, e.g., a mix - # of str and int, Sage+Python 3 may raise a TypeError when - # trying to find the spanning tree. So create a graph - # isomorphic to G but with sortable vertices. Use a copy of G, - # because self.graph() is cached, and relabeling its vertices - # would relabel the cached version. - int_to_v = dict(enumerate(G.vertex_iterator())) - v_to_int = {v: i for i, v in int_to_v.items()} - G2 = G.copy(immutable=False) - G2.relabel(v_to_int) - spanning_tree = G2.min_spanning_tree() - gens = [(int_to_v[e[0]], int_to_v[e[1]]) for e in G2.edges() - if e not in spanning_tree] - if len(gens) == 0: - return gap.TrivialGroup() - - # Edges in the graph may be sorted differently than in the - # simplicial complex, so convert the edges to frozensets so we - # don't have to worry about it. Convert spanning_tree to a set - # to make lookup faster. - spanning_tree = set(frozenset((int_to_v[e[0]], int_to_v[e[1]])) - for e in spanning_tree) - gens_dict = {frozenset(g): i for i, g in enumerate(gens)} - FG = FreeGroup(len(gens), 'e') - rels = [] - for f in self._n_cells_sorted(2): - bdry = [tuple(e) for e in f.faces()] - z = dict() - for i in range(3): - x = frozenset(bdry[i]) - if (x in spanning_tree): - z[i] = FG.one() - else: - z[i] = FG.gen(gens_dict[x]) - rels.append(z[0]*z[1].inverse()*z[2]) - if simplify: - return FG.quotient(rels).simplified() - else: - return FG.quotient(rels) - - def is_isomorphic(self, other, certificate=False): - r""" - Check whether two simplicial complexes are isomorphic. - - INPUT: - - - ``certificate`` -- if ``True``, then output is ``(a, b)``, where ``a`` - is a boolean and ``b`` is either a map or ``None`` - - This is done by creating two graphs and checking whether they - are isomorphic. - - EXAMPLES:: - - sage: Z1 = SimplicialComplex([[0,1],[1,2],[2,3,4],[4,5]]) - sage: Z2 = SimplicialComplex([['a','b'],['b','c'],['c','d','e'],['e','f']]) - sage: Z3 = SimplicialComplex([[1,2,3]]) - sage: Z1.is_isomorphic(Z2) - True - sage: Z1.is_isomorphic(Z2, certificate=True) - (True, {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f'}) - sage: Z3.is_isomorphic(Z2) - False - - We check that :trac:`20751` is fixed:: - - sage: C1 = SimplicialComplex([[1,2,3], [2,4], [3,5], [5,6]]) - sage: C2 = SimplicialComplex([['a','b','c'], ['b','d'], ['c','e'], ['e','f']]) - sage: C1.is_isomorphic(C2, certificate=True) - (True, {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f'}) - """ - # Check easy invariants agree - if (sorted(x.dimension() for x in self._facets) - != sorted(x.dimension() for x in other._facets) - or len(self.vertices()) != len(other.vertices())): - return False - g1 = Graph() - g2 = Graph() - # With Python 3, "is_isomorphic" for graphs works best if the - # vertices and edges are sortable. So we translate them all to - # ints and then if a certificate is needed, we translate - # back at the end. - self_to_int = {v: i for i, v in enumerate(list(self.vertices()) + list(self._facets))} - other_to_int = {v: i for i, v in enumerate(list(other.vertices()) + list(other._facets))} - g1.add_edges((self_to_int[v], self_to_int[f], "generic edge") for f in self._facets for v in f) - g2.add_edges((other_to_int[v], other_to_int[f], "generic edge") for f in other._facets for v in f) - fake = -1 - g1.add_edges((fake, self_to_int[v], "special_edge") - for v in self.vertices()) - g2.add_edges((fake, other_to_int[v], "special_edge") - for v in other.vertices()) - if not certificate: - return g1.is_isomorphic(g2, edge_labels=True) - isisom, tr = g1.is_isomorphic(g2, edge_labels=True, certificate=True) - - if isisom: - for f in self.facets(): - tr.pop(self_to_int[f]) - tr.pop(fake) - - int_to_self = {idx: x for x, idx in self_to_int.items()} - int_to_other = {idx: x for x, idx in other_to_int.items()} - return isisom, {int_to_self[i]: int_to_other[tr[i]] for i in tr} - - def automorphism_group(self): - r""" - Return the automorphism group of the simplicial complex. - - This is done by creating a bipartite graph, whose vertices are - vertices and facets of the simplicial complex, and computing - its automorphism group. - - .. WARNING:: - - Since :trac:`14319` the domain of the automorphism group is equal to - the graph's vertex set, and the ``translation`` argument has become - useless. - - EXAMPLES:: - - sage: S = simplicial_complexes.Simplex(3) - sage: S.automorphism_group().is_isomorphic(SymmetricGroup(4)) - True - - sage: P = simplicial_complexes.RealProjectivePlane() - sage: P.automorphism_group().is_isomorphic(AlternatingGroup(5)) - True - - sage: Z = SimplicialComplex([['1','2'],['2','3','a']]) - sage: Z.automorphism_group().is_isomorphic(CyclicPermutationGroup(2)) - True - sage: group = Z.automorphism_group() - sage: sorted(group.domain()) - ['1', '2', '3', 'a'] - - Check that :trac:`17032` is fixed:: - - sage: s = SimplicialComplex([[(0,1),(2,3)]]) - sage: s.automorphism_group().cardinality() - 2 - """ - from sage.groups.perm_gps.permgroup import PermutationGroup - - G = Graph() - G.add_vertices(self.vertices()) - G.add_edges((f.tuple(), v) for f in self.facets() for v in f) - group = G.automorphism_group(partition=[list(self.vertices()), - [f.tuple() - for f in self.facets()]]) - - gens = [[tuple(c) for c in g.cycle_tuples() - if c[0] in self.vertices()] - for g in group.gens()] - - return PermutationGroup(gens=gens, domain=self.vertices()) - - def fixed_complex(self, G): - r""" - Return the fixed simplicial complex `Fix(G)` for a subgroup `G`. - - INPUT: - - - ``G`` -- a subgroup of the automorphism group of the simplicial - complex or a list of elements of the automorphism group - - OUTPUT: - - - a simplicial complex `Fix(G)` - - Vertices in `Fix(G)` are the orbits of `G` (acting on vertices - of ``self``) that form a simplex in ``self``. More generally, - simplices in `Fix(G)` correspond to simplices in ``self`` that - are union of such orbits. - - A basic example:: - - sage: S4 = simplicial_complexes.Sphere(4) - sage: S3 = simplicial_complexes.Sphere(3) - sage: fix = S4.fixed_complex([S4.automorphism_group()([(0,1)])]) - sage: fix - Simplicial complex with vertex set (0, 2, 3, 4, 5) and 5 facets - sage: fix.is_isomorphic(S3) - True - - Another simple example:: - - sage: T = SimplicialComplex([[1,2,3],[2,3,4]]) - sage: G = T.automorphism_group() - sage: T.fixed_complex([G([(1,4)])]) - Simplicial complex with vertex set (2, 3) and facets {(2, 3)} - - A more sophisticated example:: - - sage: RP2 = simplicial_complexes.ProjectivePlane() - sage: CP2 = simplicial_complexes.ComplexProjectivePlane() - sage: G = CP2.automorphism_group() - sage: H = G.subgroup([G([(2,3),(5,6),(8,9)])]) - sage: CP2.fixed_complex(H).is_isomorphic(RP2) - True - """ - from sage.categories.groups import Groups - if G in Groups(): - gens = G.gens() - else: - gens = G - G = self.automorphism_group().subgroup(gens) - - invariant_f = [list(u) for u in self.face_iterator() - if all(sorted(sigma(j) for j in u) == sorted(u) - for sigma in gens)] - new_verts = [min(o) for o in G.orbits() if o in invariant_f] - return SimplicialComplex([[s for s in f if s in new_verts] - for f in invariant_f]) - - def _Hom_(self, other, category=None): - """ - Return the set of simplicial maps between simplicial complexes - ``self`` and ``other``. - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(1) - sage: T = simplicial_complexes.Sphere(2) - sage: H = Hom(S,T) # indirect doctest - sage: H - Set of Morphisms from Minimal triangulation of the 1-sphere - to Minimal triangulation of the 2-sphere - in Category of finite simplicial complexes - sage: f = {0:0,1:1,2:3} - sage: x = H(f) - sage: x - Simplicial complex morphism: - From: Minimal triangulation of the 1-sphere - To: Minimal triangulation of the 2-sphere - Defn: 0 |--> 0 - 1 |--> 1 - 2 |--> 3 - - sage: S._Hom_(T, Objects()) - Traceback (most recent call last): - ... - TypeError: Category of objects is not a subcategory of SimplicialComplexes() - sage: type(Hom(S, T, Objects())) - - """ - if not category.is_subcategory(SimplicialComplexes()): - raise TypeError("{} is not a subcategory of SimplicialComplexes()".format(category)) - from sage.homology.simplicial_complex_homset import SimplicialComplexHomset - return SimplicialComplexHomset(self, other) - - # @cached_method when we switch to immutable SimplicialComplex - def _is_numeric(self): - """ - Test whether all vertices are labeled by integers - - OUTPUT: - - Boolean. Whether all vertices are labeled by (not necessarily - consecutive) integers. - - EXAMPLES:: - - sage: s = SimplicialComplex() - sage: s._is_numeric() - True - sage: s.add_face(['a', 'b', 123]) - sage: s._is_numeric() - False - """ - return all(isinstance(v, (int, Integer)) - for v in self.vertices()) - - # @cached_method when we switch to immutable SimplicialComplex - def _translation_to_numeric(self): - """ - Return a dictionary enumerating the vertices - - See also :meth:`_translation_from_numeric`, which returns the - inverse map. - - OUTPUT: - - A dictionary. The keys are the vertices, and the associated - values are integers from 0 to number of vertices - 1. - - EXAMPLES:: - - sage: s = SimplicialComplex() - sage: s._translation_to_numeric() - {} - sage: s.add_face(['a', 'b', 123]) - sage: s._translation_to_numeric() # random output - {'a': 1, 123: 0, 'b': 2} - sage: set(s._translation_to_numeric().keys()) == set(['a', 'b', 123]) - True - sage: sorted(s._translation_to_numeric().values()) - [0, 1, 2] - """ - return self._vertex_to_index - - # @cached_method when we switch to immutable SimplicialComplex - def _translation_from_numeric(self): - """ - Return a dictionary mapping vertex indices to vertices - - See also :meth:`_translation_to_numeric`, which returns the - inverse map. - - OUTPUT: - - A dictionary. The keys are integers from 0 to the number of - vertices - 1. The associated values are the vertices. - - EXAMPLES:: - - sage: s = SimplicialComplex() - sage: s._translation_from_numeric() - {} - sage: s.add_face(['a', 'b', 123]) - sage: s._translation_from_numeric() # random output - {0: 123, 1: 'a', 2: 'b'} - sage: sorted(s._translation_from_numeric().keys()) - [0, 1, 2] - sage: set(s._translation_from_numeric().values()) == set(['a', 'b', 123]) - True - """ - d = self._vertex_to_index - return {idx: v for v, idx in d.items()} - - def _chomp_repr_(self): - r""" - String representation of ``self`` suitable for use by the CHomP - program. This lists each facet on its own line, and makes - sure vertices are listed as numbers. - - EXAMPLES:: - - sage: S = SimplicialComplex([(0,1,2), (2,3,5)]) - sage: print(S._chomp_repr_()) - (2, 3, 5) - (0, 1, 2) - - A simplicial complex whose vertices are tuples, not integers:: - - sage: S = SimplicialComplex([[(0,1), (1,2), (3,4)]]) - sage: S._chomp_repr_() - '(0, 1, 2)\n' - """ - s = "" - numeric = self._is_numeric() - if not numeric: - d = self._translation_to_numeric() - for f in self.facets(): - if numeric: - s += str(f) - else: - s += '(' + ', '.join(str(d[a]) for a in f) + ')' - s += '\n' - return s - - # this function overrides the standard one for GenericCellComplex, - # because it lists the maximal faces, not the total number of faces. - def _repr_(self): - """ - Print representation. - - If there are only a few vertices or faces, they are listed. If - there are lots, the number is given. - - Facets are sorted in increasing order of dimension, and within - each dimension, they are sorted using the underlying tuple. - - EXAMPLES:: - - sage: X = SimplicialComplex([[0,1], [1,2]]) - sage: X._repr_() - 'Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1), (1, 2)}' - sage: SimplicialComplex([[i for i in range(16)]]) - Simplicial complex with 16 vertices and 1 facets - """ - vertex_limit = 45 - facet_limit = 55 - vertices = self.vertices() - try: - vertices = sorted(vertices) - except TypeError: - vertices = sorted(vertices, key=str) - try: - facets = sorted(self._facets, key=lambda f: (f.dimension(), f.tuple())) - except TypeError: - # Sorting failed. - facets = self._facets - - vertex_string = "with vertex set {}".format(tuple(vertices)) - if len(vertex_string) > vertex_limit: - vertex_string = "with %s vertices" % len(vertices) - facet_string = 'facets {' + repr(facets)[1:-1] + '}' - if len(facet_string) > facet_limit: - facet_string = "%s facets" % len(facets) - return "Simplicial complex " + vertex_string + " and " + facet_string - - def set_immutable(self): - """ - Make this simplicial complex immutable. - - EXAMPLES:: - - sage: S = SimplicialComplex([[1,4], [2,4]]) - sage: S.is_mutable() - True - sage: S.set_immutable() - sage: S.is_mutable() - False - """ - self._is_immutable = True - self._facets = tuple(self._facets) - - def is_mutable(self): - """ - Return ``True`` if mutable. - - EXAMPLES:: - - sage: S = SimplicialComplex([[1,4], [2,4]]) - sage: S.is_mutable() - True - sage: S.set_immutable() - sage: S.is_mutable() - False - sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) - sage: S2.is_mutable() - False - sage: S3 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) - sage: S3.is_mutable() - False - """ - return not self._is_immutable - - def is_immutable(self): - """ - Return ``True`` if immutable. - - EXAMPLES:: - - sage: S = SimplicialComplex([[1,4], [2,4]]) - sage: S.is_immutable() - False - sage: S.set_immutable() - sage: S.is_immutable() - True - """ - return self._is_immutable - - def cone_vertices(self): - r""" - Return the list of cone vertices of ``self``. - - A vertex is a cone vertex if and only if it appears in every facet. - - EXAMPLES:: - - sage: SimplicialComplex([[1,2,3]]).cone_vertices() - [1, 2, 3] - sage: SimplicialComplex([[1,2,3], [1,3,4], [1,5,6]]).cone_vertices() - [1] - sage: SimplicialComplex([[1,2,3], [1,3,4], [2,5,6]]).cone_vertices() - [] - """ - F = self.facets() - C = set(self.vertices()) - for f in F: - C = C.intersection(list(f)) - if not C: - break - return sorted(C) - - def decone(self): - r""" - Return the subcomplex of ``self`` induced by the non-cone vertices. - - EXAMPLES:: - - sage: SimplicialComplex([[1,2,3]]).decone() - Simplicial complex with vertex set () and facets {()} - sage: SimplicialComplex([[1,2,3], [1,3,4], [1,5,6]]).decone() - Simplicial complex with vertex set (2, 3, 4, 5, 6) and facets {(2, 3), (3, 4), (5, 6)} - sage: X = SimplicialComplex([[1,2,3], [1,3,4], [2,5,6]]) - sage: X.decone() == X - True - """ - V = set(self.vertices()).difference(self.cone_vertices()) - return self.generated_subcomplex(V) - - def is_balanced(self, check_purity=False, certificate=False): - r""" - Determine whether ``self`` is balanced. - - A simplicial complex `X` of dimension `d-1` is balanced if and - only if its vertices can be colored with `d` colors such that - every face contains at most one vertex of each color. An - equivalent condition is that the 1-skeleton of `X` is - `d`-colorable. In some contexts, it is also required that `X` - be pure (i.e., that all maximal faces of `X` have the same - dimension). - - INPUT: - - - ``check_purity`` -- (default: ``False``) if this is ``True``, - require that ``self`` be pure as well as balanced - - - ``certificate`` -- (default: ``False``) if this is ``True`` and - ``self`` is balanced, then return a `d`-coloring of the 1-skeleton. - - EXAMPLES: - - A 1-dim simplicial complex is balanced iff it is bipartite:: - - sage: X = SimplicialComplex([[1,2],[1,4],[3,4],[2,5]]) - sage: X.is_balanced() - True - sage: sorted(X.is_balanced(certificate=True)) - [[1, 3, 5], [2, 4]] - sage: X = SimplicialComplex([[1,2],[1,4],[3,4],[2,4]]) - sage: X.is_balanced() - False - - Any barycentric division is balanced:: - - sage: X = SimplicialComplex([[1,2,3],[1,2,4],[2,3,4]]) - sage: X.is_balanced() - False - sage: X.barycentric_subdivision().is_balanced() - True - - A non-pure balanced complex:: - - sage: X=SimplicialComplex([[1,2,3],[3,4]]) - sage: X.is_balanced(check_purity=True) - False - sage: sorted(X.is_balanced(certificate=True)) - [[1, 4], [2], [3]] - """ - d = 1 + self.dimension() - if check_purity and not self.is_pure(): - return False - Skel = self.graph() - if certificate: - C = Skel.coloring() - C = C if len(C) == d else False - return C - else: - return Skel.chromatic_number() == d - - def is_partitionable(self, certificate=False): - r""" - Determine whether ``self`` is partitionable. - - A partitioning of a simplicial complex `X` is a decomposition - of its face poset into disjoint Boolean intervals `[R,F]`, - where `F` ranges over all facets of `X`. - - The method sets up an integer program with: - - - a variable `y_i` for each pair `(R,F)`, where `F` is a facet of `X` - and `R` is a subface of `F` - - - a constraint `y_i+y_j \leq 1` for each pair `(R_i,F_i)`, `(R_j,F_j)` - whose Boolean intervals intersect nontrivially (equivalent to - `(R_i\subseteq F_j and R_j\subseteq F_i))` - - - objective function equal to the sum of all `y_i` - - INPUT: - - - ``certificate`` -- (default: ``False``) If ``True``, - and ``self`` is partitionable, then return a list of pairs `(R,F)` - that form a partitioning. - - EXAMPLES: - - Simplices are trivially partitionable:: - - sage: X = SimplicialComplex([ [1,2,3,4] ]) - sage: X.is_partitionable() - True - sage: X.is_partitionable(certificate=True) - [((), (1, 2, 3, 4), 4)] - - Shellable complexes are partitionable:: - - sage: X = SimplicialComplex([ [1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5] ]) - sage: X.is_partitionable() - True - sage: P = X.is_partitionable(certificate=True) - sage: n_intervals_containing = lambda f: len([ RF for RF in P if RF[0].is_face(f) and f.is_face(RF[1]) ]) - sage: all( n_intervals_containing(f)==1 for k in X.faces().keys() for f in X.faces()[k] ) - True - - A non-shellable, non-Cohen-Macaulay, partitionable example, constructed by Björner:: - - sage: X = SimplicialComplex([ [1,2,3],[1,2,4],[1,3,4],[2,3,4],[1,5,6] ]) - sage: X.is_partitionable() - True - - The bowtie complex is not partitionable:: - - sage: X = SimplicialComplex([ [1,2,3],[1,4,5] ]) - sage: X.is_partitionable() - False - """ - from sage.numerical.mip import MixedIntegerLinearProgram - RFPairs = [(Simplex(r), f, f.dimension() - len(r) + 1) - for f in self.facets() for r in Set(f).subsets()] - n = len(RFPairs) - IP = MixedIntegerLinearProgram() - y = IP.new_variable(binary=True) - for i0, pair0 in enumerate(RFPairs): - for i1, pair1 in enumerate(RFPairs): - if (i0 < i1 and pair0[0].is_face(pair1[1]) and - pair1[0].is_face(pair0[1])): - IP.add_constraint(y[i0] + y[i1] <= 1) - IP.set_objective(sum(2**RFPairs[i][2] * y[i] for i in range(n))) - sol = round(IP.solve()) - if sol < sum(self.f_vector()): - return False - elif not certificate: - return True - else: - x = IP.get_values(y) - return [RFPairs[i] for i in range(n) if x[i] == 1] - - def intersection(self, other): - r""" - Calculate the intersection of two simplicial complexes. - - EXAMPLES:: - - sage: X = SimplicialComplex([[1,2,3],[1,2,4]]) - sage: Y = SimplicialComplex([[1,2,3],[1,4,5]]) - sage: Z = SimplicialComplex([[1,2,3],[1,4],[2,4]]) - sage: sorted(X.intersection(Y).facets()) - [(1, 2, 3), (1, 4)] - sage: X.intersection(X) == X - True - sage: X.intersection(Z) == X - False - sage: X.intersection(Z) == Z - True - """ - F = [] - for k in range(1 + min(self.dimension(), other.dimension())): - F = F + [s for s in self.faces()[k] if s in other.faces()[k]] - return SimplicialComplex(F) - -# Miscellaneous utility functions. - -# The following two functions can be used to generate the facets for -# the corresponding examples in sage.homology.examples. These take a -# few seconds to run, so the actual examples have the facets -# hard-coded. Thus the following functions are not currently used in -# the Sage library. - -def facets_for_RP4(): - """ - Return the list of facets for a minimal triangulation of 4-dimensional - real projective space. - - We use vertices numbered 1 through 16, define two facets, and define - a certain subgroup `G` of the symmetric group `S_{16}`. Then the set - of all facets is the `G`-orbit of the two given facets. - - See the description in Example 3.12 in Datta [Dat2007]_. - - EXAMPLES:: - - sage: from sage.homology.simplicial_complex import facets_for_RP4 - sage: A = facets_for_RP4() # long time (1 or 2 seconds) - sage: SimplicialComplex(A) == simplicial_complexes.RealProjectiveSpace(4) # long time - True - """ - # Define the group: - from sage.groups.perm_gps.permgroup import PermutationGroup - g1 = '(2,7)(4,10)(5,6)(11,12)' - g2 = '(1, 2, 3, 4, 5, 10)(6, 8, 9)(11, 12, 13, 14, 15, 16)' - G = PermutationGroup([g1, g2]) - # Define the two simplices: - t1 = (1, 2, 4, 5, 11) - t2 = (1, 2, 4, 11, 13) - # Apply the group elements to the simplices: - facets = [] - for g in G: - d = g.dict() - for t in [t1, t2]: - new = tuple([d[j] for j in t]) - if new not in facets: - facets.append(new) - return facets - - -def facets_for_K3(): - """ - Return the facets for a minimal triangulation of the K3 surface. - - This is a pure simplicial complex of dimension 4 with 16 - vertices and 288 facets. The facets are obtained by constructing a - few facets and a permutation group `G`, and then computing the - `G`-orbit of those facets. - - See Casella and Kühnel in [CK2001]_ and Spreer and Kühnel [SK2011]_; - the construction here uses the labeling from Spreer and Kühnel. - - EXAMPLES:: - - sage: from sage.homology.simplicial_complex import facets_for_K3 - sage: A = facets_for_K3() # long time (a few seconds) - sage: SimplicialComplex(A) == simplicial_complexes.K3Surface() # long time - True - """ - from sage.groups.perm_gps.permgroup import PermutationGroup - G = PermutationGroup([[(1,3,8,4,9,16,15,2,14,12,6,7,13,5,10)], - [(1,11,16),(2,10,14),(3,12,13),(4,9,15),(5,7,8)]]) - return ([tuple([g(i) for i in (1,2,3,8,12)]) for g in G] - +[tuple([g(i) for i in (1,2,5,8,14)]) for g in G]) +from sage.misc.superseded import deprecated_function_alias +import sage.topology.simplicial_complex + +for f in ['lattice_paths', + 'rename_vertex', + 'Simplex', + 'SimplicialComplex', + 'facets_for_RP4', + 'facets_for_K3']: + exec('{} = deprecated_function_alias(31925, sage.topology.simplicial_complex.{})'.format(f, f)) diff --git a/src/sage/homology/simplicial_complex_homset.py b/src/sage/homology/simplicial_complex_homset.py index df181a80938..6a9d78f69f1 100644 --- a/src/sage/homology/simplicial_complex_homset.py +++ b/src/sage/homology/simplicial_complex_homset.py @@ -1,196 +1,13 @@ r""" -Homsets between simplicial complexes - -AUTHORS: - -- Travis Scrimshaw (2012-08-18): Made all simplicial complexes immutable to - work with the homset cache. - -EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(1) - sage: T = simplicial_complexes.Sphere(2) - sage: H = Hom(S,T) - sage: f = {0:0,1:1,2:3} - sage: x = H(f) - sage: x - Simplicial complex morphism: - From: Minimal triangulation of the 1-sphere - To: Minimal triangulation of the 2-sphere - Defn: 0 |--> 0 - 1 |--> 1 - 2 |--> 3 - sage: x.is_injective() - True - sage: x.is_surjective() - False - sage: x.image() - Simplicial complex with vertex set (0, 1, 3) and facets {(0, 1), (0, 3), (1, 3)} - sage: from sage.homology.simplicial_complex import Simplex - sage: s = Simplex([1,2]) - sage: x(s) - (1, 3) - -TESTS:: - - sage: S = simplicial_complexes.Sphere(1) - sage: T = simplicial_complexes.Sphere(2) - sage: H = Hom(S,T) - sage: loads(dumps(H)) == H - True +Homsets between simplicial complexes: deprecated +The current version is :mod:`sage.topology.simplicial_complex_homset`. """ +from sage.misc.superseded import deprecated_function_alias +import sage.topology.simplicial_complex_homset -#***************************************************************************** -# Copyright (C) 2009 D. Benjamin Antieau -# -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# See the GNU General Public License for more details; the full text -# is available at: -# -# http://www.gnu.org/licenses/ -# -#***************************************************************************** - -import sage.categories.homset -from sage.homology.simplicial_complex_morphism import SimplicialComplexMorphism - -def is_SimplicialComplexHomset(x): - """ - Return ``True`` if and only if ``x`` is a simplicial complex homspace. - - EXAMPLES:: - - sage: S = SimplicialComplex(is_mutable=False) - sage: T = SimplicialComplex(is_mutable=False) - sage: H = Hom(S, T) - sage: H - Set of Morphisms from Simplicial complex with vertex set () and facets {()} - to Simplicial complex with vertex set () and facets {()} - in Category of finite simplicial complexes - sage: from sage.homology.simplicial_complex_homset import is_SimplicialComplexHomset - sage: is_SimplicialComplexHomset(H) - True - """ - return isinstance(x, SimplicialComplexHomset) - -class SimplicialComplexHomset(sage.categories.homset.Homset): - def __call__(self, f): - """ - INPUT: - - - ``f`` -- a dictionary with keys exactly the vertices of the domain - and values vertices of the codomain - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(3) - sage: T = simplicial_complexes.Sphere(2) - sage: f = {0:0,1:1,2:2,3:2,4:2} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x - Simplicial complex morphism: - From: Minimal triangulation of the 3-sphere - To: Minimal triangulation of the 2-sphere - Defn: [0, 1, 2, 3, 4] --> [0, 1, 2, 2, 2] - """ - return SimplicialComplexMorphism(f,self.domain(),self.codomain()) - - def diagonal_morphism(self,rename_vertices=True): - r""" - Return the diagonal morphism in `Hom(S, S \times S)`. - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(2) - sage: H = Hom(S,S.product(S, is_mutable=False)) - sage: d = H.diagonal_morphism() - sage: d - Simplicial complex morphism: - From: Minimal triangulation of the 2-sphere - To: Simplicial complex with 16 vertices and 96 facets - Defn: 0 |--> L0R0 - 1 |--> L1R1 - 2 |--> L2R2 - 3 |--> L3R3 - - sage: T = SimplicialComplex([[0], [1]], is_mutable=False) - sage: U = T.product(T,rename_vertices = False, is_mutable=False) - sage: G = Hom(T,U) - sage: e = G.diagonal_morphism(rename_vertices = False) - sage: e - Simplicial complex morphism: - From: Simplicial complex with vertex set (0, 1) and facets {(0,), (1,)} - To: Simplicial complex with 4 vertices and facets {((0, 0),), ((0, 1),), ((1, 0),), ((1, 1),)} - Defn: 0 |--> (0, 0) - 1 |--> (1, 1) - """ - # Preserve whether the codomain is mutable when renaming the vertices. - mutable = self._codomain.is_mutable() - X = self._domain.product(self._domain,rename_vertices=rename_vertices, is_mutable=mutable) - if self._codomain != X: - raise TypeError("diagonal morphism is only defined for Hom(X,XxX)") - f = {} - if rename_vertices: - f = {i: "L{0}R{0}".format(i) for i in self._domain.vertices()} - else: - f = {i: (i,i) for i in self._domain.vertices()} - return SimplicialComplexMorphism(f, self._domain, X) - - def identity(self): - """ - Return the identity morphism of `Hom(S,S)`. - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(2) - sage: H = Hom(S,S) - sage: i = H.identity() - sage: i.is_identity() - True - - sage: T = SimplicialComplex([[0,1]], is_mutable=False) - sage: G = Hom(T,T) - sage: G.identity() - Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - Defn: 0 |--> 0 - 1 |--> 1 - """ - if not self.is_endomorphism_set(): - raise TypeError("identity map is only defined for endomorphism sets") - f = {i: i for i in self._domain.vertices()} - return SimplicialComplexMorphism(f, self._domain, self._codomain) - - def an_element(self): - """ - Return a (non-random) element of ``self``. - - EXAMPLES:: - - sage: S = simplicial_complexes.KleinBottle() - sage: T = simplicial_complexes.Sphere(5) - sage: H = Hom(S,T) - sage: x = H.an_element() - sage: x - Simplicial complex morphism: - From: Minimal triangulation of the Klein bottle - To: Minimal triangulation of the 5-sphere - Defn: [0, 1, 2, 3, 4, 5, 6, 7] --> [0, 0, 0, 0, 0, 0, 0, 0] - """ - X_vertices = self._domain.vertices() - try: - i = next(iter(self._codomain.vertices())) - except StopIteration: - if not X_vertices: - return {} - else: - raise TypeError("there are no morphisms from a non-empty simplicial complex to an empty simplicial complex") - f = {x: i for x in X_vertices} - return SimplicialComplexMorphism(f, self._domain, self._codomain) +is_SimplicialComplexHomset = deprecated_function_alias(31925, + sage.topology.simplicial_complex_homset.is_SimplicialComplexHomset) +SimplicialComplexHomset = deprecated_function_alias(31925, + sage.topology.simplicial_complex_homset.SimplicialComplexHomset) diff --git a/src/sage/homology/simplicial_complex_morphism.py b/src/sage/homology/simplicial_complex_morphism.py index 7d1cd628aef..4673be7d414 100644 --- a/src/sage/homology/simplicial_complex_morphism.py +++ b/src/sage/homology/simplicial_complex_morphism.py @@ -1,801 +1,13 @@ r""" -Morphisms of simplicial complexes +Morphisms of simplicial complexes: deprecated -AUTHORS: - -- Benjamin Antieau (2009.06) - -- Travis Scrimshaw (2012-08-18): Made all simplicial complexes immutable to - work with the homset cache. - -This module implements morphisms of simplicial complexes. The input is given -by a dictionary on the vertex set of a simplicial complex. The initialization -checks that faces are sent to faces. - -There is also the capability to create the fiber product of two morphisms with -the same codomain. - -EXAMPLES:: - - sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) - sage: H = Hom(S,S.product(S, is_mutable=False)) - sage: H.diagonal_morphism() - Simplicial complex morphism: - From: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(0, 2), (1, 5), (3, 4)} - To: Simplicial complex with 36 vertices and 18 facets - Defn: [0, 1, 2, 3, 4, 5] --> ['L0R0', 'L1R1', 'L2R2', 'L3R3', 'L4R4', 'L5R5'] - - sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) - sage: T = SimplicialComplex([[0,2],[1,3]], is_mutable=False) - sage: f = {0:0,1:1,2:2,3:1,4:3,5:3} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x.image() - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2), (1, 3)} - sage: x.is_surjective() - True - sage: x.is_injective() - False - sage: x.is_identity() - False - - sage: S = simplicial_complexes.Sphere(2) - sage: H = Hom(S,S) - sage: i = H.identity() - sage: i.image() - Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)} - sage: i.is_surjective() - True - sage: i.is_injective() - True - sage: i.is_identity() - True - - sage: S = simplicial_complexes.Sphere(2) - sage: H = Hom(S,S) - sage: i = H.identity() - sage: j = i.fiber_product(i) - sage: j - Simplicial complex morphism: - From: Simplicial complex with 4 vertices and 4 facets - To: Minimal triangulation of the 2-sphere - Defn: L0R0 |--> 0 - L1R1 |--> 1 - L2R2 |--> 2 - L3R3 |--> 3 - sage: S = simplicial_complexes.Sphere(2) - sage: T = S.product(SimplicialComplex([[0,1]]), rename_vertices = False, is_mutable=False) - sage: H = Hom(T,S) - sage: T - Simplicial complex with 8 vertices and 12 facets - sage: sorted(T.vertices()) - [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1)] - sage: f = {(0, 0): 0, (0, 1): 0, (1, 0): 1, (1, 1): 1, (2, 0): 2, (2, 1): 2, (3, 0): 3, (3, 1): 3} - sage: x = H(f) - sage: U = simplicial_complexes.Sphere(1) - sage: G = Hom(U,S) - sage: U - Minimal triangulation of the 1-sphere - sage: g = {0:0,1:1,2:2} - sage: y = G(g) - sage: z = y.fiber_product(x) - sage: z # this is the mapping path space - Simplicial complex morphism: - From: Simplicial complex with 6 vertices and ... facets - To: Minimal triangulation of the 2-sphere - Defn: ['L0R(0, 0)', 'L0R(0, 1)', 'L1R(1, 0)', 'L1R(1, 1)', 'L2R(2, 0)', 'L2R(2, 1)'] --> [0, 0, 1, 1, 2, 2] +The current version is :mod:`sage.topology.simplicial_complex_morphism`. """ -#***************************************************************************** -# Copyright (C) 2009 D. Benjamin Antieau -# -# Distributed under the terms of the GNU General Public License (GPL) -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# See the GNU General Public License for more details; the full text -# is available at: -# -# http://www.gnu.org/licenses/ -# -#***************************************************************************** - -from sage.homology.simplicial_complex import Simplex, SimplicialComplex -from sage.matrix.constructor import matrix, zero_matrix -from sage.rings.integer_ring import ZZ -from sage.homology.chain_complex_morphism import ChainComplexMorphism -from sage.combinat.permutation import Permutation -from sage.algebras.steenrod.steenrod_algebra_misc import convert_perm -from sage.categories.morphism import Morphism -from sage.categories.homset import Hom -from sage.categories.simplicial_complexes import SimplicialComplexes - - -def is_SimplicialComplexMorphism(x): - """ - Return ``True`` if and only if ``x`` is a morphism of simplicial complexes. - - EXAMPLES:: - - sage: from sage.homology.simplicial_complex_morphism import is_SimplicialComplexMorphism - sage: S = SimplicialComplex([[0,1],[3,4]], is_mutable=False) - sage: H = Hom(S,S) - sage: f = {0:0,1:1,3:3,4:4} - sage: x = H(f) - sage: is_SimplicialComplexMorphism(x) - True - - """ - return isinstance(x, SimplicialComplexMorphism) - - -class SimplicialComplexMorphism(Morphism): - """ - An element of this class is a morphism of simplicial complexes. - """ - def __init__(self,f,X,Y): - """ - Input is a dictionary ``f``, the domain ``X``, and the codomain ``Y``. - - One can define the dictionary on the vertices of `X`. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1],[2],[3,4],[5]], is_mutable=False) - sage: H = Hom(S,S) - sage: f = {0:0,1:1,2:2,3:3,4:4,5:5} - sage: g = {0:0,1:1,2:0,3:3,4:4,5:0} - sage: x = H(f) - sage: y = H(g) - sage: x == y - False - sage: x.image() - Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(2,), (5,), (0, 1), (3, 4)} - sage: y.image() - Simplicial complex with vertex set (0, 1, 3, 4) and facets {(0, 1), (3, 4)} - sage: x.image() == y.image() - False - """ - if not isinstance(X,SimplicialComplex) or not isinstance(Y,SimplicialComplex): - raise ValueError("X and Y must be SimplicialComplexes") - if not set(f.keys()) == set(X.vertices()): - raise ValueError("f must be a dictionary from the vertex set of X to single values in the vertex set of Y") - dim = X.dimension() - Y_faces = Y.faces() - for k in range(dim+1): - for i in X.faces()[k]: - tup = i.tuple() - fi = [] - for j in tup: - fi.append(f[j]) - v = Simplex(set(fi)) - if v not in Y_faces[v.dimension()]: - raise ValueError("f must be a dictionary from the vertices of X to the vertices of Y") - self._vertex_dictionary = f - Morphism.__init__(self, Hom(X,Y,SimplicialComplexes())) - - def __eq__(self,x): - """ - Return ``True`` if and only if ``self == x``. - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(2) - sage: H = Hom(S,S) - sage: i = H.identity() - sage: i - Simplicial complex endomorphism of Minimal triangulation of the 2-sphere - Defn: 0 |--> 0 - 1 |--> 1 - 2 |--> 2 - 3 |--> 3 - sage: f = {0:0,1:1,2:2,3:2} - sage: j = H(f) - sage: i==j - False - - sage: T = SimplicialComplex([[1,2]], is_mutable=False) - sage: T - Simplicial complex with vertex set (1, 2) and facets {(1, 2)} - sage: G = Hom(T,T) - sage: k = G.identity() - sage: g = {1:1,2:2} - sage: l = G(g) - sage: k == l - True - """ - if not isinstance(x,SimplicialComplexMorphism) or self.codomain() != x.codomain() or self.domain() != x.domain() or self._vertex_dictionary != x._vertex_dictionary: - return False - else: - return True - - def __call__(self,x,orientation=False): - """ - Input is a simplex of the domain. Output is the image simplex. - - If the optional argument ``orientation`` is ``True``, then this - returns a pair ``(image simplex, oriented)`` where ``oriented`` - is 1 or `-1` depending on whether the map preserves or reverses - the orientation of the image simplex. - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(2) - sage: T = simplicial_complexes.Sphere(3) - sage: S - Minimal triangulation of the 2-sphere - sage: T - Minimal triangulation of the 3-sphere - sage: f = {0:0,1:1,2:2,3:3} - sage: H = Hom(S,T) - sage: x = H(f) - sage: from sage.homology.simplicial_complex import Simplex - sage: x(Simplex([0,2,3])) - (0, 2, 3) - - An orientation-reversing example:: - - sage: X = SimplicialComplex([[0,1]], is_mutable=False) - sage: g = Hom(X,X)({0:1, 1:0}) - sage: g(Simplex([0,1])) - (0, 1) - sage: g(Simplex([0,1]), orientation=True) - ((0, 1), -1) - """ - dim = self.domain().dimension() - if not isinstance(x, Simplex) or x.dimension() > dim or x not in self.domain().faces()[x.dimension()]: - raise ValueError("x must be a simplex of the source of f") - tup = x.tuple() - fx = [] - for j in tup: - fx.append(self._vertex_dictionary[j]) - if orientation: - if len(set(fx)) == len(tup): - oriented = Permutation(convert_perm(fx)).signature() - else: - oriented = 1 - return (Simplex(set(fx)), oriented) - else: - return Simplex(set(fx)) - - def _repr_type(self): - """ - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(1) - sage: T = simplicial_complexes.Sphere(2) - sage: H = Hom(S,T) - sage: f = {0:0,1:1,2:2} - sage: H(f)._repr_type() - 'Simplicial complex' - """ - return "Simplicial complex" - - def _repr_defn(self): - """ - If there are fewer than 5 vertices, print the image of each vertex - on a separate line. Otherwise, print the map as a single line. - - EXAMPLES:: - - sage: S = simplicial_complexes.Simplex(1) - sage: print(Hom(S,S).identity()._repr_defn()) - 0 |--> 0 - 1 |--> 1 - sage: T = simplicial_complexes.Torus() - sage: print(Hom(T,T).identity()._repr_defn()) - [0, 1, 2, 3, 4, 5, 6] --> [0, 1, 2, 3, 4, 5, 6] - """ - vd = self._vertex_dictionary - try: - keys = sorted(vd.keys()) - except TypeError: - keys = sorted(vd.keys(), key=str) - if len(vd) < 5: - return '\n'.join("{} |--> {}".format(v, vd[v]) for v in keys) - domain = list(vd.keys()) - try: - domain = sorted(domain) - except TypeError: - domain = sorted(domain, key=str) - codomain = [vd[v] for v in domain] - return "{} --> {}".format(domain, codomain) - - def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain=False): - """ - Return the associated chain complex morphism of ``self``. - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(1) - sage: T = simplicial_complexes.Sphere(2) - sage: H = Hom(S,T) - sage: f = {0:0,1:1,2:2} - sage: x = H(f) - sage: x - Simplicial complex morphism: - From: Minimal triangulation of the 1-sphere - To: Minimal triangulation of the 2-sphere - Defn: 0 |--> 0 - 1 |--> 1 - 2 |--> 2 - sage: a = x.associated_chain_complex_morphism() - sage: a - Chain complex morphism: - From: Chain complex with at most 2 nonzero terms over Integer Ring - To: Chain complex with at most 3 nonzero terms over Integer Ring - sage: a._matrix_dictionary - {0: [1 0 0] - [0 1 0] - [0 0 1] - [0 0 0], 1: [1 0 0] - [0 1 0] - [0 0 0] - [0 0 1] - [0 0 0] - [0 0 0], 2: []} - sage: x.associated_chain_complex_morphism(augmented=True) - Chain complex morphism: - From: Chain complex with at most 3 nonzero terms over Integer Ring - To: Chain complex with at most 4 nonzero terms over Integer Ring - sage: x.associated_chain_complex_morphism(cochain=True) - Chain complex morphism: - From: Chain complex with at most 3 nonzero terms over Integer Ring - To: Chain complex with at most 2 nonzero terms over Integer Ring - sage: x.associated_chain_complex_morphism(augmented=True,cochain=True) - Chain complex morphism: - From: Chain complex with at most 4 nonzero terms over Integer Ring - To: Chain complex with at most 3 nonzero terms over Integer Ring - sage: x.associated_chain_complex_morphism(base_ring=GF(11)) - Chain complex morphism: - From: Chain complex with at most 2 nonzero terms over Finite Field of size 11 - To: Chain complex with at most 3 nonzero terms over Finite Field of size 11 - - Some simplicial maps which reverse the orientation of a few simplices:: - - sage: g = {0:1, 1:2, 2:0} - sage: H(g).associated_chain_complex_morphism()._matrix_dictionary - {0: [0 0 1] - [1 0 0] - [0 1 0] - [0 0 0], 1: [ 0 -1 0] - [ 0 0 -1] - [ 0 0 0] - [ 1 0 0] - [ 0 0 0] - [ 0 0 0], 2: []} - sage: X = SimplicialComplex([[0, 1]], is_mutable=False) - sage: Hom(X,X)({0:1, 1:0}).associated_chain_complex_morphism()._matrix_dictionary - {0: [0 1] - [1 0], 1: [-1]} - """ - max_dim = max(self.domain().dimension(),self.codomain().dimension()) - min_dim = min(self.domain().dimension(),self.codomain().dimension()) - matrices = {} - if augmented is True: - m = matrix(base_ring,1,1,1) - if not cochain: - matrices[-1] = m - else: - matrices[-1] = m.transpose() - for dim in range(min_dim+1): - X_faces = self.domain()._n_cells_sorted(dim) - Y_faces = self.codomain()._n_cells_sorted(dim) - num_faces_X = len(X_faces) - num_faces_Y = len(Y_faces) - mval = [0 for i in range(num_faces_X*num_faces_Y)] - for i in X_faces: - y, oriented = self(i, orientation=True) - if y.dimension() < dim: - pass - else: - mval[X_faces.index(i)+(Y_faces.index(y)*num_faces_X)] = oriented - m = matrix(base_ring,num_faces_Y,num_faces_X,mval,sparse=True) - if not cochain: - matrices[dim] = m - else: - matrices[dim] = m.transpose() - for dim in range(min_dim+1,max_dim+1): - try: - l1 = len(self.codomain().n_cells(dim)) - except KeyError: - l1 = 0 - try: - l2 = len(self.domain().n_cells(dim)) - except KeyError: - l2 = 0 - m = zero_matrix(base_ring,l1,l2,sparse=True) - if not cochain: - matrices[dim] = m - else: - matrices[dim] = m.transpose() - if not cochain: - return ChainComplexMorphism(matrices, - self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain), - self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) - return ChainComplexMorphism(matrices, - self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain), - self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) - - def image(self): - """ - Computes the image simplicial complex of `f`. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) - sage: T = SimplicialComplex([[0,1]], is_mutable=False) - sage: f = {0:0,1:1,2:0,3:1} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x.image() - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - - sage: S = SimplicialComplex(is_mutable=False) - sage: H = Hom(S,S) - sage: i = H.identity() - sage: i.image() - Simplicial complex with vertex set () and facets {()} - sage: i.is_surjective() - True - sage: S = SimplicialComplex([[0,1]], is_mutable=False) - sage: T = SimplicialComplex([[0,1], [0,2]], is_mutable=False) - sage: f = {0:0,1:1} - sage: g = {0:0,1:1} - sage: k = {0:0,1:2} - sage: H = Hom(S,T) - sage: x = H(f) - sage: y = H(g) - sage: z = H(k) - sage: x == y - True - sage: x == z - False - sage: x.image() - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - sage: y.image() - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - sage: z.image() - Simplicial complex with vertex set (0, 2) and facets {(0, 2)} - - """ - fa = [self(i) for i in self.domain().facets()] - return SimplicialComplex(fa, maximality_check=True) - - def is_surjective(self): - """ - Return ``True`` if and only if ``self`` is surjective. - - EXAMPLES:: - - sage: S = SimplicialComplex([(0,1,2)], is_mutable=False) - sage: S - Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} - sage: T = SimplicialComplex([(0,1)], is_mutable=False) - sage: T - Simplicial complex with vertex set (0, 1) and facets {(0, 1)} - sage: H = Hom(S,T) - sage: x = H({0:0,1:1,2:1}) - sage: x.is_surjective() - True - - sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) - sage: T = SimplicialComplex([[0,1]], is_mutable=False) - sage: f = {0:0,1:1,2:0,3:1} - sage: H = Hom(S,T) - sage: x = H(f) - sage: x.is_surjective() - True - """ - return self.codomain() == self.image() - - def is_injective(self): - """ - Return ``True`` if and only if ``self`` is injective. - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(1) - sage: T = simplicial_complexes.Sphere(2) - sage: U = simplicial_complexes.Sphere(3) - sage: H = Hom(T,S) - sage: G = Hom(T,U) - sage: f = {0:0,1:1,2:0,3:1} - sage: x = H(f) - sage: g = {0:0,1:1,2:2,3:3} - sage: y = G(g) - sage: x.is_injective() - False - sage: y.is_injective() - True - - """ - v = [self._vertex_dictionary[i[0]] for i in self.domain().faces()[0]] - for i in v: - if v.count(i) > 1: - return False - return True - - def is_identity(self): - """ - If ``self`` is an identity morphism, returns ``True``. - Otherwise, ``False``. - - EXAMPLES:: - - sage: T = simplicial_complexes.Sphere(1) - sage: G = Hom(T,T) - sage: T - Minimal triangulation of the 1-sphere - sage: j = G({0:0,1:1,2:2}) - sage: j.is_identity() - True - - sage: S = simplicial_complexes.Sphere(2) - sage: T = simplicial_complexes.Sphere(3) - sage: H = Hom(S,T) - sage: f = {0:0,1:1,2:2,3:3} - sage: x = H(f) - sage: x - Simplicial complex morphism: - From: Minimal triangulation of the 2-sphere - To: Minimal triangulation of the 3-sphere - Defn: 0 |--> 0 - 1 |--> 1 - 2 |--> 2 - 3 |--> 3 - sage: x.is_identity() - False - """ - if self.domain() != self.codomain(): - return False - else: - f = dict() - for i in self.domain().vertices(): - f[i] = i - if self._vertex_dictionary != f: - return False - else: - return True - - def fiber_product(self, other, rename_vertices = True): - """ - Fiber product of ``self`` and ``other``. Both morphisms should have - the same codomain. The method returns a morphism of simplicial - complexes, which is the morphism from the space of the fiber product - to the codomain. - - EXAMPLES:: - - sage: S = SimplicialComplex([[0,1],[1,2]], is_mutable=False) - sage: T = SimplicialComplex([[0,2],[1]], is_mutable=False) - sage: U = SimplicialComplex([[0,1],[2]], is_mutable=False) - sage: H = Hom(S,U) - sage: G = Hom(T,U) - sage: f = {0:0,1:1,2:0} - sage: g = {0:0,1:1,2:1} - sage: x = H(f) - sage: y = G(g) - sage: z = x.fiber_product(y) - sage: z - Simplicial complex morphism: - From: Simplicial complex with 4 vertices and facets {...} - To: Simplicial complex with vertex set (0, 1, 2) and facets {(2,), (0, 1)} - Defn: L0R0 |--> 0 - L1R1 |--> 1 - L1R2 |--> 1 - L2R0 |--> 0 - """ - if self.codomain() != other.codomain(): - raise ValueError("self and other must have the same codomain.") - X = self.domain().product(other.domain(),rename_vertices = rename_vertices) - v = [] - f = dict() - eff1 = self.domain().vertices() - eff2 = other.domain().vertices() - for i in eff1: - for j in eff2: - if self(Simplex([i])) == other(Simplex([j])): - if rename_vertices: - v.append("L"+str(i)+"R"+str(j)) - f["L"+str(i)+"R"+str(j)] = self._vertex_dictionary[i] - else: - v.append((i,j)) - f[(i,j)] = self._vertex_dictionary[i] - return SimplicialComplexMorphism(f, X.generated_subcomplex(v), self.codomain()) - - def mapping_torus(self): - r""" - The mapping torus of a simplicial complex endomorphism - - The mapping torus is the simplicial complex formed by taking - the product of the domain of ``self`` with a `4` point - interval `[I_0, I_1, I_2, I_3]` and identifying vertices of - the form `(I_0, v)` with `(I_3, w)` where `w` is the image of - `v` under the given morphism. - - See :wikipedia:`Mapping torus` - - EXAMPLES:: - - sage: C = simplicial_complexes.Sphere(1) # Circle - sage: T = Hom(C,C).identity().mapping_torus() ; T # Torus - Simplicial complex with 9 vertices and 18 facets - sage: T.homology() == simplicial_complexes.Torus().homology() - True - - sage: f = Hom(C,C)({0:0,1:2,2:1}) - sage: K = f.mapping_torus() ; K # Klein Bottle - Simplicial complex with 9 vertices and 18 facets - sage: K.homology() == simplicial_complexes.KleinBottle().homology() - True - - TESTS:: - - sage: g = Hom(simplicial_complexes.Simplex([1]),C)({1:0}) - sage: g.mapping_torus() - Traceback (most recent call last): - ... - ValueError: self must have the same domain and codomain. - """ - if self.domain() != self.codomain(): - raise ValueError("self must have the same domain and codomain.") - map_dict = self._vertex_dictionary - interval = SimplicialComplex([["I0","I1"],["I1","I2"]]) - product = interval.product(self.domain(),False) - facets = list(product.maximal_faces()) - for facet in self.domain()._facets: - left = [ ("I0",v) for v in facet ] - right = [ ("I2",map_dict[v]) for v in facet ] - for i in range(facet.dimension()+1): - facets.append(tuple(left[:i+1]+right[i:])) - return SimplicialComplex(facets) - - def induced_homology_morphism(self, base_ring=None, cohomology=False): - """ - The map in (co)homology induced by this map - - INPUT: - - - ``base_ring`` -- must be a field (optional, default ``QQ``) - - - ``cohomology`` -- boolean (optional, default ``False``). If - ``True``, the map induced in cohomology rather than homology. - - EXAMPLES:: - - sage: S = simplicial_complexes.Sphere(1) - sage: T = S.product(S, is_mutable=False) - sage: H = Hom(S,T) - sage: diag = H.diagonal_morphism() - sage: h = diag.induced_homology_morphism(QQ) - sage: h - Graded vector space morphism: - From: Homology module of Minimal triangulation of the 1-sphere over Rational Field - To: Homology module of Simplicial complex with 9 vertices and 18 facets over Rational Field - Defn: induced by: - Simplicial complex morphism: - From: Minimal triangulation of the 1-sphere - To: Simplicial complex with 9 vertices and 18 facets - Defn: 0 |--> L0R0 - 1 |--> L1R1 - 2 |--> L2R2 - - We can view the matrix form for the homomorphism:: - - sage: h.to_matrix(0) # in degree 0 - [1] - sage: h.to_matrix(1) # in degree 1 - [1] - [1] - sage: h.to_matrix() # the entire homomorphism - [1|0] - [-+-] - [0|1] - [0|1] - [-+-] - [0|0] - - The map on cohomology should be dual to the map on homology:: - - sage: coh = diag.induced_homology_morphism(QQ, cohomology=True) - sage: coh.to_matrix(1) - [1 1] - sage: h.to_matrix() == coh.to_matrix().transpose() - True - - We can evaluate the map on (co)homology classes:: - - sage: x,y = list(T.cohomology_ring(QQ).basis(1)) - sage: coh(x) - h^{1,0} - sage: coh(2*x+3*y) - 5*h^{1,0} - - Note that the complexes must be immutable for this to - work. Many, but not all, complexes are immutable when - constructed:: - - sage: S.is_immutable() - True - sage: S.barycentric_subdivision().is_immutable() - False - sage: S2 = S.suspension() - sage: S2.is_immutable() - False - sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() - Traceback (most recent call last): - ... - ValueError: the domain and codomain complexes must be immutable - sage: S2.set_immutable(); S2.is_immutable() - True - sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() - """ - from .homology_morphism import InducedHomologyMorphism - return InducedHomologyMorphism(self, base_ring, cohomology) - - def is_contiguous_to(self, other): - r""" - Return ``True`` if ``self`` is contiguous to ``other``. - - Two morphisms `f_0, f_1: K \to L` are *contiguous* if for any - simplex `\sigma \in K`, the union `f_0(\sigma) \cup - f_1(\sigma)` is a simplex in `L`. This is not a transitive - relation, but it induces an equivalence relation on simplicial - maps: `f` is equivalent to `g` if there is a finite sequence - `f_0 = f`, `f_1`, ..., `f_n = g` such that `f_i` and `f_{i+1}` - are contiguous for each `i`. - - This is related to maps being homotopic: if they are - contiguous, then they induce homotopic maps on the geometric - realizations. Given two homotopic maps on the geometric - realizations, then after barycentrically subdividing `n` times - for some `n`, the maps have simplicial approximations which - are in the same contiguity class. (This last fact is only true - if the domain is a *finite* simplicial complex, by the way.) - - See Section 3.5 of Spanier [Spa1966]_ for details. - - ALGORITHM: - - It is enough to check when `\sigma` ranges over the facets. - - INPUT: - - - ``other`` -- a simplicial complex morphism with the same - domain and codomain as ``self`` - - EXAMPLES:: - - sage: K = simplicial_complexes.Simplex(1) - sage: L = simplicial_complexes.Sphere(1) - sage: H = Hom(K, L) - sage: f = H({0: 0, 1: 1}) - sage: g = H({0: 0, 1: 0}) - sage: f.is_contiguous_to(f) - True - sage: f.is_contiguous_to(g) - True - sage: h = H({0: 1, 1: 2}) - sage: f.is_contiguous_to(h) - False - - TESTS:: - - sage: one = Hom(K,K).identity() - sage: one.is_contiguous_to(f) - False - sage: one.is_contiguous_to(3) # nonsensical input - False - """ - if not isinstance(other, SimplicialComplexMorphism): - return False - if self.codomain() != other.codomain() or self.domain() != other.domain(): - return False - domain = self.domain() - codomain = self.codomain() - return all(Simplex(self(sigma).set().union(other(sigma))) in codomain - for sigma in domain.facets()) +from sage.misc.superseded import deprecated_function_alias +import sage.topology.simplicial_complex_morphism +is_SimplicialComplexMorphism = deprecated_function_alias(31925, + sage.topology.simplicial_complex_morphism.is_SimplicialComplexMorphism) +SimplicialComplexMorphism = deprecated_function_alias(31925, + sage.topology.simplicial_complex_morphism.SimplicialComplexMorphism) diff --git a/src/sage/homology/simplicial_set.py b/src/sage/homology/simplicial_set.py index 7f6403299cb..51e1a36047a 100644 --- a/src/sage/homology/simplicial_set.py +++ b/src/sage/homology/simplicial_set.py @@ -1,4062 +1,21 @@ # -*- coding: utf-8 -*- r""" -Simplicial sets +Simplicial sets: deprecated -AUTHORS: - -- John H. Palmieri (2016-07) - -This module implements simplicial sets. - -A *simplicial set* `X` is a collection of sets `X_n` indexed by the -non-negative integers; the set `X_n` is called the set of -`n`-simplices. These sets are connected by maps - -.. MATH:: - - d_i: X_n \to X_{n-1}, \ \ 0 \leq i \leq n \ \ \text{(face maps)} \\ - s_j: X_n \to X_{n+1}, \ \ 0 \leq j \leq n \ \ \text{(degeneracy maps)} - -satisfying the *simplicial identities*: - -.. MATH:: - - d_i d_j &= d_{j-1} d_i \ \ \text{if } ij+1 \\ - s_i s_j &= s_{j+1} s_{i} \ \ \text{if } i - - sage: Sigma3 = groups.permutation.Symmetric(3) - sage: BSigma3 = Sigma3.nerve() - sage: pi = BSigma3.fundamental_group(); pi - Finitely presented group < e0, e1 | e0^2, e1^3, (e0*e1^-1)^2 > - sage: pi.order() - 6 - sage: pi.is_abelian() - False - - sage: RP6 = simplicial_sets.RealProjectiveSpace(6) - sage: RP6.homology(reduced=False, base_ring=GF(2)) - {0: Vector space of dimension 1 over Finite Field of size 2, - 1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2, - 3: Vector space of dimension 1 over Finite Field of size 2, - 4: Vector space of dimension 1 over Finite Field of size 2, - 5: Vector space of dimension 1 over Finite Field of size 2, - 6: Vector space of dimension 1 over Finite Field of size 2} - sage: RP6.homology(reduced=False, base_ring=QQ) - {0: Vector space of dimension 1 over Rational Field, - 1: Vector space of dimension 0 over Rational Field, - 2: Vector space of dimension 0 over Rational Field, - 3: Vector space of dimension 0 over Rational Field, - 4: Vector space of dimension 0 over Rational Field, - 5: Vector space of dimension 0 over Rational Field, - 6: Vector space of dimension 0 over Rational Field} - -When infinite simplicial sets are involved, most computations are done -by taking an `n`-skeleton for an appropriate `n`, either implicitly or -explicitly:: - - sage: B3 = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([3])) - sage: B3.disjoint_union(B3).n_skeleton(3) - Disjoint union: (Simplicial set with 15 non-degenerate simplices u Simplicial set with 15 non-degenerate simplices) - sage: S1 = simplicial_sets.Sphere(1) - sage: B3.product(S1).homology(range(4)) - {0: 0, 1: Z x C3, 2: C3, 3: C3} - -Without the ``range`` argument, this would raise an error, since -``B3`` is infinite:: - - sage: B3.product(S1).homology() - Traceback (most recent call last): - ... - NotImplementedError: this simplicial set may be infinite, so specify dimensions when computing homology - -It should be easy to construct many simplicial sets from the -predefined ones using pushouts, pullbacks, etc., but they can also be -constructed "by hand": first define some simplices, then define a -simplicial set by specifying their faces:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: f = AbstractSimplex(1, name='f') - sage: X = SimplicialSet({e: (v,w), f: (w,w)}) - -Now `e` is an edge from `v` to `w` and `f` is an edge starting and -ending at `w`. Therefore the first homology group of `X` should be a -copy of the integers:: - - sage: X.homology(1) - Z +The current version is :mod:`sage.topology.simplicial_set`. """ -#***************************************************************************** -# Copyright (C) 2016 John H. Palmieri -# -# Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# See the GNU General Public License for more details; the full text -# is available at: -# -# http://www.gnu.org/licenses/ -# -#***************************************************************************** - -import copy - -from sage.graphs.graph import Graph -from sage.matrix.constructor import matrix -from sage.misc.cachefunc import cached_method -from sage.misc.fast_methods import WithEqualityById -from sage.rings.integer import Integer -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.structure.parent import Parent -from sage.structure.sage_object import SageObject - -from .algebraic_topological_model import algebraic_topological_model_delta_complex -from .cell_complex import GenericCellComplex -from .chain_complex import ChainComplex -from .chains import Chains, Cochains -from .delta_complex import DeltaComplex -from .simplicial_complex import SimplicialComplex - -from sage.misc.lazy_import import lazy_import -lazy_import('sage.categories.simplicial_sets', 'SimplicialSets') - -######################################################################## -# The classes for simplices. - -class AbstractSimplex_class(SageObject): - """ - A simplex of dimension ``dim``. - - INPUT: - - - ``dim`` -- integer, the dimension - - ``degeneracies`` (optional) -- iterable, the indices of the - degeneracy maps - - ``underlying`` (optional) -- a non-degenerate simplex - - ``name`` (optional) -- string - - ``latex_name`` (optional) -- string - - Users should not call this directly, but instead use - :func:`AbstractSimplex`. See that function for more documentation. - """ - def __init__(self, dim, degeneracies=(), underlying=None, name=None, - latex_name=None): - """ - A simplex of dimension ``dim``. - - INPUT: - - - ``dim`` -- integer, the dimension - - ``degeneracies`` (optional) -- iterable, the indices of the degeneracy maps - - ``underlying`` (optional) -- a non-degenerate simplex - - ``name`` (optional) -- string - - ``latex_name`` (optional) -- string - - Users should not call this directly, but instead use - :func:`AbstractSimplex`. See that function for more - documentation. - - TESTS:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: AbstractSimplex(3, (1,2)) - s_3 s_1 Delta^3 - sage: AbstractSimplex(3, None) - Delta^3 - - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(0, (0,), underlying=v) - sage: e - s_0 v - sage: e.nondegenerate() is v - True - - sage: AbstractSimplex(3.2, None) - Traceback (most recent call last): - ... - ValueError: the dimension must be an integer - sage: AbstractSimplex(-3, None) - Traceback (most recent call last): - ... - ValueError: the dimension must be non-negative - - sage: AbstractSimplex(0, (1,)) - Traceback (most recent call last): - ... - ValueError: invalid list of degeneracy maps on 0-simplex - - Distinct non-degenerate simplices should never be equal, even - if they have the same starting data:: - - sage: from sage.homology.simplicial_set import AbstractSimplex_class - sage: AbstractSimplex_class(3) == AbstractSimplex_class(3) - False - sage: AbstractSimplex(3) == AbstractSimplex(3) - False - - Hashing:: - - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: hash(v) == hash(w) - False - sage: x = v.apply_degeneracies(2,1,0) - sage: hash(x) == hash(v.apply_degeneracies(2,1,0)) - True - - Equality:: - - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: v == w - False - sage: v.apply_degeneracies(2,1,0) is v.apply_degeneracies(2,1,0) - False - sage: v.apply_degeneracies(2,1,0) == v.apply_degeneracies(2,1,0) - True - sage: v == None - False - """ - try: - Integer(dim) - except TypeError: - raise ValueError('the dimension must be an integer') - if dim < 0: - raise ValueError('the dimension must be non-negative') - self._dim = dim - if degeneracies: - self._degens = standardize_degeneracies(*degeneracies) - for (d, s) in enumerate(reversed(self._degens)): - if d + dim < s: - raise ValueError('invalid list of degeneracy maps ' - 'on {}-simplex'.format(dim)) - if underlying is None: - self._underlying = NonDegenerateSimplex(dim) - else: - self._underlying = underlying - else: - self._degens = () - if underlying is None: - self._underlying = self - else: - self._underlying = underlying - if name is not None: - self.rename(name) - self._latex_name = latex_name - - def __hash__(self): - """ - If nondegenerate: return the id of this simplex. - - Otherwise, combine the id of its underlying nondegenerate - simplex with the tuple of indeterminacies. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: hash(v) == hash(w) - False - sage: x = v.apply_degeneracies(2,1,0) - sage: id(x) == id(v.apply_degeneracies(2,1,0)) - False - sage: hash(x) == hash(v.apply_degeneracies(2,1,0)) - True - """ - if self.is_nondegenerate(): - return id(self) - return hash(self.nondegenerate()) ^ hash(self._degens) - - def __eq__(self, other): - """ - Two nondegenerate simplices are equal if they are identical. - Two degenerate simplices are equal if their underlying - nondegenerate simplices are identical and their tuples of - degeneracies are equal. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: v == w - False - sage: v.apply_degeneracies(2,1,0) is v.apply_degeneracies(2,1,0) - False - sage: v.apply_degeneracies(2,1,0) == v.apply_degeneracies(2,1,0) - True - - TESTS:: - - sage: v == None - False - """ - if not isinstance(other, AbstractSimplex_class): - return False - return (self._degens == other._degens - and self.nondegenerate() is other.nondegenerate()) - - def __ne__(self, other): - """ - This returns the negation of `__eq__`. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: v != w - True - sage: x = v.apply_degeneracies(1, 0) - sage: y = v.apply_degeneracies(1, 0) - sage: x != y - False - """ - return not self == other - - def __lt__(self, other): - """ - We implement sorting in the hopes that sorted lists of simplices, - for example as defining data for a simplicial set, will be - well-defined invariants. - - Sort by dimension first. If dimensions are equal, if only one - has a custom name (as set by specifying ``name=NAME`` upon - creation or by calling ``object.rename('NAME')``, put it - first. If both have custom names, sort by the names. As a last - resort, sort by their id. - - TESTS:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - - At this point, comparison between v and w is random, based on - their location in memory. :: - - sage: v < w and w < v - False - sage: v < w or w < v - True - sage: (v < w and w > v) or (w < v and v > w) - True - - Now we add names to force an ordering:: - - sage: w.rename('w') - sage: v < w - False - sage: v > w - True - sage: v >= w - True - sage: v.rename('v') - sage: v < w - True - sage: v <= w - True - sage: v > w - False - - Test other sorting. Dimensions:: - - sage: AbstractSimplex(0) < AbstractSimplex(3) - True - sage: AbstractSimplex(0, ((0,0,0))) <= AbstractSimplex(2) - False - - Degenerate comes after non-degenerate, and if both are - degenerate, sort on the degeneracies:: - - sage: AbstractSimplex(0, ((0,0))) <= AbstractSimplex(2) - False - sage: AbstractSimplex(1, ((0,))) < AbstractSimplex(1, ((1,))) - True - sage: v.apply_degeneracies(1,0) > w.apply_degeneracies(1,0) - False - sage: w.rename('a') - sage: v.apply_degeneracies(1,0) > w.apply_degeneracies(1,0) - True - - Testing `<=`, `>`, `>=`:: - - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: v <= v - True - sage: w <= v - False - sage: v.apply_degeneracies(1,0) <= w.apply_degeneracies(1,0) - True - - sage: v > v - False - sage: w > v - True - sage: v.apply_degeneracies(1,0) > w.apply_degeneracies(1,0) - False - - sage: v >= v - True - sage: w >= v - True - sage: v.apply_degeneracies(1,0) >= w.apply_degeneracies(1,0) - False - """ - if self.dimension() < other.dimension(): - return True - if self.dimension() > other.dimension(): - return False - if self.degeneracies() and not other.degeneracies(): - return False - if other.degeneracies() and not self.degeneracies(): - return True - if self.degeneracies() and other.degeneracies() and self.degeneracies() != other.degeneracies(): - return self.degeneracies() < other.degeneracies() - if hasattr(self.nondegenerate(), '__custom_name'): - if hasattr(other.nondegenerate(), '__custom_name'): - return str(self) < str(other) - return True - else: - if (hasattr(other, '__custom_name') - or hasattr(other.nondegenerate(), '__custom_name')): - return False - return id(self) < id(other) - - def __gt__(self, other): - """ - See :meth:`__lt__` for more doctests. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: e = AbstractSimplex(1, (1,0), name='e') - sage: f = AbstractSimplex(1, (2,1), name='f') - sage: e > f - False - """ - return not (self < other or self == other) - - def __le__(self, other): - """ - See :meth:`__lt__` for more doctests. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: e = AbstractSimplex(1, (1,0), name='e') - sage: f = AbstractSimplex(1, (2,1), name='f') - sage: e <= f - True - """ - return self < other or self == other - - def __ge__(self, other): - """ - See :meth:`__lt__` for more doctests. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: e = AbstractSimplex(1, (1,0), name='e') - sage: f = AbstractSimplex(1, (2,1), name='f') - sage: e >= f - False - """ - return not self < other - - def nondegenerate(self): - """ - The non-degenerate simplex underlying this one. - - Therefore return itself if this simplex is non-degenerate. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: v = AbstractSimplex(0, name='v') - sage: sigma = v.apply_degeneracies(1, 0) - sage: sigma.nondegenerate() - v - sage: tau = AbstractSimplex(1, (3,2,1)) - sage: x = tau.nondegenerate(); x - Delta^1 - sage: x == tau.nondegenerate() - True - - sage: AbstractSimplex(1, None) - Delta^1 - sage: AbstractSimplex(1, None) == x - False - sage: AbstractSimplex(1, None) == tau.nondegenerate() - False - """ - return self._underlying - - def degeneracies(self): - """ - Return the list of indices for the degeneracy maps for this - simplex. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: AbstractSimplex(4, (0,0,0)).degeneracies() - [2, 1, 0] - sage: AbstractSimplex(4, None).degeneracies() - [] - """ - return list(self._degens) - - def is_degenerate(self): - """ - True if this simplex is degenerate. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: AbstractSimplex(3, (2,1)).is_degenerate() - True - sage: AbstractSimplex(3, None).is_degenerate() - False - """ - return bool(self.degeneracies()) - - def is_nondegenerate(self): - """ - True if this simplex is non-degenerate. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: AbstractSimplex(3, (2,1)).is_nondegenerate() - False - sage: AbstractSimplex(3, None).is_nondegenerate() - True - sage: AbstractSimplex(5).is_nondegenerate() - True - """ - return not self.is_degenerate() - - def dimension(self): - """ - The dimension of this simplex. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: AbstractSimplex(3, (2,1)).dimension() - 5 - sage: AbstractSimplex(3, None).dimension() - 3 - sage: AbstractSimplex(7).dimension() - 7 - """ - return self._dim + len(self.degeneracies()) - - def apply_degeneracies(self, *args): - """ - Apply the degeneracies given by the arguments ``args`` to this simplex. - - INPUT: - - - ``args`` -- integers - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: v = AbstractSimplex(0) - sage: e = v.apply_degeneracies(0) - sage: e.nondegenerate() == v - True - sage: f = e.apply_degeneracies(0) - sage: f - s_1 s_0 Delta^0 - sage: f.degeneracies() - [1, 0] - sage: f.nondegenerate() == v - True - sage: v.apply_degeneracies(1, 0) - s_1 s_0 Delta^0 - - TESTS:: - - sage: e.apply_degeneracies() == e - True - - Do not pass an explicit list or tuple as the argument: call - this with the syntax ``x.apply_degeneracies(1,0)``, not - ``x.apply_degeneracies([1,0])``:: - - sage: e.apply_degeneracies([1,0]) - Traceback (most recent call last): - ... - TypeError: degeneracies are indexed by non-negative integers; do not use an explicit list or tuple - """ - if not args: - return self - underlying = self.nondegenerate() - return AbstractSimplex(underlying.dimension(), - degeneracies= list(args) + self.degeneracies(), - underlying=underlying) - - def __copy__(self): - """ - Return a copy of this simplex. - - Forget the "underlying" non-degenerate simplex. If this - simplex has a name, then its copy's name is obtained by adding - a prime ``'`` at the end. - - TESTS:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: v = AbstractSimplex(0) - sage: copy(v) == v - False - sage: copy(v).nondegenerate() == v - False - sage: x = v.apply_degeneracies(1, 0) - sage: y = copy(v).apply_degeneracies(1, 0) - sage: z = copy(x) - sage: x == y or x == z or y == z - False - sage: x.nondegenerate() == copy(v) - False - sage: y.nondegenerate() == v - False - - sage: v.rename('v') - sage: copy(v) - v' - sage: copy(copy(v)) - v'' - """ - # Don't preserve the underlying simplex when copying, just the - # dimension, the degeneracies, and the name (with a prime - # added). - sigma = AbstractSimplex(self._dim, degeneracies=self.degeneracies()) - if hasattr(self, '__custom_name'): - sigma.rename(str(self) + "'") - return sigma - - def __deepcopy__(self, memo): - """ - Return a "deep" copy of this simplex. - - INPUT: - - - ``memo`` -- "memo" dictionary required by the ``copy.deepcopy`` method - - This returns the same object as the :meth:`__copy__` method - and also updates ``memo``. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: import copy - sage: v = AbstractSimplex(0) - sage: copy.deepcopy(v) == v - False - - TESTS: - - The purpose for this method is to be able to make distinct - copies of simplicial sets:: - - sage: from sage.homology.simplicial_set import SimplicialSet - sage: RP3 = simplicial_sets.RealProjectiveSpace(3) - sage: dict(copy.copy(RP3._data)) == dict(RP3._data) - True - sage: dict(copy.deepcopy(RP3._data)) == dict(RP3._data) - False - sage: SimplicialSet(RP3) == RP3 - False - sage: copy.copy(RP3) == RP3 - False - """ - underlying = self.nondegenerate() - degens = self.degeneracies() - try: - return memo[underlying].apply_degeneracies(*degens) - except KeyError: - sigma = AbstractSimplex(underlying._dim) - if hasattr(underlying, '__custom_name'): - sigma.rename(str(self) + "'") - memo[underlying] = sigma - return sigma.apply_degeneracies(*degens) - - def _repr_(self): - """ - Print representation. - - TESTS:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: AbstractSimplex(3, None) - Delta^3 - sage: AbstractSimplex(3, (0,)) - s_0 Delta^3 - sage: AbstractSimplex(3, (0, 0)) - s_1 s_0 Delta^3 - - Test renaming:: - - sage: v = AbstractSimplex(0) - sage: v - Delta^0 - sage: v.rename('v') - sage: v - v - sage: v.apply_degeneracies(1, 0) - s_1 s_0 v - """ - if self.degeneracies(): - degens = ' '.join(['s_{}'.format(i) for i in self.degeneracies()]) - return degens + ' {}'.format(self.nondegenerate()) - return 'Delta^{}'.format(self._dim) - - def _latex_(self): - r""" - LaTeX representation. - - TESTS:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: latex(AbstractSimplex(18, None)) - \Delta^{18} - sage: latex(AbstractSimplex(3, (0, 0,))) - s_{1} s_{0} \Delta^{3} - sage: latex(AbstractSimplex(3, (0, 0,), name='x')) - x - sage: latex(AbstractSimplex(3, name='x').apply_degeneracies(0, 0)) - s_{1} s_{0} x - sage: latex(AbstractSimplex(3, (0, 0,), name='x', latex_name='y')) - y - sage: latex(AbstractSimplex(3, name='x', latex_name='y').apply_degeneracies(0, 0)) - s_{1} s_{0} y - """ - if self._latex_name is not None: - return self._latex_name - if hasattr(self, '__custom_name'): - return str(self) - if self.nondegenerate()._latex_name is not None: - simplex = self.nondegenerate()._latex_name - elif hasattr(self.nondegenerate(), '__custom_name'): - simplex = str(self.nondegenerate()) - else: - simplex = "\\Delta^{{{}}}".format(self._dim) - if self.degeneracies(): - degens = ' '.join(['s_{{{}}}'.format(i) for i in self.degeneracies()]) - return degens + ' ' + simplex - return simplex - - -# If we inherit from AbstractSimplex_class first in the following, -# then we have to override __eq__ and __hash__. If we inherit from -# WithEqualityById first, then we have to override __lt__, __gt__, -# __ge__, __le__. Inheriting from AbstractSimplex_class first seems to -# be slightly faster. -class NonDegenerateSimplex(AbstractSimplex_class, WithEqualityById): - def __init__(self, dim, name=None, latex_name=None): - """ - A nondegenerate simplex. - - INPUT: - - - ``dim`` -- non-negative integer, the dimension - - - ``name`` (optional) -- string, a name for this simplex. - - - ``latex_name`` (optional) -- string, a name for this simplex to - use in the LaTeX representation. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: v = AbstractSimplex(0, name='v') - sage: v - v - sage: type(v) - - - Distinct non-degenerate simplices should never be equal, even - if they have the same starting data. :: - - sage: v == AbstractSimplex(0, name='v') - False - sage: AbstractSimplex(3) == AbstractSimplex(3) - False - - sage: from sage.homology.simplicial_set import NonDegenerateSimplex - sage: x = NonDegenerateSimplex(0, name='x') - sage: x == NonDegenerateSimplex(0, name='x') - False - """ - AbstractSimplex_class.__init__(self, dim, name=name, latex_name=latex_name) - - __eq__ = WithEqualityById.__eq__ - __hash__ = WithEqualityById.__hash__ - - -# The following function returns an instance of either -# AbstractSimplex_class or NonDegenerateSimplex. - -def AbstractSimplex(dim, degeneracies=(), underlying=None, - name=None, latex_name=None): - r""" - An abstract simplex, a building block of a simplicial set. - - In a simplicial set, a simplex either is non-degenerate or is - obtained by applying degeneracy maps to a non-degenerate simplex. - - INPUT: - - - ``dim`` -- a non-negative integer, the dimension of the - underlying non-degenerate simplex. - - - ``degeneracies`` (optional, default ``None``) -- a list or tuple of - non-negative integers, the degeneracies to be applied. - - - ``underlying`` (optional) -- a non-degenerate simplex to which - the degeneracies are being applied. - - - ``name`` (optional) -- string, a name for this simplex. - - - ``latex_name`` (optional) -- string, a name for this simplex to - use in the LaTeX representation. - - So to define a simplex formed by applying the degeneracy maps `s_2 - s_1` to a 1-simplex, call ``AbstractSimplex(1, (2, 1))``. - - Specify ``underlying`` if you need to keep explicit track of the - underlying non-degenerate simplex, for example when computing - faces of another simplex. This is mainly for use by the method - :meth:`AbstractSimplex_class.apply_degeneracies`. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex - sage: AbstractSimplex(3, (3, 1)) - s_3 s_1 Delta^3 - sage: AbstractSimplex(3, None) - Delta^3 - sage: AbstractSimplex(3) - Delta^3 - - Simplices may be named (or renamed), affecting how they are printed:: - - sage: AbstractSimplex(0) - Delta^0 - sage: v = AbstractSimplex(0, name='v') - sage: v - v - sage: v.rename('w_0') - sage: v - w_0 - sage: latex(v) - w_0 - sage: latex(AbstractSimplex(0, latex_name='\\sigma')) - \sigma - - The simplicial identities are used to put the degeneracies in - standard decreasing form:: - - sage: x = AbstractSimplex(0, (0, 0, 0)) - sage: x - s_2 s_1 s_0 Delta^0 - sage: x.degeneracies() - [2, 1, 0] - - Use of the ``underlying`` argument:: - - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(0, (0,), underlying=v) - sage: e - s_0 v - sage: e.nondegenerate() is v - True - - sage: e.dimension() - 1 - sage: e.is_degenerate() - True - - Distinct non-degenerate simplices are never equal:: - - sage: AbstractSimplex(0, None) == AbstractSimplex(0, None) - False - sage: AbstractSimplex(0, (2,1,0)) == AbstractSimplex(0, (2,1,0)) - False - - sage: e = AbstractSimplex(0, ((0,))) - sage: f = AbstractSimplex(0, ((0,))) - sage: e == f - False - sage: e.nondegenerate() == f.nondegenerate() - False - - This means that if, when defining a simplicial set, you specify - the faces of a 2-simplex as:: - - (e, e, e) - - then the faces are the same degenerate vertex, but if you specify - the faces as:: - - (AbstractSimplex(0, ((0,))), AbstractSimplex(0, ((0,))), AbstractSimplex(0, ((0,)))) - - then the faces are three different degenerate vertices. - - View a command like ``AbstractSimplex(0, (2,1,0))`` as first - constructing ``AbstractSimplex(0)`` and then applying degeneracies - to it, and you always get distinct simplices from different calls - to ``AbstractSimplex(0)``. On the other hand, if you apply - degeneracies to the same non-degenerate simplex, the resulting - simplices are equal:: - - sage: v = AbstractSimplex(0) - sage: v.apply_degeneracies(1, 0) == v.apply_degeneracies(1, 0) - True - sage: AbstractSimplex(1, (0,), underlying=v) == AbstractSimplex(1, (0,), underlying=v) - True - """ - if degeneracies: - if underlying is None: - underlying = NonDegenerateSimplex(dim) - return AbstractSimplex_class(dim, degeneracies=degeneracies, - underlying=underlying, - name=name, - latex_name=latex_name) - else: - return NonDegenerateSimplex(dim, name=name, - latex_name=latex_name) - - -######################################################################## -# The main classes for simplicial sets. - -class SimplicialSet_arbitrary(Parent): - r""" - A simplicial set. - - A simplicial set `X` is a collection of sets `X_n`, the - *n-simplices*, indexed by the non-negative integers, together with - maps - - .. MATH:: - - d_i: X_n \to X_{n-1}, \ \ 0 \leq i \leq n \ \ \text{(face maps)} \\ - s_j: X_n \to X_{n+1}, \ \ 0 \leq j \leq n \ \ \text{(degeneracy maps)} - - satisfying the *simplicial identities*: - - .. MATH:: - - d_i d_j &= d_{j-1} d_i \ \ \text{if } ij+1 \\ - s_i s_j &= s_{j+1} s_{i} \ \ \text{if } i simplex.dimension(): - raise ValueError('cannot compute face {} of {}-dimensional ' - 'simplex'.format(i, simplex.dimension())) - faces = self.faces(simplex) - if faces is not None: - return self.faces(simplex)[i] - return None - - def __contains__(self, x): - """ - Return ``True`` if ``x`` is a simplex which is contained in this complex. - - EXAMPLES:: - - sage: S0 = simplicial_sets.Sphere(0) - sage: S1 = simplicial_sets.Sphere(1) - sage: v0 = S0.n_cells(0)[0] - sage: v0 in S0 - True - sage: v0 in S1 - False - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: e = AbstractSimplex(1) - sage: K = SimplicialSet({e: (v, v)}) # the circle - sage: v in K - True - sage: v0 in K - False - sage: S1.n_cells(1)[0] in K - False - - TESTS: - - Make sure we answer gracefully for unexpected input:: - - sage: 248 in K - False - """ - try: - underlying = x.nondegenerate() - return underlying in self.n_cells(underlying.dimension()) - except AttributeError: - return False - - def alexander_whitney(self, simplex, dim_left): - r""" - Return the 'subdivision' of ``simplex`` in this simplicial set - into a pair of simplices. - - The left factor should have dimension ``dim_left``, so the - right factor should have dimension ``dim - dim_left``, if - ``dim`` is the dimension of the starting simplex. The results - are obtained by applying iterated face maps to - ``simplex``. Writing `d` for ``dim`` and `j` for ``dim_left``: - apply `d_{j+1} d_{j+2} ... d_{d}` to get the left factor, - `d_0 ... d_0` to get the right factor. - - INPUT: - - - ``dim_left`` -- integer, the dimension of the left-hand factor - - OUTPUT: a list containing the triple ``(c, left, right)``, - where ``left`` and ``right`` are the two simplices described - above. If either ``left`` or ``right`` is degenerate, ``c`` is - 0; otherwise, ``c`` is 1. This is so that, when used to - compute cup products, it is easy to ignore terms which have - degenerate factors. - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: sigma = S2.n_cells(2)[0] - sage: S2.alexander_whitney(sigma, 0) - [(1, v_0, sigma_2)] - sage: S2.alexander_whitney(sigma, 1) - [(0, s_0 v_0, s_0 v_0)] - """ - dim = simplex.dimension() - if dim_left < 0 or dim_left > dim: - raise ValueError('alexander_whitney is only valid if dim_left ' - 'is between 0 and the dimension of the simplex') - left = simplex - for i in range(dim, dim_left, -1): - left = self.face(left, i) - right = simplex - for i in range(dim_left): - right = self.face(right, 0) - if left.is_degenerate() or right.is_degenerate(): - c = ZZ.zero() - else: - c = ZZ.one() - return [(c, left, right)] - - def nondegenerate_simplices(self, max_dim=None): - """ - Return the sorted list of non-degenerate simplices in this simplicial set. - - INPUT: - - - ``max_dim`` -- optional, default ``None``. If specified, - return the non-degenerate simplices of this dimension or - smaller. This argument is required if this simplicial set is - infinite. - - The sorting is in increasing order of dimension, and within - each dimension, by the name (if present) of each simplex. - - .. NOTE:: - - The sorting is done when the simplicial set is - constructed, so changing the name of a simplex after - construction will not affect the ordering. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: S0 = SimplicialSet({v: None, w: None}) - sage: S0.nondegenerate_simplices() - [Delta^0, Delta^0] - - Name the vertices and reconstruct the simplicial set: they - should be ordered alphabetically:: - - sage: v.rename('v') - sage: w.rename('w') - sage: S0 = SimplicialSet({v: None, w: None}) - sage: S0.nondegenerate_simplices() - [v, w] - - Rename but do not reconstruct the set; the ordering does not - take the new names into account:: - - sage: v.rename('z') - sage: S0.nondegenerate_simplices() # old ordering is used - [z, w] - - sage: X0 = SimplicialSet({v: None, w: None}) - sage: X0.nondegenerate_simplices() # new ordering is used - [w, z] - - Test an infinite example:: - - sage: C3 = groups.misc.MultiplicativeAbelian([3]) - sage: BC3 = simplicial_sets.ClassifyingSpace(C3) - sage: BC3.nondegenerate_simplices(2) - [1, f, f^2, f * f, f * f^2, f^2 * f, f^2 * f^2] - sage: BC3.nondegenerate_simplices() - Traceback (most recent call last): - ... - NotImplementedError: this simplicial set may be infinite, so specify max_dim - """ - if self.is_finite(): - if max_dim is None: - return list(self._simplices) - return list(sigma for sigma in self._simplices if sigma.dimension() <= max_dim) - if max_dim is None: - raise NotImplementedError('this simplicial set may be ' - 'infinite, so specify max_dim') - return list(sigma for sigma in self.n_skeleton(max_dim)._simplices) - - def cells(self, subcomplex=None, max_dim=None): - """ - Return a dictionary of all non-degenerate simplices. - - INPUT: - - - ``subcomplex`` (optional) -- a subsimplicial set of this - simplicial set. If ``subcomplex`` is specified, then return the - simplices in the quotient by the subcomplex. - - - ``max_dim`` -- optional, default ``None``. If specified, - return the non-degenerate simplices of this dimension or - smaller. This argument is required if this simplicial set is - infinite. - - Each key is a dimension, and the corresponding value is the - list of simplices in that dimension. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: S0 = SimplicialSet({v: None, w: None}) - sage: S0.cells() - {0: [Delta^0, Delta^0]} - - sage: v.rename('v') - sage: w.rename('w') - sage: S0.cells() - {0: [v, w]} - - sage: e = AbstractSimplex(1, name='e') - sage: S1 = SimplicialSet({e: (v, v)}) - sage: S1.cells() - {0: [v], 1: [e]} - - sage: S0.cells(S0.subsimplicial_set([v, w])) - {0: [*]} - - sage: X = SimplicialSet({e: (v,w)}) - sage: X.cells(X.subsimplicial_set([v, w])) - {0: [*], 1: [e]} - - Test an infinite example:: - - sage: C3 = groups.misc.MultiplicativeAbelian([3]) - sage: BC3 = simplicial_sets.ClassifyingSpace(C3) - sage: BC3.cells(max_dim=2) - {0: [1], 1: [f, f^2], 2: [f * f, f * f^2, f^2 * f, f^2 * f^2]} - sage: BC3.cells() - Traceback (most recent call last): - ... - NotImplementedError: this simplicial set may be infinite, so specify max_dim - """ - if subcomplex is None: - if self.is_finite(): - simplices = {} - for sigma in self.nondegenerate_simplices(): - if sigma.dimension() in simplices: - simplices[sigma.dimension()].append(sigma) - else: - simplices[sigma.dimension()] = [sigma] - if max_dim is not None: - return {d: sorted(simplices[d]) for d in simplices - if d <= max_dim} - return {d: sorted(simplices[d]) for d in simplices} - # Infinite case: - if max_dim is None: - raise NotImplementedError('this simplicial set may be ' - 'infinite, so specify max_dim') - return self.n_skeleton(max_dim).cells() - # subcomplex is not None: - return self.quotient(subcomplex).cells(max_dim=max_dim) - - def n_cells(self, n, subcomplex=None): - """ - Return the list of cells of dimension ``n`` of this cell complex. - If the optional argument ``subcomplex`` is present, then - return the ``n``-dimensional faces in the quotient by this - subcomplex. - - INPUT: - - - ``n`` -- the dimension - - - ``subcomplex`` (optional, default ``None``) -- a subcomplex - of this cell complex. Return the cells which are in the - quotient by this subcomplex. - - EXAMPLES:: - - sage: simplicial_sets.Sphere(3).n_cells(3) - [sigma_3] - sage: simplicial_sets.Sphere(3).n_cells(2) - [] - sage: C2 = groups.misc.MultiplicativeAbelian([2]) - sage: BC2 = C2.nerve() - sage: BC2.n_cells(3) - [f * f * f] - """ - cells = self.cells(subcomplex=subcomplex, max_dim=n) - try: - return list(cells[n]) - except KeyError: - # Don't barf if someone asks for n_cells in a dimension - # where there are none. - return [] - - def _an_element_(self): - """ - Return an element: a vertex of this simplicial set. - - Return ``None`` if the simplicial set is empty. - - EXAMPLES:: - - sage: S4 = simplicial_sets.Sphere(4) - sage: S4._an_element_() - v_0 - sage: S4._an_element_() in S4 - True - sage: from sage.homology.simplicial_set_examples import Empty - sage: Empty()._an_element_() is None - True - """ - vertices = self.n_cells(0) - if vertices: - return vertices[0] - return None - - def all_n_simplices(self, n): - """ - Return a list of all simplices, non-degenerate and degenerate, in dimension ``n``. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: degen = v.apply_degeneracies(0) - sage: tau = AbstractSimplex(2, name='tau') - sage: Y = SimplicialSet({tau: (degen, degen, degen), w: None}) - - ``Y`` is the disjoint union of a 2-sphere, with vertex ``v`` - and non-degenerate 2-simplex ``tau``, and a point ``w``. :: - - sage: Y.all_n_simplices(0) - [v, w] - sage: Y.all_n_simplices(1) - [s_0 v, s_0 w] - sage: Y.all_n_simplices(2) - [tau, s_1 s_0 v, s_1 s_0 w] - - An example involving an infinite simplicial set:: - - sage: C3 = groups.misc.MultiplicativeAbelian([3]) - sage: BC3 = simplicial_sets.ClassifyingSpace(C3) - sage: BC3.all_n_simplices(2) - [f * f, - f * f^2, - f^2 * f, - f^2 * f^2, s_0 f, s_0 f^2, s_1 f, s_1 f^2, s_1 s_0 1] - """ - non_degen = [_ for _ in self.nondegenerate_simplices(max_dim=n)] - ans = set([_ for _ in non_degen if _.dimension() == n]) - for sigma in non_degen: - d = sigma.dimension() - ans.update([sigma.apply_degeneracies(*_) - for _ in all_degeneracies(d, n-d)]) - return sorted(ans) - - def _map_from_empty_set(self): - """ - Return the unique map from the empty set to this simplicial set. - - This is used to in the method :meth:`disjoint_union` to - construct disjoint unions as pushouts. - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: T._map_from_empty_set() - Simplicial set morphism: - From: Empty simplicial set - To: Torus - Defn: [] --> [] - """ - from sage.homology.simplicial_set_examples import Empty - return Empty().Hom(self)({}) - - def identity(self): - """ - Return the identity map on this simplicial set. - - EXAMPLES:: - - sage: S3 = simplicial_sets.Sphere(3) - sage: S3.identity() - Simplicial set endomorphism of S^3 - Defn: Identity map - - sage: BC3 = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([3])) - sage: one = BC3.identity() - sage: [(sigma, one(sigma)) for sigma in BC3.n_cells(2)] - [(f * f, f * f), - (f * f^2, f * f^2), - (f^2 * f, f^2 * f), - (f^2 * f^2, f^2 * f^2)] - """ - return self.Hom(self).identity() - - def constant_map(self, codomain=None, point=None): - """ - Return a constant map with this simplicial set as its domain. - - INPUT: - - - ``codomain`` -- optional, default ``None``. If ``None``, the - codomain is the standard one-point space constructed by - :func:`Point`. Otherwise, either the codomain must be a - pointed simplicial set, in which case the map is constant at - the base point, or ``point`` must be specified. - - ``point`` -- optional, default ``None``. If specified, it - must be a 0-simplex in the codomain, and it will be the - target of the constant map. - - EXAMPLES:: - - sage: S4 = simplicial_sets.Sphere(4) - sage: S4.constant_map() - Simplicial set morphism: - From: S^4 - To: Point - Defn: Constant map at * - sage: S0 = simplicial_sets.Sphere(0) - sage: S4.constant_map(codomain=S0) - Simplicial set morphism: - From: S^4 - To: S^0 - Defn: Constant map at v_0 - - sage: Sigma3 = groups.permutation.Symmetric(3) - sage: Sigma3.nerve().constant_map() - Simplicial set morphism: - From: Nerve of Symmetric group of order 3! as a permutation group - To: Point - Defn: Constant map at * - - TESTS:: - - sage: S0 = S0.unset_base_point() - sage: S4.constant_map(codomain=S0) - Traceback (most recent call last): - ... - ValueError: codomain is not pointed, so specify a target for the constant map - """ - from sage.homology.simplicial_set_examples import Point - if codomain is None: - codomain = Point() - return self.Hom(codomain).constant_map(point) - - def is_reduced(self): - """ - Return ``True`` if this simplicial set has only one vertex. - - EXAMPLES:: - - sage: simplicial_sets.Sphere(0).is_reduced() - False - sage: simplicial_sets.Sphere(3).is_reduced() - True - """ - return len(self.n_cells(0)) == 1 - - def graph(self): - """ - Return the 1-skeleton of this simplicial set, as a graph. - - EXAMPLES:: - - sage: Delta3 = simplicial_sets.Simplex(3) - sage: G = Delta3.graph() - sage: G.edges() - [((0,), (1,), (0, 1)), - ((0,), (2,), (0, 2)), - ((0,), (3,), (0, 3)), - ((1,), (2,), (1, 2)), - ((1,), (3,), (1, 3)), - ((2,), (3,), (2, 3))] - - sage: T = simplicial_sets.Torus() - sage: T.graph() - Looped multi-graph on 1 vertex - sage: len(T.graph().edges()) - 3 - - sage: CP3 = simplicial_sets.ComplexProjectiveSpace(3) - sage: G = CP3.graph() - sage: len(G.vertices()) - 1 - sage: len(G.edges()) - 0 - - sage: Sigma3 = groups.permutation.Symmetric(3) - sage: Sigma3.nerve().is_connected() - True - """ - skel = self.n_skeleton(1) - edges = skel.n_cells(1) - vertices = skel.n_cells(0) - used_vertices = set() # vertices which are in an edge - d = {} - for e in edges: - v = skel.face(e, 0) - w = skel.face(e, 1) - if v in d: - if w in d[v]: - d[v][w] = d[v][w] + [e] - else: - d[v][w] = [e] - else: - d[v] = {w: [e]} - used_vertices.update([v, w]) - for v in vertices: - if v not in used_vertices: - d[v] = {} - return Graph(d, format='dict_of_dicts') - - def is_connected(self): - """ - Return ``True`` if this simplicial set is connected. - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: K = simplicial_sets.KleinBottle() - sage: X = T.disjoint_union(K) - sage: T.is_connected() - True - sage: K.is_connected() - True - sage: X.is_connected() - False - sage: simplicial_sets.Sphere(0).is_connected() - False - """ - return self.graph().is_connected() - - def subsimplicial_set(self, simplices): - """ - Return the sub-simplicial set of this simplicial set - determined by ``simplices``, a set of nondegenerate simplices. - - INPUT: - - - ``simplices`` -- set, list, or tuple of nondegenerate - simplices in this simplicial set, or a simplicial - complex -- see below. - - Each sub-simplicial set comes equipped with an inclusion map - to its ambient space, and you can easily recover its ambient - space. - - If ``simplices`` is a simplicial complex, then the original - simplicial set should itself have been converted from a - simplicial complex, and ``simplices`` should be a subcomplex - of that. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: f = AbstractSimplex(1, name='f') - - sage: X = SimplicialSet({e: (v, w), f: (w, v)}) - sage: Y = X.subsimplicial_set([e]) - sage: Y - Simplicial set with 3 non-degenerate simplices - sage: Y.nondegenerate_simplices() - [v, w, e] - - sage: S3 = simplicial_complexes.Sphere(3) - sage: K = SimplicialSet(S3) - sage: tau = K.n_cells(3)[0] - sage: tau.dimension() - 3 - sage: K.subsimplicial_set([tau]) - Simplicial set with 15 non-degenerate simplices - - A subsimplicial set knows about its ambient space and the - inclusion map into it:: - - sage: RP4 = simplicial_sets.RealProjectiveSpace(4) - sage: M = RP4.n_skeleton(2) - sage: M - Simplicial set with 3 non-degenerate simplices - sage: M.ambient_space() - RP^4 - sage: M.inclusion_map() - Simplicial set morphism: - From: Simplicial set with 3 non-degenerate simplices - To: RP^4 - Defn: [1, f, f * f] --> [1, f, f * f] - - An infinite ambient simplicial set:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: BxB = B.product(B) - sage: BxB.n_cells(2)[5:] - [(s_0 f, s_1 f), (s_1 f, f * f), (s_1 f, s_0 f), (s_1 s_0 1, f * f)] - sage: BxB.subsimplicial_set(BxB.n_cells(2)[5:]) - Simplicial set with 8 non-degenerate simplices - - TESTS: - - Make sure vertices are treated properly:: - - sage: X.subsimplicial_set([v]).nondegenerate_simplices() - [v] - sage: X.subsimplicial_set([v, w]).nondegenerate_simplices() - [v, w] - sage: S0 = SimplicialSet({v: None, w: None}) - sage: S0.subsimplicial_set([w]).nondegenerate_simplices() - [w] - - Raise an error if an element of ``simplices`` is not actually - in the original simplicial set:: - - sage: sigma = AbstractSimplex(2, name='sigma_2') - sage: Z = X.subsimplicial_set([e, sigma]) - Traceback (most recent call last): - ... - ValueError: not all simplices are in the original simplicial set - - Simplicial complexes:: - - sage: X = simplicial_complexes.ComplexProjectivePlane() - sage: Y = X._contractible_subcomplex() - sage: CP2 = SimplicialSet(X) - sage: sub = CP2.subsimplicial_set(Y) - sage: CP2.f_vector() - [9, 36, 84, 90, 36] - sage: K = CP2.quotient(sub) - sage: K.f_vector() - [1, 0, 16, 30, 16] - sage: K.homology() - {0: 0, 1: 0, 2: Z, 3: 0, 4: Z} - - Try to construct a subcomplex from a simplicial complex which - is not actually contained in ``self``:: - - sage: Z = SimplicialComplex([[0,1,2,3,4]]) - sage: CP2.subsimplicial_set(Z) - Traceback (most recent call last): - ... - ValueError: not all simplices are in the original simplicial set - """ - # If simplices is a simplicial complex, turn it into a list of - # nondegenerate simplices. - from .simplicial_set_constructions import SubSimplicialSet - if isinstance(simplices, SimplicialComplex): - new = [] - for f in simplices.facets(): - d = f.dimension() - found = False - for x in self.n_cells(d): - if str(x) == str(tuple(sorted(tuple(f), key=str))): - new.append(x) - found = True - break - if not found: - raise ValueError('not all simplices are in the original simplicial set') - simplices = new - - if not self.is_finite(): - max_dim = max(sigma.dimension() for sigma in simplices) - data = self.n_skeleton(max_dim).face_data() - nondegenerate_simplices = self.nondegenerate_simplices(max_dim) - else: - data = self.face_data() - nondegenerate_simplices = self.nondegenerate_simplices() - vertices = set() - keep = set(simplices) - old_keep = set() - while keep != old_keep: - old_keep = copy.copy(keep) - for x in old_keep: - underlying = x.nondegenerate() - if underlying not in data.keys(): - raise ValueError('not all simplices are in the original simplicial set') - keep.add(underlying) - if underlying in data and data[underlying]: - keep.update([f.nondegenerate() for f in data[underlying]]) - else: - # x is a vertex - assert(underlying.dimension() == 0) - vertices.add(underlying) - missing = set(nondegenerate_simplices).difference(keep) - for x in missing: - if x in data: - del data[x] - for x in vertices: - data[x] = None - return SubSimplicialSet(data, self) - - def chain_complex(self, dimensions=None, base_ring=ZZ, augmented=False, - cochain=False, verbose=False, subcomplex=None, - check=False): - r""" - Return the normalized chain complex. - - INPUT: - - - ``dimensions`` -- if ``None``, compute the chain complex in all - dimensions. If a list or tuple of integers, compute the - chain complex in those dimensions, setting the chain groups - in all other dimensions to zero. - - - ``base_ring`` (optional, default ``ZZ``) -- commutative ring - - - ``augmented`` (optional, default ``False``) -- if ``True``, - return the augmented chain complex (that is, include a class - in dimension `-1` corresponding to the empty cell). - - - ``cochain`` (optional, default ``False``) -- if ``True``, - return the cochain complex (that is, the dual of the chain - complex). - - - ``verbose`` (optional, default ``False``) -- ignored. - - - ``subcomplex`` (optional, default ``None``) -- if present, - compute the chain complex relative to this subcomplex. - - - ``check`` (optional, default ``False``) -- If ``True``, make - sure that the chain complex is actually a chain complex: - the differentials are composable and their product is zero. - - .. NOTE:: - - If this simplicial set is not finite, you must specify - dimensions in which to compute its chain complex via the - argument ``dimensions``. - - EXAMPLES:: - - sage: simplicial_sets.Sphere(5).chain_complex() - Chain complex with at most 3 nonzero terms over Integer Ring - - sage: C3 = groups.misc.MultiplicativeAbelian([3]) - sage: BC3 = simplicial_sets.ClassifyingSpace(C3) - sage: BC3.chain_complex(range(4), base_ring=GF(3)) - Chain complex with at most 4 nonzero terms over Finite Field of size 3 - - TESTS:: - - sage: BC3.chain_complex() - Traceback (most recent call last): - ... - NotImplementedError: this simplicial set may be infinite, so specify dimensions when computing its chain complex - """ - kwds = {'base_ring': base_ring, 'augmented': augmented, 'cochain': cochain, - 'verbose': verbose, 'subcomplex': subcomplex, 'check': check} - if not self.is_finite(): - if dimensions is None: - raise NotImplementedError('this simplicial set may be infinite, ' - 'so specify dimensions when computing ' - 'its chain complex') - else: - max_dim = max(dimensions) - return SimplicialSet_finite.chain_complex(self.n_skeleton(max_dim+1), - dimensions=dimensions, - **kwds) - return SimplicialSet_finite.chain_complex(self, dimensions=dimensions, - **kwds) - - def homology(self, dim=None, **kwds): - r""" - Return the (reduced) homology of this simplicial set. - - INPUT: - - - ``dim`` (optional, default ``None`` -- If ``None``, then - return the homology in every dimension. If ``dim`` is an - integer or list, return the homology in the given - dimensions. (Actually, if ``dim`` is a list, return the - homology in the range from ``min(dim)`` to ``max(dim)``.) - - - ``base_ring`` (optional, default ``ZZ``) -- commutative - ring, must be ``ZZ`` or a field. - - Other arguments are also allowed: see the documentation for - :meth:`.cell_complex.GenericCellComplex.homology`. - - .. NOTE:: - - If this simplicial set is not finite, you must specify - dimensions in which to compute homology via the argument - ``dim``. - - EXAMPLES:: - - sage: simplicial_sets.Sphere(5).homology() - {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: Z} - - sage: C3 = groups.misc.MultiplicativeAbelian([3]) - sage: BC3 = simplicial_sets.ClassifyingSpace(C3) - sage: BC3.homology(range(4), base_ring=GF(3)) - {0: Vector space of dimension 0 over Finite Field of size 3, - 1: Vector space of dimension 1 over Finite Field of size 3, - 2: Vector space of dimension 1 over Finite Field of size 3, - 3: Vector space of dimension 1 over Finite Field of size 3} - - sage: BC2 = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: BK = BC2.product(BC2) - sage: BK.homology(range(4)) - {0: 0, 1: C2 x C2, 2: C2, 3: C2 x C2 x C2} - - TESTS:: - - sage: S3 = simplicial_sets.Sphere(3) - sage: S3.homology(0) - 0 - sage: S3.homology((0,)) - {0: 0} - sage: S3.homology(0, reduced=False) - Z - - sage: BC3.homology() - Traceback (most recent call last): - ... - NotImplementedError: this simplicial set may be infinite, so specify dimensions when computing homology - """ - if not self.is_finite(): - if dim is None: - raise NotImplementedError('this simplicial set may be infinite, so ' - 'specify dimensions when computing homology') - else: - if isinstance(dim, (list, tuple, range)): - dim = list(dim) - max_dim = max(dim) - space = self.n_skeleton(max_dim+1) - min_dim = min(dim) - H = GenericCellComplex.homology(space, **kwds) - return {n: H[n] for n in H if n<=max_dim and n >= min_dim} - else: - max_dim = dim - space = self.n_skeleton(max_dim+1) - else: - space = self - return GenericCellComplex.homology(space, dim=dim, **kwds) - - def cohomology(self, dim=None, **kwds): - r""" - Return the cohomology of this simplicial set. - - INPUT: - - - ``dim`` (optional, default ``None`` -- If ``None``, then - return the homology in every dimension. If ``dim`` is an - integer or list, return the homology in the given - dimensions. (Actually, if ``dim`` is a list, return the - homology in the range from ``min(dim)`` to ``max(dim)``.) - - - ``base_ring`` (optional, default ``ZZ``) -- commutative - ring, must be ``ZZ`` or a field. - - Other arguments are also allowed, the same as for the - :meth:`homology` method -- see - :meth:`.cell_complex.GenericCellComplex.homology` for complete - documentation -- except that :meth:`homology` accepts a - ``cohomology`` key word, while this function does not: - ``cohomology`` is automatically true here. Indeed, this - function just calls :meth:`homology` with argument - ``cohomology=True``. - - .. NOTE:: - - If this simplicial set is not finite, you must specify - dimensions in which to compute homology via the argument - ``dim``. - - EXAMPLES:: - - sage: simplicial_sets.KleinBottle().homology(1) - Z x C2 - sage: simplicial_sets.KleinBottle().cohomology(1) - Z - sage: simplicial_sets.KleinBottle().cohomology(2) - C2 - - TESTS:: - - sage: C3 = groups.misc.MultiplicativeAbelian([3]) - sage: BC3 = simplicial_sets.ClassifyingSpace(C3) - sage: BC3.cohomology() - Traceback (most recent call last): - ... - NotImplementedError: this simplicial set may be infinite, so specify dimensions when computing homology - """ - return self.homology(dim=dim, cohomology=True, **kwds) - - def betti(self, dim=None, subcomplex=None): - r""" - The Betti numbers of this simplicial complex as a dictionary - (or a single Betti number, if only one dimension is given): - the ith Betti number is the rank of the ith homology group. - - INPUT: - - - ``dim`` (optional, default ``None`` -- If ``None``, then - return the homology in every dimension. If ``dim`` is an - integer or list, return the homology in the given - dimensions. (Actually, if ``dim`` is a list, return the - homology in the range from ``min(dim)`` to ``max(dim)``.) - - - ``subcomplex`` (optional, default ``None``) -- a subcomplex - of this cell complex. Compute the Betti numbers of the - homology relative to this subcomplex. - - .. NOTE:: - - If this simplicial set is not finite, you must specify - dimensions in which to compute Betti numbers via the - argument ``dim``. - - EXAMPLES: - - Build the two-sphere as a three-fold join of a - two-point space with itself:: - - sage: simplicial_sets.Sphere(5).betti() - {0: 1, 1: 0, 2: 0, 3: 0, 4: 0, 5: 1} - - sage: C3 = groups.misc.MultiplicativeAbelian([3]) - sage: BC3 = simplicial_sets.ClassifyingSpace(C3) - sage: BC3.betti(range(4)) - {0: 1, 1: 0, 2: 0, 3: 0} - """ - dict = {} - H = self.homology(dim, base_ring=QQ, subcomplex=subcomplex) - try: - for n in H.keys(): - dict[n] = H[n].dimension() - if n == 0: - dict[n] += 1 - return dict - except AttributeError: - return H.dimension() - - def n_chains(self, n, base_ring=ZZ, cochains=False): - r""" - Return the free module of (normalized) chains in degree ``n`` - over ``base_ring``. - - This is the free module on the nondegenerate simplices in the - given dimension. - - INPUT: - - - ``n`` -- integer - - ``base_ring`` -- ring (optional, default `\ZZ`) - - ``cochains`` -- boolean (optional, default ``False``); if - ``True``, return cochains instead - - The only difference between chains and cochains is notation: - the generator corresponding to the dual of a simplex - ``sigma`` is written as ``"\chi_sigma"`` in the group of - cochains. - - EXAMPLES:: - - sage: S3 = simplicial_sets.Sphere(3) - sage: C = S3.n_chains(3, cochains=True) - sage: list(C.basis()) - [\chi_sigma_3] - sage: Sigma3 = groups.permutation.Symmetric(3) - sage: BSigma3 = simplicial_sets.ClassifyingSpace(Sigma3) - sage: list(BSigma3.n_chains(1).basis()) - [(1,2), (1,2,3), (1,3), (1,3,2), (2,3)] - sage: list(BSigma3.n_chains(1, cochains=True).basis()) - [\chi_(1,2), \chi_(1,2,3), \chi_(1,3), \chi_(1,3,2), \chi_(2,3)] - """ - if self.is_finite(): - return GenericCellComplex.n_chains(self, n=n, - base_ring=base_ring, - cochains=cochains) - n_cells = tuple(self.n_cells(n)) - if cochains: - return Cochains(self, n, n_cells, base_ring) - else: - return Chains(self, n, n_cells, base_ring) - - def quotient(self, subcomplex, vertex_name='*'): - """ - Return the quotient of this simplicial set by ``subcomplex``. - - That is, ``subcomplex`` is replaced by a vertex. - - INPUT: - - - ``subcomplex`` -- subsimplicial set of this simplicial set, - or a list, tuple, or set of simplices defining a - subsimplicial set. - - - ``vertex_name`` (optional) -- string, name to be given to the new - vertex. By default, use ``'*'``. - - In Sage, from a quotient simplicial set, you can recover the - ambient space, the subcomplex, and (if the ambient space is - finite) the quotient map. - - Base points: if the original simplicial set has a base point - not contained in ``subcomplex`` and if the original simplicial - set is finite, then use its image as the base point for the - quotient. In all other cases, ``*`` is the base point. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: f = AbstractSimplex(1, name='f') - sage: X = SimplicialSet({e: (v, w), f: (v, w)}) - sage: Y = X.quotient([f]) - sage: Y.nondegenerate_simplices() - [*, e] - sage: Y.homology(1) - Z - - sage: E = SimplicialSet({e: (v, w)}) - sage: Z = E.quotient([v, w]) - sage: Z.nondegenerate_simplices() - [*, e] - sage: Z.homology(1) - Z - - sage: F = E.quotient([v]) - sage: F.nondegenerate_simplices() - [*, w, e] - sage: F.base_point() - * - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP2 = RP5.n_skeleton(2) - sage: RP5_2 = RP5.quotient(RP2) - sage: RP5_2.homology(base_ring=GF(2)) - {0: Vector space of dimension 0 over Finite Field of size 2, - 1: Vector space of dimension 0 over Finite Field of size 2, - 2: Vector space of dimension 0 over Finite Field of size 2, - 3: Vector space of dimension 1 over Finite Field of size 2, - 4: Vector space of dimension 1 over Finite Field of size 2, - 5: Vector space of dimension 1 over Finite Field of size 2} - - sage: RP5_2.ambient() - RP^5 - sage: RP5_2.subcomplex() - Simplicial set with 3 non-degenerate simplices - sage: RP5_2.quotient_map() - Simplicial set morphism: - From: RP^5 - To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) - Defn: [1, f, f * f, f * f * f, f * f * f * f, f * f * f * f * f] --> [*, s_0 *, s_1 s_0 *, f * f * f, f * f * f * f, f * f * f * f * f] - - Behavior of base points:: - - sage: K = simplicial_sets.Simplex(3) - sage: K.is_pointed() - False - sage: L = K.subsimplicial_set([K.n_cells(1)[-1]]) - sage: L.nondegenerate_simplices() - [(2,), (3,), (2, 3)] - sage: K.quotient([K.n_cells(1)[-1]]).base_point() - * - - sage: K = K.set_base_point(K.n_cells(0)[0]) - sage: K.base_point() - (0,) - sage: L = K.subsimplicial_set([K.n_cells(1)[-1]]) - sage: L.nondegenerate_simplices() - [(2,), (3,), (2, 3)] - sage: K.quotient(L).base_point() - (0,) - - TESTS:: - - sage: pt = RP5.quotient(RP5.n_skeleton(5)) - sage: pt - Quotient: (RP^5/RP^5) - sage: len(pt.nondegenerate_simplices()) - 1 - """ - from .simplicial_set_constructions import SubSimplicialSet - from .simplicial_set_constructions import QuotientOfSimplicialSet, \ - QuotientOfSimplicialSet_finite - if not isinstance(subcomplex, SimplicialSet_finite): - # If it's not a simplicial set, subcomplex should be a - # list, tuple, or set of simplices, so form the actual - # subcomplex: - subcomplex = self.subsimplicial_set(subcomplex) - else: - # Test whether subcomplex is actually a subcomplex of - # self. - if (not isinstance(subcomplex, SubSimplicialSet) - and subcomplex.ambient_space() == self): - raise ValueError('the "subcomplex" is not actually a subcomplex') - if self.is_finite(): - return QuotientOfSimplicialSet_finite(subcomplex.inclusion_map(), - vertex_name=vertex_name) - else: - return QuotientOfSimplicialSet(subcomplex.inclusion_map(), - vertex_name=vertex_name) - - def disjoint_union(self, *others): - """ - Return the disjoint union of this simplicial set with ``others``. - - INPUT: - - - ``others`` -- one or several simplicial sets - - As long as the factors are all finite, the inclusion map from - each factor is available. Any factors which are empty are - ignored completely: they do not appear in the list of factors, - etc. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: f = AbstractSimplex(1, name='f') - sage: X = SimplicialSet({e: (v, v)}) - sage: Y = SimplicialSet({f: (v, w)}) - sage: Z = X.disjoint_union(Y) - - Since ``X`` and ``Y`` have simplices in common, Sage uses a - copy of ``Y`` when constructing the disjoint union. Note the - name conflict in the list of simplices: ``v`` appears twice:: - - sage: Z = X.disjoint_union(Y) - sage: Z.nondegenerate_simplices() - [v, v, w, e, f] - - Factors and inclusion maps:: - - sage: T = simplicial_sets.Torus() - sage: S2 = simplicial_sets.Sphere(2) - sage: A = T.disjoint_union(S2) - sage: A.factors() - (Torus, S^2) - sage: i = A.inclusion_map(0) - sage: i.domain() - Torus - sage: i.codomain() - Disjoint union: (Torus u S^2) - - Empty factors are ignored:: - - sage: from sage.homology.simplicial_set_examples import Empty - sage: E = Empty() - sage: K = S2.disjoint_union(S2, E, E, S2) - sage: K == S2.disjoint_union(S2, S2) - True - sage: K.factors() - (S^2, S^2, S^2) - """ - from .simplicial_set_constructions import DisjointUnionOfSimplicialSets, \ - DisjointUnionOfSimplicialSets_finite - if all(space.is_finite() for space in [self] + list(others)): - return DisjointUnionOfSimplicialSets_finite((self,) + others) - else: - return DisjointUnionOfSimplicialSets((self,) + others) - - def coproduct(self, *others): - """ - Return the coproduct of this simplicial set with ``others``. - - INPUT: - - - ``others`` -- one or several simplicial sets - - If these simplicial sets are pointed, return their wedge sum; - if they are not, return their disjoint union. If some are - pointed and some are not, raise an error: it is not clear in - which category to work. - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: K = simplicial_sets.KleinBottle() - sage: D3 = simplicial_sets.Simplex(3) - sage: Y = S2.unset_base_point() - sage: Z = K.unset_base_point() - - sage: S2.coproduct(K).is_pointed() - True - sage: S2.coproduct(K) - Wedge: (S^2 v Klein bottle) - sage: D3.coproduct(Y, Z).is_pointed() - False - sage: D3.coproduct(Y, Z) - Disjoint union: (3-simplex u Simplicial set with 2 non-degenerate simplices u Simplicial set with 6 non-degenerate simplices) - - The coproduct comes equipped with an inclusion map from each - summand, as long as the summands are all finite:: - - sage: S2.coproduct(K).inclusion_map(0) - Simplicial set morphism: - From: S^2 - To: Wedge: (S^2 v Klein bottle) - Defn: [v_0, sigma_2] --> [*, sigma_2] - sage: D3.coproduct(Y, Z).inclusion_map(2) - Simplicial set morphism: - From: Simplicial set with 6 non-degenerate simplices - To: Disjoint union: (3-simplex u Simplicial set with 2 non-degenerate simplices u Simplicial set with 6 non-degenerate simplices) - Defn: [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] --> [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] - - TESTS:: - - sage: D3.coproduct(S2, Z) - Traceback (most recent call last): - ... - ValueError: some, but not all, of the simplicial sets are pointed, so the categorical coproduct is not defined: the category is ambiguous - """ - if self.is_pointed() and all(X.is_pointed() for X in others): - return self.wedge(*others) - if self.is_pointed() or any(X.is_pointed() for X in others): - raise ValueError('some, but not all, of the simplicial sets are pointed, ' - 'so the categorical coproduct is not defined: the ' - 'category is ambiguous') - return self.disjoint_union(*others) - - def product(self, *others): - r""" - Return the product of this simplicial set with ``others``. - - INPUT: - - - ``others`` -- one or several simplicial sets - - If `X` and `Y` are simplicial sets, then their product `X - \times Y` is defined to be the simplicial set with - `n`-simplices `X_n \times Y_n`. See - :class:`.simplicial_set_constructions.ProductOfSimplicialSets` - for more information. - - If a simplicial set is constructed as a product, the factors - are recorded and are accessible via the method - :meth:`.simplicial_set_constructions.Factors.factors`. - If each factor is finite, then you can also construct the - projection maps onto each factor, the wedge as a subcomplex, - and the fat wedge as a subcomplex. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: X = SimplicialSet({e: (v, w)}) - sage: square = X.product(X) - - ``square`` is now the standard triangulation of the square: 4 - vertices, 5 edges (the four on the border and the diagonal), 2 - triangles:: - - sage: square.f_vector() - [4, 5, 2] - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = S1.product(S1) - sage: T.homology(reduced=False) - {0: Z, 1: Z x Z, 2: Z} - - Since ``S1`` is pointed, so is ``T``:: - - sage: S1.is_pointed() - True - sage: S1.base_point() - v_0 - sage: T.is_pointed() - True - sage: T.base_point() - (v_0, v_0) - - sage: S2 = simplicial_sets.Sphere(2) - sage: S3 = simplicial_sets.Sphere(3) - sage: S2xS3 = S2.product(S3) - sage: S2xS3.homology(reduced=False) - {0: Z, 1: 0, 2: Z, 3: Z, 4: 0, 5: Z} - - sage: S2xS3.factors() == (S2, S3) - True - sage: S2xS3.factors() == (S3, S2) - False - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: B.rename('RP^oo') - sage: X = B.product(B, S2) - sage: X - RP^oo x RP^oo x S^2 - sage: X.factor(1) - RP^oo - sage: X.factors() - (RP^oo, RP^oo, S^2) - - Projection maps and wedges:: - - sage: S2xS3.projection_map(0) - Simplicial set morphism: - From: S^2 x S^3 - To: S^2 - Defn: ... - sage: S2xS3.wedge_as_subset().homology() - {0: 0, 1: 0, 2: Z, 3: Z} - - In the case of pointed simplicial sets, there is an inclusion - of each factor into the product. These are not automatically - defined in Sage, but they are easy to construct using identity - maps and constant maps and the universal property of the - product:: - - sage: one = S2.identity() - sage: const = S2.constant_map(codomain=S3) - sage: S2xS3.universal_property(one, const) - Simplicial set morphism: - From: S^2 - To: S^2 x S^3 - Defn: [v_0, sigma_2] --> [(v_0, v_0), (sigma_2, s_1 s_0 v_0)] - """ - from .simplicial_set_constructions import ProductOfSimplicialSets, \ - ProductOfSimplicialSets_finite - if self.is_finite() and all(X.is_finite() for X in others): - return ProductOfSimplicialSets_finite((self,) + others) - else: - return ProductOfSimplicialSets((self,) + others) - - cartesian_product = product - - def pushout(self, *maps): - r""" - Return the pushout obtained from given ``maps``. - - INPUT: - - - ``maps`` -- several maps of simplicial sets, each of which - has this simplicial set as its domain - - If only a single map `f: X \to Y` is given, then return - `Y`. If more than one map is given, say `f_i: X \to Y_i` for - `0 \leq i \leq m`, then return the pushout defined by those - maps. If no maps are given, return the empty simplicial set. - - In addition to the defining maps `f_i` used to construct the - pushout `P`, there are also maps `\bar{f}_i: Y_i \to P`, which - we refer to as *structure maps*. The pushout also has a - universal property: given maps `g_i: Y_i \to Z` such that `g_i - f_i = g_j f_j` for all `i`, `j`, then there is a unique map - `g: P \to Z` making the appropriate diagram commute: that is, - `g \bar{f}_i = g_i` for all `i`. - - In Sage, a pushout is equipped with its defining maps, and as - long as the simplicial sets involved are finite, you can also - access the structure maps and the universal property. - - EXAMPLES: - - Construct the 4-sphere as a quotient of a 4-simplex:: - - sage: K = simplicial_sets.Simplex(4) - sage: L = K.n_skeleton(3) - sage: S4 = L.pushout(L.constant_map(), L.inclusion_map()) - sage: S4 - Pushout of maps: - Simplicial set morphism: - From: Simplicial set with 30 non-degenerate simplices - To: Point - Defn: Constant map at * - Simplicial set morphism: - From: Simplicial set with 30 non-degenerate simplices - To: 4-simplex - Defn: [(0,), (1,), (2,), (3,), (4,), (0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 2, 3), (0, 2, 4), (0, 3, 4), (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4), (0, 1, 2, 3), (0, 1, 2, 4), (0, 1, 3, 4), (0, 2, 3, 4), (1, 2, 3, 4)] --> [(0,), (1,), (2,), (3,), (4,), (0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 2, 3), (0, 2, 4), (0, 3, 4), (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4), (0, 1, 2, 3), (0, 1, 2, 4), (0, 1, 3, 4), (0, 2, 3, 4), (1, 2, 3, 4)] - sage: len(S4.nondegenerate_simplices()) - 2 - sage: S4.homology(4) - Z - - The associated maps:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = S1.product(S1) - sage: K = T.factor(0, as_subset=True) - sage: W = S1.wedge(T) # wedge, constructed as a pushout - sage: W.defining_map(1) - Simplicial set morphism: - From: Point - To: S^1 x S^1 - Defn: Constant map at (v_0, v_0) - sage: W.structure_map(0) - Simplicial set morphism: - From: S^1 - To: Wedge: (S^1 v S^1 x S^1) - Defn: [v_0, sigma_1] --> [*, sigma_1] - - sage: f = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) - - The maps `f: S^1 \to T` and `1: T \to T` induce a map `S^1 \vee T \to T`:: - - sage: g = W.universal_property(f, Hom(T,T).identity()) - sage: g.domain() == W - True - sage: g.codomain() == T - True - - TESTS:: - - sage: K = simplicial_sets.Simplex(5) - sage: K.pushout() - Empty simplicial set - - sage: S0 = simplicial_sets.Sphere(0) - sage: pt_map = S0.base_point_map() - sage: pt_map.domain().pushout(pt_map) == S0 - True - - sage: K.pushout(K.constant_map(), pt_map) - Traceback (most recent call last): - ... - ValueError: the domains of the maps must be equal - """ - from .simplicial_set_constructions import PushoutOfSimplicialSets, \ - PushoutOfSimplicialSets_finite - if any(self != f.domain() for f in maps): - raise ValueError('the domains of the maps must be equal') - if not maps: - return PushoutOfSimplicialSets_finite() - if all(f.codomain().is_finite() for f in maps): - return PushoutOfSimplicialSets_finite(maps) - else: - return PushoutOfSimplicialSets(maps) - - def pullback(self, *maps): - r""" - Return the pullback obtained from given ``maps``. - - INPUT: - - - ``maps`` -- several maps of simplicial sets, each of which - has this simplicial set as its codomain - - If only a single map `f: X \to Y` is given, then return - `X`. If more than one map is given, say `f_i: X_i \to Y` for - `0 \leq i \leq m`, then return the pullback defined by those - maps. If no maps are given, return the one-point simplicial - set. - - In addition to the defining maps `f_i` used to construct the - pullback `P`, there are also maps `\bar{f}_i: P \to X_i`, - which we refer to as *structure maps* or *projection - maps*. The pullback also has a universal property: given maps - `g_i: Z \to X_i` such that `f_i g_i = f_j g_j` for all `i`, - `j`, then there is a unique map `g: Z \to P` making the - appropriate diagram commute: that is, `\bar{f}_i g = g_i` for - all `i`. For example, given maps `f: X \to Y` and `g: X \to - Z`, there is an induced map `g: X \to Y \times Z`. - - In Sage, a pullback is equipped with its defining maps, and as - long as the simplicial sets involved are finite, you can also - access the structure maps and the universal property. - - EXAMPLES: - - Construct a product as a pullback:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: pt = simplicial_sets.Point() - sage: P = pt.pullback(S2.constant_map(), S2.constant_map()) - sage: P.homology(2) - Z x Z - - If the pullback is defined via maps `f_i: X_i \to Y`, then - there are structure maps `\bar{f}_i: Y_i \to P`. The structure - maps are only available in Sage when all of the maps involved - have finite domains. :: - - sage: S2 = simplicial_sets.Sphere(2) - sage: one = S2.Hom(S2).identity() - sage: P = S2.pullback(one, one) - sage: P.homology() - {0: 0, 1: 0, 2: Z} - - sage: P.defining_map(0) == one - True - sage: P.structure_map(1) - Simplicial set morphism: - From: Pullback of maps: - Simplicial set endomorphism of S^2 - Defn: Identity map - Simplicial set endomorphism of S^2 - Defn: Identity map - To: S^2 - Defn: [(v_0, v_0), (sigma_2, sigma_2)] --> [v_0, sigma_2] - sage: P.structure_map(0).domain() == P - True - sage: P.structure_map(0).codomain() == S2 - True - - The universal property:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = S1.product(S1) - sage: K = T.factor(0, as_subset=True) - sage: f = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) - sage: D = S1.cone() # the cone C(S^1) - sage: g = D.map_from_base() # map from S^1 to C(S^1) - sage: P = T.product(D) - sage: h = P.universal_property(f, g) - sage: h.domain() == S1 - True - sage: h.codomain() == P - True - - TESTS:: - - sage: pt.pullback(S2.constant_map(), S2.base_point_map()) - Traceback (most recent call last): - ... - ValueError: the codomains of the maps must be equal - """ - from .simplicial_set_constructions import PullbackOfSimplicialSets, \ - PullbackOfSimplicialSets_finite - if any(self != f.codomain() for f in maps): - raise ValueError('the codomains of the maps must be equal') - if not maps: - return PullbackOfSimplicialSets_finite() - if self.is_finite() and all(f.domain().is_finite() for f in maps): - return PullbackOfSimplicialSets_finite(maps) - else: - return PullbackOfSimplicialSets(maps) - - # Ideally, this would be defined at the category level and only - # for pointed simplicial sets, but the abstract_method "wedge" in - # cell_complex.py would shadow that. - def wedge(self, *others): - r""" - Return the wedge sum of this pointed simplicial set with ``others``. - - - ``others`` -- one or several simplicial sets - - This constructs the quotient of the disjoint union in which - the base points of all of the simplicial sets have been - identified. This is the coproduct in the category of pointed - simplicial sets. - - This raises an error if any of the factors is not pointed. - - From the wedge, you can access the factors, and if the - simplicial sets involved are all finite, you can also access - the inclusion map of each factor into the wedge, as well as - the projection map onto each factor. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(1, name='e') - sage: w = AbstractSimplex(0, name='w') - sage: f = AbstractSimplex(1, name='f') - sage: X = SimplicialSet({e: (v, v)}, base_point=v) - sage: Y = SimplicialSet({f: (w, w)}, base_point=w) - sage: W = X.wedge(Y) - sage: W.nondegenerate_simplices() - [*, e, f] - sage: W.homology() - {0: 0, 1: Z x Z} - sage: S2 = simplicial_sets.Sphere(2) - sage: X.wedge(S2).homology(reduced=False) - {0: Z, 1: Z, 2: Z} - sage: X.wedge(X).nondegenerate_simplices() - [*, e, e] - - sage: S3 = simplicial_sets.Sphere(3) - sage: W = S2.wedge(S3, S2) - sage: W.inclusion_map(2) - Simplicial set morphism: - From: S^2 - To: Wedge: (S^2 v S^3 v S^2) - Defn: [v_0, sigma_2] --> [*, sigma_2] - sage: W.projection_map(1) - Simplicial set morphism: - From: Wedge: (S^2 v S^3 v S^2) - To: Quotient: (Wedge: (S^2 v S^3 v S^2)/Simplicial set with 3 non-degenerate simplices) - Defn: [*, sigma_2, sigma_2, sigma_3] --> [*, s_1 s_0 *, s_1 s_0 *, sigma_3] - - Note that the codomain of the projection map is not identical - to the original ``S2``, but is instead a quotient of the wedge - which is isomorphic to ``S2``:: - - sage: S2.f_vector() - [1, 0, 1] - sage: W.projection_map(2).codomain().f_vector() - [1, 0, 1] - sage: (W.projection_map(2) * W.inclusion_map(2)).is_bijective() - True - - TESTS:: - - sage: Z = SimplicialSet({e: (v,w)}) - sage: X.wedge(Z) - Traceback (most recent call last): - ... - ValueError: the simplicial sets must be pointed - """ - from .simplicial_set_constructions import WedgeOfSimplicialSets, \ - WedgeOfSimplicialSets_finite - if all(space.is_finite() for space in [self] + list(others)): - return WedgeOfSimplicialSets_finite((self,) + others) - else: - return WedgeOfSimplicialSets((self,) + others) - - def cone(self): - r""" - Return the (reduced) cone on this simplicial set. - - If this simplicial set `X` is not pointed, construct the - ordinary cone: add a point `v` (which will become the base - point) and for each simplex `\sigma` in `X`, add both `\sigma` - and a simplex made up of `v` and `\sigma` (topologically, form - the join of `v` and `\sigma`). - - If this simplicial set is pointed, then construct the reduced - cone: take the quotient of the unreduced cone by the 1-simplex - connecting the old base point to the new one. - - In either case, as long as the simplicial set is finite, it - comes equipped in Sage with a map from it into the cone. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(1, name='e') - sage: X = SimplicialSet({e: (v, v)}) - sage: CX = X.cone() # unreduced cone, since X not pointed - sage: CX.nondegenerate_simplices() - [*, v, (v,*), e, (e,*)] - sage: CX.base_point() - * - - `X` as a subset of the cone, and also the map from `X`, in the - unreduced case:: - - sage: CX.base_as_subset() - Simplicial set with 2 non-degenerate simplices - sage: CX.map_from_base() - Simplicial set morphism: - From: Simplicial set with 2 non-degenerate simplices - To: Cone of Simplicial set with 2 non-degenerate simplices - Defn: [v, e] --> [v, e] - - In the reduced case, only the map from `X` is available:: - - sage: X = X.set_base_point(v) - sage: CX = X.cone() # reduced cone - sage: CX.nondegenerate_simplices() - [*, e, (e,*)] - sage: CX.map_from_base() - Simplicial set morphism: - From: Simplicial set with 2 non-degenerate simplices - To: Reduced cone of Simplicial set with 2 non-degenerate simplices - Defn: [v, e] --> [*, e] - """ - from .simplicial_set_constructions import \ - ConeOfSimplicialSet, ConeOfSimplicialSet_finite, \ - ReducedConeOfSimplicialSet, ReducedConeOfSimplicialSet_finite - if self.is_pointed(): - if self.is_finite(): - return ReducedConeOfSimplicialSet_finite(self) - else: - return ReducedConeOfSimplicialSet(self) - if self.is_finite(): - return ConeOfSimplicialSet_finite(self) - else: - return ConeOfSimplicialSet(self) - - def suspension(self, n=1): - """ - Return the (reduced) `n`-th suspension of this simplicial set. - - INPUT: - - - ``n`` (optional, default 1) -- integer, suspend this many - times. - - If this simplicial set `X` is not pointed, return the - suspension: the quotient `CX/X`, where `CX` is the (ordinary, - unreduced) cone on `X`. If `X` is pointed, then use the - reduced cone instead, and so return the reduced suspension. - - EXAMPLES:: - - sage: RP4 = simplicial_sets.RealProjectiveSpace(4) - sage: S1 = simplicial_sets.Sphere(1) - sage: SigmaRP4 = RP4.suspension() - sage: S1_smash_RP4 = S1.smash_product(RP4) - sage: SigmaRP4.homology() == S1_smash_RP4.homology() - True - - The version of the suspension obtained by the smash product is - typically less efficient than the reduced suspension produced - here:: - - sage: SigmaRP4.f_vector() - [1, 0, 1, 1, 1, 1] - sage: S1_smash_RP4.f_vector() - [1, 1, 4, 6, 8, 5] - - TESTS:: - - sage: RP4.suspension(-3) - Traceback (most recent call last): - ... - ValueError: n must be non-negative - """ - from .simplicial_set_constructions import \ - SuspensionOfSimplicialSet, SuspensionOfSimplicialSet_finite - if n < 0: - raise ValueError('n must be non-negative') - if n == 0: - return self - if self.is_finite(): - Sigma = SuspensionOfSimplicialSet_finite(self) - else: - Sigma = SuspensionOfSimplicialSet(self) - if n == 1: - return Sigma - return Sigma.suspension(n-1) - - def join(self, *others): - """ - The join of this simplicial set with ``others``. - - Not implemented. See - https://ncatlab.org/nlab/show/join+of+simplicial+sets for a - few descriptions, for anyone interested in implementing - this. See also P. J. Ehlers and Tim Porter, Joins for - (Augmented) Simplicial Sets, Jour. Pure Applied Algebra, 145 - (2000) 37-44 :arxiv:`9904039`. - - - ``others`` -- one or several simplicial sets - - EXAMPLES:: - - sage: K = simplicial_sets.Simplex(2) - sage: K.join(K) - Traceback (most recent call last): - ... - NotImplementedError: joins are not implemented for simplicial sets - """ - raise NotImplementedError('joins are not implemented for simplicial sets') - - def reduce(self): - """ - Reduce this simplicial set. - - That is, take the quotient by a spanning tree of the - 1-skeleton, so that the resulting simplicial set has only one - vertex. This only makes sense if the simplicial set is - connected, so raise an error if not. If already reduced, - return itself. - - EXAMPLES:: - - sage: K = simplicial_sets.Simplex(2) - sage: K.is_reduced() - False - sage: X = K.reduce() - sage: X.is_reduced() - True - - ``X`` is reduced, so calling ``reduce`` on it again - returns ``X`` itself:: - - sage: X is X.reduce() - True - sage: K is K.reduce() - False - - Raise an error for disconnected simplicial sets:: - - sage: S0 = simplicial_sets.Sphere(0) - sage: S0.reduce() - Traceback (most recent call last): - ... - ValueError: this simplicial set is not connected - """ - if self.is_reduced(): - return self - if not self.is_connected(): - raise ValueError("this simplicial set is not connected") - graph = self.graph() - spanning_tree = [e[2] for e in graph.min_spanning_tree()] - return self.quotient(spanning_tree) - - def _Hom_(self, other, category=None): - """ - Return the set of simplicial maps between simplicial sets - ``self`` and ``other``. - - INPUT: - - - ``other`` -- another simplicial set - - ``category`` -- optional, the category in which to compute - the maps. By default this is ``SimplicialSets``, and it must - be a subcategory of this or else an error is raised. - - EXAMPLES:: - - sage: S3 = simplicial_sets.Sphere(3) - sage: S2 = simplicial_sets.Sphere(2) - sage: S3._Hom_(S2) - Set of Morphisms from S^3 to S^2 in Category of finite pointed simplicial sets - sage: Hom(S3, S2) - Set of Morphisms from S^3 to S^2 in Category of finite pointed simplicial sets - sage: K4 = simplicial_sets.Simplex(4) - sage: S3._Hom_(K4) - Set of Morphisms from S^3 to 4-simplex in Category of finite simplicial sets - """ - # Import this here to prevent circular imports. - from sage.homology.simplicial_set_morphism import SimplicialSetHomset - # Error-checking on the ``category`` argument is done when - # calling Hom(X,Y), so no need to do it again here. - if category is None: - if self.is_finite() and other.is_finite(): - if self.is_pointed() and other.is_pointed(): - category = SimplicialSets().Finite().Pointed() - else: - category = SimplicialSets().Finite() - else: - if self.is_pointed() and other.is_pointed(): - category = SimplicialSets().Pointed() - else: - category = SimplicialSets() - return SimplicialSetHomset(self, other, category=category) - - def rename_latex(self, s): - """ - Rename or set the LaTeX name for this simplicial set. - - INPUT: - - - ``s`` -- string, the LaTeX representation. Or ``s`` can be - ``None``, in which case the LaTeX name is unset. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: X = SimplicialSet({v: None}, latex_name='*') - sage: latex(X) - * - sage: X.rename_latex('x_0') - sage: latex(X) - x_0 - """ - self._latex_name = s - - def _latex_(self): - r""" - LaTeX representation. - - If ``latex_name`` is set when the simplicial set is defined, - or if :meth:`rename_latex` is used to set the LaTeX name, use - that. Otherwise, use its string representation. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: X = SimplicialSet({v: None}, latex_name='*') - sage: latex(X) - * - sage: X.rename_latex('y_0') - sage: latex(X) - y_0 - sage: X.rename_latex(None) - sage: latex(X) - Simplicial set with 1 non-degenerate simplex - sage: X.rename('v') - sage: latex(X) - v - """ - if hasattr(self, '_latex_name') and self._latex_name is not None: - return self._latex_name - return str(self) - - def _repr_(self): - """ - Print representation. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: degen = v.apply_degeneracies(0) - sage: tau = AbstractSimplex(2) - sage: SimplicialSet({tau: (degen, degen, degen), w: None}) - Simplicial set with 3 non-degenerate simplices - sage: SimplicialSet({w: None}) - Simplicial set with 1 non-degenerate simplex - - Test names and renaming:: - - sage: SimplicialSet({w: None}, name='pt') - pt - sage: K = SimplicialSet({w: None}, name='pt') - sage: K.rename('point') - sage: K - point - """ - num = len(self.nondegenerate_simplices()) - if num == 1: - return "Simplicial set with 1 non-degenerate simplex" - return "Simplicial set with {} non-degenerate simplices".format(num) - - -class SimplicialSet_finite(SimplicialSet_arbitrary, GenericCellComplex): - r""" - A finite simplicial set. - - A simplicial set `X` is a collection of sets `X_n`, the - *n-simplices*, indexed by the non-negative integers, together with - face maps `d_i` and degeneracy maps `s_j`. A simplex is - *degenerate* if it is in the image of some `s_j`, and a simplicial - set is *finite* if there are only finitely many non-degenerate - simplices. - - INPUT: - - - ``data`` -- the data defining the simplicial set. See below for - details. - - - ``base_point`` (optional, default ``None``) -- 0-simplex in this - simplicial set, its base point - - - ``name`` (optional, default ``None``) -- string, the name of the - simplicial set - - - ``check`` (optional, default ``True``) -- boolean. If ``True``, - check the simplicial identity on the face maps when defining the - simplicial set. - - - ``category`` (optional, default ``None``) -- the category in - which to define this simplicial set. The default is either - finite simplicial sets or finite pointed simplicial sets, - depending on whether a base point is defined. - - - ``latex_name`` (optional, default ``None``) -- string, the LaTeX - representation of the simplicial set. - - ``data`` should have one of the following forms: it could be a - simplicial complex or `\Delta`-complex, in case it is converted to - a simplicial set. Alternatively, it could be a dictionary. The - keys are the nondegenerate simplices of the simplicial set, and - the value corresponding to a simplex `\sigma` is a tuple listing - the faces of `\sigma`. The 0-dimensional simplices may be omitted - from ``data`` if they (or their degeneracies) are faces of other - simplices; otherwise they must be included with value ``None``. - - See :mod:`.simplicial_set` and the methods for simplicial sets for - more information and examples. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: u = AbstractSimplex(0, name='u') - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: f = AbstractSimplex(1, name='f') - - In the following simplicial set, ``u`` is an isolated vertex:: - - sage: X = SimplicialSet({e: (v,w), f: (w,w), u: None}) - sage: X - Simplicial set with 5 non-degenerate simplices - sage: X.rename('X') - sage: X - X - sage: X = SimplicialSet({e: (v,w), f: (w,w), u: None}, name='Y') - sage: X - Y - """ - def __init__(self, data, base_point=None, name=None, check=True, - category=None, latex_name=None): - r""" - TESTS:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: e = AbstractSimplex(1) - sage: SimplicialSet({e: (v, v, v)}) - Traceback (most recent call last): - ... - ValueError: wrong number of faces for simplex in dimension 1 - sage: SimplicialSet({e: (v,)}) - Traceback (most recent call last): - ... - ValueError: wrong number of faces for simplex in dimension 1 - - Base points:: - - sage: SimplicialSet({e: (v,v)}, base_point=AbstractSimplex(0)) - Traceback (most recent call last): - ... - ValueError: the base point is not a simplex in this simplicial set - sage: SimplicialSet({e: (v,v)}, base_point=e) - Traceback (most recent call last): - ... - ValueError: the base "point" is not a zero-simplex - - Simplicial identity:: - - sage: sigma = AbstractSimplex(2) - sage: w = AbstractSimplex(0) - sage: K = SimplicialSet({sigma: (v.apply_degeneracies(0), - ....: v.apply_degeneracies(0), - ....: v.apply_degeneracies(0))}) - sage: SimplicialSet({sigma: (v.apply_degeneracies(0), - ....: v.apply_degeneracies(0), - ....: w.apply_degeneracies(0))}) - Traceback (most recent call last): - ... - ValueError: simplicial identity d_i d_j = d_{j-1} d_i fails in dimension 2 - - Returning a copy of the original:: - - sage: v = AbstractSimplex(0) - sage: e = AbstractSimplex(1) - sage: S1 = SimplicialSet({e: (v, v)}) - sage: SimplicialSet(S1) == S1 - False - - Test suites:: - - sage: skip = ["_test_pickling", "_test_elements"] - sage: TestSuite(S1).run(skip=skip) - sage: TestSuite(simplicial_sets.Sphere(5)).run(skip=skip) - sage: TestSuite(simplicial_sets.RealProjectiveSpace(6)).run(skip=skip) - """ - def face(sigma, i): - """ - Return the i-th face of sigma, a simplex in this simplicial set. - - Once the simplicial set has been fully initialized, use - the :meth:`face` method instead. - """ - if sigma.is_nondegenerate(): - return data[sigma][i] - else: - underlying = sigma.nondegenerate() - J, t = face_degeneracies(i, sigma.degeneracies()) - if t is None: - return underlying.apply_degeneracies(*J) - else: - return data[underlying][t].apply_degeneracies(*J) - - if isinstance(data, GenericCellComplex): - # Construct new data appropriately. - if isinstance(data, SimplicialComplex): - simplices = {} - faces = {} - for d in range(data.dimension()+1): - old_faces = faces - faces = {} - for idx, sigma in enumerate(data.n_cells(d)): - new_sigma = AbstractSimplex(d) - new_sigma.rename(str(tuple(sorted(tuple(sigma), key=str)))) - if d > 0: - simplices[new_sigma] = [old_faces[_] for _ in sigma.faces()] - else: - simplices[new_sigma] = None - faces[sigma] = new_sigma - data = simplices - - elif isinstance(data, DeltaComplex): - simplices = {} - current = [] - for d in range(data.dimension()+1): - faces = tuple(current) - current = [] - for idx, sigma in enumerate(data.n_cells(d)): - new_sigma = AbstractSimplex(d) - # Name: Delta_{d,idx} where d is dimension, - # idx is its index in the list of d-simplices. - new_sigma.rename('Delta_{{{},{}}}'.format(d, idx)) - if d > 0: - simplices[new_sigma] = [faces[_] for _ in sigma] - else: - simplices[new_sigma] = None - current.append(new_sigma) - data = simplices - elif isinstance(data, SimplicialSet_finite): - data = dict(copy.deepcopy(data._data)) - else: - raise NotImplementedError('I do not know how to convert this ' - 'to a simplicial set') - # Convert each value in data to a tuple, and then convert all - # of data to a tuple, so that it is hashable. - for x in data: - if data[x]: - if x.dimension() != len(data[x]) - 1: - raise ValueError('wrong number of faces for simplex ' - 'in dimension {}'.format(x.dimension())) - if not all(y.dimension() == x.dimension() - 1 for y in data[x]): - raise ValueError('faces of a {}-simplex have the wrong ' - 'dimension'.format(x.dimension())) - data[x] = tuple(data[x]) - - # To obtain the non-degenerate simplices, look at both the - # keys for data and also the underlying non-degenerate - # simplices in its values. - simplices = set(data.keys()) - for t in data.values(): - if t: - simplices.update([_.nondegenerate() for _ in t]) - - for x in simplices: - if x not in data: - # x had better be a vertex. - assert(x.dimension() == 0) - data[x] = None - - # Check the simplicial identity d_i d_j = d_{j-1} d_i. - if check: - for sigma in simplices: - d = sigma.dimension() - if d >= 2: - for j in range(d+1): - for i in range(j): - if face(face(sigma, j), i) != face(face(sigma, i), j-1): - raise ValueError('simplicial identity d_i d_j ' - '= d_{{j-1}} d_i fails ' - 'in dimension {}'.format(d)) - - # Now define the attributes for an instance of this class. - # self._data: a tuple representing the defining data of the - # simplicial set. - self._data = tuple(data.items()) - # self._simplices: a sorted tuple of non-degenerate simplices. - self._simplices = sorted(tuple(simplices)) - # self._basepoint: the base point, or None. - if base_point is not None: - if base_point not in simplices: - raise ValueError('the base point is not a simplex in ' - 'this simplicial set') - if base_point.dimension() != 0: - raise ValueError('the base "point" is not a zero-simplex') - self._basepoint = base_point - if category is None: - if base_point is None: - category = SimplicialSets().Finite() - else: - category = SimplicialSets().Finite().Pointed() - Parent.__init__(self, category=category) - if name: - self.rename(name) - self._latex_name = latex_name - - def __eq__(self, other): - """ - Return ``True`` if ``self`` and ``other`` are equal as simplicial sets. - - Two simplicial sets are equal if they have the same defining - data. This means that they have *the same* simplices in each - dimension, not just that they have the same numbers of - `n`-simplices for each `n` with corresponding face maps. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: e = AbstractSimplex(1) - sage: X = SimplicialSet({e: (v, v)}) - sage: Y = SimplicialSet({e: (w, w)}) - sage: X == X - True - sage: X == SimplicialSet({e: (v, v)}) - True - sage: X == Y - False - """ - if self.is_pointed(): - return (isinstance(other, SimplicialSet_finite) - and other.is_pointed() - and sorted(self._data) == sorted(other._data) - and self.base_point() == other.base_point()) - else: - return (isinstance(other, SimplicialSet_finite) - and not other.is_pointed() - and sorted(self._data) == sorted(other._data)) - - def __ne__(self, other): - """ - Return ``True`` if ``self`` and ``other`` are not equal as simplicial sets. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: X = SimplicialSet({v: None}) - sage: Y = SimplicialSet({w: None}) - sage: X != X - False - sage: X != SimplicialSet({v: None}) - False - sage: X != Y - True - """ - return not (self == other) - - # This is cached because it is used frequently in simplicial set - # construction: the last two lines in the __init__ method access - # dictionaries which use instances of SimplicialSet_finite as keys and so - # computes their hash. If the tuple self._data is long, this can - # take a long time. - @cached_method - def __hash__(self): - """ - The hash is formed from that of the tuple ``self._data``. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: X = SimplicialSet({v: None}) - sage: degen = v.apply_degeneracies(0) - sage: tau = AbstractSimplex(2) - sage: Y = SimplicialSet({tau: (degen, degen, degen)}) - - sage: hash(X) # random - 17 - sage: hash(X) != hash(Y) - True - """ - if self.is_pointed(): - return hash(self._data) ^ hash(self.base_point()) - else: - return hash(self._data) - - def __copy__(self): - """ - Return a distinct copy of this simplicial set. - - The copy will not be equal to the original simplicial set. - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: copy(T) == T - False - sage: T.n_cells(0)[0] == copy(T).n_cells(0)[0] - False - sage: T.homology() == copy(T).homology() - True - """ - return SimplicialSet(dict(copy.deepcopy(self._data))) - - def face_data(self): - """ - Return the face-map data -- a dictionary -- defining this simplicial set. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: X = SimplicialSet({e: (v, w)}) - sage: X.face_data()[e] - (v, w) - - sage: Y = SimplicialSet({v: None, w: None}) - sage: v in Y.face_data() - True - sage: Y.face_data()[v] is None - True - """ - return dict(self._data) - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the subsimplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: degen = v.apply_degeneracies(0) - sage: tau = AbstractSimplex(2, name='tau') - sage: Y = SimplicialSet({tau: (degen, degen, degen), w: None}) - - ``Y`` is the disjoint union of a 2-sphere, with vertex ``v`` - and non-degenerate 2-simplex ``tau``, and a point ``w``. :: - - sage: Y.nondegenerate_simplices() - [v, w, tau] - sage: Y.n_skeleton(1).nondegenerate_simplices() - [v, w] - sage: Y.n_skeleton(2).nondegenerate_simplices() - [v, w, tau] - """ - data = [x for x in self.nondegenerate_simplices() - if x.dimension() <= n] - return self.subsimplicial_set(data) - - def _facets_(self): - r""" - Return the list of facets of this simplicial set, where by - "facet" we mean a non-degenerate simplex which is not a face - of another non-degenerate simplex. - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: T._facets_() - [(s_0 sigma_1, s_1 sigma_1), - (s_1 sigma_1, s_0 sigma_1)] - sage: S5 = simplicial_sets.Sphere(5) - sage: S5._facets_() - [sigma_5] - sage: simplicial_sets.Sphere(0)._facets_() - [v_0, w_0] - """ - faces = set() - for dim in range(self.dimension(), 0, -1): - for sigma in self.n_cells(dim): - faces.update([tau.nondegenerate() for tau in self.faces(sigma)]) - return sorted(set(self.nondegenerate_simplices()).difference(faces)) - - def f_vector(self): - """ - Return the list of the number of non-degenerate simplices in each - dimension. - - Unlike for some other cell complexes in Sage, this does not - include the empty simplex in dimension `-1`; thus its `i`-th - entry is the number of `i`-dimensional simplices. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: w = AbstractSimplex(0) - sage: S0 = SimplicialSet({v: None, w: None}) - sage: S0.f_vector() - [2] - - sage: e = AbstractSimplex(1) - sage: S1 = SimplicialSet({e: (v, v)}) - sage: S1.f_vector() - [1, 1] - sage: simplicial_sets.Sphere(3).f_vector() - [1, 0, 0, 1] - """ - return [len(self.n_cells(_)) for _ in range(self.dimension()+1)] - - def euler_characteristic(self): - r""" - Return the Euler characteristic of this simplicial set: the - alternating sum over `n \geq 0` of the number of - nondegenerate `n`-simplices. - - EXAMPLES:: - - sage: simplicial_sets.RealProjectiveSpace(4).euler_characteristic() - 1 - sage: simplicial_sets.Sphere(6).euler_characteristic() - 2 - sage: simplicial_sets.KleinBottle().euler_characteristic() - 0 - """ - return sum([(-1)**n * num for (n, num) in enumerate(self.f_vector())]) - - def chain_complex(self, dimensions=None, base_ring=ZZ, augmented=False, - cochain=False, verbose=False, subcomplex=None, - check=False): - r""" - Return the normalized chain complex. - - INPUT: - - - ``dimensions`` -- if ``None``, compute the chain complex in all - dimensions. If a list or tuple of integers, compute the - chain complex in those dimensions, setting the chain groups - in all other dimensions to zero. - - - ``base_ring`` (optional, default ``ZZ``) -- commutative ring - - - ``augmented`` (optional, default ``False``) -- if ``True``, - return the augmented chain complex (that is, include a class - in dimension `-1` corresponding to the empty cell). - - - ``cochain`` (optional, default ``False``) -- if ``True``, - return the cochain complex (that is, the dual of the chain - complex). - - - ``verbose`` (optional, default ``False``) -- ignored. - - - ``subcomplex`` (optional, default ``None``) -- if present, - compute the chain complex relative to this subcomplex. - - - ``check`` (optional, default ``False``) -- If ``True``, make - sure that the chain complex is actually a chain complex: - the differentials are composable and their product is zero. - - The normalized chain complex of a simplicial set is isomorphic - to the chain complex obtained by modding out by degenerate - simplices, and the latter is what is actually constructed - here. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0) - sage: degen = v.apply_degeneracies(1, 0) # s_1 s_0 applied to v - sage: sigma = AbstractSimplex(3) - sage: S3 = SimplicialSet({sigma: (degen, degen, degen, degen)}) # the 3-sphere - sage: S3.chain_complex().homology() - {0: Z, 3: Z} - sage: S3.chain_complex(augmented=True).homology() - {-1: 0, 0: 0, 3: Z} - sage: S3.chain_complex(dimensions=range(3), base_ring=QQ).homology() - {0: Vector space of dimension 1 over Rational Field} - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP2 = RP5.n_skeleton(2) - sage: RP5.chain_complex(subcomplex=RP2).homology() - {0: Z, 3: C2, 4: 0, 5: Z} - - TESTS: - - Convert some simplicial complexes and `\Delta`-complexes to - simplicial sets, and compare homology calculations:: - - sage: T = simplicial_complexes.Torus() - sage: T.homology() == SimplicialSet(T).homology() - True - sage: RP2 = delta_complexes.RealProjectivePlane() - sage: RP2.homology() == SimplicialSet(RP2).homology() - True - sage: RP2.cohomology(base_ring=GF(2)) == SimplicialSet(RP2).cohomology(base_ring=GF(2)) - True - """ - if dimensions is None: - if not self.cells(): # Empty - if cochain: - return ChainComplex({-1: matrix(base_ring, 0, 0)}, - degree_of_differential=1) - return ChainComplex({0: matrix(base_ring, 0, 0)}, - degree_of_differential=-1) - dimensions = list(range(self.dimension() + 1)) - else: - if not isinstance(dimensions, (list, tuple, range)): - dimensions = list(range(dimensions - 1, dimensions + 2)) - else: - dimensions = [n for n in dimensions if n >= 0] - if not dimensions: - # Return the empty chain complex. - if cochain: - return ChainComplex(base_ring=base_ring, degree=1) - else: - return ChainComplex(base_ring=base_ring, degree=-1) - - differentials = {} - # Convert the tuple self._data to a dictionary indexed by the - # non-degenerate simplices. - if subcomplex: - X = self.quotient(subcomplex) - face_data = X.face_data() - nondegens = X.nondegenerate_simplices() - else: - face_data = self.face_data() - nondegens = self.nondegenerate_simplices() - # simplices: dictionary indexed by dimension, values the list - # of non-degenerate simplices in that dimension. - simplices = {} - for sigma in nondegens: - if sigma.dimension() in simplices: - simplices[sigma.dimension()].append(sigma) - else: - simplices[sigma.dimension()] = [sigma] - first = dimensions.pop(0) - if first in simplices: - rank = len(simplices[first]) - current = sorted(simplices[first]) - else: - rank = 0 - current = [] - if augmented and first == 0: - differentials[first-1] = matrix(base_ring, 0, 1) - differentials[first] = matrix(base_ring, 1, rank, - [1] * rank) - else: - differentials[first] = matrix(base_ring, 0, rank) - - for d in dimensions: - old_rank = rank - faces = {_[1]:_[0] for _ in enumerate(current)} - if d in simplices: - current = sorted(simplices[d]) - rank = len(current) - # old_rank: number of simplices in dimension d-1. - # faces: list of simplices in dimension d-1. - # rank: number of simplices in dimension d. - # current: list of simplices in dimension d. - if not faces: - differentials[d] = matrix(base_ring, old_rank, rank) - else: - matrix_data = {} - for col, sigma in enumerate(current): - sign = 1 - for tau in face_data[sigma]: - if tau.is_nondegenerate(): - row = faces[tau] - if (row, col) in matrix_data: - matrix_data[(row, col)] += sign - else: - matrix_data[(row, col)] = sign - sign *= -1 - - differentials[d] = matrix(base_ring, old_rank, - rank, matrix_data) - - else: - rank = 0 - current = [] - differentials[d] = matrix(base_ring, old_rank, rank) - - if cochain: - new_diffs = {} - for d in differentials: - new_diffs[d-1] = differentials[d].transpose() - return ChainComplex(new_diffs, degree_of_differential=1, - check=check) - return ChainComplex(differentials, degree_of_differential=-1, - check=check) - - @cached_method - def algebraic_topological_model(self, base_ring=None): - r""" - Return the algebraic topological model for this simplicial set - with coefficients in ``base_ring``. - - The term "algebraic topological model" is defined by Pilarczyk - and Réal [PR2015]_. - - INPUT: - - - ``base_ring`` - coefficient ring (optional, default - ``QQ``). Must be a field. - - Denote by `C` the chain complex associated to this simplicial - set. The algebraic topological model is a chain complex `M` - with zero differential, with the same homology as `C`, along - with chain maps `\pi: C \to M` and `\iota: M \to C` satisfying - `\iota \pi = 1_M` and `\pi \iota` chain homotopic to - `1_C`. The chain homotopy `\phi` must satisfy - - - `\phi \phi = 0`, - - `\pi \phi = 0`, - - `\phi \iota = 0`. - - Such a chain homotopy is called a *chain contraction*. - - OUTPUT: a pair consisting of - - - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and - `\iota` - - the chain complex `M` - - Note that from the chain contraction ``phi``, one can recover the - chain maps `\pi` and `\iota` via ``phi.pi()`` and - ``phi.iota()``. Then one can recover `C` and `M` from, for - example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, - respectively. - - EXAMPLES:: - - sage: RP2 = simplicial_sets.RealProjectiveSpace(2) - sage: phi, M = RP2.algebraic_topological_model(GF(2)) - sage: M.homology() - {0: Vector space of dimension 1 over Finite Field of size 2, - 1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2} - sage: T = simplicial_sets.Torus() - sage: phi, M = T.algebraic_topological_model(QQ) - sage: M.homology() - {0: Vector space of dimension 1 over Rational Field, - 1: Vector space of dimension 2 over Rational Field, - 2: Vector space of dimension 1 over Rational Field} - """ - if base_ring is None: - base_ring = QQ - return algebraic_topological_model_delta_complex(self, base_ring) - - -# TODO: possibly turn SimplicialSet into a function, for example -# allowing for the construction of infinite simplicial sets. -SimplicialSet = SimplicialSet_finite - - -######################################################################## -# Functions for manipulating face and degeneracy maps. - -def standardize_degeneracies(*L): - r""" - Return list of indices of degeneracy maps in standard (decreasing) - order. - - INPUT: - - - ``L`` -- list of integers, representing a composition of - degeneracies in a simplicial set. - - OUTPUT: an equivalent list of degeneracies, standardized to be - written in decreasing order, using the simplicial identity - - .. MATH:: - - s_i s_j = s_{j+1} s_i \ \ \text{if } i \leq j. - - For example, `s_0 s_2 = s_3 s_0` and `s_0 s_0 = s_1 s_0`. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import standardize_degeneracies - sage: standardize_degeneracies(0, 0) - (1, 0) - sage: standardize_degeneracies(0, 0, 0, 0) - (3, 2, 1, 0) - sage: standardize_degeneracies(1, 2) - (3, 1) - - TESTS:: - - sage: standardize_degeneracies() - () - sage: standardize_degeneracies(2, -1) - Traceback (most recent call last): - ... - ValueError: degeneracies are indexed by non-negative integers - sage: standardize_degeneracies([2, 1]) - Traceback (most recent call last): - ... - TypeError: degeneracies are indexed by non-negative integers; do not use an explicit list or tuple - """ - J = list(L) - for m in J: - try: - if Integer(m) < 0: - raise ValueError('degeneracies are indexed by non-negative integers') - except TypeError: - # Likely if called via standard_degeneracies([1,2,3]) - # rather than standard_degeneracies(1,2,3). - raise TypeError('degeneracies are indexed by non-negative integers; do not use an explicit list or tuple') - inadmissible = True - while inadmissible: - inadmissible = False - for idx in range(len(J)-1): - if J[idx] <= J[idx + 1]: - inadmissible = True - tmp = J[idx] - J[idx] = J[idx + 1] + 1 - J[idx + 1] = tmp - return tuple(J) - -def all_degeneracies(n, l=1): - r""" - Return list of all composites of degeneracies (written in - "admissible" form, i.e., as a strictly decreasing sequence) of - length `l` on an `n`-simplex. - - INPUT: - - - ``n``, ``l`` -- integers - - On an `n`-simplex, one may apply the degeneracies `s_i` for `0 - \leq i \leq n`. Then on the resulting `n+1`-simplex, one may apply - `s_i` for `0 \leq i \leq n+1`, and so on. But one also has to take - into account the simplicial identity - - .. MATH:: - - s_i s_j = s_{j+1} s_i \ \ \text{if } i \leq j. - - There are `\binom{l+n}{n}` such composites: each non-degenerate - `n`-simplex leads to `\binom{l+n}{n}` degenerate `l+n` simplices. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import all_degeneracies - sage: all_degeneracies(0, 3) - {(2, 1, 0)} - sage: all_degeneracies(1, 1) - {(0,), (1,)} - sage: all_degeneracies(1, 3) - {(2, 1, 0), (3, 1, 0), (3, 2, 0), (3, 2, 1)} - """ - if l == 0: - return set(()) - if l == 1: - return set([tuple([_]) for _ in range(n+1)]) - ans = set() - for i in range(n+l): - ans.update(set([tuple(standardize_degeneracies(*([i] + list(_)))) - for _ in all_degeneracies(n, l-1)])) - return ans - -def standardize_face_maps(*L): - r""" - Return list of indices of face maps in standard (non-increasing) - order. - - INPUT: - - - ``L`` -- list of integers, representing a composition of - face maps in a simplicial set. - - OUTPUT: an equivalent list of face maps, standardized to be - written in non-increasing order, using the simplicial identity - - .. MATH:: - - d_i d_j = d_{j-1} d_i \ \ \text{if } i - -See the main documentation for simplicial sets, as well as for the -classes for pushouts, pullbacks, etc., for more details. - -Many of the classes defined here inherit from -:class:`sage.structure.unique_representation.UniqueRepresentation`. This -means that they produce identical output if given the same input, so -for example, if ``K`` is a simplicial set, calling ``K.suspension()`` -twice returns the same result both times:: - - sage: CP2.suspension() is CP2.suspension() - True - -So on one hand, a command like ``simplicial_sets.Sphere(2)`` -constructs a distinct copy of a 2-sphere each time it is called; on -the other, once you have constructed a 2-sphere, then constructing its -cone, its suspension, its product with another simplicial set, etc., -will give you the same result each time:: - - sage: simplicial_sets.Sphere(2) == simplicial_sets.Sphere(2) - False - sage: S2 = simplicial_sets.Sphere(2) - sage: S2.product(S2) == S2.product(S2) - True - sage: S2.disjoint_union(CP2, S2) == S2.disjoint_union(CP2, S2) - True - -AUTHORS: - -- John H. Palmieri (2016-07) +The current version is :mod:`sage.topology.simplicial_set_constructions`. """ -#***************************************************************************** -# Copyright (C) 2016 John H. Palmieri -# -# Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# See the GNU General Public License for more details; the full text -# is available at: -# -# http://www.gnu.org/licenses/ -# -#***************************************************************************** - -import itertools - -from sage.graphs.graph import Graph -from sage.misc.latex import latex -from sage.sets.set import Set -from sage.structure.parent import Parent -from sage.structure.unique_representation import UniqueRepresentation - -from .simplicial_set import AbstractSimplex, \ - SimplicialSet_arbitrary, SimplicialSet_finite, \ - standardize_degeneracies, face_degeneracies -from .simplicial_set_examples import Empty, Point - -from sage.misc.lazy_import import lazy_import -lazy_import('sage.categories.simplicial_sets', 'SimplicialSets') - -######################################################################## -# classes which inherit from SimplicialSet_arbitrary - -# Note: many of the classes below for infinite simplicial sets have an -# attribute '_n_skeleton'. This is used to cache the highest -# dimensional skeleton calculated so far for this simplicial set, -# along with its dimension, so for example, the starting value is -# often (-1, Empty()): the (-1)-skeleton is the empty simplicial -# set. It gets used and updated in the n_skeleton method. - -class SubSimplicialSet(SimplicialSet_finite, UniqueRepresentation): - @staticmethod - def __classcall__(self, data, ambient=None): - """ - Convert ``data`` from a dict to a tuple. - - TESTS:: - - sage: from sage.homology.simplicial_set_constructions import SubSimplicialSet - sage: K = simplicial_sets.Simplex(2) - sage: e = K.n_cells(1)[0] - sage: A = SubSimplicialSet({e: K.faces(e)}, ambient=K) - sage: B = SubSimplicialSet({e: list(K.faces(e))}, ambient=K) - sage: A == B - True - """ - L = [] - for x in data: - if data[x] is None: - L.append((x, None)) - else: - L.append((x, tuple(data[x]))) - return super(SubSimplicialSet, self).__classcall__(self, tuple(L), ambient) - - def __init__(self, data, ambient=None): - r""" - Return a finite simplicial set as a subsimplicial set of another - simplicial set. - - This keeps track of the ambient simplicial set and the - inclusion map from the subcomplex into it. - - INPUT: - - - ``data`` -- the data defining the subset: a dictionary where - the keys are simplices from the ambient simplicial set and - the values are their faces. - - - ``ambient`` -- the ambient simplicial set. If omitted, use - the same simplicial set as the subset and the ambient - complex. - - EXAMPLES:: - - sage: S3 = simplicial_sets.Sphere(3) - sage: K = simplicial_sets.KleinBottle() - sage: X = S3.disjoint_union(K) - sage: Y = X.structure_map(0).image() # the S3 summand - sage: Y.inclusion_map() - Simplicial set morphism: - From: Simplicial set with 2 non-degenerate simplices - To: Disjoint union: (S^3 u Klein bottle) - Defn: [v_0, sigma_3] --> [v_0, sigma_3] - sage: Y.ambient_space() - Disjoint union: (S^3 u Klein bottle) - - TESTS:: - - sage: T = simplicial_sets.Torus() - sage: latex(T.n_skeleton(2)) - S^{1} \times S^{1} - - sage: T.n_skeleton(1).n_skeleton(1) == T.n_skeleton(1) - True - - sage: T.n_skeleton(1) is T.n_skeleton(1) - True - """ - data = dict(data) - if ambient is None: - ambient = self - if (ambient.is_pointed() - and hasattr(ambient, '_basepoint') - and ambient.base_point() in data): - SimplicialSet_finite.__init__(self, data, base_point=ambient.base_point()) - else: - SimplicialSet_finite.__init__(self, data) - if self == ambient: - if hasattr(ambient, '__custom_name'): - self.rename(str(ambient)) - self._latex_name = latex(ambient) - # When constructing the inclusion map, we do not need to check - # the validity of the morphism, and more importantly, we - # cannot check it in the infinite case: the appropriate data - # may not have yet been constructed. So use "check=False". - self._inclusion = self.Hom(ambient)({x:x for x in data}, check=False) - - def inclusion_map(self): - r""" - Return the inclusion map from this subsimplicial set into its - ambient space. - - EXAMPLES:: - - sage: RP6 = simplicial_sets.RealProjectiveSpace(6) - sage: K = RP6.n_skeleton(2) - sage: K.inclusion_map() - Simplicial set morphism: - From: Simplicial set with 3 non-degenerate simplices - To: RP^6 - Defn: [1, f, f * f] --> [1, f, f * f] - - `RP^6` itself is constructed as a subsimplicial set of - `RP^\infty`:: - - sage: latex(RP6.inclusion_map()) - RP^{6} \to RP^{\infty} - """ - return self._inclusion - - def ambient_space(self): - """ - Return the simplicial set of which this is a subsimplicial set. - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: eight = T.wedge_as_subset() - sage: eight - Simplicial set with 3 non-degenerate simplices - sage: eight.fundamental_group() - Finitely presented group < e0, e1 | > - sage: eight.ambient_space() - Torus - """ - return self._inclusion.codomain() - - -class PullbackOfSimplicialSets(SimplicialSet_arbitrary, UniqueRepresentation): - @staticmethod - def __classcall_private__(self, maps=None): - """ - TESTS:: - - sage: from sage.homology.simplicial_set_constructions import PullbackOfSimplicialSets - sage: S2 = simplicial_sets.Sphere(2) - sage: one = S2.Hom(S2).identity() - sage: PullbackOfSimplicialSets([one, one]) == PullbackOfSimplicialSets((one, one)) - True - """ - if maps: - return super(PullbackOfSimplicialSets, self).__classcall__(self, tuple(maps)) - return super(PullbackOfSimplicialSets, self).__classcall__(self) - - def __init__(self, maps=None): - r""" - Return the pullback obtained from the morphisms ``maps``. - - INPUT: - - - ``maps`` -- a list or tuple of morphisms of simplicial sets - - If only a single map `f: X \to Y` is given, then return - `X`. If no maps are given, return the one-point simplicial - set. Otherwise, given a simplicial set `Y` and maps `f_i: X_i - \to Y` for `0 \leq i \leq m`, construct the pullback `P`: see - :wikipedia:`Pullback_(category_theory)`. This is constructed - as pullbacks of sets for each set of `n`-simplices, so `P_n` - is the subset of the product `\prod (X_i)_n` consisting of - those elements `(x_i)` for which `f_i(x_i) = f_j(x_j)` for all - `i`, `j`. - - This is pointed if the maps `f_i` are. - - EXAMPLES: - - The pullback of a quotient map by a subsimplicial set and the - base point map gives a simplicial set isomorphic to the - original subcomplex:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: K = RP5.quotient(RP5.n_skeleton(2)) - sage: X = K.pullback(K.quotient_map(), K.base_point_map()) - sage: X.homology() == RP5.n_skeleton(2).homology() - True - - Pullbacks of identity maps:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: one = S2.Hom(S2).identity() - sage: P = S2.pullback(one, one) - sage: P.homology() - {0: 0, 1: 0, 2: Z} - - The pullback is constructed in terms of the product -- of - course, the product is a special case of the pullback -- and - the simplices are named appropriately:: - - sage: P.nondegenerate_simplices() - [(v_0, v_0), (sigma_2, sigma_2)] - """ - # Import this here to prevent circular imports. - from sage.homology.simplicial_set_morphism import SimplicialSetMorphism - if maps and any(not isinstance(f, SimplicialSetMorphism) for f in maps): - raise ValueError('the maps must be morphisms of simplicial sets') - - Cat = SimplicialSets() - if maps: - if all(f.domain().is_finite() for f in maps): - Cat = Cat.Finite() - if all(f.is_pointed() for f in maps): - Cat = Cat.Pointed() - Parent.__init__(self, category=Cat) - self._maps = maps - self._n_skeleton = (-1, Empty()) - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the simplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - The `n`-skeleton of the pullback is computed as the pullback - of the `n`-skeleta of the component simplicial sets. - - EXAMPLES:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: one = Hom(B,B).identity() - sage: c = Hom(B,B).constant_map() - sage: P = B.pullback(one, c) - sage: P.n_skeleton(2) - Pullback of maps: - Simplicial set endomorphism of Simplicial set with 3 non-degenerate simplices - Defn: Identity map - Simplicial set endomorphism of Simplicial set with 3 non-degenerate simplices - Defn: Constant map at 1 - sage: P.n_skeleton(3).homology() - {0: 0, 1: C2, 2: 0, 3: Z} - """ - if self.is_finite(): - maps = self._maps - if maps: - codomain = SimplicialSet_finite.n_skeleton(maps[0].codomain(), n) - domains = [SimplicialSet_finite.n_skeleton(f.domain(), n) for f in maps] - new_maps = [f.n_skeleton(n, d, codomain) for (f, d) in zip(maps, domains)] - return PullbackOfSimplicialSets_finite(new_maps) - return PullbackOfSimplicialSets_finite(maps) - start, skel = self._n_skeleton - if start == n: - return skel - elif start > n: - return skel.n_skeleton(n) - ans = PullbackOfSimplicialSets_finite([f.n_skeleton(n) for f in self._maps]) - self._n_skeleton = (n, ans) - return ans - - def defining_map(self, i): - r""" - Return the `i`-th map defining the pullback. - - INPUT: - - - ``i`` -- integer - - If this pullback was constructed as ``Y.pullback(f_0, f_1, ...)``, - this returns `f_i`. - - EXAMPLES:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: K = RP5.quotient(RP5.n_skeleton(2)) - sage: Y = K.pullback(K.quotient_map(), K.base_point_map()) - sage: Y.defining_map(1) - Simplicial set morphism: - From: Point - To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) - Defn: Constant map at * - sage: Y.defining_map(0).domain() - RP^5 - """ - return self._maps[i] - - def _repr_(self): - """ - Print representation - - EXAMPLES:: - - sage: S3 = simplicial_sets.Sphere(3) - sage: c = Hom(S3,S3).constant_map() - sage: one = Hom(S3, S3).identity() - sage: S3.pullback(c, one) - Pullback of maps: - Simplicial set endomorphism of S^3 - Defn: Constant map at v_0 - Simplicial set endomorphism of S^3 - Defn: Identity map - """ - if not self._maps: - return 'Point' - s = 'Pullback of maps:' - for f in self._maps: - t = '\n' + str(f) - s += t.replace('\n', '\n ') - return s - - -class PullbackOfSimplicialSets_finite(PullbackOfSimplicialSets, SimplicialSet_finite): - """ - The pullback of finite simplicial sets obtained from ``maps``. - - When the simplicial sets involved are all finite, there are more - methods available to the resulting pullback, as compared to case - when some of the components are infinite: the structure maps from - the pullback and the pullback's universal property: see - :meth:`structure_map` and :meth:`universal_property`. - """ - @staticmethod - def __classcall_private__(self, maps=None): - """ - TESTS:: - - sage: from sage.homology.simplicial_set_constructions import PullbackOfSimplicialSets_finite - sage: S2 = simplicial_sets.Sphere(2) - sage: one = S2.Hom(S2).identity() - sage: PullbackOfSimplicialSets_finite([one, one]) == PullbackOfSimplicialSets_finite((one, one)) - True - """ - if maps: - return super(PullbackOfSimplicialSets_finite, self).__classcall__(self, tuple(maps)) - return super(PullbackOfSimplicialSets_finite, self).__classcall__(self) - - def __init__(self, maps=None): - r""" - Return the pullback obtained from the morphisms ``maps``. - - See :class:`PullbackOfSimplicialSets` for more information. - - INPUT: - - - ``maps`` -- a list or tuple of morphisms of simplicial sets - - EXAMPLES:: - - sage: eta = simplicial_sets.HopfMap() - sage: S3 = eta.domain() - sage: S2 = eta.codomain() - sage: c = Hom(S2,S2).constant_map() - sage: S2.pullback(eta, c).is_finite() - True - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: one = Hom(B,B).identity() - sage: c = Hom(B,B).constant_map() - sage: B.pullback(one, c).is_finite() - False - - TESTS:: - - sage: P = simplicial_sets.Point() - sage: P.pullback(P.constant_map(), P.constant_map()) - Pullback of maps: - Simplicial set endomorphism of Point - Defn: Identity map - Simplicial set endomorphism of Point - Defn: Identity map - """ - # Import this here to prevent circular imports. - from sage.homology.simplicial_set_morphism import SimplicialSetMorphism - if maps and any(not isinstance(f, SimplicialSetMorphism) for f in maps): - raise ValueError('the maps must be morphisms of simplicial sets') - if not maps: - star = AbstractSimplex(0, name='*') - SimplicialSet_finite.__init__(self, {star: None}, base_point=star, name='Point') - self._maps = () - self._translation = {} - return - if len(maps) == 1: - f = maps[0] - if f.is_pointed(): - SimplicialSet_finite.__init__(self, f.domain().face_data(), - base_point=f.domain().base_point()) - else: - SimplicialSet_finite.__init__(self, f.domain().face_data()) - self._maps = (f,) - return - codomain = maps[0].codomain() - if any(codomain != f.codomain() for f in maps[1:]): - raise ValueError('the codomains of the maps must be equal') - # Now construct the pullback by constructing the product and only - # keeping the appropriate simplices. - domains = [f.domain() for f in maps] - nondegen = [X.nondegenerate_simplices() for X in domains] - data_factors = [X.face_data() for X in domains] - # data: dictionary to construct the new simplicial set. - data = {} - # translate: keep track of the nondegenerate simplices in the - # new simplicial set for computing faces: keys are tuples of - # pairs (sigma, degens), with sigma a nondegenerate simplex in - # one of the factors, degens the tuple of applied - # degeneracies. The associated value is the actual simplex in - # the product. - translate = {} - for simplices in itertools.product(*nondegen): - dims = [_.dimension() for _ in simplices] - dim_max = max(dims) - sum_dims = sum(dims) - for d in range(dim_max, sum_dims + 1): - S = Set(range(d)) - # Is there a way to speed up the following? Given the - # tuple dims=(n_1, n_2, ..., n_k) and given d between - # max(dims) and sum(dims), we are trying to construct - # k-tuples of subsets (D_1, D_2, ..., D_k) of range(d) - # such that the intersection of all of the D_i's is - # empty. - for I in itertools.product(*[S.subsets(d - _) for _ in dims]): - if set.intersection(*[set(_) for _ in I]): - # To get a nondegenerate face, can't have a - # degeneracy in common for all the factors. - continue - degens = [tuple(sorted(_, reverse=True)) for _ in I] - - sigma = simplices[0].apply_degeneracies(*degens[0]) - target = maps[0](sigma) - if any(target != f(tau.apply_degeneracies(*degen)) - for (f, tau, degen) in zip(maps[1:], simplices[1:], degens[1:])): - continue - - simplex_factors = tuple(zip(simplices, tuple(degens))) - s = '(' + ', '.join(['{}'.format(_[0].apply_degeneracies(*_[1])) - for _ in simplex_factors]) + ')' - ls = '(' + ', '.join(['{}'.format(latex(_[0].apply_degeneracies(*_[1]))) - for _ in simplex_factors]) + ')' - simplex = AbstractSimplex(d, name=s, latex_name=ls) - translate[simplex_factors] = simplex - # Now compute the faces of simplex. - if d == 0: - # It's a vertex, so it has no faces. - faces = None - else: - faces = [] - for i in range(d+1): - # Compute d_i on simplex. - # - # face_degens: tuple of pairs (J, t): J is the - # list of degeneracies to apply to the - # corresponding entry in simplex_factors, t is - # the face map to apply. - face_degens = [face_degeneracies(i, _) for _ in degens] - face_factors = [] - new_degens = [] - for x, Face, face_dict in zip(simplices, face_degens, data_factors): - J = Face[0] - t = Face[1] - if t is None: - face_factors.append(x.nondegenerate()) - else: - underlying = face_dict[x][t] - temp_degens = underlying.degeneracies() - underlying = underlying.nondegenerate() - J = standardize_degeneracies(*(J + list(temp_degens))) - face_factors.append(underlying) - new_degens.append(J) - - # By the simplicial identities, s_{i_1} - # s_{i_2} ... s_{i_n} z (if decreasing) is in - # the image of s_{i_k} for each k. - # - # So find the intersection K of each J, the - # degeneracies applied to left_face and - # right_face. Then the face will be s_{K} - # (s_{J'_L} left_face, s_{J'_R} right_face), - # where you get J'_L from J_L by pulling out K - # from J_L. - # - # J'_L is obtained as follows: for each j in - # J_L, decrease j by q if q = #{x in K: x < j} - K = set.intersection(*[set(J) for J in new_degens]) - - face_degens = [] - for J in new_degens: - new_J = [] - for j in J: - if j not in K: - q = len([x for x in K if x < j]) - new_J.append(j - q) - face_degens.append(tuple(new_J)) - K = sorted(K, reverse=True) - underlying_face = translate[tuple(zip(tuple(face_factors), tuple(face_degens)))] - faces.append(underlying_face.apply_degeneracies(*K)) - data[simplex] = faces - - if all(f.is_pointed() for f in maps): - basept = translate[tuple([(sset.base_point(), ()) for sset in domains])] - if not data: - data = {basept: None} - SimplicialSet_finite.__init__(self, data, base_point=basept) - else: - SimplicialSet_finite.__init__(self, data) - self._maps = maps - # self._translation: tuple converted from dict. keys: tuples - # of pairs (sigma, degens), with sigma a nondegenerate simplex - # in one of the factors, degens the tuple of applied - # degeneracies. The associated value is the actual simplex in - # the product. - self._translation = tuple(translate.items()) - - def structure_map(self, i): - r""" - Return the `i`-th projection map of the pullback. - - INPUT: - - - ``i`` -- integer - - If this pullback `P` was constructed as ``Y.pullback(f_0, f_1, - ...)``, where `f_i: X_i \to Y`, then there are structure maps - `\bar{f}_i: P \to X_i`. This method constructs `\bar{f}_i`. - - EXAMPLES:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: K = RP5.quotient(RP5.n_skeleton(2)) - sage: Y = K.pullback(K.quotient_map(), K.base_point_map()) - sage: Y.structure_map(0) - Simplicial set morphism: - From: Pullback of maps: - Simplicial set morphism: - From: RP^5 - To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) - Defn: [1, f, f * f, f * f * f, f * f * f * f, f * f * f * f * f] --> [*, s_0 *, s_1 s_0 *, f * f * f, f * f * f * f, f * f * f * f * f] - Simplicial set morphism: - From: Point - To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) - Defn: Constant map at * - To: RP^5 - Defn: [(1, *), (f, s_0 *), (f * f, s_1 s_0 *)] --> [1, f, f * f] - sage: Y.structure_map(1).codomain() - Point - - These maps are also accessible via ``projection_map``:: - - sage: Y.projection_map(1).codomain() - Point - """ - if len(self._maps) == 1: - return self.Hom(self).identity() - f = {} - for x in self._translation: - f[x[1]] = x[0][i][0].apply_degeneracies(*x[0][i][1]) - codomain = self.defining_map(i).domain() - return self.Hom(codomain)(f) - - projection_map = structure_map - - def universal_property(self, *maps): - r""" - Return the map induced by ``maps``. - - INPUT: - - - ``maps`` -- maps from a simplicial set `Z` to the "factors" - `X_i` forming the pullback. - - If the pullback `P` is formed by maps `f_i: X_i \to Y`, then - given maps `g_i: Z \to X_i` such that `f_i g_i = f_j g_j` for - all `i`, `j`, then there is a unique map `g: Z \to P` making - the appropriate diagram commute. This constructs that map. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = S1.product(S1) - sage: K = T.factor(0, as_subset=True) - sage: f = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) - sage: P = S1.product(T) - sage: P.universal_property(S1.Hom(S1).identity(), f) - Simplicial set morphism: - From: S^1 - To: S^1 x S^1 x S^1 - Defn: [v_0, sigma_1] --> [(v_0, (v_0, v_0)), (sigma_1, (sigma_1, s_0 v_0))] - """ - if len(self._maps) != len(maps): - raise ValueError('wrong number of maps specified') - if len(self._maps) == 1: - return maps[0] - domain = maps[0].domain() - if any(g.domain() != domain for g in maps[1:]): - raise ValueError('the maps do not all have the same codomain') - composite = self._maps[0] * maps[0] - if any(f*g != composite for f,g in zip(self._maps[1:], maps[1:])): - raise ValueError('the maps are not compatible') - data = {} - translate = dict(self._translation) - for sigma in domain.nondegenerate_simplices(): - target = tuple([(f(sigma).nondegenerate(), tuple(f(sigma).degeneracies())) - for f in maps]) - # If there any degeneracies in common, remove them: the - # dictionary "translate" has nondegenerate simplices as - # its keys. - in_common = set.intersection(*[set(_[1]) for _ in target]) - if in_common: - target = tuple((tau, tuple(sorted(set(degens).difference(in_common), - reverse=True))) - for tau, degens in target) - in_common = sorted(in_common, reverse=True) - data[sigma] = translate[target].apply_degeneracies(*in_common) - return domain.Hom(self)(data) - -class Factors(object): - """ - Classes which inherit from this should define a ``_factors`` - attribute for their instances, and this class accesses that - attribute. This is used by :class:`ProductOfSimplicialSets`, - :class:`WedgeOfSimplicialSets`, and - :class:`DisjointUnionOfSimplicialSets`. - """ - def factors(self): - """ - Return the factors involved in this construction of simplicial sets. - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: S3 = simplicial_sets.Sphere(3) - sage: S2.wedge(S3).factors() == (S2, S3) - True - sage: S2.product(S3).factors()[0] - S^2 - """ - return self._factors - - def factor(self, i): - r""" - Return the $i$-th factor of this construction of simplicial sets. - - INPUT: - - - ``i`` -- integer, the index of the factor - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: S3 = simplicial_sets.Sphere(3) - sage: K = S2.disjoint_union(S3) - sage: K.factor(0) - S^2 - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: X = B.wedge(S3, B) - sage: X.factor(1) - S^3 - sage: X.factor(2) - Classifying space of Multiplicative Abelian group isomorphic to C2 - """ - return self.factors()[i] - - -class ProductOfSimplicialSets(PullbackOfSimplicialSets, Factors): - @staticmethod - def __classcall__(cls, factors=None): - """ - TESTS:: - - sage: from sage.homology.simplicial_set_constructions import ProductOfSimplicialSets - sage: S2 = simplicial_sets.Sphere(2) - sage: ProductOfSimplicialSets([S2, S2]) == ProductOfSimplicialSets((S2, S2)) - True - """ - if factors: - return super(ProductOfSimplicialSets, cls).__classcall__(cls, factors=tuple(factors)) - return super(ProductOfSimplicialSets, cls).__classcall__(cls) - - def __init__(self, factors=None): - r""" - Return the product of simplicial sets. - - INPUT: - - - ``factors`` -- a list or tuple of simplicial sets - - Return the product of the simplicial sets in ``factors``. - - If `X` and `Y` are simplicial sets, then their product `X - \times Y` is defined to be the simplicial set with - `n`-simplices `X_n \times Y_n`. Therefore the simplices in - the product have the form `(s_I \sigma, s_J \tau)`, where `s_I - = s_{i_1} ... s_{i_p}` and `s_J = s_{j_1} ... s_{j_q}` are - composites of degeneracy maps, written in decreasing order. - Such a simplex is nondegenerate if the indices `I` and `J` are - disjoint. Therefore if `\sigma` and `\tau` are nondegenerate - simplices of dimensions `m` and `n`, in the product they will - lead to nondegenerate simplices up to dimension `m+n`, and no - further. - - This extends in the more or less obvious way to products with - more than two factors: with three factors, a simplex `(s_I - \sigma, s_J \tau, s_K \rho)` is nondegenerate if `I \cap J - \cap K` is empty, etc. - - If a simplicial set is constructed as a product, the factors - are recorded and are accessible via the method - :meth:`Factors.factors`. If it is constructed as a product and then - copied, this information is lost. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: X = SimplicialSet({e: (v, w)}) - sage: square = X.product(X) - - ``square`` is now the standard triangulation of the square: 4 - vertices, 5 edges (the four on the border plus the diagonal), - 2 triangles:: - - sage: square.f_vector() - [4, 5, 2] - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = S1.product(S1) - sage: T.homology(reduced=False) - {0: Z, 1: Z x Z, 2: Z} - - Since ``S1`` is pointed, so is ``T``:: - - sage: S1.is_pointed() - True - sage: S1.base_point() - v_0 - sage: T.is_pointed() - True - sage: T.base_point() - (v_0, v_0) - - sage: S2 = simplicial_sets.Sphere(2) - sage: S3 = simplicial_sets.Sphere(3) - sage: Z = S2.product(S3) - sage: Z.homology() - {0: 0, 1: 0, 2: Z, 3: Z, 4: 0, 5: Z} - - Products involving infinite simplicial sets:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: B.rename('RP^oo') - sage: X = B.product(B) - sage: X - RP^oo x RP^oo - sage: X.n_cells(1) - [(f, f), (f, s_0 1), (s_0 1, f)] - sage: X.homology(range(3), base_ring=GF(2)) - {0: Vector space of dimension 0 over Finite Field of size 2, - 1: Vector space of dimension 2 over Finite Field of size 2, - 2: Vector space of dimension 3 over Finite Field of size 2} - sage: Y = B.product(S2) - sage: Y.homology(range(5), base_ring=GF(2)) - {0: Vector space of dimension 0 over Finite Field of size 2, - 1: Vector space of dimension 1 over Finite Field of size 2, - 2: Vector space of dimension 2 over Finite Field of size 2, - 3: Vector space of dimension 2 over Finite Field of size 2, - 4: Vector space of dimension 2 over Finite Field of size 2} - """ - PullbackOfSimplicialSets.__init__(self, [space.constant_map() - for space in factors]) - self._factors = factors - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the simplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - In the finite case, this returns the ordinary `n`-skeleton. In - the infinite case, it computes the `n`-skeleton of the product - of the `n`-skeleta of the factors. - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: S3 = simplicial_sets.Sphere(3) - sage: S2.product(S3).n_skeleton(2) - Simplicial set with 2 non-degenerate simplices - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: X = B.product(B) - sage: X.n_skeleton(2) - Simplicial set with 13 non-degenerate simplices - """ - n_skel = SimplicialSet_finite.n_skeleton - if self.is_finite(): - n_skel = SimplicialSet_finite.n_skeleton - return n_skel(self, n) - start, skel = self._n_skeleton - if start == n: - return skel - elif start > n: - return skel.n_skeleton(n) - ans = n_skel(ProductOfSimplicialSets_finite([X.n_skeleton(n) for X in self._factors]), n) - self._n_skeleton = (n, ans) - return ans - - def factor(self, i, as_subset=False): - r""" - Return the $i$-th factor of the product. - - INPUT: - - - ``i`` -- integer, the index of the factor - - - ``as_subset`` -- boolean, optional (default ``False``) - - If ``as_subset`` is ``True``, return the $i$-th factor as a - subsimplicial set of the product, identifying it with its - product with the base point in each other factor. As a - subsimplicial set, it comes equipped with an inclusion - map. This option will raise an error if any factor does not - have a base point. - - If ``as_subset`` is ``False``, return the $i$-th factor in - its original form as a simplicial set. - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: S3 = simplicial_sets.Sphere(3) - sage: K = S2.product(S3) - sage: K.factor(0) - S^2 - - sage: K.factor(0, as_subset=True) - Simplicial set with 2 non-degenerate simplices - sage: K.factor(0, as_subset=True).homology() - {0: 0, 1: 0, 2: Z} - - sage: K.factor(0) is S2 - True - sage: K.factor(0, as_subset=True) is S2 - False - """ - if as_subset: - if any(not _.is_pointed() for _ in self.factors()): - raise ValueError('"as_subset=True" is only valid ' - 'if each factor is pointed') - - basept_factors = [sset.base_point() for sset in self.factors()] - basept_factors = basept_factors[:i] + basept_factors[i+1:] - to_factors = dict((v,k) for k,v in self._translation) - simps = [] - for x in self.nondegenerate_simplices(): - simplices = [sigma[0] for sigma in to_factors[x]] - if simplices[:i] + simplices[i+1:] == basept_factors: - simps.append(x) - return self.subsimplicial_set(simps) - return self.factors()[i] - - def _repr_(self): - """ - Print representation - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: K = simplicial_sets.KleinBottle() - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: S2.product(S2) - S^2 x S^2 - sage: S2.product(K, B) - S^2 x Klein bottle x Classifying space of Multiplicative Abelian group isomorphic to C2 - """ - return ' x '.join([str(X) for X in self._factors]) - - def _latex_(self): - r""" - LaTeX representation - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: latex(S2.product(S2)) - S^{2} \times S^{2} - sage: RPoo = simplicial_sets.RealProjectiveSpace(Infinity) - sage: latex(S2.product(RPoo, S2)) - S^{2} \times RP^{\infty} \times S^{2} - """ - return ' \\times '.join([latex(X) for X in self._factors]) - - -class ProductOfSimplicialSets_finite(ProductOfSimplicialSets, PullbackOfSimplicialSets_finite): - r""" - The product of finite simplicial sets. - - When the factors are all finite, there are more methods available - for the resulting product, as compared to products with infinite - factors: projection maps, the wedge as a subcomplex, and the fat - wedge as a subcomplex. See :meth:`projection_map`, - :meth:`wedge_as_subset`, and :meth:`fat_wedge_as_subset` - """ - def __init__(self, factors=None): - r""" - Return the product of finite simplicial sets. - - See :class:`ProductOfSimplicialSets` for more information. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(1) - sage: X = SimplicialSet({e: (v, v)}) - sage: W = X.product(X, X) - sage: W.homology() - {0: 0, 1: Z x Z x Z, 2: Z x Z x Z, 3: Z} - sage: W.is_pointed() - False - - sage: X = X.set_base_point(v) - sage: w = AbstractSimplex(0, name='w') - sage: f = AbstractSimplex(1) - sage: Y = SimplicialSet({f: (v,w)}, base_point=w) - sage: Z = Y.product(X) - sage: Z.is_pointed() - True - sage: Z.base_point() - (w, v) - """ - PullbackOfSimplicialSets_finite.__init__(self, [space.constant_map() - for space in factors]) - self._factors = tuple([f.domain() for f in self._maps]) - - def projection_map(self, i): - """ - Return the map projecting onto the $i$-th factor. - - INPUT: - - - ``i`` -- integer, the index of the projection map - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: f_0 = T.projection_map(0) - sage: f_1 = T.projection_map(1) - sage: m_0 = f_0.induced_homology_morphism().to_matrix(1) # matrix in dim 1 - sage: m_1 = f_1.induced_homology_morphism().to_matrix(1) - sage: m_0.rank() - 1 - sage: m_0 == m_1 - False - """ - return self.structure_map(i) - - def wedge_as_subset(self): - """ - Return the wedge as a subsimplicial set of this product of pointed - simplicial sets. - - This will raise an error if any factor is not pointed. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(1, name='e') - sage: w = AbstractSimplex(0, name='w') - sage: f = AbstractSimplex(1, name='f') - sage: X = SimplicialSet({e: (v, v)}, base_point=v) - sage: Y = SimplicialSet({f: (w, w)}, base_point=w) - sage: P = X.product(Y) - sage: W = P.wedge_as_subset() - sage: W.nondegenerate_simplices() - [(v, w), (e, s_0 w), (s_0 v, f)] - sage: W.homology() - {0: 0, 1: Z x Z} - """ - basept_factors = [sset.base_point() for sset in self.factors()] - to_factors = dict((v,k) for k,v in self._translation) - simps = [] - for x in self.nondegenerate_simplices(): - simplices = to_factors[x] - not_base_pt = 0 - for sigma, star in zip(simplices, basept_factors): - if not_base_pt > 1: - continue - if sigma[0].nondegenerate() != star: - not_base_pt += 1 - if not_base_pt <= 1: - simps.append(x) - return self.subsimplicial_set(simps) - - def fat_wedge_as_subset(self): - """ - Return the fat wedge as a subsimplicial set of this product of - pointed simplicial sets. - - The fat wedge consists of those terms where at least one - factor is the base point. Thus with two factors this is the - ordinary wedge, but with more factors, it is larger. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: X = S1.product(S1, S1) - sage: W = X.fat_wedge_as_subset() - sage: W.homology() - {0: 0, 1: Z x Z x Z, 2: Z x Z x Z} - """ - basept_factors = [sset.base_point() for sset in self.factors()] - to_factors = {v: k for k, v in self._translation} - simps = [] - for x in self.nondegenerate_simplices(): - simplices = to_factors[x] - combined = zip(simplices, basept_factors) - if any(sigma[0] == pt for (sigma, pt) in combined): - simps.append(x) - return self.subsimplicial_set(simps) - - -class PushoutOfSimplicialSets(SimplicialSet_arbitrary, UniqueRepresentation): - @staticmethod - def __classcall_private__(cls, maps=None, vertex_name=None): - """ - TESTS:: - - sage: from sage.homology.simplicial_set_constructions import PushoutOfSimplicialSets - sage: S2 = simplicial_sets.Sphere(2) - sage: one = S2.Hom(S2).identity() - sage: PushoutOfSimplicialSets([one, one]) == PushoutOfSimplicialSets((one, one)) - True - """ - if maps: - return super(PushoutOfSimplicialSets, cls).__classcall__(cls, maps=tuple(maps), - vertex_name=vertex_name) - return super(PushoutOfSimplicialSets, cls).__classcall__(cls, vertex_name=vertex_name) - - def __init__(self, maps=None, vertex_name=None): - r""" - Return the pushout obtained from the morphisms ``maps``. - - INPUT: - - - ``maps`` -- a list or tuple of morphisms of simplicial sets - - ``vertex_name`` -- optional, default ``None`` - - If only a single map `f: X \to Y` is given, then return - `Y`. If no maps are given, return the empty simplicial - set. Otherwise, given a simplicial set `X` and maps `f_i: X - \to Y_i` for `0 \leq i \leq m`, construct the pushout `P`: see - :wikipedia:`Pushout_(category_theory)`. This is constructed as - pushouts of sets for each set of `n`-simplices, so `P_n` is - the disjoint union of the sets `(Y_i)_n`, with elements - `f_i(x)` identified for `n`-simplex `x` in `X`. - - Simplices in the pushout are given names as follows: if a - simplex comes from a single `Y_i`, it inherits its - name. Otherwise it must come from a simplex (or several) in - `X`, and then it inherits one of those names, and it should be - the first alphabetically. For example, if vertices `v`, `w`, - and `z` in `X` are glued together, then the resulting vertex - in the pushout will be called `v`. - - Base points are taken care of automatically: if each of the - maps `f_i` is pointed, so is the pushout. If `X` is a point or - if `X` is nonempty and any of the spaces `Y_i` is a point, use - those for the base point. In all of these cases, if - ``vertex_name`` is ``None``, generate the name of the base - point automatically; otherwise, use ``vertex_name`` for its - name. - - In all other cases, the pushout is not pointed. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: a = AbstractSimplex(0, name='a') - sage: b = AbstractSimplex(0, name='b') - sage: c = AbstractSimplex(0, name='c') - sage: e0 = AbstractSimplex(1, name='e_0') - sage: e1 = AbstractSimplex(1, name='e_1') - sage: e2 = AbstractSimplex(1, name='e_2') - sage: X = SimplicialSet({e2: (b, a)}) - sage: Y0 = SimplicialSet({e2: (b,a), e0: (c,b), e1: (c,a)}) - sage: Y1 = simplicial_sets.Simplex(0) - sage: f0_data = {a:a, b:b, e2: e2} - sage: v = Y1.n_cells(0)[0] - sage: f1_data = {a:v, b:v, e2:v.apply_degeneracies(0)} - sage: f0 = X.Hom(Y0)(f0_data) - sage: f1 = X.Hom(Y1)(f1_data) - sage: P = X.pushout(f0, f1) - sage: P.nondegenerate_simplices() - [a, c, e_0, e_1] - - There are defining maps `f_i: X \to Y_i` and structure maps - `\bar{f}_i: Y_i \to P`; the latter are only implemented in - Sage when each `Y_i` is finite. :: - - sage: P.defining_map(0) == f0 - True - sage: P.structure_map(1) - Simplicial set morphism: - From: 0-simplex - To: Pushout of maps: - Simplicial set morphism: - From: Simplicial set with 3 non-degenerate simplices - To: Simplicial set with 6 non-degenerate simplices - Defn: [a, b, e_2] --> [a, b, e_2] - Simplicial set morphism: - From: Simplicial set with 3 non-degenerate simplices - To: 0-simplex - Defn: Constant map at (0,) - Defn: Constant map at a - sage: P.structure_map(0).domain() == Y0 - True - sage: P.structure_map(0).codomain() == P - True - - An inefficient way of constructing a suspension for an - unpointed set: take the pushout of two copies of the inclusion - map `X \to CX`:: - - sage: T = simplicial_sets.Torus() - sage: T = T.unset_base_point() - sage: CT = T.cone() - sage: inc = CT.base_as_subset().inclusion_map() - sage: P = T.pushout(inc, inc) - sage: P.homology() - {0: 0, 1: 0, 2: Z x Z, 3: Z} - sage: len(P.nondegenerate_simplices()) - 20 - - It is more efficient to construct the suspension as the - quotient `CX/X`:: - - sage: len(CT.quotient(CT.base_as_subset()).nondegenerate_simplices()) - 8 - - It is more efficient still if the original simplicial set has - a base point:: - - sage: T = simplicial_sets.Torus() - sage: len(T.suspension().nondegenerate_simplices()) - 6 - - sage: S1 = simplicial_sets.Sphere(1) - sage: pt = simplicial_sets.Point() - sage: bouquet = pt.pushout(S1.base_point_map(), S1.base_point_map(), S1.base_point_map()) - sage: bouquet.homology(1) - Z x Z x Z - """ - # Import this here to prevent circular imports. - from sage.homology.simplicial_set_morphism import SimplicialSetMorphism - if maps and any(not isinstance(f, SimplicialSetMorphism) for f in maps): - raise ValueError('the maps must be morphisms of simplicial sets') - Cat = SimplicialSets() - if maps: - if all(f.codomain().is_finite() for f in maps): - Cat = Cat.Finite() - if all(f.is_pointed() for f in maps): - Cat = Cat.Pointed() - Parent.__init__(self, category=Cat) - self._maps = maps - self._n_skeleton = (-1, Empty()) - self._vertex_name = vertex_name - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the simplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - The `n`-skeleton of the pushout is computed as the pushout - of the `n`-skeleta of the component simplicial sets. - - EXAMPLES:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: K = B.n_skeleton(3) - sage: Q = K.pushout(K.inclusion_map(), K.constant_map()) - sage: Q.n_skeleton(5).homology() - {0: 0, 1: 0, 2: 0, 3: 0, 4: Z, 5: Z} - - Of course, computing the `n`-skeleton and then taking homology - need not yield the same answer as asking for homology through - dimension `n`, since the latter computation will use the - `(n+1)`-skeleton:: - - sage: Q.homology(range(6)) - {0: 0, 1: 0, 2: 0, 3: 0, 4: Z, 5: C2} - """ - if self.is_finite(): - maps = self._maps - if maps: - domain = SimplicialSet_finite.n_skeleton(maps[0].domain(), n) - codomains = [SimplicialSet_finite.n_skeleton(f.codomain(), n) for f in maps] - new_maps = [f.n_skeleton(n, domain, c) for (f, c) in zip(maps, codomains)] - return PushoutOfSimplicialSets_finite(new_maps, - vertex_name=self._vertex_name) - return PushoutOfSimplicialSets_finite(maps, - vertex_name=self._vertex_name) - start, skel = self._n_skeleton - if start == n: - return skel - elif start > n: - return skel.n_skeleton(n) - ans = PushoutOfSimplicialSets_finite([f.n_skeleton(n) for f in self._maps], - vertex_name=self._vertex_name) - self._n_skeleton = (n, ans) - return ans - - def defining_map(self, i): - r""" - Return the `i`-th map defining the pushout. - - INPUT: - - - ``i`` -- integer - - If this pushout was constructed as ``X.pushout(f_0, f_1, ...)``, - this returns `f_i`. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = simplicial_sets.Torus() - sage: X = S1.wedge(T) # a pushout - sage: X.defining_map(0) - Simplicial set morphism: - From: Point - To: S^1 - Defn: Constant map at v_0 - sage: X.defining_map(1).domain() - Point - sage: X.defining_map(1).codomain() - Torus - """ - return self._maps[i] - - def _repr_(self): - """ - Print representation - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: S3 = simplicial_sets.Sphere(3) - sage: pt = simplicial_sets.Point() - sage: pt.pushout(S2.base_point_map(), S3.base_point_map()) - Pushout of maps: - Simplicial set morphism: - From: Point - To: S^2 - Defn: Constant map at v_0 - Simplicial set morphism: - From: Point - To: S^3 - Defn: Constant map at v_0 - """ - if not self._maps: - return 'Empty simplicial set' - s = 'Pushout of maps:' - for f in self._maps: - t = '\n' + str(f) - s += t.replace('\n', '\n ') - return s - - -class PushoutOfSimplicialSets_finite(PushoutOfSimplicialSets, SimplicialSet_finite): - """ - The pushout of finite simplicial sets obtained from ``maps``. - - When the simplicial sets involved are all finite, there are more - methods available to the resulting pushout, as compared to case - when some of the components are infinite: the structure maps to the - pushout and the pushout's universal property: see - :meth:`structure_map` and :meth:`universal_property`. - """ - @staticmethod - def __classcall_private__(cls, maps=None, vertex_name=None): - """ - TESTS:: - - sage: from sage.homology.simplicial_set_constructions import PushoutOfSimplicialSets_finite - sage: S2 = simplicial_sets.Sphere(2) - sage: one = S2.Hom(S2).identity() - sage: PushoutOfSimplicialSets_finite([one, one]) == PushoutOfSimplicialSets_finite((one, one)) - True - """ - if maps: - return super(PushoutOfSimplicialSets_finite, cls).__classcall__(cls, maps=tuple(maps), - vertex_name=vertex_name) - return super(PushoutOfSimplicialSets_finite, cls).__classcall__(cls, vertex_name=vertex_name) - - def __init__(self, maps=None, vertex_name=None): - r""" - Return the pushout obtained from the morphisms ``maps``. - - See :class:`PushoutOfSimplicialSets` for more information. - - INPUT: - - - ``maps`` -- a list or tuple of morphisms of simplicial sets - - ``vertex_name`` -- optional, default ``None`` - - EXAMPLES:: - - sage: from sage.homology.simplicial_set_constructions import PushoutOfSimplicialSets_finite - sage: T = simplicial_sets.Torus() - sage: S2 = simplicial_sets.Sphere(2) - sage: PushoutOfSimplicialSets_finite([T.base_point_map(), S2.base_point_map()]).n_cells(0)[0] - * - sage: PushoutOfSimplicialSets_finite([T.base_point_map(), S2.base_point_map()], vertex_name='v').n_cells(0)[0] - v - """ - # Import this here to prevent circular imports. - from sage.homology.simplicial_set_morphism import SimplicialSetMorphism - if maps and any(not isinstance(f, SimplicialSetMorphism) for f in maps): - raise ValueError('the maps must be morphisms of simplicial sets') - if not maps: - SimplicialSet_finite.__init__(self, {}) - self._maps = () - self._structure = () - return - domain = maps[0].domain() - if len(maps) == 1: - # f: X --> Y - f = maps[0] - codomain = f.codomain() - if f.is_pointed(): - base_point=codomain.base_point() - if vertex_name is not None: - base_point.rename(vertex_name) - SimplicialSet_finite.__init__(self, codomain.face_data(), - base_point=base_point) - elif len(domain.nondegenerate_simplices()) == 1: - # X is a point. - base_point = f(domain().n_cells(0)[0]) - if vertex_name is not None: - base_point.rename(vertex_name) - SimplicialSet_finite.__init__(self, codomain.face_data(), - base_point=base_point) - elif len(codomain.nondegenerate_simplices()) == 1: - # Y is a point. - base_point = codomain.n_cells(0)[0] - if vertex_name is not None: - base_point.rename(vertex_name) - SimplicialSet_finite.__init__(self, codomain.face_data(), - base_point=base_point) - else: - SimplicialSet_finite.__init__(self, codomain.face_data()) - self._maps = (f,) - self._structure = (f,) - return - if any(domain != f.domain() for f in maps[1:]): - raise ValueError('the domains of the maps must be equal') - # Data to define the pushout: - data = {} - codomains = [f.codomain() for f in maps] - # spaces: indexed list of spaces. Entries are of the form - # (space, int) where int=-1 for the domain, and for the - # codomains, int is the corresponding index. - spaces = [(Y,i-1) for (i,Y) in enumerate([domain] + codomains)] - # Dictionaries to translate from simplices in domain, - # codomains to simplices in the pushout. The keys are of the - # form (space, int). int=-1 for the domain, and for the - # codomains, int is the corresponding index. - _to_P = {Y:{} for Y in spaces} - max_dim = max(Y.dimension() for Y in codomains) - for n in range(1 + max_dim): - # Now we impose an equivalence relation on the simplices, - # setting x equivalent to f_i(x) for each simplex x in X - # and each defining map f_i. We do this by constructing a - # graph and finding its connected components: the vertices - # of the graph are the n-cells of X and the Y_i, and - # there are edges from x to f_i(x). - vertices = [] - for (Y,i) in spaces: - vertices.extend([(cell,i) for cell in Y.n_cells(n)]) - edges = [] - for x in domain.n_cells(n): - edges.extend([[(x,-1), (f(x),i)] for (i,f) in enumerate(maps)]) - G = Graph([vertices, edges], format='vertices_and_edges') - data[n] = [set(_) for _ in G.connected_components()] - # data is now a dictionary indexed by dimension, and data[n] - # consists of sets of n-simplices of the domain and the - # codomains, each set an equivalence class of n-simplices - # under the gluing. So if any element of one of those sets is - # degenerate, we can throw the whole thing away. Otherwise, we - # can choose a representative to compute the faces. - simplices = {} - for dim in sorted(data): - for s in data[dim]: - degenerate = any(sigma[0].is_degenerate() for sigma in s) - if degenerate: - # Identify the degeneracies involved. - degens = [] - for (sigma, j) in s: - if len(sigma.degeneracies()) > len(degens): - degens = sigma.degeneracies() - space = spaces[j+1] - old = _to_P[space][sigma.nondegenerate()] - for (sigma,j) in s: - # Now update the _to_P[space] dictionaries. - space = spaces[j+1] - _to_P[space][sigma] = old.apply_degeneracies(*degens) - else: # nondegenerate - if len(s) == 1: - name = str(list(s)[0][0]) - latex_name = latex(list(s)[0][0]) - else: - # Choose a name from a simplex in domain. - for (sigma,j) in sorted(s): - if j == -1: - name = str(sigma) - latex_name = latex(sigma) - break - new = AbstractSimplex(dim, name=name, - latex_name=latex_name) - if dim == 0: - faces = None - for (sigma,j) in s: - space = spaces[j+1] - _to_P[space][sigma] = new - if dim > 0: - faces = [_to_P[space][tau.nondegenerate()].apply_degeneracies(*tau.degeneracies()) - for tau in space[0].faces(sigma)] - simplices[new] = faces - - some_Y_is_pt = False - if len(domain.nondegenerate_simplices()) > 1: - # Only investigate this if X is not empty and not a point. - for (Y,i) in spaces: - if len(Y.nondegenerate_simplices()) == 1: - some_Y_is_pt = True - break - if len(domain.nondegenerate_simplices()) == 1: - # X is a point. - base_point = _to_P[(domain,-1)][domain.n_cells(0)[0]] - if vertex_name is not None: - base_point.rename(vertex_name) - SimplicialSet_finite.__init__(self, simplices, base_point=base_point) - elif some_Y_is_pt: - # We found (Y,i) above. - base_point = _to_P[(Y,i)][Y.n_cells(0)[0]] - if vertex_name is not None: - base_point.rename(vertex_name) - SimplicialSet_finite.__init__(self, simplices, base_point=base_point) - elif all(f.is_pointed() for f in maps): - pt = _to_P[(codomains[0],0)][codomains[0].base_point()] - if any(_to_P[(Y,i)][Y.base_point()] != pt for (Y,i) in spaces[2:]): - raise ValueError('something unexpected went wrong ' - 'with base points') - base_point = _to_P[(domain,-1)][domain.base_point()] - if vertex_name is not None: - base_point.rename(vertex_name) - SimplicialSet_finite.__init__(self, simplices, base_point=base_point) - else: - SimplicialSet_finite.__init__(self, simplices) - # The relevant maps: - self._maps = maps - self._structure = tuple([Y.Hom(self)(_to_P[(Y,i)]) - for (Y,i) in spaces[1:]]) - self._vertex_name = vertex_name - - def structure_map(self, i): - r""" - Return the $i$-th structure map of the pushout. - - INPUT: - - - ``i`` -- integer - - If this pushout `Z` was constructed as ``X.pushout(f_0, f_1, ...)``, - where `f_i: X \to Y_i`, then there are structure maps - `\bar{f}_i: Y_i \to Z`. This method constructs `\bar{f}_i`. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = simplicial_sets.Torus() - sage: X = S1.disjoint_union(T) # a pushout - sage: X.structure_map(0) - Simplicial set morphism: - From: S^1 - To: Disjoint union: (S^1 u Torus) - Defn: [v_0, sigma_1] --> [v_0, sigma_1] - sage: X.structure_map(1).domain() - Torus - sage: X.structure_map(1).codomain() - Disjoint union: (S^1 u Torus) - """ - return self._structure[i] - - def universal_property(self, *maps): - r""" - Return the map induced by ``maps`` - - INPUT: - - - ``maps`` -- maps "factors" `Y_i` forming the pushout to a - fixed simplicial set `Z`. - - If the pushout `P` is formed by maps `f_i: X \to Y_i`, then - given maps `g_i: Y_i \to Z` such that `g_i f_i = g_j f_j` for - all `i`, `j`, then there is a unique map `g: P \to Z` making - the appropriate diagram commute. This constructs that map. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: x = AbstractSimplex(0, name='x') - sage: evw = AbstractSimplex(1, name='vw') - sage: evx = AbstractSimplex(1, name='vx') - sage: ewx = AbstractSimplex(1, name='wx') - sage: X = SimplicialSet({evw: (w, v), evx: (x, v)}) - sage: Y_0 = SimplicialSet({evw: (w, v), evx: (x, v), ewx: (x, w)}) - sage: Y_1 = SimplicialSet({evx: (x, v)}) - - sage: f_0 = Hom(X, Y_0)({v:v, w:w, x:x, evw:evw, evx:evx}) - sage: f_1 = Hom(X, Y_1)({v:v, w:v, x:x, evw:v.apply_degeneracies(0), evx:evx}) - sage: P = X.pushout(f_0, f_1) - - sage: one = Hom(Y_1, Y_1).identity() - sage: g = Hom(Y_0, Y_1)({v:v, w:v, x:x, evw:v.apply_degeneracies(0), evx:evx, ewx:evx}) - sage: P.universal_property(g, one) - Simplicial set morphism: - From: Pushout of maps: - Simplicial set morphism: - From: Simplicial set with 5 non-degenerate simplices - To: Simplicial set with 6 non-degenerate simplices - Defn: [v, w, x, vw, vx] --> [v, w, x, vw, vx] - Simplicial set morphism: - From: Simplicial set with 5 non-degenerate simplices - To: Simplicial set with 3 non-degenerate simplices - Defn: [v, w, x, vw, vx] --> [v, v, x, s_0 v, vx] - To: Simplicial set with 3 non-degenerate simplices - Defn: [v, x, vx, wx] --> [v, x, vx, vx] - """ - codomain = maps[0].codomain() - if any(g.codomain() != codomain for g in maps[1:]): - raise ValueError('the maps do not all have the same codomain') - composite = maps[0] * self._maps[0] - if any(g*f != composite for g,f in zip(maps[1:], self._maps[1:])): - raise ValueError('the maps are not compatible') - data = {} - for i,g in enumerate(maps): - f_i_dict = self.structure_map(i)._dictionary - for sigma in f_i_dict: - tau = f_i_dict[sigma] - # For sigma_i in Y_i, define the map G by - # G(\bar{f}_i)(sigma_i) = g_i(sigma_i). - if tau not in data: - data[tau] = g(sigma) - return self.Hom(codomain)(data) - - -class QuotientOfSimplicialSet(PushoutOfSimplicialSets): - def __init__(self, inclusion, vertex_name='*'): - r""" - Return the quotient of a simplicial set by a subsimplicial set. - - INPUT: - - - ``inclusion`` -- inclusion map of a subcomplex (= - subsimplicial set) of a simplicial set - - ``vertex_name`` -- optional, default ``'*'`` - - A subcomplex `A` comes equipped with the inclusion map `A \to - X` to its ambient complex `X`, and this constructs the - quotient `X/A`, collapsing `A` to a point. The resulting point - is called ``vertex_name``, which is ``'*'`` by default. - - When the simplicial sets involved are finite, there is a - :meth:`QuotientOfSimplicialSet_finite.quotient_map` method available. - - EXAMPLES:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP2 = RP5.n_skeleton(2) - sage: RP5_2 = RP5.quotient(RP2) - sage: RP5_2 - Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) - sage: RP5_2.quotient_map() - Simplicial set morphism: - From: RP^5 - To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) - Defn: [1, f, f * f, f * f * f, f * f * f * f, f * f * f * f * f] --> [*, s_0 *, s_1 s_0 *, f * f * f, f * f * f * f, f * f * f * f * f] - """ - subcomplex = inclusion.domain() - PushoutOfSimplicialSets.__init__(self, [inclusion, - subcomplex.constant_map()], - vertex_name=vertex_name) - - ambient = inclusion.codomain() - if ambient.is_pointed() and ambient.is_finite(): - if ambient.base_point() not in subcomplex: - self._basepoint = self.structure_map(0)(ambient.base_point()) - - def ambient(self): - """ - Return the ambient space. - - That is, if this quotient is `K/L`, return `K`. - - EXAMPLES:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP2 = RP5.n_skeleton(2) - sage: RP5_2 = RP5.quotient(RP2) - sage: RP5_2.ambient() - RP^5 - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: K = B.n_skeleton(3) - sage: Q = B.quotient(K) - sage: Q.ambient() - Classifying space of Multiplicative Abelian group isomorphic to C2 - """ - return self._maps[0].codomain() - - def subcomplex(self): - """ - Return the subcomplex space associated to this quotient. - - That is, if this quotient is `K/L`, return `L`. - - EXAMPLES:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP2 = RP5.n_skeleton(2) - sage: RP5_2 = RP5.quotient(RP2) - sage: RP5_2.subcomplex() - Simplicial set with 3 non-degenerate simplices - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: K = B.n_skeleton(3) - sage: Q = B.quotient(K) - sage: Q.subcomplex() - Simplicial set with 4 non-degenerate simplices - """ - return self._maps[0].domain() - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the simplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - The `n`-skeleton of the quotient is computed as the quotient - of the `n`-skeleta. - - EXAMPLES:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: K = B.n_skeleton(3) - sage: Q = B.quotient(K) - sage: Q.n_skeleton(6) - Quotient: (Simplicial set with 7 non-degenerate simplices/Simplicial set with 4 non-degenerate simplices) - sage: Q.n_skeleton(6).homology() - {0: 0, 1: 0, 2: 0, 3: 0, 4: Z, 5: C2, 6: 0} - """ - if self.is_finite(): - ambient = SimplicialSet_finite.n_skeleton(self.ambient(), n) - subcomplex = SimplicialSet_finite.n_skeleton(self.subcomplex(), n) - subcomplex = ambient.subsimplicial_set(subcomplex.nondegenerate_simplices()) - return QuotientOfSimplicialSet_finite(subcomplex.inclusion_map(), - vertex_name=self._vertex_name) - start, skel = self._n_skeleton - if start == n: - return skel - elif start > n: - return skel.n_skeleton(n) - ambient = self.ambient().n_skeleton(n) - subcomplex = ambient.subsimplicial_set(self.subcomplex().nondegenerate_simplices(n)) - ans = QuotientOfSimplicialSet_finite(subcomplex.inclusion_map(), - vertex_name=self._vertex_name) - self._n_skeleton = (n, ans) - return ans - - def _repr_(self): - """ - Print representation - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: T.quotient(T.n_skeleton(1)) - Quotient: (Torus/Simplicial set with 4 non-degenerate simplices) - """ - return 'Quotient: ({}/{})'.format(self.ambient(), self.subcomplex()) - - def _latex_(self): - r""" - LaTeX representation - - EXAMPLES:: - - sage: RPoo = simplicial_sets.RealProjectiveSpace(Infinity) - sage: RP3 = RPoo.n_skeleton(3) - sage: RP3.rename_latex('RP^{3}') - sage: latex(RPoo.quotient(RP3)) - RP^{\infty} / RP^{3} - """ - return '{} / {}'.format(latex(self.ambient()), latex(self.subcomplex())) - - -class QuotientOfSimplicialSet_finite(QuotientOfSimplicialSet, - PushoutOfSimplicialSets_finite): - """ - The quotient of finite simplicial sets. - - When the simplicial sets involved are finite, there is a - :meth:`quotient_map` method available. - """ - def __init__(self, inclusion, vertex_name='*'): - r""" - Return the quotient of a simplicial set by a subsimplicial set. - - See :class:`QuotientOfSimplicialSet` for more information. - - EXAMPLES:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP2 = RP5.n_skeleton(2) - sage: RP5_2 = RP5.quotient(RP2) - sage: RP5_2 - Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) - sage: RP5_2.quotient_map() - Simplicial set morphism: - From: RP^5 - To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) - Defn: [1, f, f * f, f * f * f, f * f * f * f, f * f * f * f * f] --> [*, s_0 *, s_1 s_0 *, f * f * f, f * f * f * f, f * f * f * f * f] - """ - subcomplex = inclusion.domain() - PushoutOfSimplicialSets_finite.__init__(self, [inclusion, - subcomplex.constant_map()], - vertex_name=vertex_name) - ambient = inclusion.codomain() - if ambient.is_pointed(): - if ambient.base_point() not in subcomplex: - self._basepoint = self.structure_map(0)(ambient.base_point()) - - def quotient_map(self): - """ - Return the quotient map from the original simplicial set to the - quotient. - - EXAMPLES:: - - sage: K = simplicial_sets.Simplex(1) - sage: S1 = K.quotient(K.n_skeleton(0)) - sage: q = S1.quotient_map() - sage: q - Simplicial set morphism: - From: 1-simplex - To: Quotient: (1-simplex/Simplicial set with 2 non-degenerate simplices) - Defn: [(0,), (1,), (0, 1)] --> [*, *, (0, 1)] - sage: q.domain() == K - True - sage: q.codomain() == S1 - True - """ - return self.structure_map(0) - - -class SmashProductOfSimplicialSets_finite(QuotientOfSimplicialSet_finite, - Factors): - @staticmethod - def __classcall__(cls, factors=None): - """ - TESTS:: - - sage: from sage.homology.simplicial_set_constructions import SmashProductOfSimplicialSets_finite as Smash - sage: S2 = simplicial_sets.Sphere(2) - sage: Smash([S2, S2]) == Smash((S2, S2)) - True - """ - if factors: - return super(SmashProductOfSimplicialSets_finite, cls).__classcall__(cls, factors=tuple(factors)) - return super(SmashProductOfSimplicialSets_finite, cls).__classcall__(cls) - - def __init__(self, factors=None): - r""" - Return the smash product of finite pointed simplicial sets. - - INPUT: - - - ``factors`` -- a list or tuple of simplicial sets - - Return the smash product of the simplicial sets in - ``factors``: the smash product `X \wedge Y` is defined to be - the quotient `(X \times Y) / (X \vee Y)`, where `X \vee Y` is - the wedge sum. - - Each element of ``factors`` must be finite and pointed. (As of - July 2016, constructing the wedge as a subcomplex of the - product is only possible in Sage for finite simplicial sets.) - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: S2 = simplicial_sets.Sphere(2) - sage: T.smash_product(S2).homology() == T.suspension(2).homology() - True - """ - if any(not space.is_pointed() for space in factors): - raise ValueError('the simplicial sets must be pointed') - prod = ProductOfSimplicialSets_finite(factors) - wedge = prod.wedge_as_subset() - QuotientOfSimplicialSet_finite.__init__(self, wedge.inclusion_map()) - self._factors = factors - - def _repr_(self): - """ - Print representation - - EXAMPLES:: - - sage: RP4 = simplicial_sets.RealProjectiveSpace(4) - sage: S1 = simplicial_sets.Sphere(1) - sage: S1.smash_product(RP4, S1) - Smash product: (S^1 ^ RP^4 ^ S^1) - """ - s = 'Smash product: (' - s += ' ^ '.join([str(X) for X in self._factors]) - s += ')' - return s - - def _latex_(self): - r""" - LaTeX representation - - EXAMPLES:: - - sage: RP4 = simplicial_sets.RealProjectiveSpace(4) - sage: S1 = simplicial_sets.Sphere(1) - sage: latex(S1.smash_product(RP4, S1)) - S^{1} \wedge RP^{4} \wedge S^{1} - """ - return ' \\wedge '.join([latex(X) for X in self._factors]) - - -class WedgeOfSimplicialSets(PushoutOfSimplicialSets, Factors): - @staticmethod - def __classcall__(cls, factors=None): - """ - TESTS:: - - sage: from sage.homology.simplicial_set_constructions import WedgeOfSimplicialSets - sage: S2 = simplicial_sets.Sphere(2) - sage: WedgeOfSimplicialSets([S2, S2]) == WedgeOfSimplicialSets((S2, S2)) - True - """ - if factors: - return super(WedgeOfSimplicialSets, cls).__classcall__(cls, factors=tuple(factors)) - return super(WedgeOfSimplicialSets, cls).__classcall__(cls) - - def __init__(self, factors=None): - r""" - Return the wedge sum of pointed simplicial sets. - - INPUT: - - - ``factors`` -- a list or tuple of simplicial sets - - Return the wedge of the simplicial sets in ``factors``: the - wedge sum `X \vee Y` is formed by taking the disjoint - union of `X` and `Y` and identifying their base points. In - this construction, the new base point is renamed '*'. - - The wedge comes equipped with maps to and from each factor, or - actually, maps from each factor, and maps to simplicial sets - isomorphic to each factor. The codomains of the latter maps - are quotients of the wedge, not identical to the original - factors. - - EXAMPLES:: - - sage: CP2 = simplicial_sets.ComplexProjectiveSpace(2) - sage: K = simplicial_sets.KleinBottle() - sage: W = CP2.wedge(K) - sage: W.homology() - {0: 0, 1: Z x C2, 2: Z, 3: 0, 4: Z} - - sage: W.inclusion_map(1) - Simplicial set morphism: - From: Klein bottle - To: Wedge: (CP^2 v Klein bottle) - Defn: [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] --> [*, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] - - sage: W.projection_map(0).domain() - Wedge: (CP^2 v Klein bottle) - sage: W.projection_map(0).codomain() # copy of CP^2 - Quotient: (Wedge: (CP^2 v Klein bottle)/Simplicial set with 6 non-degenerate simplices) - sage: W.projection_map(0).codomain().homology() - {0: 0, 1: 0, 2: Z, 3: 0, 4: Z} - - An error occurs if any of the factors is not pointed:: - - sage: CP2.wedge(simplicial_sets.Simplex(1)) - Traceback (most recent call last): - ... - ValueError: the simplicial sets must be pointed - """ - if any(not space.is_pointed() for space in factors): - raise ValueError('the simplicial sets must be pointed') - PushoutOfSimplicialSets.__init__(self, [space.base_point_map() - for space in factors]) - if factors: - vertices = PushoutOfSimplicialSets_finite([space.n_skeleton(0).base_point_map() - for space in factors]) - self._basepoint = vertices.base_point() - self.base_point().rename('*') - self._factors = factors - - summands = Factors.factors - summand = Factors.factor - - def _repr_(self): - """ - Print representation - - EXAMPLES:: - - sage: K = simplicial_sets.KleinBottle() - sage: K.wedge(K, K) - Wedge: (Klein bottle v Klein bottle v Klein bottle) - """ - s = 'Wedge: (' - s += ' v '.join([str(X) for X in self._factors]) - s += ')' - return s - - def _latex_(self): - """ - LaTeX representation - - EXAMPLES:: - - sage: RP4 = simplicial_sets.RealProjectiveSpace(4) - sage: S1 = simplicial_sets.Sphere(1) - sage: latex(S1.wedge(RP4, S1)) - S^{1} \vee RP^{4} \vee S^{1} - """ - return ' \\vee '.join([latex(X) for X in self._factors]) - - -class WedgeOfSimplicialSets_finite(WedgeOfSimplicialSets, PushoutOfSimplicialSets_finite): - """ - The wedge sum of finite pointed simplicial sets. - """ - def __init__(self, factors=None): - r""" - Return the wedge sum of finite pointed simplicial sets. - - INPUT: - - - ``factors`` -- a tuple of simplicial sets - - If there are no factors, a point is returned. - - See :class:`WedgeOfSimplicialSets` for more information. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set_constructions import WedgeOfSimplicialSets_finite - sage: K = simplicial_sets.Simplex(3) - sage: WedgeOfSimplicialSets_finite((K,K)) - Traceback (most recent call last): - ... - ValueError: the simplicial sets must be pointed - """ - if not factors: - # An empty wedge is a point, constructed as a pushout. - PushoutOfSimplicialSets_finite.__init__(self, [Point().identity()]) - else: - if any(not space.is_pointed() for space in factors): - raise ValueError('the simplicial sets must be pointed') - PushoutOfSimplicialSets_finite.__init__(self, [space.base_point_map() - for space in factors]) - self.base_point().rename('*') - self._factors = factors - - def inclusion_map(self, i): - """ - Return the inclusion map of the $i$-th factor. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: S2 = simplicial_sets.Sphere(2) - sage: W = S1.wedge(S2, S1) - sage: W.inclusion_map(1) - Simplicial set morphism: - From: S^2 - To: Wedge: (S^1 v S^2 v S^1) - Defn: [v_0, sigma_2] --> [*, sigma_2] - sage: W.inclusion_map(0).domain() - S^1 - sage: W.inclusion_map(2).domain() - S^1 - """ - return self.structure_map(i) - - def projection_map(self, i): - """ - Return the projection map onto the $i$-th factor. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: S2 = simplicial_sets.Sphere(2) - sage: W = S1.wedge(S2, S1) - sage: W.projection_map(1) - Simplicial set morphism: - From: Wedge: (S^1 v S^2 v S^1) - To: Quotient: (Wedge: (S^1 v S^2 v S^1)/Simplicial set with 3 non-degenerate simplices) - Defn: [*, sigma_1, sigma_1, sigma_2] --> [*, s_0 *, s_0 *, sigma_2] - sage: W.projection_map(1).image().homology(1) - 0 - sage: W.projection_map(1).image().homology(2) - Z - """ - m = len(self._factors) - simplices = ([self.inclusion_map(j).image().nondegenerate_simplices() - for j in range(i)] - + [self.inclusion_map(j).image().nondegenerate_simplices() - for j in range(i+1,m)]) - return self.quotient(list(itertools.chain(*simplices))).quotient_map() - - -class DisjointUnionOfSimplicialSets(PushoutOfSimplicialSets, Factors): - @staticmethod - def __classcall__(cls, factors=None): - """ - TESTS:: - - sage: from sage.homology.simplicial_set_constructions import DisjointUnionOfSimplicialSets - sage: from sage.homology.simplicial_set_examples import Empty - sage: S2 = simplicial_sets.Sphere(2) - sage: DisjointUnionOfSimplicialSets([S2, S2]) == DisjointUnionOfSimplicialSets((S2, S2)) - True - sage: DisjointUnionOfSimplicialSets([S2, Empty(), S2, Empty()]) == DisjointUnionOfSimplicialSets((S2, S2)) - True - """ - if factors: - # Discard any empty factors. - factors = [F for F in factors if F != Empty()] - if factors: - return super(DisjointUnionOfSimplicialSets, cls).__classcall__(cls, factors=tuple(factors)) - return super(DisjointUnionOfSimplicialSets, cls).__classcall__(cls) - - def __init__(self, factors=None): - r""" - Return the disjoint union of simplicial sets. - - INPUT: - - - ``factors`` -- a list or tuple of simplicial sets - - Discard any factors which are empty and return the disjoint - union of the remaining simplicial sets in ``factors``. The - disjoint union comes equipped with a map from each factor, as - long as all of the factors are finite. - - EXAMPLES:: - - sage: CP2 = simplicial_sets.ComplexProjectiveSpace(2) - sage: K = simplicial_sets.KleinBottle() - sage: W = CP2.disjoint_union(K) - sage: W.homology() - {0: Z, 1: Z x C2, 2: Z, 3: 0, 4: Z} - - sage: W.inclusion_map(1) - Simplicial set morphism: - From: Klein bottle - To: Disjoint union: (CP^2 u Klein bottle) - Defn: [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] --> [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] - """ - PushoutOfSimplicialSets.__init__(self, [space._map_from_empty_set() - for space in factors]) - self._factors = factors - self._n_skeleton = (-1, Empty()) - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the simplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - The `n`-skeleton of the disjoint union is computed as the - disjoint union of the `n`-skeleta of the component simplicial - sets. - - EXAMPLES:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: T = simplicial_sets.Torus() - sage: X = B.disjoint_union(T) - sage: X.n_skeleton(3).homology() - {0: Z, 1: Z x Z x C2, 2: Z, 3: Z} - """ - if self.is_finite(): - return DisjointUnionOfSimplicialSets_finite(tuple([X.n_skeleton(n) - for X in self._factors])) - start, skel = self._n_skeleton - if start == n: - return skel - elif start > n: - return skel.n_skeleton(n) - ans = DisjointUnionOfSimplicialSets_finite(tuple([X.n_skeleton(n) - for X in self._factors])) - self._n_skeleton = (n, ans) - return ans - - summands = Factors.factors - summand = Factors.factor - - def _repr_(self): - """ - Print representation - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: RP3 = simplicial_sets.RealProjectiveSpace(3) - sage: T.disjoint_union(T, RP3) - Disjoint union: (Torus u Torus u RP^3) - """ - s = 'Disjoint union: (' - s += ' u '.join([str(X) for X in self._factors]) - s += ')' - return s - - def _latex_(self): - """ - LaTeX representation - - EXAMPLES:: - - sage: RP4 = simplicial_sets.RealProjectiveSpace(4) - sage: S1 = simplicial_sets.Sphere(1) - sage: latex(S1.disjoint_union(RP4, S1)) - S^{1} \amalg RP^{4} \amalg S^{1} - """ - return ' \\amalg '.join([latex(X) for X in self._factors]) - - -class DisjointUnionOfSimplicialSets_finite(DisjointUnionOfSimplicialSets, - PushoutOfSimplicialSets_finite): - """ - The disjoint union of finite simplicial sets. - """ - def __init__(self, factors=None): - r""" - Return the disjoint union of finite simplicial sets. - - INPUT: - - - ``factors`` -- a tuple of simplicial sets - - Return the disjoint union of the simplicial sets in - ``factors``. The disjoint union comes equipped with a map - from each factor. If there are no factors, this returns the - empty simplicial set. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set_constructions import DisjointUnionOfSimplicialSets_finite - sage: from sage.homology.simplicial_set_examples import Empty - sage: S = simplicial_sets.Sphere(4) - sage: DisjointUnionOfSimplicialSets_finite((S,S,S)) - Disjoint union: (S^4 u S^4 u S^4) - sage: DisjointUnionOfSimplicialSets_finite([Empty(), Empty()]) == Empty() - True - """ - if not factors: - PushoutOfSimplicialSets_finite.__init__(self) - else: - PushoutOfSimplicialSets_finite.__init__(self, [space._map_from_empty_set() - for space in factors]) - self._factors = factors - - def inclusion_map(self, i): - """ - Return the inclusion map of the $i$-th factor. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: S2 = simplicial_sets.Sphere(2) - sage: W = S1.disjoint_union(S2, S1) - sage: W.inclusion_map(1) - Simplicial set morphism: - From: S^2 - To: Disjoint union: (S^1 u S^2 u S^1) - Defn: [v_0, sigma_2] --> [v_0, sigma_2] - sage: W.inclusion_map(0).domain() - S^1 - sage: W.inclusion_map(2).domain() - S^1 - """ - return self.structure_map(i) - - -class ConeOfSimplicialSet(SimplicialSet_arbitrary, UniqueRepresentation): - def __init__(self, base): - r""" - Return the unreduced cone on a finite simplicial set. - - INPUT: - - - ``base`` -- return the cone on this simplicial set. - - Add a point `*` (which will become the base point) and for - each simplex `\sigma` in ``base``, add both `\sigma` and a - simplex made up of `*` and `\sigma` (topologically, form the - join of `*` and `\sigma`). - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(1, name='e') - sage: X = SimplicialSet({e: (v, v)}) - sage: CX = X.cone() # indirect doctest - sage: CX.nondegenerate_simplices() - [*, v, (v,*), e, (e,*)] - sage: CX.base_point() - * - """ - Cat = SimplicialSets().Pointed() - if base.is_finite(): - Cat = Cat.Finite() - Parent.__init__(self, category=Cat) - star = AbstractSimplex(0, name='*') - self._base = base - self._basepoint = star - self._n_skeleton = (-1, Empty()) - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the simplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - In the case when the cone is infinite, the `n`-skeleton of the - cone is computed as the `n`-skeleton of the cone of the - `n`-skeleton. - - EXAMPLES:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: X = B.disjoint_union(B) - sage: CX = B.cone() - sage: CX.n_skeleton(3).homology() - {0: 0, 1: 0, 2: 0, 3: Z} - """ - if self.is_finite(): - return SimplicialSet_finite.n_skeleton(self, n) - start, skel = self._n_skeleton - if start == n: - return skel - elif start > n: - return skel.n_skeleton(n) - ans = ConeOfSimplicialSet_finite(self._base.n_skeleton(n)).n_skeleton(n) - self._n_skeleton = (n, ans) - self._basepoint = ans.base_point() - return ans - - def _repr_(self): - """ - Print representation - - EXAMPLES:: - - sage: simplicial_sets.Simplex(3).cone() - Cone of 3-simplex - """ - return 'Cone of {}'.format(self._base) - - def _latex_(self): - r""" - LaTeX representation - - EXAMPLES:: - - sage: latex(simplicial_sets.Simplex(3).cone()) - C \Delta^{3} - """ - return 'C {}'.format(latex(self._base)) - - -class ConeOfSimplicialSet_finite(ConeOfSimplicialSet, SimplicialSet_finite): - def __init__(self, base): - r""" - Return the unreduced cone on a finite simplicial set. - - INPUT: - - - ``base`` -- return the cone on this simplicial set. - - Add a point `*` (which will become the base point) and for - each simplex `\sigma` in ``base``, add both `\sigma` and a - simplex made up of `*` and `\sigma` (topologically, form the - join of `*` and `\sigma`). - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(1, name='e') - sage: X = SimplicialSet({e: (v, v)}) - sage: CX = X.cone() # indirect doctest - sage: CX.nondegenerate_simplices() - [*, v, (v,*), e, (e,*)] - sage: CX.base_point() - * - """ - star = AbstractSimplex(0, name='*') - data = {} - data[star] = None - # Dictionary for translating old simplices to new: keys are - # old simplices, corresponding value is the new simplex - # (sigma, *). - new_simplices = {'cone': star} - for sigma in base.nondegenerate_simplices(): - new = AbstractSimplex(sigma.dimension()+1, - name='({},*)'.format(sigma), - latex_name='({},*)'.format(latex(sigma))) - if sigma.dimension() == 0: - data[sigma] = None - data[new] = (star, sigma) - else: - sigma_faces = base.face_data()[sigma] - data[sigma] = sigma_faces - new_faces = [new_simplices[face.nondegenerate()].apply_degeneracies(*face.degeneracies()) - for face in sigma_faces] - data[new] = (new_faces + [sigma]) - new_simplices[sigma] = new - SimplicialSet_finite.__init__(self, data, base_point=star) - # self._base: original simplicial set. - self._base = base - # self._joins: dictionary, each key is a simplex sigma in - # base, the corresponding value is the new simplex (sigma, *) - # in the cone. Also, one other key is 'cone', and the value is - # the cone vertex. This is used in the suspension class to - # construct the suspension of a morphism. It could be used to - # construct the cone of a morphism, also, although cones of - # morphisms are not yet implemented. - self._joins = new_simplices - - def base_as_subset(self): - """ - If this is the cone `CX` on `X`, return `X` as a subsimplicial set. - - EXAMPLES:: - - sage: X = simplicial_sets.RealProjectiveSpace(4).unset_base_point() - sage: Y = X.cone() - sage: Y.base_as_subset() - Simplicial set with 5 non-degenerate simplices - sage: Y.base_as_subset() == X - True - """ - X = self._base - return self.subsimplicial_set(X.nondegenerate_simplices()) - - def map_from_base(self): - r""" - If this is the cone `CX` on `X`, return the inclusion map from `X`. - - EXAMPLES:: - - sage: X = simplicial_sets.Simplex(2).n_skeleton(1) - sage: Y = X.cone() - sage: Y.map_from_base() - Simplicial set morphism: - From: Simplicial set with 6 non-degenerate simplices - To: Cone of Simplicial set with 6 non-degenerate simplices - Defn: [(0,), (1,), (2,), (0, 1), (0, 2), (1, 2)] --> [(0,), (1,), (2,), (0, 1), (0, 2), (1, 2)] - """ - return self.base_as_subset().inclusion_map() - - -class ReducedConeOfSimplicialSet(QuotientOfSimplicialSet): - def __init__(self, base): - r""" - Return the reduced cone on a simplicial set. - - INPUT: - - - ``base`` -- return the cone on this simplicial set. - - Start with the unreduced cone: take ``base`` and add a point - `*` (which will become the base point) and for each simplex - `\sigma` in ``base``, add both `\sigma` and a simplex made up - of `*` and `\sigma` (topologically, form the join of `*` and - `\sigma`). - - Now reduce: take the quotient by the 1-simplex connecting the - old base point to the new one. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(1, name='e') - sage: X = SimplicialSet({e: (v, v)}) - sage: X = X.set_base_point(v) - sage: CX = X.cone() # indirect doctest - sage: CX.nondegenerate_simplices() - [*, e, (e,*)] - """ - C = ConeOfSimplicialSet(base) - for t in C.n_cells(1): - edge_faces = sorted([C.base_point(), base.base_point()]) - if sorted(C.faces(t)) == edge_faces: - edge = t - break - inc = C.subsimplicial_set([edge]).inclusion_map() - QuotientOfSimplicialSet.__init__(self, inc) - self._base = base - self._n_skeleton = (-1, Empty()) - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the simplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - In the case when the cone is infinite, the `n`-skeleton of the - cone is computed as the `n`-skeleton of the cone of the - `n`-skeleton. - - EXAMPLES:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: B.cone().n_skeleton(3).homology() - {0: 0, 1: 0, 2: 0, 3: Z} - """ - if self.is_finite(): - return SimplicialSet_finite.n_skeleton(self, n) - start, skel = self._n_skeleton - if start == n: - return skel - elif start > n: - return skel.n_skeleton(n) - ans = ReducedConeOfSimplicialSet_finite(self._base.n_skeleton(n)).n_skeleton(n) - self._n_skeleton = (n, ans) - return ans - - def _repr_(self): - """ - Print representation - - EXAMPLES:: - - sage: X = simplicial_sets.Sphere(4) - sage: X.cone() - Reduced cone of S^4 - """ - return 'Reduced cone of {}'.format(self._base) - - def _latex_(self): - """ - LaTeX representation - - EXAMPLES:: - - sage: latex(simplicial_sets.Sphere(4).cone()) - C S^{4} - """ - return 'C {}'.format(latex(self._base)) - - -class ReducedConeOfSimplicialSet_finite(ReducedConeOfSimplicialSet, - QuotientOfSimplicialSet_finite): - def __init__(self, base): - r""" - Return the reduced cone on a simplicial set. - - INPUT: - - - ``base`` -- return the cone on this simplicial set. - - Start with the unreduced cone: take ``base`` and add a point - `*` (which will become the base point) and for each simplex - `\sigma` in ``base``, add both `\sigma` and a simplex made up - of `*` and `\sigma` (topologically, form the join of `*` and - `\sigma`). - - Now reduce: take the quotient by the 1-simplex connecting the - old base point to the new one. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: e = AbstractSimplex(1, name='e') - sage: X = SimplicialSet({e: (v, v)}) - sage: X = X.set_base_point(v) - sage: CX = X.cone() # indirect doctest - sage: CX.nondegenerate_simplices() - [*, e, (e,*)] - """ - C = ConeOfSimplicialSet_finite(base) - edge_faces = sorted([C.base_point(), base.base_point()]) - for t in C.n_cells(1): - if sorted(C.faces(t)) == edge_faces: - edge = t - break - inc = C.subsimplicial_set([edge]).inclusion_map() - QuotientOfSimplicialSet_finite.__init__(self, inc) - self._base = base - q = self.quotient_map() - self._joins = {sigma:q(C._joins[sigma]) for sigma in C._joins} - - def map_from_base(self): - r""" - If this is the cone `\tilde{C}X` on `X`, return the map from `X`. - - The map is defined to be the composite `X \to CX \to - \tilde{C}X`. This is used by the - :class:`SuspensionOfSimplicialSet_finite` class to construct - the reduced suspension: take the quotient of the reduced cone - by the image of `X` therein. - - EXAMPLES:: - - sage: S3 = simplicial_sets.Sphere(3) - sage: CS3 = S3.cone() - sage: CS3.map_from_base() - Simplicial set morphism: - From: S^3 - To: Reduced cone of S^3 - Defn: [v_0, sigma_3] --> [*, sigma_3] - """ - quotient_map = self.quotient_map() - unreduced = quotient_map.domain() - temp_map = unreduced.map_from_base() - X = self._base - incl = X.Hom(unreduced)(temp_map._dictionary) - return quotient_map * incl - - -class SuspensionOfSimplicialSet(SimplicialSet_arbitrary, UniqueRepresentation): - def __init__(self, base): - r""" - Return the (reduced) suspension of a simplicial set. - - INPUT: - - - ``base`` -- return the suspension of this simplicial set. - - If this simplicial set ``X=base`` is not pointed, or if it is - itself an unreduced suspension, return the unreduced - suspension: the quotient `CX/X`, where `CX` is the (ordinary, - unreduced) cone on `X`. If `X` is pointed, then use the - reduced cone instead, and so return the reduced suspension. - - We use `S` to denote unreduced suspension, `\Sigma` for - reduced suspension. - - EXAMPLES:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: B.suspension() - Sigma(Classifying space of Multiplicative Abelian group isomorphic to C2) - sage: B.suspension().n_skeleton(3).homology() - {0: 0, 1: 0, 2: C2, 3: 0} - - If ``X`` is finite, the suspension comes with a quotient map - from the cone:: - - sage: S3 = simplicial_sets.Sphere(3) - sage: S4 = S3.suspension() - sage: S4.quotient_map() - Simplicial set morphism: - From: Reduced cone of S^3 - To: Sigma(S^3) - Defn: [*, sigma_3, (sigma_3,*)] --> [*, s_2 s_1 s_0 *, (sigma_3,*)] - - TESTS:: - - sage: S3.suspension() == S3.suspension() - True - sage: S3.suspension() == simplicial_sets.Sphere(3).suspension() - False - sage: B.suspension() == B.suspension() - True - """ - Cat = SimplicialSets() - if base.is_finite(): - Cat = Cat.Finite() - reduced = (base.is_pointed() - and (not hasattr(base, '_reduced') - or (hasattr(base, '_reduced') and base._reduced))) - if reduced: - Cat = Cat.Pointed() - Parent.__init__(self, category=Cat) - self._reduced = reduced - self._base = base - self._n_skeleton = (-1, Empty()) - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the simplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - In the case when the suspension is infinite, the `n`-skeleton - of the suspension is computed as the `n`-skeleton of the - suspension of the `n`-skeleton. - - EXAMPLES:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: SigmaB = B.suspension() - sage: SigmaB.n_skeleton(4).homology(base_ring=GF(2)) - {0: Vector space of dimension 0 over Finite Field of size 2, - 1: Vector space of dimension 0 over Finite Field of size 2, - 2: Vector space of dimension 1 over Finite Field of size 2, - 3: Vector space of dimension 1 over Finite Field of size 2, - 4: Vector space of dimension 1 over Finite Field of size 2} - """ - if self.is_finite(): - return SimplicialSet_finite.n_skeleton(self, n) - start, skel = self._n_skeleton - if start == n: - return skel - elif start > n: - return skel.n_skeleton(n) - ans = SuspensionOfSimplicialSet_finite(self._base.n_skeleton(n)).n_skeleton(n) - self._n_skeleton = (n, ans) - return ans - - def __repr_or_latex__(self, output_type=None): - r""" - Print representation, for either :meth:`_repr_` or :meth:`_latex_`. - - INPUT: - - - ``output_type`` -- either ``"latex"`` for LaTeX output or - anything else for ``str`` output. - - We use `S` to denote unreduced suspension, `\Sigma` for - reduced suspension. - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: K = T.suspension(10) - sage: K.__repr_or_latex__() - 'Sigma^10(Torus)' - sage: K.__repr_or_latex__('latex') - '\\Sigma^{10}(S^{1} \\times S^{1})' - """ - latex_output = (output_type == 'latex') - base = self._base - if self._reduced: - # Reduced suspension. - if latex_output: - symbol = '\\Sigma' - else: - symbol = 'Sigma' - else: - # Unreduced suspension. - symbol = 'S' - idx = 1 - while isinstance(base, SuspensionOfSimplicialSet): - idx += 1 - base = base._base - if latex_output: - base = latex(base) - exp = '^{{{}}}' - else: - exp = '^{}' - if idx > 1: - return ('{}' + exp + '({})').format(symbol, idx, base) - else: - return ('{}({})').format(symbol, base) - - def _repr_(self): - r""" - Print representation - - We use `S` to denote unreduced suspension, `\Sigma` for - reduced suspension. - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: S2.suspension(3) - Sigma^3(S^2) - sage: K = simplicial_sets.Simplex(2) - sage: K.suspension(3) - S^3(2-simplex) - sage: K.suspension() - S(2-simplex) - """ - return self.__repr_or_latex__() - - def _latex_(self): - r""" - LaTeX representation - - We use `S` to denote unreduced suspension, `\Sigma` for - reduced suspension. - - EXAMPLES:: - - sage: S2 = simplicial_sets.Sphere(2) - sage: latex(S2.suspension(3)) - \Sigma^{3}(S^{2}) - sage: K = simplicial_sets.Simplex(2) - sage: latex(K.suspension(3)) - S^{3}(\Delta^{2}) - sage: latex(K.suspension()) - S(\Delta^{2}) - """ - return self.__repr_or_latex__('latex') - - -class SuspensionOfSimplicialSet_finite(SuspensionOfSimplicialSet, - QuotientOfSimplicialSet_finite): - """ - The (reduced) suspension of a finite simplicial set. - - See :class:`SuspensionOfSimplicialSet` for more information. - """ - def __init__(self, base): - r""" - INPUT: - - - ``base`` -- return the suspension of this finite simplicial set. - - See :class:`SuspensionOfSimplicialSet` for more information. - - EXAMPLES:: - - sage: X = simplicial_sets.Sphere(3) - sage: X.suspension(2) - Sigma^2(S^3) - sage: Y = X.unset_base_point() - sage: Y.suspension(2) - S^2(Simplicial set with 2 non-degenerate simplices) - """ - self._base = base - reduced = (base.is_pointed() - and (not hasattr(base, '_reduced') - or (hasattr(base, '_reduced') and base._reduced))) - if reduced: - C = ReducedConeOfSimplicialSet_finite(base) - subcomplex = C.map_from_base().image() - else: - C = ConeOfSimplicialSet_finite(base) - subcomplex = C.base_as_subset() - QuotientOfSimplicialSet_finite.__init__(self, subcomplex.inclusion_map()) - self._reduced = reduced - # self._suspensions: dictionary, each key is a simplex sigma - # in base, the corresponding value is the new simplex (sigma, *) - # in S(base). Another key is 'cone', and its value is the cone - # vertex in C(base). This is used to construct the suspension of a - # morphism. - q = self.quotient_map() - self._suspensions = {sigma: q(C._joins[sigma]) for sigma in C._joins} +from sage.misc.superseded import deprecated_function_alias +import sage.topology.simplicial_set_constructions + +for f in ['SubSimplicialSet', + 'PullbackOfSimplicialSets', + 'PullbackOfSimplicialSets_finite', + 'Factors', + 'ProductOfSimplicialSets', + 'ProductOfSimplicialSets_finite', + 'PushoutOfSimplicialSets', + 'PushoutOfSimplicialSets_finite', + 'QuotientOfSimplicialSet', + 'QuotientOfSimplicialSet_finite', + 'SmashProductOfSimplicialSets_finite', + 'WedgeOfSimplicialSets', + 'WedgeOfSimplicialSets_finite', + 'DisjointUnionOfSimplicialSets', + 'DisjointUnionOfSimplicialSets_finite', + 'ConeOfSimplicialSet', + 'ConeOfSimplicialSet_finite', + 'ReducedConeOfSimplicialSet', + 'ReducedConeOfSimplicialSet_finite', + 'SuspensionOfSimplicialSet', + 'SuspensionOfSimplicialSet_finite']: + exec('{} = deprecated_function_alias(31925, sage.topology.simplicial_set_constructions.{})'.format(f, f)) diff --git a/src/sage/homology/simplicial_set_examples.py b/src/sage/homology/simplicial_set_examples.py index 9374ba03cae..8a19cef5a86 100644 --- a/src/sage/homology/simplicial_set_examples.py +++ b/src/sage/homology/simplicial_set_examples.py @@ -1,793 +1,25 @@ # -*- coding: utf-8 -*- r""" -Examples of simplicial sets. +Examples of simplicial sets: deprecated -These are accessible via ``simplicial_sets.Sphere(3)``, -``simplicial_sets.Torus()``, etc. Type ``simplicial_sets.[TAB]`` to -see a complete list. - -AUTHORS: - -- John H. Palmieri (2016-07) +The current version is :mod:`sage.topology.simplicial_set_examples`. """ -#***************************************************************************** -# Copyright (C) 2016 John H. Palmieri -# -# Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# See the GNU General Public License for more details; the full text -# is available at: -# -# http://www.gnu.org/licenses/ -# -#***************************************************************************** - -import re -import os -from pyparsing import OneOrMore, nestedExpr - -from sage.env import SAGE_ENV -from sage.groups.abelian_gps.abelian_group import AbelianGroup -from sage.misc.cachefunc import cached_method, cached_function -from sage.misc.latex import latex -from sage.rings.infinity import Infinity -from sage.rings.integer import Integer -from sage.structure.parent import Parent - -from .delta_complex import delta_complexes -from .simplicial_set import AbstractSimplex, \ - SimplicialSet_arbitrary, SimplicialSet_finite - -import sage.homology.simplicial_complexes_catalog as simplicial_complexes - -from sage.misc.lazy_import import lazy_import -lazy_import('sage.categories.simplicial_sets', 'SimplicialSets') - -######################################################################## -# The nerve of a finite monoid, used in sage.categories.finite_monoid. - -class Nerve(SimplicialSet_arbitrary): - def __init__(self, monoid): - """ - The nerve of a multiplicative monoid. - - INPUT: - - - ``monoid`` -- a multiplicative monoid - - See - :meth:`sage.categories.finite_monoids.FiniteMonoids.ParentMethods.nerve` - for full documentation. - - EXAMPLES:: - - sage: M = FiniteMonoids().example() - sage: M - An example of a finite multiplicative monoid: the integers modulo 12 - sage: X = M.nerve() - sage: list(M) - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] - sage: X.n_cells(0) - [1] - sage: X.n_cells(1) - [0, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9] - """ - category = SimplicialSets().Pointed() - Parent.__init__(self, category=category) - self.rename("Nerve of {}".format(str(monoid))) - self.rename_latex("B{}".format(latex(monoid))) - - e = AbstractSimplex(0, name=str(monoid.one()), - latex_name=latex(monoid.one())) - self._basepoint = e - vertex = SimplicialSet_finite({e: None}, base_point=e) - # self._n_skeleton: cache the highest dimensional skeleton - # calculated so far for this simplicial set, along with its - # dimension. - self._n_skeleton = (0, vertex) - self._monoid = monoid - # self._simplex_data: a tuple whose elements are pairs (simplex, list - # of monoid elements). Omit the base point. - self._simplex_data = () - - def __eq__(self, other): - """ - Return ``True`` if ``self`` and ``other`` are equal. - - This checks that the underlying monoids and the underlying - base points are the same. Because the base points will be - different each time the nerve is constructed, different - instances will not be equal. - - EXAMPLES:: - - sage: C3 = groups.misc.MultiplicativeAbelian([3]) - sage: C3.nerve() == C3.nerve() - False - sage: BC3 = C3.nerve() - sage: BC3 == BC3 - True - """ - return (isinstance(other, Nerve) - and self._monoid == other._monoid - and self.base_point() == other.base_point()) - - def __ne__(self, other): - """ - Return the negation of `__eq__`. - - EXAMPLES:: - - sage: C3 = groups.misc.MultiplicativeAbelian([3]) - sage: G3 = groups.permutation.Cyclic(3) - sage: C3.nerve() != G3.nerve() - True - sage: C3.nerve() != C3.nerve() - True - """ - return not self == other - - @cached_method - def __hash__(self): - """ - The hash is formed from the monoid and the base point. - - EXAMPLES:: - - sage: G3 = groups.permutation.Cyclic(3) - sage: hash(G3.nerve()) # random - 17 - - Different instances yield different base points, hence different hashes:: - - sage: X = G3.nerve() - sage: Y = G3.nerve() - sage: X.base_point() != Y.base_point() - True - sage: hash(X) != hash(Y) - True - """ - return hash(self._monoid) ^ hash(self.base_point()) - - def n_skeleton(self, n): - """ - Return the `n`-skeleton of this simplicial set. - - That is, the simplicial set generated by all nondegenerate - simplices of dimension at most `n`. - - INPUT: - - - ``n`` -- the dimension - - EXAMPLES:: - - sage: K4 = groups.misc.MultiplicativeAbelian([2,2]) - sage: BK4 = simplicial_sets.ClassifyingSpace(K4) - sage: BK4.n_skeleton(3) - Simplicial set with 40 non-degenerate simplices - sage: BK4.n_cells(1) == BK4.n_skeleton(3).n_cells(1) - True - sage: BK4.n_cells(3) == BK4.n_skeleton(1).n_cells(3) - False - """ - from .simplicial_set_constructions import SubSimplicialSet - monoid = self._monoid - one = monoid.one() - # Build up chains of elements inductively, from dimension d-1 - # to dimension d. We start with the cached - # self._n_skeleton. If only the 0-skeleton has been - # constructed, we construct the 1-cells by hand. - start, skel = self._n_skeleton - if start == n: - return skel - elif start > n: - return skel.n_skeleton(n) - - # There is a single vertex. Name it after the identity - # element of the monoid. - e = skel.n_cells(0)[0] - # Build the dictionary simplices, to be used for - # constructing the simplicial set. - simplices = skel.face_data() - - # face_dict: dictionary of simplices: keys are - # composites of monoid elements (as tuples), values are - # the corresponding simplices. - face_dict = dict(self._simplex_data) - - if start == 0: - for g in monoid: - if g != one: - x = AbstractSimplex(1, name=str(g), latex_name=latex(g)) - simplices[x] = (e, e) - face_dict[(g,)] = x - start = 1 - - for d in range(start+1, n+1): - for g in monoid: - if g == one: - continue - new_faces = {} - for t in face_dict.keys(): - if len(t) != d-1: - continue - # chain: chain of group elements to multiply, - # as a tuple. - chain = t + (g,) - # bdries: the face maps applied to chain, in a - # format suitable for passing to the DeltaComplex - # constructor. - x = AbstractSimplex(d, - name=' * '.join(str(_) for _ in chain), - latex_name = ' * '.join(latex(_) for _ in chain)) - new_faces[chain] = x - - # Compute faces of x. - faces = [face_dict[chain[1:]]] - for i in range(d-1): - product = chain[i] * chain[i+1] - if product == one: - # Degenerate. - if d == 2: - face = e.apply_degeneracies(i) - else: - face = (face_dict[chain[:i] - + chain[i+2:]].apply_degeneracies(i)) - else: - # Non-degenerate. - face = (face_dict[chain[:i] - + (product,) + chain[i+2:]]) - faces.append(face) - faces.append(face_dict[chain[:-1]]) - simplices[x] = faces - face_dict.update(new_faces) - - K = SubSimplicialSet(simplices, self) - self._n_skeleton = (n, K) - self._simplex_data = face_dict.items() - return K - - -######################################################################## -# Catalog of examples. These are accessed via simplicial_set_catalog.py. - -def Sphere(n): - r""" - Return the `n`-sphere as a simplicial set. - - It is constructed with two non-degenerate simplices: a vertex - `v_0` (which is the base point) and an `n`-simplex `\sigma_n`. - - INPUT: - - - ``n`` -- integer - - EXAMPLES:: - - sage: S0 = simplicial_sets.Sphere(0) - sage: S0 - S^0 - sage: S0.nondegenerate_simplices() - [v_0, w_0] - sage: S0.is_pointed() - True - sage: simplicial_sets.Sphere(4) - S^4 - sage: latex(simplicial_sets.Sphere(4)) - S^{4} - sage: simplicial_sets.Sphere(4).nondegenerate_simplices() - [v_0, sigma_4] - """ - v_0 = AbstractSimplex(0, name='v_0') - if n == 0: - w_0 = AbstractSimplex(0, name='w_0') - return SimplicialSet_finite({v_0: None, w_0: None}, base_point=v_0, - name='S^0') - degens = range(n-2, -1, -1) - degen_v = v_0.apply_degeneracies(*degens) - sigma = AbstractSimplex(n, name='sigma_{}'.format(n), - latex_name='\\sigma_{}'.format(n)) - return SimplicialSet_finite({sigma: [degen_v] * (n+1)}, base_point=v_0, - name='S^{}'.format(n), - latex_name='S^{{{}}}'.format(n)) - - -def ClassifyingSpace(group): - r""" - Return the classifying space of ``group``, as a simplicial set. - - INPUT: - - - ``group`` -- a finite group or finite monoid - - See - :meth:`sage.categories.finite_monoids.FiniteMonoids.ParentMethods.nerve` - for more details and more examples. - - EXAMPLES:: - - sage: C2 = groups.misc.MultiplicativeAbelian([2]) - sage: BC2 = simplicial_sets.ClassifyingSpace(C2) - sage: H = BC2.homology(range(9), base_ring=GF(2)) - sage: [H[i].dimension() for i in range(9)] - [0, 1, 1, 1, 1, 1, 1, 1, 1] - - sage: Klein4 = groups.misc.MultiplicativeAbelian([2, 2]) - sage: BK = simplicial_sets.ClassifyingSpace(Klein4) - sage: BK - Classifying space of Multiplicative Abelian group isomorphic to C2 x C2 - sage: BK.homology(range(5), base_ring=GF(2)) # long time (1 second) - {0: Vector space of dimension 0 over Finite Field of size 2, - 1: Vector space of dimension 2 over Finite Field of size 2, - 2: Vector space of dimension 3 over Finite Field of size 2, - 3: Vector space of dimension 4 over Finite Field of size 2, - 4: Vector space of dimension 5 over Finite Field of size 2} - """ - X = group.nerve() - X.rename('Classifying space of {}'.format(group)) - return X - - -def RealProjectiveSpace(n): - r""" - Return real `n`-dimensional projective space, as a simplicial set. - - This is constructed as the `n`-skeleton of the nerve of the group - of order 2, and therefore has a single non-degenerate simplex in - each dimension up to `n`. - - EXAMPLES:: - - sage: simplicial_sets.RealProjectiveSpace(7) - RP^7 - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP5.homology() - {0: 0, 1: C2, 2: 0, 3: C2, 4: 0, 5: Z} - sage: RP5 - RP^5 - sage: latex(RP5) - RP^{5} - - sage: BC2 = simplicial_sets.RealProjectiveSpace(Infinity) - sage: latex(BC2) - RP^{\infty} - """ - if n == Infinity: - X = AbelianGroup([2]).nerve() - X.rename('RP^oo') - X.rename_latex('RP^{\\infty}') - else: - X = RealProjectiveSpace(Infinity).n_skeleton(n) - X.rename('RP^{}'.format(n)) - X.rename_latex('RP^{{{}}}'.format(n)) - return X - - -def KleinBottle(): - r""" - Return the Klein bottle as a simplicial set. - - This converts the `\Delta`-complex version to a simplicial set. It - has one 0-simplex, three 1-simplices, and two 2-simplices. - - EXAMPLES:: - - sage: K = simplicial_sets.KleinBottle() - sage: K.f_vector() - [1, 3, 2] - sage: K.homology(reduced=False) - {0: Z, 1: Z x C2, 2: 0} - sage: K - Klein bottle - """ - temp = SimplicialSet_finite(delta_complexes.KleinBottle()) - pt = temp.n_cells(0)[0] - return SimplicialSet_finite(temp.face_data(), base_point=pt, - name='Klein bottle') - - -def Torus(): - r""" - Return the torus as a simplicial set. - - This computes the product of the circle with itself, where the - circle is represented using a single 0-simplex and a single - 1-simplex. Thus it has one 0-simplex, three 1-simplices, and two - 2-simplices. - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: T.f_vector() - [1, 3, 2] - sage: T.homology(reduced=False) - {0: Z, 1: Z x Z, 2: Z} - """ - S1 = Sphere(1) - T = S1.product(S1) - T.rename('Torus') - return T - - -def Simplex(n): - r""" - Return the `n`-simplex as a simplicial set. - - EXAMPLES:: - - sage: K = simplicial_sets.Simplex(2) - sage: K - 2-simplex - sage: latex(K) - \Delta^{2} - sage: K.n_cells(0) - [(0,), (1,), (2,)] - sage: K.n_cells(1) - [(0, 1), (0, 2), (1, 2)] - sage: K.n_cells(2) - [(0, 1, 2)] - """ - return SimplicialSet_finite(simplicial_complexes.Simplex(n), - name='{}-simplex'.format(n), - latex_name='\\Delta^{{{}}}'.format(n)) - - -@cached_function -def Empty(): - """ - Return the empty simplicial set. - - This should return the same simplicial set each time it is called. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set_examples import Empty - sage: E = Empty() - sage: E - Empty simplicial set - sage: E.nondegenerate_simplices() - [] - sage: E is Empty() - True - """ - return SimplicialSet_finite({}, name='Empty simplicial set') - - -@cached_function -def Point(): - """ - Return a single point called "*" as a simplicial set. - - This should return the same simplicial set each time it is called. - - EXAMPLES:: - - sage: P = simplicial_sets.Point() - sage: P.is_pointed() - True - sage: P.nondegenerate_simplices() - [*] - - sage: Q = simplicial_sets.Point() - sage: P is Q - True - sage: P == Q - True - """ - star = AbstractSimplex(0, name='*') - return SimplicialSet_finite({star: None}, base_point=star, - name='Point', - latex_name='*') - - -def Horn(n, k): - r""" - Return the horn $\Lambda^n_k$. - - This is the subsimplicial set of the $n$-simplex obtained by - removing its $k$-th face. - - EXAMPLES:: - - sage: L = simplicial_sets.Horn(3, 0) - sage: L - (3, 0)-Horn - sage: L.n_cells(3) - [] - sage: L.n_cells(2) - [(0, 1, 2), (0, 1, 3), (0, 2, 3)] - - sage: L20 = simplicial_sets.Horn(2, 0) - sage: latex(L20) - \Lambda^{2}_{0} - sage: L20.inclusion_map() - Simplicial set morphism: - From: (2, 0)-Horn - To: 2-simplex - Defn: [(0,), (1,), (2,), (0, 1), (0, 2)] --> [(0,), (1,), (2,), (0, 1), (0, 2)] - """ - K = Simplex(n) - sigma = K.n_cells(n)[0] - L = K.subsimplicial_set(K.faces(sigma)[:k] + K.faces(sigma)[k+1:]) - L.rename('({}, {})-Horn'.format(n, k)) - L.rename_latex('\\Lambda^{{{}}}_{{{}}}'.format(n, k)) - return L - - -def ComplexProjectiveSpace(n): - r""" - Return complex `n`-dimensional projective space, as a simplicial set. - - This is only defined when `n` is at most 4. It is constructed - using the simplicial set decomposition provided by Kenzo, as - described by Sergeraert [Ser2010]_ - - EXAMPLES:: - - sage: simplicial_sets.ComplexProjectiveSpace(2).homology(reduced=False) - {0: Z, 1: 0, 2: Z, 3: 0, 4: Z} - sage: CP3 = simplicial_sets.ComplexProjectiveSpace(3) - sage: CP3 - CP^3 - sage: latex(CP3) - CP^{3} - sage: CP3.f_vector() - [1, 0, 3, 10, 25, 30, 15] - - sage: K = CP3.suspension() # long time (1 second) - sage: R = K.cohomology_ring(GF(2)) # long time - sage: R.gens() # long time - (h^{0,0}, h^{3,0}, h^{5,0}, h^{7,0}) - sage: x = R.gens()[1] # long time - sage: x.Sq(2) # long time - h^{5,0} - - sage: simplicial_sets.ComplexProjectiveSpace(4).f_vector() - [1, 0, 4, 22, 97, 255, 390, 315, 105] - - sage: simplicial_sets.ComplexProjectiveSpace(5) - Traceback (most recent call last): - ... - ValueError: complex projective spaces are only available in dimensions between 0 and 4 - """ - if n < 0 or n > 4: - raise ValueError('complex projective spaces are only available in dimensions between 0 and 4') - if n == 0: - return Point() - if n == 1: - return Sphere(2) - if n == 2: - # v: Kenzo name <> - v = AbstractSimplex(0, name='v') - # f_2_i: Kenzo name <<- NIL>>> for i=1,2 - f2_1 = AbstractSimplex(2, name='rho_0') - f2_2 = AbstractSimplex(2, name='rho_1') - # f3_110: Kenzo name <<0 NIL><- NIL>>> - # f3_011: Kenzo name <<- (1)><- NIL>>> - # f3_111: Kenzo name <<- (1)><- NIL>>> - f3_110 = AbstractSimplex(3, name='sigma_0', latex_name='\\sigma_0') - f3_011 = AbstractSimplex(3, name='sigma_1', latex_name='\\sigma_1') - f3_111 = AbstractSimplex(3, name='sigma_2', latex_name='\\sigma_2') - # f4_101101: Kenzo name <<1-0 NIL><- (1)><- NIL>>> - # f4_201110: Kenzo name <<1 (1)><0 NIL><- NIL>>> - # f4_211010: Kenzo name <<0 (1)><0 NIL><- NIL>>> - f4_101101 = AbstractSimplex(4, name='tau_0', latex_name='\\tau_0') - f4_201110 = AbstractSimplex(4, name='tau_1', latex_name='\\tau_1') - f4_211010 = AbstractSimplex(4, name='tau_2', latex_name='\\tau_2') - K = SimplicialSet_finite({f2_1: (v.apply_degeneracies(0), - v.apply_degeneracies(0), - v.apply_degeneracies(0)), - f2_2: (v.apply_degeneracies(0), - v.apply_degeneracies(0), - v.apply_degeneracies(0)), - f3_110: (f2_1, f2_2, f2_1, v.apply_degeneracies(1, 0)), - f3_011: (f2_1, f2_1, f2_1, f2_1), - f3_111: (v.apply_degeneracies(1, 0), f2_1, f2_2, f2_1), - f4_101101: (f2_1.apply_degeneracies(0), - f2_1.apply_degeneracies(0), - f3_011, - f2_1.apply_degeneracies(2), - f2_1.apply_degeneracies(2)), - f4_201110: (f2_1.apply_degeneracies(1), - f3_111, - f3_011, - f3_110, - f2_1.apply_degeneracies(1)), - f4_211010: (f2_1.apply_degeneracies(2), - f3_111, - f2_1.apply_degeneracies(1), - f3_110, - f2_1.apply_degeneracies(0))}, - base_point=v, name='CP^2', - latex_name='CP^{2}') - return K - if n == 3: - file = os.path.join(SAGE_ENV['SAGE_EXTCODE'], 'kenzo', 'CP3.txt') - data = simplicial_data_from_kenzo_output(file) - v = [_ for _ in data.keys() if _.dimension() == 0][0] - K = SimplicialSet_finite(data, base_point=v, name='CP^3', - latex_name='CP^{3}') - return K - if n == 4: - file = os.path.join(SAGE_ENV['SAGE_EXTCODE'], 'kenzo', 'CP4.txt') - data = simplicial_data_from_kenzo_output(file) - v = [_ for _ in data.keys() if _.dimension() == 0][0] - K = SimplicialSet_finite(data, base_point=v, name='CP^4', - latex_name='CP^{4}') - return K - - -def simplicial_data_from_kenzo_output(filename): - """ - Return data to construct a simplicial set, given Kenzo output. - - INPUT: - - - ``filename`` -- name of file containing the output from Kenzo's - :func:`show-structure` function - - OUTPUT: data to construct a simplicial set from the Kenzo output - - Several files with Kenzo output are in the directory - :file:`SAGE_EXTCODE/kenzo/`. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set_examples import simplicial_data_from_kenzo_output - sage: from sage.homology.simplicial_set import SimplicialSet - sage: sphere = os.path.join(SAGE_ENV['SAGE_EXTCODE'], 'kenzo', 'S4.txt') - sage: S4 = SimplicialSet(simplicial_data_from_kenzo_output(sphere)) - sage: S4.homology(reduced=False) - {0: Z, 1: 0, 2: 0, 3: 0, 4: Z} - """ - with open(filename, 'r') as f: - data = f.read() - dim = 0 - start = 0 - # simplex_data: data for constructing the simplicial set. - simplex_data = {} - # simplex_names: simplices indexed by their names - simplex_names = {} - dim_idx = data.find('Dimension = {}:'.format(dim), start) - while dim_idx != -1: - start = dim_idx + len('Dimension = {}:'.format(dim)) - new_dim_idx = data.find('Dimension = {}:'.format(dim+1), start) - if new_dim_idx == -1: - end = len(data) - else: - end = new_dim_idx - if dim == 0: - simplex_string = data[data.find('Vertices :') + len('Vertices :'):end] - vertices = OneOrMore(nestedExpr()).parseString(simplex_string).asList()[0] - for v in vertices: - vertex = AbstractSimplex(0, name=v) - simplex_data[vertex] = None - simplex_names[v] = vertex - else: - simplex_string = data[start:end].strip() - - for s in [_.strip() for _ in simplex_string.split('Simplex : ')]: - if s: - name, face_str = [_.strip() for _ in s.split('Faces : ')] - face_str = face_str.strip('()') - face_str = face_str.split('', possibly with a trailing space. - # DEGENS is a hyphen-separated list, like - # '3-2-1-0' or '0' or '-'. - m = re.match('[-[0-9]+', f) - degen_str = m.group(0) - if degen_str.find('-') != -1: - if degen_str == '-': - degens = [] - else: - degens = [Integer(_) - for _ in degen_str.split('-')] - else: - degens = [Integer(degen_str)] - - face_name = f[m.end(0):].strip()[:-1] - nondegen = simplex_names[face_name] - faces.append(nondegen.apply_degeneracies(*degens)) - - simplex = AbstractSimplex(dim, name=name) - simplex_data[simplex] = faces - simplex_names[name] = simplex - dim += 1 - dim_idx = new_dim_idx - return simplex_data - - -def HopfMap(): - r""" - Return a simplicial model of the Hopf map `S^3 \to S^2` - - This is taken from Exemple II.1.19 in the thesis of Clemens Berger - [Ber1991]_. - - The Hopf map is a fibration `S^3 \to S^2`. If it is viewed as - attaching a 4-cell to the 2-sphere, the resulting adjunction space - is 2-dimensional complex projective space. The resulting model is - a bit larger than the one obtained from - ``simplicial_sets.ComplexProjectiveSpace(2)``. - - EXAMPLES:: - - sage: g = simplicial_sets.HopfMap() - sage: g.domain() - Simplicial set with 20 non-degenerate simplices - sage: g.codomain() - S^2 - - Using the Hopf map to attach a cell:: - - sage: X = g.mapping_cone() - sage: CP2 = simplicial_sets.ComplexProjectiveSpace(2) - sage: X.homology() == CP2.homology() - True - - sage: X.f_vector() - [1, 0, 5, 9, 6] - sage: CP2.f_vector() - [1, 0, 2, 3, 3] - """ - # The 2-sphere and its simplices. - S2 = Sphere(2) - sigma = S2.n_cells(2)[0] - s0_sigma = sigma.apply_degeneracies(0) - s1_sigma = sigma.apply_degeneracies(1) - s2_sigma = sigma.apply_degeneracies(2) - # The 3-sphere and its simplices. - w_0 = AbstractSimplex(0, name='w') - w_1 = w_0.apply_degeneracies(0) - w_2 = w_0.apply_degeneracies(0, 0) - beta_11 = AbstractSimplex(1, name='beta_11', latex_name='\\beta_{11}') - beta_22 = AbstractSimplex(1, name='beta_22', latex_name='\\beta_{22}') - beta_23 = AbstractSimplex(1, name='beta_23', latex_name='\\beta_{23}') - beta_44 = AbstractSimplex(1, name='beta_44', latex_name='\\beta_{44}') - beta_1 = AbstractSimplex(2, name='beta_1', latex_name='\\beta_1') - beta_2 = AbstractSimplex(2, name='beta_2', latex_name='\\beta_2') - beta_3 = AbstractSimplex(2, name='beta_3', latex_name='\\beta_3') - beta_4 = AbstractSimplex(2, name='beta_4', latex_name='\\beta_4') - alpha_12 = AbstractSimplex(2, name='alpha_12', latex_name='\\alpha_{12}') - alpha_23 = AbstractSimplex(2, name='alpha_23', latex_name='\\alpha_{23}') - alpha_34 = AbstractSimplex(2, name='alpha_34', latex_name='\\alpha_{34}') - alpha_45 = AbstractSimplex(2, name='alpha_45', latex_name='\\alpha_{45}') - alpha_56 = AbstractSimplex(2, name='alpha_56', latex_name='\\alpha_{56}') - alpha_1 = AbstractSimplex(3, name='alpha_1', latex_name='\\alpha_1') - alpha_2 = AbstractSimplex(3, name='alpha_2', latex_name='\\alpha_2') - alpha_3 = AbstractSimplex(3, name='alpha_3', latex_name='\\alpha_3') - alpha_4 = AbstractSimplex(3, name='alpha_4', latex_name='\\alpha_4') - alpha_5 = AbstractSimplex(3, name='alpha_5', latex_name='\\alpha_5') - alpha_6 = AbstractSimplex(3, name='alpha_6', latex_name='\\alpha_6') - S3 = SimplicialSet_finite({beta_11: (w_0, w_0), beta_22: (w_0, w_0), - beta_23: (w_0, w_0), beta_44: (w_0, w_0), - beta_1: (w_1, beta_11, w_1), - beta_2: (w_1, beta_22, beta_23), - beta_3: (w_1, beta_23, w_1), - beta_4: (w_1, beta_44, w_1), - alpha_12: (beta_11, beta_23, w_1), - alpha_23: (beta_11, beta_22, w_1), - alpha_34: (beta_11, beta_22, beta_44), - alpha_45: (w_1, beta_23, beta_44), - alpha_56: (w_1, beta_23, w_1), - alpha_1: (beta_1, beta_3, alpha_12, w_2), - alpha_2: (beta_11.apply_degeneracies(1), beta_2, - alpha_23, alpha_12), - alpha_3: (beta_11.apply_degeneracies(0), alpha_34, - alpha_23, beta_4), - alpha_4: (beta_1, beta_2, alpha_34, alpha_45), - alpha_5: (w_2, alpha_45, alpha_56, beta_4), - alpha_6: (w_2, beta_3, alpha_56, w_2)}, - base_point=w_0) - return S3.Hom(S2)({alpha_1:s0_sigma, alpha_2:s1_sigma, - alpha_3:s2_sigma, alpha_4:s0_sigma, - alpha_5:s2_sigma, alpha_6:s1_sigma}) +from sage.misc.superseded import deprecated_function_alias +import sage.topology.simplicial_set_examples + +for f in ['Nerve', + 'Sphere', + 'ClassifyingSpace', + 'RealProjectiveSpace', + 'KleinBottle', + 'Torus', + 'Simplex', + 'Empty', + 'Point', + 'Horn', + 'ComplexProjectiveSpace', + 'simplicial_data_from_kenzo_output', + 'HopfMap']: + exec('{} = deprecated_function_alias(31925, sage.topology.simplicial_set_examples.{})'.format(f, f)) + diff --git a/src/sage/homology/simplicial_set_morphism.py b/src/sage/homology/simplicial_set_morphism.py index 7e2c5046ef9..9df5cb9a40d 100644 --- a/src/sage/homology/simplicial_set_morphism.py +++ b/src/sage/homology/simplicial_set_morphism.py @@ -1,1449 +1,12 @@ r""" -Morphisms and homsets for simplicial sets +Morphisms and homsets for simplicial sets: deprecated -.. NOTE:: - - Morphisms with infinite domain are not implemented in general: - only constant maps and identity maps are currently implemented. - -AUTHORS: - -- John H. Palmieri (2016-07) - -This module implements morphisms and homsets of simplicial sets. +The current version is :mod:`sage.topology.simplicial_set_morphism`. """ +from sage.misc.superseded import deprecated_function_alias +import sage.topology.simplicial_set_morphism -#***************************************************************************** -# Copyright (C) 2016 John H. Palmieri -# -# Distributed under the terms of the GNU General Public License (GPL) -# http://www.gnu.org/licenses/ -# -# This code is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# See the GNU General Public License for more details; the full text -# is available at: -# -# http://www.gnu.org/licenses/ -# -#***************************************************************************** - -import itertools - -from sage.categories.homset import Hom, Homset -from sage.categories.morphism import Morphism -from sage.categories.simplicial_sets import SimplicialSets -from sage.matrix.constructor import matrix, zero_matrix -from sage.misc.latex import latex -from sage.rings.integer_ring import ZZ - -from .chain_complex_morphism import ChainComplexMorphism -from .homology_morphism import InducedHomologyMorphism -from .simplicial_set import SimplicialSet_arbitrary - -class SimplicialSetHomset(Homset): - r""" - A set of morphisms between simplicial sets. - - Once a homset has been constructed in Sage, typically via - ``Hom(X,Y)`` or ``X.Hom(Y)``, one can use it to construct a - morphism `f` by specifying a dictionary, the keys of which are the - nondegenerate simplices in the domain, and the value corresponding - to `\sigma` is the simplex `f(\sigma)` in the codomain. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: f = AbstractSimplex(1, name='f') - sage: X = SimplicialSet({e: (v, w), f: (w, v)}) - sage: Y = SimplicialSet({e: (v, v)}) - - Define the homset:: - - sage: H = Hom(X, Y) - - Now define a morphism by specifying a dictionary:: - - sage: H({v: v, w: v, e: e, f: e}) - Simplicial set morphism: - From: Simplicial set with 4 non-degenerate simplices - To: Simplicial set with 2 non-degenerate simplices - Defn: [v, w, e, f] --> [v, v, e, e] - """ - def __call__(self, f, check=True): - r""" - INPUT: - - - ``f`` -- a dictionary with keys the simplices of the domain - and values simplices of the codomain - - - ``check`` -- optional, default ``True``. Pass this to the - morphism constructor. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: v0 = S1.n_cells(0)[0] - sage: e = S1.n_cells(1)[0] - sage: f = {v0: v0, e: v0.apply_degeneracies(0)} # constant map - sage: Hom(S1, S1)(f) - Simplicial set endomorphism of S^1 - Defn: Constant map at v_0 - """ - return SimplicialSetMorphism(f, self.domain(), self.codomain(), check=check) - - def diagonal_morphism(self): - r""" - Return the diagonal morphism in `\operatorname{Hom}(S, S \times S)`. - - EXAMPLES:: - - sage: RP2 = simplicial_sets.RealProjectiveSpace(2) - sage: Hom(RP2, RP2.product(RP2)).diagonal_morphism() - Simplicial set morphism: - From: RP^2 - To: RP^2 x RP^2 - Defn: [1, f, f * f] --> [(1, 1), (f, f), (f * f, f * f)] - """ - domain = self.domain() - codomain = self.codomain() - if not hasattr(codomain, 'factors'): - raise ValueError('diagonal morphism is only defined for Hom(X, XxX)') - factors = codomain.factors() - if len(factors) != 2 or factors[0] != domain or factors[1] != domain: - raise ValueError('diagonal morphism is only defined for Hom(X, XxX)') - f = {} - for i in range(domain.dimension()+1): - for s in domain.n_cells(i): - f[s] = dict(codomain._translation)[((s, ()), (s, ()))] - return self(f) - - def identity(self): - r""" - Return the identity morphism in `\operatorname{Hom}(S, S)`. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: Hom(S1, S1).identity() - Simplicial set endomorphism of S^1 - Defn: Identity map - sage: T = simplicial_sets.Torus() - sage: Hom(S1, T).identity() - Traceback (most recent call last): - ... - TypeError: identity map is only defined for endomorphism sets - """ - return SimplicialSetMorphism(domain=self.domain(), - codomain=self.codomain(), - identity=True) - - def constant_map(self, point=None): - r""" - Return the constant map in this homset. - - INPUT: - - - ``point`` -- optional, default ``None``. If specified, it - must be a 0-simplex in the codomain, and it will be the - target of the constant map. - - If ``point`` is specified, it is the target of the constant - map. Otherwise, if the codomain is pointed, the target is its - base point. If the codomain is not pointed and ``point`` is - not specified, raise an error. - - EXAMPLES:: - - sage: S3 = simplicial_sets.Sphere(3) - sage: T = simplicial_sets.Torus() - sage: T.n_cells(0)[0].rename('w') - sage: Hom(S3,T).constant_map() - Simplicial set morphism: - From: S^3 - To: Torus - Defn: Constant map at w - - sage: S0 = simplicial_sets.Sphere(0) - sage: v, w = S0.n_cells(0) - sage: Hom(S3, S0).constant_map(v) - Simplicial set morphism: - From: S^3 - To: S^0 - Defn: Constant map at v_0 - sage: Hom(S3, S0).constant_map(w) - Simplicial set morphism: - From: S^3 - To: S^0 - Defn: Constant map at w_0 - - This constant map is not pointed, since it doesn't send the - base point of `S^3` to the base point of `S^0`:: - - sage: Hom(S3, S0).constant_map(w).is_pointed() - False - - TESTS:: - - sage: S0 = S0.unset_base_point() - sage: Hom(S3, S0).constant_map() - Traceback (most recent call last): - ... - ValueError: codomain is not pointed, so specify a target for the constant map - """ - codomain = self.codomain() - if point is None: - if codomain.is_pointed(): - point = codomain.base_point() - else: - raise ValueError('codomain is not pointed, so specify a ' - 'target for the constant map') - return SimplicialSetMorphism(domain=self.domain(), - codomain=self.codomain(), - constant=point) - - def an_element(self): - """ - Return an element of this homset: a constant map. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: S2 = simplicial_sets.Sphere(2) - sage: Hom(S2, S1).an_element() - Simplicial set morphism: - From: S^2 - To: S^1 - Defn: Constant map at v_0 - - sage: K = simplicial_sets.Simplex(3) - sage: L = simplicial_sets.Simplex(4) - sage: d = {K.n_cells(3)[0]: L.n_cells(0)[0].apply_degeneracies(2, 1, 0)} - sage: Hom(K,L)(d) == Hom(K,L).an_element() - True - """ - codomain = self.codomain() - if codomain.is_pointed(): - target = codomain.base_point() - else: - target = codomain.n_cells(0)[0] - return self.constant_map(target) - - def __iter__(self): - """ - Iterate through all morphisms in this homset. - - This is very slow: it tries all possible targets for the - maximal nondegenerate simplices and yields those which are - valid morphisms of simplicial sets. ("Maximal" means - nondegenerate simplices which are not the faces of other - nondegenerate simplices.) So if either the domain or the - codomain has many simplices, the number of possibilities may - be quite large. - - This is only implemented when the domain is finite. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = simplicial_sets.Torus() - sage: H = Hom(S1, T) - sage: list(H) - [Simplicial set morphism: - From: S^1 - To: Torus - Defn: [v_0, sigma_1] --> [(v_0, v_0), (s_0 v_0, sigma_1)], - Simplicial set morphism: - From: S^1 - To: Torus - Defn: [v_0, sigma_1] --> [(v_0, v_0), (sigma_1, s_0 v_0)], - Simplicial set morphism: - From: S^1 - To: Torus - Defn: [v_0, sigma_1] --> [(v_0, v_0), (sigma_1, sigma_1)], - Simplicial set morphism: - From: S^1 - To: Torus - Defn: Constant map at (v_0, v_0)] - sage: [f.induced_homology_morphism().to_matrix() for f in H] - [ - [ 1| 0] [1|0] [1|0] [1|0] - [--+--] [-+-] [-+-] [-+-] - [ 0|-1] [0|1] [0|0] [0|0] - [ 0| 1] [0|0] [0|1] [0|0] - [--+--] [-+-] [-+-] [-+-] - [ 0| 0], [0|0], [0|0], [0|0] - ] - """ - if not self.domain().is_finite(): - raise NotImplementedError('domain must be finite to iterate ' - 'through all morphisms') - codomain = self.codomain() - facets = self.domain()._facets_() - dims = [f.dimension() for f in facets] - # Record all of the n-simplices in the codomain once for each - # relevant dimension. - all_n_simplices = {d: codomain.all_n_simplices(d) for d in set(dims)} - for target in itertools.product(*[all_n_simplices[d] for d in dims]): - try: - yield self({sigma: tau for (sigma, tau) in zip(facets, target)}) - except ValueError: - # Not a valid morphism. - pass - - def _latex_(self): - r""" - LaTeX representation - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = simplicial_sets.Torus() - sage: H = Hom(S1, T) - sage: latex(H) - \operatorname{Map} (S^{1}, S^{1} \times S^{1}) - """ - return '\\operatorname{{Map}} ({}, {})'.format(latex(self.domain()), latex(self.codomain())) - - -class SimplicialSetMorphism(Morphism): - def __init__(self, data=None, domain=None, codomain=None, - constant=None, identity=False, check=True): - r""" - Return a morphism of simplicial sets. - - INPUT: - - - ``data`` -- optional. Dictionary defining the map. - - ``domain`` -- simplicial set - - ``codomain`` -- simplicial set - - ``constant`` -- optional: if not ``None``, then this should - be a vertex in the codomain, in which case return the - constant map with this vertex as the target. - - ``identity`` -- optional: if ``True``, return the identity - morphism. - - ``check`` -- optional, default ``True``. If ``True``, check - that this is actually a morphism: it commutes with the face - maps. - - So to define a map, you must specify ``domain`` and - ``codomain``. If the map is constant, specify the target (a - vertex in the codomain) as ``constant``. If the map is the - identity map, specify ``identity=True``. Otherwise, pass a - dictionary, ``data``. The keys of the dictionary are the - nondegenerate simplices of the domain, the corresponding - values are simplices in the codomain. - - In fact, the keys in ``data`` do not need to include all of - the nondegenerate simplices, only those which are not faces of - other nondegenerate simplices: if `\sigma` is a face of - `\tau`, then the image of `\sigma` need not be specified. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set_morphism import SimplicialSetMorphism - sage: K = simplicial_sets.Simplex(1) - sage: S1 = simplicial_sets.Sphere(1) - sage: v0 = K.n_cells(0)[0] - sage: v1 = K.n_cells(0)[1] - sage: e01 = K.n_cells(1)[0] - sage: w = S1.n_cells(0)[0] - sage: sigma = S1.n_cells(1)[0] - - sage: f = {v0: w, v1: w, e01: sigma} - sage: SimplicialSetMorphism(f, K, S1) - Simplicial set morphism: - From: 1-simplex - To: S^1 - Defn: [(0,), (1,), (0, 1)] --> [v_0, v_0, sigma_1] - - The same map can be defined as follows:: - - sage: H = Hom(K, S1) - sage: H(f) - Simplicial set morphism: - From: 1-simplex - To: S^1 - Defn: [(0,), (1,), (0, 1)] --> [v_0, v_0, sigma_1] - - Also, this map can be defined by specifying where the - 1-simplex goes; the vertices then go where they have to, to - satisfy the condition `d_i \circ f = f \circ d_i`:: - - sage: H = Hom(K, S1) - sage: H({e01: sigma}) - Simplicial set morphism: - From: 1-simplex - To: S^1 - Defn: [(0,), (1,), (0, 1)] --> [v_0, v_0, sigma_1] - - A constant map:: - - sage: g = {e01: w.apply_degeneracies(0)} - sage: SimplicialSetMorphism(g, K, S1) - Simplicial set morphism: - From: 1-simplex - To: S^1 - Defn: Constant map at v_0 - - The same constant map:: - - sage: SimplicialSetMorphism(domain=K, codomain=S1, constant=w) - Simplicial set morphism: - From: 1-simplex - To: S^1 - Defn: Constant map at v_0 - - An identity map:: - - sage: SimplicialSetMorphism(domain=K, codomain=K, identity=True) - Simplicial set endomorphism of 1-simplex - Defn: Identity map - - Defining a map by specifying it on only some of the simplices - in the domain:: - - sage: S5 = simplicial_sets.Sphere(5) - sage: s = S5.n_cells(5)[0] - sage: one = S5.Hom(S5)({s: s}) - sage: one - Simplicial set endomorphism of S^5 - Defn: Identity map - - TESTS: - - A non-map:: - - sage: h = {w: v0, sigma: e01} - sage: SimplicialSetMorphism(h, S1, K) - Traceback (most recent call last): - ... - ValueError: the dictionary does not define a map of simplicial sets - - Another non-map:: - - sage: h = {w: v0, v0: w, sigma: e01} - sage: SimplicialSetMorphism(h, S1, K) - Traceback (most recent call last): - ... - ValueError: at least one simplex in the defining dictionary is not in the domain - - A non-identity map:: - - sage: SimplicialSetMorphism(domain=K, codomain=S1, identity=True) - Traceback (most recent call last): - ... - TypeError: identity map is only defined for endomorphism sets - - An improperly partially defined map:: - - sage: h = {w: v0} - sage: SimplicialSetMorphism(h, S1, K) - Traceback (most recent call last): - ... - ValueError: the image of at least one simplex in the domain is not defined - """ - self._is_identity = False - if not domain.is_finite(): - if identity: - if codomain is None: - codomain = domain - elif domain is not codomain: - raise TypeError("identity map is only defined for endomorphism sets") - self._is_identity = True - Morphism.__init__(self, Hom(domain, codomain, SimplicialSets())) - return - if constant is not None: - # If self._constant is set, it should be a vertex in - # the codomain, the target of the constant map. - self._constant = constant - Morphism.__init__(self, Hom(domain, codomain, SimplicialSets())) - return - raise NotImplementedError('morphisms with infinite domain ' - 'are not implemented in general') - else: - if identity: - self._is_identity = True - check = False - if domain is not codomain: - raise TypeError("identity map is only defined for endomorphism sets") - data = {} - for i in range(domain.dimension() + 1): - for s in domain.n_cells(i): - data[s] = s - if constant is not None: - self._constant = constant - check = False - data = {sigma: constant.apply_degeneracies(*range(sigma.dimension()-1,-1,-1)) - for sigma in domain.nondegenerate_simplices()} - - if (not isinstance(domain, SimplicialSet_arbitrary) - or not isinstance(codomain, SimplicialSet_arbitrary)): - raise TypeError('the domain and codomain must be simplicial sets') - if any(x.nondegenerate() not in - domain.nondegenerate_simplices() for x in data.keys()): - raise ValueError('at least one simplex in the defining ' - 'dictionary is not in the domain') - # Remove degenerate simplices from the domain specification. - d = {sigma:data[sigma] for sigma in data if sigma.is_nondegenerate()} - # For each simplex in d.keys(), add its faces, and the faces - # of its faces, etc., to d. - for simplex in list(d): - faces = domain.faces(simplex) - add = [] - if faces: - for (i, sigma) in enumerate(faces): - nondegen = sigma.nondegenerate() - if nondegen not in d: - add.append((sigma, i, simplex)) - while add: - (sigma, i, tau) = add.pop() - # sigma is the ith face of tau. - face_f = codomain.face(d[tau], i) - degens = sigma.degeneracies() - x = face_f - for j in degens: - x = codomain.face(x, j) - d[sigma.nondegenerate()] = x - faces = domain.faces(sigma.nondegenerate()) - if faces: - for (i,rho) in enumerate(faces): - nondegen = rho.nondegenerate() - if nondegen not in d: - add.append((rho,i,sigma)) - # Now check that the proposed map commutes with the face - # maps. (The degeneracy maps should work automatically.) - if check: - for simplex in d: - # Compare d[d_i (simplex)] to d_i d[simplex]. Since - # d_i(simplex) may be degenerate, we have to be careful - # when applying f to it. We can skip vertices and start - # with 1-simplices. - bad = False - for i in range(simplex.dimension()+1): - face_f = codomain.face(d[simplex], i) - face = domain.face(simplex, i) - if face is None: - f_face = None - elif face.is_nondegenerate(): - f_face = d[face] - else: - nondegen = face.nondegenerate() - f_face = d[nondegen].apply_degeneracies(*face.degeneracies()) - if face_f != f_face: - bad = True - break - if bad: - raise ValueError('the dictionary does not define a map of simplicial sets') - if any(x not in d.keys() for x in domain.nondegenerate_simplices()): - raise ValueError('the image of at least one simplex in ' - 'the domain is not defined') - self._dictionary = d - Morphism.__init__(self, Hom(domain, codomain, SimplicialSets())) - - def __eq__(self, other): - """ - Two morphisms are equal iff their domains are the same, their - codomains are the same, and their defining dictionaries are - the same. - - EXAMPLES:: - - sage: S = simplicial_sets.Sphere(1) - sage: T = simplicial_sets.Torus() - sage: T_c = T.constant_map() * T.base_point_map() - sage: S_c = S.constant_map() * S.base_point_map() - sage: T_c == S_c - True - sage: T.constant_map() == S.constant_map() - False - sage: K = simplicial_sets.Sphere(1) - sage: K.constant_map() == S.constant_map() - False - - sage: Point = simplicial_sets.Point() - sage: f = Point._map_from_empty_set() - sage: Empty = f.domain() - sage: g = Empty.constant_map() - sage: f == g - True - """ - if self.domain().is_finite() and other.domain().is_finite(): - return (self.domain() == other.domain() - and self.codomain() == other.codomain() - and self._dictionary == other._dictionary) - else: - return False - - def __ne__(self, other): - """ - The negation of ``__eq__``. - - EXAMPLES:: - - sage: S0 = simplicial_sets.Sphere(0) - sage: v,w = S0.n_cells(0) - sage: H = Hom(S0, S0) - sage: H({v:v, w:w}) != H({v:w, w:v}) - True - sage: H({v:v, w:w}) != H({w:w, v:v}) - False - """ - return not self == other - - def __call__(self, x): - """ - INPUT: a simplex of the domain. - - Return its image under this morphism. - - EXAMPLES:: - - sage: K = simplicial_sets.Simplex(1) - sage: S1 = simplicial_sets.Sphere(1) - sage: v0 = K.n_cells(0)[0] - sage: v1 = K.n_cells(0)[1] - sage: e01 = K.n_cells(1)[0] - sage: w = S1.n_cells(0)[0] - sage: sigma = S1.n_cells(1)[0] - sage: d = {v0: w, v1: w, e01: sigma} - sage: f = Hom(K, S1)(d) - sage: f(e01) # indirect doctest - sigma_1 - - sage: one = Hom(S1, S1).identity() - sage: e = S1.n_cells(1)[0] - sage: one(e) == e - True - - sage: B = AbelianGroup([2]).nerve() - sage: c = B.constant_map() - sage: c(B.n_cells(2)[0]) - s_1 s_0 * - """ - if x not in self.domain(): - raise ValueError('element is not a simplex in the domain') - if self.is_constant(): - target = self._constant - return target.apply_degeneracies(*range(x.dimension()-1, -1, -1)) - if self._is_identity: - return x - return self._dictionary[x.nondegenerate()].apply_degeneracies(*x.degeneracies()) - - def _composition_(self, right, homset): - """ - Return the composition of two morphisms. - - INPUT: - - - ``self``, ``right`` -- maps - - ``homset`` -- a homset - - ASSUMPTION: - - The codomain of ``right`` is contained in the domain of - ``self``. This assumption should be verified by the - ``Map.__mul__`` method in ``categories/map.pyx``, so we don't - need to check it here. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: f = S1.Hom(S1).identity() - sage: f * f # indirect doctest - Simplicial set endomorphism of S^1 - Defn: Identity map - sage: T = S1.product(S1) - sage: K = T.factor(0, as_subset=True) - sage: g = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) - sage: g - Simplicial set morphism: - From: S^1 - To: S^1 x S^1 - Defn: [v_0, sigma_1] --> [(v_0, v_0), (sigma_1, s_0 v_0)] - sage: (g*f).image() - Simplicial set with 2 non-degenerate simplices - sage: f.image().homology() - {0: 0, 1: Z} - """ - if self.is_identity(): - return right - if right.is_identity(): - return self - d = {} - for sigma in right._dictionary: - d[sigma] = self(right(sigma)) - return homset(d) - - def image(self): - """ - Return the image of this morphism as a subsimplicial set of the - codomain. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: T = S1.product(S1) - sage: K = T.factor(0, as_subset=True) - sage: f = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) - sage: f - Simplicial set morphism: - From: S^1 - To: S^1 x S^1 - Defn: [v_0, sigma_1] --> [(v_0, v_0), (sigma_1, s_0 v_0)] - sage: f.image() - Simplicial set with 2 non-degenerate simplices - sage: f.image().homology() - {0: 0, 1: Z} - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: B.constant_map().image() - Point - sage: Hom(B,B).identity().image() == B - True - """ - if self._is_identity: - return self.codomain() - if self.is_constant(): - return self.codomain().subsimplicial_set([self._constant]) - simplices = self._dictionary.values() - if set(simplices) == set(self.codomain().nondegenerate_simplices()): - return self.codomain() - return self.codomain().subsimplicial_set(simplices) - - def is_identity(self): - """ - Return ``True`` if this morphism is an identity map. - - EXAMPLES:: - - sage: K = simplicial_sets.Simplex(1) - sage: v0 = K.n_cells(0)[0] - sage: v1 = K.n_cells(0)[1] - sage: e01 = K.n_cells(1)[0] - sage: L = simplicial_sets.Simplex(2).n_skeleton(1) - sage: w0 = L.n_cells(0)[0] - sage: w1 = L.n_cells(0)[1] - sage: w2 = L.n_cells(0)[2] - sage: f01 = L.n_cells(1)[0] - sage: f02 = L.n_cells(1)[1] - sage: f12 = L.n_cells(1)[2] - - sage: d = {v0:w0, v1:w1, e01:f01} - sage: f = K.Hom(L)(d) - sage: f.is_identity() - False - sage: d = {w0:v0, w1:v1, w2:v1, f01:e01, f02:e01, f12: v1.apply_degeneracies(0,)} - sage: g = L.Hom(K)(d) - sage: (g*f).is_identity() - True - sage: (f*g).is_identity() - False - sage: (f*g).induced_homology_morphism().to_matrix(1) - [0] - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP5.n_skeleton(2).inclusion_map().is_identity() - False - sage: RP5.n_skeleton(5).inclusion_map().is_identity() - True - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: Hom(B,B).identity().is_identity() - True - sage: Hom(B,B).constant_map().is_identity() - False - """ - ans = (self._is_identity or - (self.domain() == self.codomain() - and self.domain().is_finite() - and all(a == b for a,b in self._dictionary.items()))) - self._is_identity = ans - return ans - - def is_surjective(self): - """ - Return ``True`` if this map is surjective. - - EXAMPLES:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP2 = RP5.n_skeleton(2) - sage: RP2.inclusion_map().is_surjective() - False - - sage: RP5_2 = RP5.quotient(RP2) - sage: RP5_2.quotient_map().is_surjective() - True - - sage: K = RP5_2.pullback(RP5_2.quotient_map(), RP5_2.base_point_map()) - sage: f = K.universal_property(RP2.inclusion_map(), RP2.constant_map()) - sage: f.is_surjective() - True - """ - return self._is_identity or self.image() == self.codomain() - - def is_injective(self): - """ - Return ``True`` if this map is injective. - - EXAMPLES:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP2 = RP5.n_skeleton(2) - sage: RP2.inclusion_map().is_injective() - True - - sage: RP5_2 = RP5.quotient(RP2) - sage: RP5_2.quotient_map().is_injective() - False - - sage: K = RP5_2.pullback(RP5_2.quotient_map(), RP5_2.base_point_map()) - sage: f = K.universal_property(RP2.inclusion_map(), RP2.constant_map()) - sage: f.is_injective() - True - """ - if self._is_identity: - return True - domain = self.domain() - for n in range(domain.dimension()+1): - input = domain.n_cells(n) - output = set([self(sigma) for sigma in input if self(sigma).is_nondegenerate()]) - if len(input) > len(output): - return False - return True - - def is_bijective(self): - """ - Return ``True`` if this map is bijective. - - EXAMPLES:: - - sage: RP5 = simplicial_sets.RealProjectiveSpace(5) - sage: RP2 = RP5.n_skeleton(2) - sage: RP2.inclusion_map().is_bijective() - False - - sage: RP5_2 = RP5.quotient(RP2) - sage: RP5_2.quotient_map().is_bijective() - False - - sage: K = RP5_2.pullback(RP5_2.quotient_map(), RP5_2.base_point_map()) - sage: f = K.universal_property(RP2.inclusion_map(), RP2.constant_map()) - sage: f.is_bijective() - True - """ - return self.is_injective() and self.is_surjective() - - def is_pointed(self): - """ - Return ``True`` if this is a pointed map. - - That is, return ``True`` if the domain and codomain are - pointed and this morphism preserves the base point. - - EXAMPLES:: - - sage: S0 = simplicial_sets.Sphere(0) - sage: f = Hom(S0,S0).identity() - sage: f.is_pointed() - True - sage: v = S0.n_cells(0)[0] - sage: w = S0.n_cells(0)[1] - sage: g = Hom(S0,S0)({v:v, w:v}) - sage: g.is_pointed() - True - sage: t = Hom(S0,S0)({v:w, w:v}) - sage: t.is_pointed() - False - """ - return (self.domain().is_pointed() and self.codomain().is_pointed() - and self(self.domain().base_point()) == self.codomain().base_point()) - - def is_constant(self): - """ - Return ``True`` if this morphism is a constant map. - - EXAMPLES:: - - sage: K = simplicial_sets.KleinBottle() - sage: S4 = simplicial_sets.Sphere(4) - sage: c = Hom(K, S4).constant_map() - sage: c.is_constant() - True - sage: X = S4.n_skeleton(3) # a point - sage: X.inclusion_map().is_constant() - True - sage: eta = simplicial_sets.HopfMap() - sage: eta.is_constant() - False - """ - try: - return self._constant is not None - except AttributeError: - pass - if not self.domain().is_finite(): - # The domain is infinite, so there is no safe way to - # determine if the map is constant. - return False - targets = [tau.nondegenerate() for tau in self._dictionary.values()] - if len(set(targets)) == 1: - # It's constant, so save the target. - self._constant = targets[0] - return True - return False - - def pushout(self, *others): - """ - Return the pushout of this morphism along with ``others``. - - INPUT: - - - ``others`` -- morphisms of simplicial sets, the domains of - which must all equal that of ``self``. - - This returns the pushout as a simplicial set. See - :class:`sage.homology.simplicial_set_constructions.PushoutOfSimplicialSets` - for more documentation and examples. - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: K = simplicial_sets.KleinBottle() - sage: init_T = T._map_from_empty_set() - sage: init_K = K._map_from_empty_set() - sage: D = init_T.pushout(init_K) # the disjoint union as a pushout - sage: D - Pushout of maps: - Simplicial set morphism: - From: Empty simplicial set - To: Torus - Defn: [] --> [] - Simplicial set morphism: - From: Empty simplicial set - To: Klein bottle - Defn: [] --> [] - """ - domain = self.domain() - if any(domain != f.domain() for f in others): - raise ValueError('the domains of the maps must be equal') - return self.domain().pushout(*(self,) + others) - - def pullback(self, *others): - """ - Return the pullback of this morphism along with ``others``. - - INPUT: - - - ``others`` -- morphisms of simplicial sets, the codomains of - which must all equal that of ``self``. - - This returns the pullback as a simplicial set. See - :class:`sage.homology.simplicial_set_constructions.PullbackOfSimplicialSets` - for more documentation and examples. - - EXAMPLES:: - - sage: T = simplicial_sets.Torus() - sage: K = simplicial_sets.KleinBottle() - sage: term_T = T.constant_map() - sage: term_K = K.constant_map() - sage: P = term_T.pullback(term_K) # the product as a pullback - sage: P - Pullback of maps: - Simplicial set morphism: - From: Torus - To: Point - Defn: Constant map at * - Simplicial set morphism: - From: Klein bottle - To: Point - Defn: Constant map at * - """ - codomain = self.codomain() - if any(codomain != f.codomain() for f in others): - raise ValueError('the codomains of the maps must be equal') - return self.codomain().pullback(*(self,) + others) - - def equalizer(self, other): - r""" - Return the equalizer of this map with ``other``. - - INPUT: - - - ``other`` -- a morphism with the same domain and codomain as this map - - If the two maps are `f, g: X \to Y`, then the equalizer `P` is - constructed as the pullback :: - - P ----> X - | | - V V - X --> X x Y - - where the two maps `X \to X \times Y` are `(1,f)` and `(1,g)`. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: x = AbstractSimplex(0, name='x') - sage: evw = AbstractSimplex(1, name='vw') - sage: evx = AbstractSimplex(1, name='vx') - sage: ewx = AbstractSimplex(1, name='wx') - sage: X = SimplicialSet({evw: (w, v), evx: (x, v)}) - sage: Y = SimplicialSet({evw: (w, v), evx: (x, v), ewx: (x, w)}) - - Here `X` is a wedge of two 1-simplices (a horn, that is), and - `Y` is the boundary of a 2-simplex. The map `f` includes the - two 1-simplices into `Y`, while the map `g` maps both - 1-simplices to the same edge in `Y`. :: - - sage: f = Hom(X, Y)({v:v, w:w, x:x, evw:evw, evx:evx}) - sage: g = Hom(X, Y)({v:v, w:x, x:x, evw:evx, evx:evx}) - sage: P = f.equalizer(g) - sage: P - Pullback of maps: - Simplicial set morphism: - From: Simplicial set with 5 non-degenerate simplices - To: Simplicial set with 5 non-degenerate simplices x Simplicial set with 6 non-degenerate simplices - Defn: [v, w, x, vw, vx] --> [(v, v), (w, w), (x, x), (vw, vw), (vx, vx)] - Simplicial set morphism: - From: Simplicial set with 5 non-degenerate simplices - To: Simplicial set with 5 non-degenerate simplices x Simplicial set with 6 non-degenerate simplices - Defn: [v, w, x, vw, vx] --> [(v, v), (w, x), (x, x), (vw, vx), (vx, vx)] - """ - domain = self.domain() - codomain = self.codomain() - if domain != other.domain() or codomain != other.codomain(): - raise ValueError('the maps must have the same domain and the same codomain') - prod = domain.product(codomain) - one = domain.Hom(domain).identity() - f = prod.universal_property(one, self) - g = prod.universal_property(one, other) - return f.pullback(g) - - def coequalizer(self, other): - r""" - Return the coequalizer of this map with ``other``. - - INPUT: - - - ``other`` -- a morphism with the same domain and codomain as this map - - If the two maps are `f, g: X \to Y`, then the coequalizer `P` is - constructed as the pushout :: - - X v Y --> Y - | | - V V - Y ----> P - - where the upper left corner is the coproduct of `X` and `Y` - (the wedge if they are pointed, the disjoint union otherwise), - and the two maps `X \amalg Y \to Y` are `f \amalg 1` and `g - \amalg 1`. - - EXAMPLES:: - - sage: L = simplicial_sets.Simplex(2) - sage: pt = L.n_cells(0)[0] - sage: e = L.n_cells(1)[0] - sage: K = L.subsimplicial_set([e]) - sage: f = K.inclusion_map() - sage: v,w = K.n_cells(0) - sage: g = Hom(K,L)({v:pt, w:pt, e:pt.apply_degeneracies(0)}) - sage: P = f.coequalizer(g) - sage: P - Pushout of maps: - Simplicial set morphism: - From: Disjoint union: (Simplicial set with 3 non-degenerate simplices u 2-simplex) - To: 2-simplex - Defn: ... - Simplicial set morphism: - From: Disjoint union: (Simplicial set with 3 non-degenerate simplices u 2-simplex) - To: 2-simplex - Defn: ... - """ - domain = self.domain() - codomain = self.codomain() - if domain != other.domain() or codomain != other.codomain(): - raise ValueError('the maps must have the same domain and the same codomain') - coprod = domain.coproduct(codomain) - one = codomain.Hom(codomain).identity() - f = coprod.universal_property(self, one) - g = coprod.universal_property(other, one) - return f.pushout(g) - - def mapping_cone(self): - r""" - Return the mapping cone defined by this map. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: v_0, sigma_1 = S1.nondegenerate_simplices() - sage: K = simplicial_sets.Simplex(2).n_skeleton(1) - - The mapping cone will be a little smaller if we use only - pointed simplicial sets. `S^1` is already pointed, but not - `K`. :: - - sage: L = K.set_base_point(K.n_cells(0)[0]) - sage: u,v,w = L.n_cells(0) - sage: e,f,g = L.n_cells(1) - sage: h = L.Hom(S1)({u:v_0, v:v_0, w:v_0, e:sigma_1, f:v_0.apply_degeneracies(0), g:sigma_1}) - sage: h - Simplicial set morphism: - From: Simplicial set with 6 non-degenerate simplices - To: S^1 - Defn: [(0,), (1,), (2,), (0, 1), (0, 2), (1, 2)] --> [v_0, v_0, v_0, sigma_1, s_0 v_0, sigma_1] - sage: h.induced_homology_morphism().to_matrix() - [1|0] - [-+-] - [0|2] - sage: X = h.mapping_cone() - sage: X.homology() == simplicial_sets.RealProjectiveSpace(2).homology() - True - """ - dom = self.domain() - cone = dom.cone() - i = cone.map_from_base() - return self.pushout(i) - - def product(self, *others): - r""" - Return the product of this map with ``others``. - - - ``others`` -- morphisms of simplicial sets. - - If the relevant maps are `f_i: X_i \to Y_i`, this returns the - natural map `\prod X_i \to \prod Y_i`. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: f = Hom(S1,S1).identity() - sage: f.product(f).is_bijective() - True - sage: g = S1.constant_map(S1) - sage: g.product(g).is_bijective() - False - """ - domain = self.domain().product(*[g.domain() for g in others]) - codomain = self.codomain().product(*[g.codomain() for g in others]) - factors = [] - for (i,f) in enumerate([self] + list(others)): - factors.append(f * domain.projection_map(i)) - return codomain.universal_property(*factors) - - def coproduct(self, *others): - r""" - Return the coproduct of this map with ``others``. - - - ``others`` -- morphisms of simplicial sets. - - If the relevant maps are `f_i: X_i \to Y_i`, this returns the - natural map `\amalg X_i \to \amalg Y_i`. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: f = Hom(S1,S1).identity() - sage: f.coproduct(f).is_bijective() - True - sage: g = S1.constant_map(S1) - sage: g.coproduct(g).is_bijective() - False - """ - codomain = self.codomain().coproduct(*[g.codomain() for g in others]) - factors = [] - for i, f in enumerate([self] + list(others)): - factors.append(codomain.inclusion_map(i) * f) - return codomain.universal_property(*factors) - - def suspension(self, n=1): - """ - Return the `n`-th suspension of this morphism of simplicial sets. - - INPUT: - - - ``n`` (optional) -- non-negative integer, default 1 - - EXAMPLES:: - - sage: eta = simplicial_sets.HopfMap() - sage: susp_eta = eta.suspension() - sage: susp_eta.mapping_cone().homology() == eta.mapping_cone().suspension().homology() - True - - This uses reduced suspensions if the original morphism is - pointed, unreduced otherwise. So for example, if a constant - map is not pointed, its suspension is not a constant map:: - - sage: L = simplicial_sets.Simplex(1) - sage: L.constant_map().is_pointed() - False - sage: f = L.constant_map().suspension() - sage: f.is_constant() - False - - sage: K = simplicial_sets.Sphere(3) - sage: K.constant_map().is_pointed() - True - sage: g = K.constant_map().suspension() - sage: g.is_constant() - True - - sage: h = K.identity().suspension() - sage: h.is_identity() - True - """ - domain = self.domain() - codomain = self.codomain() - if not self.is_pointed(): - # Make sure to use unreduced suspensions for both domain - # and codomain. - if domain.is_pointed(): - domain = domain.unset_base_point() - if codomain.is_pointed(): - codomain = codomain.unset_base_point() - f = self - for i in range(n): - new_dom = domain.suspension() - new_cod = codomain.suspension() - data = {new_dom.base_point(): new_cod.base_point()} - for sigma in f._dictionary: - target = f(sigma) - underlying = target.nondegenerate() - degens = target.degeneracies() - data[new_dom._suspensions[sigma]] = new_cod._suspensions[underlying].apply_degeneracies(*degens) - f = new_dom.Hom(new_cod)(data) - domain = f.domain() - codomain = f.codomain() - return f - - def n_skeleton(self, n, domain=None, codomain=None): - """ - Return the restriction of this morphism to the n-skeleta of the - domain and codomain - - INPUT: - - - ``n`` -- the dimension - - - ``domain`` -- optional, the domain. Specify this to - explicitly specify the domain; otherwise, Sage will attempt - to compute it. Specifying this can be useful if the domain - is built as a pushout or pullback, so trying to compute it - may lead to computing the `n`-skeleton of a map, causing an - infinite recursion. (Users should not have to specify this, - but it may be useful for developers.) - - - ``codomain`` -- optional, the codomain. - - EXAMPLES:: - - sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) - sage: one = Hom(B,B).identity() - sage: one.n_skeleton(3) - Simplicial set endomorphism of Simplicial set with 4 non-degenerate simplices - Defn: Identity map - sage: c = Hom(B,B).constant_map() - sage: c.n_skeleton(3) - Simplicial set endomorphism of Simplicial set with 4 non-degenerate simplices - Defn: Constant map at 1 - - sage: K = simplicial_sets.Simplex(2) - sage: L = K.subsimplicial_set(K.n_cells(0)[:2]) - sage: L.nondegenerate_simplices() - [(0,), (1,)] - sage: L.inclusion_map() - Simplicial set morphism: - From: Simplicial set with 2 non-degenerate simplices - To: 2-simplex - Defn: [(0,), (1,)] --> [(0,), (1,)] - sage: L.inclusion_map().n_skeleton(1) - Simplicial set morphism: - From: Simplicial set with 2 non-degenerate simplices - To: Simplicial set with 6 non-degenerate simplices - Defn: [(0,), (1,)] --> [(0,), (1,)] - """ - if domain is None: - domain = self.domain().n_skeleton(n) - if codomain is None: - codomain = self.codomain().n_skeleton(n) - if self.is_constant(): - return Hom(domain, codomain).constant_map(self._constant) - if self.is_identity(): - return Hom(domain, domain).identity() - old = self._dictionary - new = {d: old[d] for d in old if d.dimension() <= n} - return Hom(domain, codomain)(new) - - def associated_chain_complex_morphism(self, base_ring=ZZ, - augmented=False, cochain=False): - """ - Return the associated chain complex morphism of ``self``. - - INPUT: - - - ``base_ring`` -- default ``ZZ`` - - ``augmented`` -- boolean, default ``False``. If ``True``, - return the augmented complex. - - ``cochain`` -- boolean, default ``False``. If ``True``, - return the cochain complex. - - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: v0 = S1.n_cells(0)[0] - sage: e = S1.n_cells(1)[0] - sage: f = {v0: v0, e: v0.apply_degeneracies(0)} # constant map - sage: g = Hom(S1, S1)(f) - sage: g.associated_chain_complex_morphism().to_matrix() - [1|0] - [-+-] - [0|0] - """ - # One or the other chain complex is trivial between these - # dimensions: - max_dim = max(self.domain().dimension(), self.codomain().dimension()) - min_dim = min(self.domain().dimension(), self.codomain().dimension()) - matrices = {} - if augmented is True: - m = matrix(base_ring,1,1,1) - if not cochain: - matrices[-1] = m - else: - matrices[-1] = m.transpose() - for dim in range(min_dim+1): - X_faces = list(self.domain().n_cells(dim)) - Y_faces = list(self.codomain().n_cells(dim)) - num_faces_X = len(X_faces) - num_faces_Y = len(Y_faces) - mval = [0 for _ in range(num_faces_X * num_faces_Y)] - for idx,x in enumerate(X_faces): - y = self(x) - if y.is_nondegenerate(): - mval[idx + (Y_faces.index(y) * num_faces_X)] = 1 - m = matrix(base_ring, num_faces_Y, num_faces_X, mval, sparse=True) - if not cochain: - matrices[dim] = m - else: - matrices[dim] = m.transpose() - for dim in range(min_dim+1,max_dim+1): - try: - l1 = len(self.codomain().n_cells(dim)) - except KeyError: - l1 = 0 - try: - l2 = len(self.domain().n_cells(dim)) - except KeyError: - l2 = 0 - m = zero_matrix(base_ring,l1,l2,sparse=True) - if not cochain: - matrices[dim] = m - else: - matrices[dim] = m.transpose() - if not cochain: - return ChainComplexMorphism(matrices, - self.domain().chain_complex(base_ring=base_ring, augmented=augmented, cochain=False), - self.codomain().chain_complex(base_ring=base_ring, augmented=augmented, cochain=False)) - else: - return ChainComplexMorphism(matrices, - self.codomain().chain_complex(base_ring=base_ring, augmented=augmented, cochain=True), - self.domain().chain_complex(base_ring=base_ring, augmented=augmented, cochain=True)) - - def induced_homology_morphism(self, base_ring=None, cohomology=False): - """ - Return the map in (co)homology induced by this map - - INPUT: - - - ``base_ring`` -- must be a field (optional, default ``QQ``) - - - ``cohomology`` -- boolean (optional, default ``False``). If - ``True``, the map induced in cohomology rather than homology. - - EXAMPLES:: - - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet - sage: v = AbstractSimplex(0, name='v') - sage: w = AbstractSimplex(0, name='w') - sage: e = AbstractSimplex(1, name='e') - sage: f = AbstractSimplex(1, name='f') - sage: X = SimplicialSet({e: (v, w), f: (w, v)}) - sage: Y = SimplicialSet({e: (v, v)}) - sage: H = Hom(X, Y) - sage: f = H({v: v, w: v, e: e, f: e}) - sage: g = f.induced_homology_morphism() - sage: g.to_matrix() - [1|0] - [-+-] - [0|2] - sage: g3 = f.induced_homology_morphism(base_ring=GF(3), cohomology=True) - sage: g3.to_matrix() - [1|0] - [-+-] - [0|2] - """ - return InducedHomologyMorphism(self, base_ring, cohomology) - - def _repr_type(self): - """ - EXAMPLES:: - - sage: S1 = simplicial_sets.Sphere(1) - sage: f = Hom(S1,S1).identity() - sage: f._repr_type() - 'Simplicial set' - """ - return "Simplicial set" - - def _repr_defn(self): - """ - EXAMPLES:: - - sage: K1 = simplicial_sets.Simplex(1) - sage: v = K1.n_cells(0)[0] - sage: e = K1.n_cells(1)[0] - sage: f = Hom(K1,K1)({e:v.apply_degeneracies(0)}) - sage: f._repr_defn() - 'Constant map at (0,)' - - sage: K2 = simplicial_sets.Simplex(2) - sage: tau = K2.n_cells(1)[0] - sage: Hom(K1, K2)({e:tau})._repr_defn() - '[(0,), (1,), (0, 1)] --> [(0,), (1,), (0, 1)]' - - sage: S1 = simplicial_sets.Sphere(1) - sage: Hom(S1,S1).identity()._repr_defn() - 'Identity map' - """ - if self.is_identity(): - return 'Identity map' - if self.is_constant(): - return 'Constant map at {}'.format(self._constant) - d = self._dictionary - keys = sorted(d.keys()) - return "{} --> {}".format(keys, [d[x] for x in keys]) - - def _latex_(self): - """ - LaTeX representation. - - EXAMPLES:: - - sage: eta = simplicial_sets.HopfMap() - sage: eta.domain().rename_latex('S^{3}') - sage: latex(eta) - S^{3} \to S^{2} - """ - return '{} \\to {}'.format(latex(self.domain()), latex(self.codomain())) +deprecated_function_alias(31925, + sage.topology.simplicial_set_morphism.SimplicialSetHomset) +deprecated_function_alias(31925, + sage.topology.simplicial_set_morphism.SimplicialSetMorphism) diff --git a/src/sage/homology/tests.py b/src/sage/homology/tests.py index b95adea8e30..467d9f35d59 100644 --- a/src/sage/homology/tests.py +++ b/src/sage/homology/tests.py @@ -22,7 +22,7 @@ from sage.matrix.constructor import random_matrix from sage.homology.chain_complex import ChainComplex from sage.rings.integer_ring import ZZ -from sage.homology.examples import RandomComplex +from sage.topology.simplicial_complex_examples import RandomComplex def random_chain_complex(level=1): """ diff --git a/src/sage/interacts/library.py b/src/sage/interacts/library.py index d174cc7d9ef..63b6ff674a4 100644 --- a/src/sage/interacts/library.py +++ b/src/sage/interacts/library.py @@ -1,4 +1,4 @@ -""" +r""" Sage Interacts Sage interacts are applications of the `@interact decorator <../../sagenb/notebook/interact.html>`_. @@ -20,44 +20,63 @@ AUTHORS: - William Stein - -- Harald Schilly, Robert Marik (2011-01-16): added many examples (#9623) partially based on work by Lauri Ruotsalainen - +- Harald Schilly, Robert Marik (2011-01-16): added many examples (#9623) + partially based on work by Lauri Ruotsalainen """ -#***************************************************************************** +# ***************************************************************************** # Copyright (C) 2009 William Stein # Copyright (C) 2011 Harald Schilly # # 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/ -#***************************************************************************** - +# +# https://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.arith.misc import factor +from sage.arith.srange import srange +from sage.calculus.all import symbolic_expression +from sage.calculus.functional import derivative +from sage.calculus.integration import numerical_integral as integral_numerical +from sage.ext.fast_callable import fast_callable +from sage.functions.log import exp +from sage.functions.other import sqrt +from sage.functions.trig import (acos, cos, sin, tan) +from sage.misc.decorators import sage_wraps +from sage.misc.functional import N +from sage.misc.latex import latex +from sage.misc.sage_eval import sage_eval +from sage.misc.table import table +from sage.plot.circle import circle +from sage.plot.complex_plot import complex_plot +from sage.plot.disk import disk +from sage.plot.graphics import Graphics +from sage.plot.line import (line, line2d) +from sage.plot.matrix_plot import matrix_plot +from sage.plot.plot import (graphics_array, parametric_plot, plot) +from sage.plot.point import (point, points) +from sage.plot.polygon import polygon2d +from sage.plot.text import text +from sage.repl.rich_output.pretty_print import (pretty_print, show) +from sage.rings.complex_double import CDF +from sage.rings.integer import Integer +from sage.symbolic.constants import pi +from sage.symbolic.relation import solve +from sage.symbolic.ring import SR +import math -from sage.all import * x = SR.var('x') # It is important that this file is lazily imported for this to work from sage.repl.user_globals import get_global -# Get a bunch of functions from the user globals. In SageNB, this will -# refer to SageNB functions; in Jupyter, this will refer to Jupyter -# functions. In the command-line and for doctests, we import the -# SageNB functions as fall-back. -for name in ("interact", "checkbox", "input_box", "input_grid", - "range_slider", "selector", "slider", "text_control"): - try: - obj = get_global(name) - except NameError: - import sagenb.notebook.interact - obj = sagenb.notebook.interact.__dict__[name] - globals()[name] = obj - +from sage.repl.ipython_kernel.all_jupyter import (interact, checkbox, + input_box, input_grid, range_slider, selector, slider, text_control) def library_interact(f): - """ + r""" This is a decorator for using interacts in the Sage library. This is just the ``interact`` function wrapped in an additional @@ -82,7 +101,7 @@ def library_wrapper(): def html(obj): - """ + r""" Shorthand to pretty print HTML EXAMPLES:: @@ -97,13 +116,13 @@ def html(obj): @library_interact def demo(n=slider(range(10)), m=slider(range(10))): - """ + r""" This is a demo interact that sums two numbers. INPUT: - - ``n`` -- integer slider - - ``m`` -- integer slider + - ``n`` -- integer slider + - ``m`` -- integer slider EXAMPLES: @@ -123,12 +142,14 @@ def demo(n=slider(range(10)), m=slider(range(10))): def taylor_polynomial( title = text_control('

Taylor polynomial

'), f=input_box(sin(x)*exp(-x),label="$f(x)=$"), order=slider(range(1,13))): - """ - An interact which illustrates the Taylor polynomial approximation + r""" + Illustrate the Taylor polynomial approximation of various orders around `x=0`. - - ``f`` -- function expression - - ```order``` -- integer slider + INPUT: + + - ``f`` -- function expression + - ``order`` -- integer slider EXAMPLES: @@ -162,16 +183,16 @@ def definite_integral( interval = range_slider(-10,10,default=(0,3), label="Interval"), x_range = range_slider(-10,10,default=(0,3), label = "plot range (x)"), selection = selector(["f", "g", "f and g", "f - g"], default="f and g", label="Select")): - """ + r""" This is a demo interact for plotting the definite integral of a function based on work by Lauri Ruotsalainen, 2010. INPUT: - - ``function`` -- input box, function in x - - ``interval`` -- interval for the definite integral - - ``x_range`` -- range slider for plotting range - - ``selection`` -- selector on how to visualize the integrals + - ``function`` -- input box, function in x + - ``interval`` -- interval for the definite integral + - ``x_range`` -- range slider for plotting range + - ``selection`` -- selector on how to visualize the integrals EXAMPLES: @@ -187,12 +208,15 @@ def definite_integral( g: EvalText(value=u'x^2', description=u'$g(x)=$', layout=Layout(max_width=u'81em')) interval: IntRangeSlider(value=(0, 3), description=u'Interval', max=10, min=-10) x_range: IntRangeSlider(value=(0, 3), description=u'plot range (x)', max=10, min=-10) - selection: Dropdown(description=u'Select', index=2, options=('f', 'g', 'f and g', 'f - g'), value='f and g') + selection: Dropdown(description=u'Select', index=2, + options=('f', 'g', 'f and g', 'f - g'), value='f and g') """ x = SR.var('x') f = symbolic_expression(f).function(x) g = symbolic_expression(g).function(x) - f_plot = Graphics(); g_plot = Graphics(); h_plot = Graphics(); + f_plot = Graphics() + g_plot = Graphics() + h_plot = Graphics() text = "" # Plot function f. @@ -244,7 +268,7 @@ def function_derivative( function = input_box(default="x^5-3*x^3+1", label="Function:"), x_range = range_slider(-15,15,0.1, default=(-2,2), label="Range (x)"), y_range = range_slider(-15,15,0.1, default=(-8,6), label="Range (y)")): - """ + r""" This is a demo interact for plotting derivatives of a function based on work by Lauri Ruotsalainen, 2010. @@ -290,16 +314,16 @@ def difference_quotient( interval= range_slider(0, 10, 0.1, default=(0.0,10.0), label="Range"), a = slider(0, 10, None, 5.5, label = '$a$'), x0 = slider(0, 10, None, 2.5, label = '$x_0$ (start point)')): - """ + r""" This is a demo interact for difference quotient based on work by Lauri Ruotsalainen, 2010. INPUT: - - ``f`` -- input box, function in `x` - - ``interval`` -- range slider for plotting - - ``a`` -- slider for `a` - - ``x0`` -- slider for starting point `x_0` + - ``f`` -- input box, function in `x` + - ``interval`` -- range slider for plotting + - ``a`` -- slider for `a` + - ``x0`` -- slider for starting point `x_0` EXAMPLES: @@ -351,15 +375,15 @@ def difference_quotient( @library_interact def quadratic_equation(A = slider(-7, 7, 1, 1), B = slider(-7, 7, 1, 1), C = slider(-7, 7, 1, -2)): - """ + r""" This is a demo interact for solving quadratic equations based on work by Lauri Ruotsalainen, 2010. INPUT: - - ``A`` -- integer slider - - ``B`` -- integer slider - - ``C`` -- integer slider + - ``A`` -- integer slider + - ``B`` -- integer slider + - ``C`` -- integer slider EXAMPLES: @@ -412,15 +436,15 @@ def trigonometric_properties_triangle( a0 = slider(0, 360, 1, 30, label="A"), a1 = slider(0, 360, 1, 180, label="B"), a2 = slider(0, 360, 1, 300, label="C")): - """ + r""" This is an interact for demonstrating trigonometric properties in a triangle based on work by Lauri Ruotsalainen, 2010. INPUT: - - ``a0`` -- angle - - ``a1`` -- angle - - ``a2`` -- angle + - ``a0`` -- angle + - ``a1`` -- angle + - ``a2`` -- angle EXAMPLES: @@ -492,14 +516,14 @@ def area(alpha, a, b): def unit_circle( function = selector([(0, sin(x)), (1, cos(x)), (2, tan(x))]), x = slider(0,2*pi, 0.005*pi, 0)): - """ + r""" This is an interact for Sin, Cos and Tan in the Unit Circle based on work by Lauri Ruotsalainen, 2010. INPUT: - - ``function`` -- select Sin, Cos or Tan - - ``x`` -- slider to select angle in unit circle + - ``function`` -- select Sin, Cos or Tan + - ``x`` -- slider to select angle in unit circle EXAMPLES: @@ -574,21 +598,21 @@ def special_points( show_ab = checkbox(False, label="Angle Bisectors"), show_incircle = checkbox(False, label="Incircle"), show_euler = checkbox(False, label="Euler's Line")): - """ + r""" This interact demo shows special points in a triangle based on work by Lauri Ruotsalainen, 2010. INPUT: - - ``a0`` -- angle - - ``a1`` -- angle - - ``a2`` -- angle - - ``show_median`` -- checkbox - - ``show_pb`` -- checkbox to show perpendicular bisectors - - ``show_alt`` -- checkbox to show altitudes - - ``show_ab`` -- checkbox to show angle bisectors - - ``show_incircle`` -- checkbox to show incircle - - ``show_euler`` -- checkbox to show euler's line + - ``a0`` -- angle + - ``a1`` -- angle + - ``a2`` -- angle + - ``show_median`` -- checkbox + - ``show_pb`` -- checkbox to show perpendicular bisectors + - ``show_alt`` -- checkbox to show altitudes + - ``show_ab`` -- checkbox to show angle bisectors + - ``show_incircle`` -- checkbox to show incircle + - ``show_euler`` -- checkbox to show euler's line EXAMPLES: @@ -743,7 +767,7 @@ def line_to_points(x1_y1, x2_y2, **plot_kwargs): @library_interact def coin(n = slider(2,10000, 100, default=1000, label="Number of Tosses"), interval = range_slider(0, 1, default=(0.45, 0.55), label="Plotting range (y)")): - """ + r""" This interact demo simulates repeated tosses of a coin, based on work by Lauri Ruotsalainen, 2010. @@ -755,9 +779,8 @@ def coin(n = slider(2,10000, 100, default=1000, label="Number of Tosses"), inter INPUT: - - ``n`` -- number of tosses - - ``interval`` -- plot range along - vertical axis + - ``n`` -- number of tosses + - ``interval`` -- plot range along vertical axis EXAMPLES: @@ -787,16 +810,16 @@ def bisection_method( interval = range_slider(-5,5,default=(0, 4), label="range"), d = slider(1, 8, 1, 3, label="$10^{-d}$ precision"), maxn = slider(0,50,1,10, label="max iterations")): - """ + r""" Interact explaining the bisection method, based on similar interact explaining secant method and Wiliam Stein's example from wiki. INPUT: - - ``f`` -- function - - ``interval`` -- range slider for the search interval - - ``d`` -- slider for the precision (`10^{-d}`) - - ``maxn`` -- max number of iterations + - ``f`` -- function + - ``interval`` -- range slider for the search interval + - ``d`` -- slider for the precision (`10^{-d}`) + - ``maxn`` -- max number of iterations EXAMPLES: @@ -821,7 +844,9 @@ def _bisection_method(f, a, b, maxn, eps): c = (b+a)/two if abs(f(c)) < h or round >= maxn: break - fa = f(a); fb = f(b); fc = f(c) + fa = f(a) + fb = f(b) + fc = f(c) if abs(fc) < eps: return c, intervals if fa*fc < 0: @@ -862,17 +887,17 @@ def secant_method( interval = range_slider(-5,5,default=(0, 4), label="range"), d = slider(1, 16, 1, 3, label="10^-d precision"), maxn = slider(0,15,1,10, label="max iterations")): - """ + r""" Interact explaining the secant method, based on work by Lauri Ruotsalainen, 2010. Originally this is based on work by William Stein. INPUT: - - ``f`` -- function - - ``interval`` -- range slider for the search interval - - ``d`` -- slider for the precision (10^-d) - - ``maxn`` -- max number of iterations + - ``f`` -- function + - ``interval`` -- range slider for the search interval + - ``d`` -- slider for the precision (10^-d) + - ``maxn`` -- max number of iterations EXAMPLES: @@ -931,19 +956,19 @@ def newton_method( maxn = slider(0, 15, 1, 10, label="max iterations"), interval = range_slider(-10,10, default = (0,6), label="Interval"), list_steps = checkbox(default=False, label="List steps")): - """ + r""" Interact explaining the Newton method, based on work by Lauri Ruotsalainen, 2010. Originally this is based on work by William Stein. INPUT: - - ``f`` -- function - - ``c`` -- starting position (`x`) - - ``d`` -- slider for the precision (`10^{-d}`) - - ``maxn`` -- max number of iterations - - ``interval`` -- range slider for the search interval - - ``list_steps`` -- checkbox, if true shows the steps numerically + - ``f`` -- function + - ``c`` -- starting position (`x`) + - ``d`` -- slider for the precision (`10^{-d}`) + - ``maxn`` -- max number of iterations + - ``interval`` -- range slider for the search interval + - ``list_steps`` -- checkbox, if true shows the steps numerically EXAMPLES: @@ -1004,19 +1029,21 @@ def trapezoid_integration( interval_g = input_grid(1,2,default=[[0,8]], label="keyboard: "), output_form = selector(['traditional','table','none'], label='Computations form', buttons=True) ): - """ - Interact explaining the trapezoid method for definite integrals, based on work by + r""" + Interact explaining the trapezoid method for definite integrals. + + Based on work by Lauri Ruotsalainen, 2010 (based on the application "Numerical integrals with various rules" by Marshall Hampton and Nick Alexander) INPUT: - - ``f`` -- function of variable x to integrate - - ``n`` -- number of divisions - - ``interval_input`` -- swithes the input for interval between slider and keyboard - - ``interval_s`` -- slider for interval to integrate - - ``interval_g`` -- input grid for interval to integrate - - ``output_form`` -- the computation is formatted in a traditional form, in a table or missing + - ``f`` -- function of variable x to integrate + - ``n`` -- number of divisions + - ``interval_input`` -- switches the input for interval between slider and keyboard + - ``interval_s`` -- slider for interval to integrate + - ``interval_g`` -- input grid for interval to integrate + - ``output_form`` -- the computation is formatted in a traditional form, in a table or missing EXAMPLES: @@ -1118,19 +1145,21 @@ def simpson_integration( interval_s = range_slider(-10,10,default=(0,10), label="slider: "), interval_g = input_grid(1,2,default=[[0,10]], label="keyboard: "), output_form = selector(['traditional','table','none'], label='Computations form', buttons=True)): - """ - Interact explaining the simpson method for definite integrals, based on work by + r""" + Interact explaining the simpson method for definite integrals. + + Based on work by Lauri Ruotsalainen, 2010 (based on the application "Numerical integrals with various rules" by Marshall Hampton and Nick Alexander) INPUT: - - ``f`` -- function of variable x to integrate - - ``n`` -- number of divisions (mult. of 2) - - ``interval_input`` -- swithes the input for interval between slider and keyboard - - ``interval_s`` -- slider for interval to integrate - - ``interval_g`` -- input grid for interval to integrate - - ``output_form`` -- the computation is formatted in a traditional form, in a table or missing + - ``f`` -- function of variable x to integrate + - ``n`` -- number of divisions (mult. of 2) + - ``interval_input`` -- switches the input for interval between slider and keyboard + - ``interval_s`` -- slider for interval to integrate + - ``interval_g`` -- input grid for interval to integrate + - ``output_form`` -- the computation is formatted in a traditional form, in a table or missing EXAMPLES: @@ -1161,7 +1190,8 @@ def parabola(a, b, c): K = solve([A*a[0]**2+B*a[0]+C==a[1], A*b[0]**2+B*b[0]+C==b[1], A*c[0]**2+B*c[0]+C==c[1]], [A, B, C], solution_dict=True)[0] f = K[A]*x**2+K[B]*x+K[C] return f - xs = []; ys = [] + xs = [] + ys = [] dx = float(interval[1]-interval[0])/n for i in range(n+1): @@ -1250,14 +1280,14 @@ def riemann_sum( hr2 = text_control('
'), list_table = checkbox(default=False, label="List table"), auto_update = False): - """ + r""" Interact explaining the definition of Riemann integral INPUT: - ``f`` -- function of variable x to integrate - ``n`` -- number of divisions - - ``interval_input`` -- swithes the input for interval between slider and keyboard + - ``interval_input`` -- switches the input for interval between slider and keyboard - ``interval_s`` -- slider for interval to integrate - ``interval_g`` -- input grid for interval to integrate - ``list_table`` -- print table with values of the function @@ -1283,7 +1313,7 @@ def riemann_sum( AUTHORS: - - Robert Marik (08-2010) + - Robert Marik (2010-08) """ x = SR.var('x') from random import random @@ -1335,19 +1365,19 @@ def function_tool(f=sin(x), g=cos(x), xrange=range_slider(-3,3,default=(0,1),lab 'f+g', 'f-g', 'f*g', 'f/g', 'f(g)'], width=15, nrows=5, label="h = "), do_plot = ("Draw Plots", True)): - """ + r""" `Function Plotting Tool `_ (by William Stein (?)) INPUT: - - ``f`` -- function f(x) - - ``g`` -- function g(x) - - ``xrange`` -- range for plotting (x) - - ``yrange`` -- range for plotting ('auto' is default, otherwise a tuple) - - ``a`` -- factor ``a`` - - ``action`` -- select given operation on or combination of functions - - ``do_plot`` -- if true, a plot is drawn + - ``f`` -- function f(x) + - ``g`` -- function g(x) + - ``xrange`` -- range for plotting (x) + - ``yrange`` -- range for plotting ('auto' is default, otherwise a tuple) + - ``a`` -- factor ``a`` + - ``action`` -- select given operation on or combination of functions + - ``do_plot`` -- if true, a plot is drawn EXAMPLES: @@ -1368,14 +1398,17 @@ def function_tool(f=sin(x), g=cos(x), xrange=range_slider(-3,3,default=(0,1),lab """ x = SR.var('x') try: - f = SR(f); g = SR(g); a = SR(a) + f = SR(f) + g = SR(g) + a = SR(a) except TypeError as msg: print(msg[-200:]) print("Unable to make sense of f,g, or a as symbolic expressions in single variable x.") return if not (isinstance(xrange, tuple) and len(xrange) == 2): xrange = (0,1) - h = 0; lbl = '' + h = 0 + lbl = '' if action == 'f': h = f lbl = 'f' @@ -1395,7 +1428,7 @@ def function_tool(f=sin(x), g=cos(x), xrange=range_slider(-3,3,default=(0,1),lab h = 1/f lbl = r'\frac{1}{f}' elif action == 'finv': - h = solve(f == var('y'), x)[0].rhs() + h = solve(f == SR.var('y'), x)[0].rhs() lbl = 'f^{-1}(y)' elif action == 'f+a': h = f+a @@ -1458,20 +1491,20 @@ def julia(expo = slider(-10,10,0.1,2), zoom_y = range_slider(-2,2,0.01,(-1.5,1.5), label='Zoom Y'), plot_points = slider(20,400,20, default=150, label='plot points'), dpi = slider(20, 200, 10, default=80, label='dpi')): - """ + r""" Julia Fractal, based on `Julia by Harald Schilly `_. INPUT: - - ``exponent`` -- exponent ``e`` in `z^e+c` - - ``c_real`` -- real part of the constant ``c`` - - ``c_imag`` -- imaginary part of the constant ``c`` - - ``iterations`` -- number of iterations - - ``zoom_x`` -- range slider for zoom in x direction - - ``zoom_y`` -- range slider for zoom in y direction - - ``plot_points`` -- number of points to plot - - ``dpi`` -- dots-per-inch parameter for the plot + - ``exponent`` -- exponent ``e`` in `z^e+c` + - ``c_real`` -- real part of the constant ``c`` + - ``c_imag`` -- imaginary part of the constant ``c`` + - ``iterations`` -- number of iterations + - ``zoom_x`` -- range slider for zoom in x direction + - ``zoom_y`` -- range slider for zoom in y direction + - ``plot_points`` -- number of points to plot + - ``dpi`` -- dots-per-inch parameter for the plot EXAMPLES: @@ -1509,18 +1542,18 @@ def mandelbrot(expo = slider(-10,10,0.1,2), zoom_y = range_slider(-2,2,0.01,(-1.5,1.5), label='Zoom Y'), plot_points = slider(20,400,20, default=150, label='plot points'), dpi = slider(20, 200, 10, default=80, label='dpi')): - """ + r""" Mandelbrot Fractal, based on `Mandelbrot by Harald Schilly `_. INPUT: - - ``exponent`` -- exponent ``e`` in `z^e+c` - - ``iterations`` -- number of iterations - - ``zoom_x`` -- range slider for zoom in x direction - - ``zoom_y`` -- range slider for zoom in y direction - - ``plot_points`` -- number of points to plot - - ``dpi`` -- dots-per-inch parameter for the plot + - ``exponent`` -- exponent ``e`` in `z^e+c` + - ``iterations`` -- number of iterations + - ``zoom_x`` -- range slider for zoom in x direction + - ``zoom_y`` -- range slider for zoom in y direction + - ``plot_points`` -- number of points to plot + - ``dpi`` -- dots-per-inch parameter for the plot EXAMPLES: @@ -1554,7 +1587,7 @@ def cellular_automaton( N=slider(1,500,1,label='Number of iterations',default=100), rule_number=slider(0, 255, 1, default=110, label='Rule number'), size = slider(1, 11, step_size=1, default=6, label='size of graphic')): - """ + r""" Yields a matrix showing the evolution of a `Wolfram's cellular automaton `_. @@ -1562,9 +1595,9 @@ def cellular_automaton( INPUT: - - ``N`` -- iterations - - ``rule_number`` -- rule number (0 to 255) - - ``size`` -- size of the shown picture + - ``N`` -- iterations + - ``rule_number`` -- rule number (0 to 255) + - ``size`` -- size of the shown picture EXAMPLES: @@ -1609,7 +1642,7 @@ def polar_prime_spiral( show_curves = True, n = slider(1,200, 1, default=89, label="number $n$"), dpi = slider(10,300, 10, default=100, label="dpi")): - """ + r""" Polar Prime Spiral interact, based on work by David Runde. For more information about the factors in the spiral, @@ -1617,12 +1650,12 @@ def polar_prime_spiral( INPUT: - - ``interval`` -- range slider to specify start and end - - ``show_factors`` -- if true, show factors - - ``highlight_primes`` -- if true, prime numbers are highlighted - - ``show_curves`` -- if true, curves are plotted - - ``n`` -- number `n` - - ``dpi`` -- dots per inch resolution for plotting + - ``interval`` -- range slider to specify start and end + - ``show_factors`` -- if true, show factors + - ``highlight_primes`` -- if true, prime numbers are highlighted + - ``show_curves`` -- if true, curves are plotted + - ``n`` -- number `n` + - ``dpi`` -- dots per inch resolution for plotting EXAMPLES: @@ -1670,38 +1703,43 @@ def polar_prime_spiral( list2 = [] if not show_factors: for i in srange(start, end, include_endpoint = True): - if Integer(i).is_pseudoprime(): list.append(f(i-start+1)) #Primes list - else: list2.append(f(i-start+1)) #Composites list + if Integer(i).is_pseudoprime(): + list.append(f(i-start+1)) # primes list + else: + list2.append(f(i-start+1)) # composites list P = points(list) - R = points(list2, alpha = .1) #Faded Composites + R = points(list2, alpha=.1) # faded composites else: for i in srange(start, end, include_endpoint = True): - list.append(disk((f(i-start+1)),0.05*pow(2,len(factor(i))-1), (0,2*pi))) #resizes each of the dots depending of the number of factors of each number - if Integer(i).is_pseudoprime() and highlight_primes: list2.append(f(i-start+1)) + # Resize each of the dots depending of the number of factors of each number + list.append(disk((f(i-start+1)),0.05*pow(2,len(factor(i))-1), (0,2*pi))) + if Integer(i).is_pseudoprime() and highlight_primes: + list2.append(f(i-start+1)) P = Graphics() for g in list: P += g - p_size = 5 #the orange dot size of the prime markers - if not highlight_primes: list2 = [(f(n-start+1))] + p_size = 5 # the orange dot size of the prime markers + if not highlight_primes: + list2 = [(f(n-start+1))] R = points(list2, hue = .1, pointsize = p_size) if n > 0: html('$n = %s$' % factor(n)) p = 1 - #The X which marks the given n + # The X which marks the given n W1 = disk((f(n-start+1)), p, (pi/6, 2*pi/6), alpha=.1) W2 = disk((f(n-start+1)), p, (4*pi/6, 5*pi/6), alpha=.1) W3 = disk((f(n-start+1)), p, (7*pi/6, 8*pi/6), alpha=.1) W4 = disk((f(n-start+1)), p, (10*pi/6, 11*pi/6), alpha=.1) Q = W1 + W2 + W3 + W4 - n = n - start +1 #offsets the n for different start values to ensure accurate plotting + n -= start - 1 # offset n for different start values to ensure accurate plotting if show_curves: begin_curve = 0 t = SR.var('t') - a=1.0 - b=0.0 + a = 1.0 + b = 0.0 S = int(sqrt(n)) if n <= S * (S + 1): c = n - S**2 @@ -1715,16 +1753,20 @@ def polar_prime_spiral( r = symbolic_expression(sqrt(g(m))).function(m) theta = symbolic_expression(r(m)- m*sqrt(a)).function(m) S1 = parametric_plot(((r(t))*cos(2*pi*(theta(t))),(r(t))*sin(2*pi*(theta(t)))), - (begin_curve, ceil(sqrt(end-start))), color=hue(0.8), thickness = .3) #Pink Line + (begin_curve, ceil(sqrt(end-start))), + color=hue(0.8), thickness=.3) # pink line b = 1 - c = c2; + c = c2 g = symbolic_expression(a*m**2+b*m+c).function(m) r = symbolic_expression(sqrt(g(m))).function(m) theta = symbolic_expression(r(m)- m*sqrt(a)).function(m) S2 = parametric_plot(((r(t))*cos(2*pi*(theta(t))),(r(t))*sin(2*pi*(theta(t)))), - (begin_curve, ceil(sqrt(end-start))), color=hue(0.6), thickness = .3) #Green Line + (begin_curve, ceil(sqrt(end-start))), + color=hue(0.6), thickness=.3) # green line - show(R+P+S1+S2+Q, aspect_ratio = 1, axes = False, dpi = dpi) - else: show(R+P+Q, aspect_ratio = 1, axes = False, dpi = dpi) - else: show(R+P, aspect_ratio = 1, axes = False, dpi = dpi) + show(R+P+S1+S2+Q, aspect_ratio=1, axes=False, dpi=dpi) + else: + show(R+P+Q, aspect_ratio=1, axes=False, dpi=dpi) + else: + show(R+P, aspect_ratio=1, axes=False, dpi=dpi) diff --git a/src/sage/interfaces/axiom.py b/src/sage/interfaces/axiom.py index 361b13c5a56..22ed05c8606 100644 --- a/src/sage/interfaces/axiom.py +++ b/src/sage/interfaces/axiom.py @@ -470,9 +470,8 @@ def _eval_line(self, line, reformat=True, allow_use_file=False, if line[i:] == "": i = 0 outs = outs[1:] - break; - out = "\n".join(line[i:] for line in outs[1:]) - return out + break + return "\n".join(line[i:] for line in outs[1:]) # define relational operators def _equality_symbol(self): diff --git a/src/sage/interfaces/chomp.py b/src/sage/interfaces/chomp.py index 1416a79f70d..00f4b3f93b3 100644 --- a/src/sage/interfaces/chomp.py +++ b/src/sage/interfaces/chomp.py @@ -134,8 +134,8 @@ def __call__(self, program, complex, subcomplex=None, **kwds): {0: 0, 1: Z x Z, 2: Z} """ from sage.misc.temporary_file import tmp_filename - from sage.homology.all import CubicalComplex, cubical_complexes - from sage.homology.all import SimplicialComplex, Simplex + from sage.topology.cubical_complex import CubicalComplex, cubical_complexes + from sage.topology.simplicial_complex import SimplicialComplex, Simplex from sage.homology.chain_complex import HomologyGroup from subprocess import Popen, PIPE from sage.rings.all import QQ, ZZ @@ -476,7 +476,7 @@ def homsimpl(complex=None, subcomplex=None, **kwds): sage: homsimpl(S1.join(S1), generators=True, base_ring=GF(2))[3][1] # optional - CHomP [('L0', 'L1', 'R0', 'R1') + ('L0', 'L1', 'R0', 'R2') + ('L0', 'L1', 'R1', 'R2') + ('L0', 'L2', 'R0', 'R1') + ('L0', 'L2', 'R0', 'R2') + ('L0', 'L2', 'R1', 'R2') + ('L1', 'L2', 'R0', 'R1') + ('L1', 'L2', 'R0', 'R2') + ('L1', 'L2', 'R1', 'R2')] """ - from sage.homology.all import SimplicialComplex + from sage.topology.simplicial_complex import SimplicialComplex help = kwds.get('help', False) if help: return CHomP().help('homsimpl') @@ -529,7 +529,7 @@ def homcubes(complex=None, subcomplex=None, **kwds): sage: homcubes(cubical_complexes.Sphere(1), generators=True, base_ring=GF(2))[1][1] # optional - CHomP [[[1,1] x [0,1]] + [[0,1] x [1,1]] + [[0,1] x [0,0]] + [[0,0] x [0,1]]] """ - from sage.homology.all import CubicalComplex + from sage.topology.cubical_complex import CubicalComplex help = kwds.get('help', False) if help: return CHomP().help('homcubes') @@ -648,7 +648,7 @@ def process_generators_cubical(gen_string, dim): sage: len(process_generators_cubical(s, 1)) # only one generator 1 """ - from sage.homology.cubical_complex import Cube + from sage.topology.cubical_complex import Cube # each dim in gen_string starts with "The generator for # H_3 follows:". So search for "T" to find the # end of the current list of generators. @@ -751,7 +751,7 @@ def process_generators_simplicial(gen_string, dim, complex): sage: process_generators_simplicial(s, 1, simplicial_complexes.Torus()) [[(-1, (1, 6)), (1, (1, 4))]] """ - from sage.homology.all import Simplex + from sage.topology.simplicial_complex import Simplex # each dim in gen_string starts with "The generator for H_3 # follows:". So search for "T" to find the end of the current # list of generators. diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py index bfd2817cc53..055b7c4b79d 100644 --- a/src/sage/interfaces/expect.py +++ b/src/sage/interfaces/expect.py @@ -1453,7 +1453,8 @@ class ExpectElement(InterfaceElement): def __init__(self, parent, value, is_name=False, name=None): RingElement.__init__(self, parent) self._create = value - if parent is None: return # means "invalid element" + if parent is None: + return # means "invalid element" # idea: Joe Wetherell -- try to find out if the output # is too long and if so get it using file, otherwise # don't. diff --git a/src/sage/interfaces/fricas.py b/src/sage/interfaces/fricas.py index 9c5f201a073..a73330eeda3 100644 --- a/src/sage/interfaces/fricas.py +++ b/src/sage/interfaces/fricas.py @@ -1461,6 +1461,23 @@ def _sage_expression(fricas_InputForm): sage: fricas(s).sage() # optional - fricas 1/3840*n^10 - 5/2304*n^9 + 5/1152*n^8 + 31/5760*n^7 - 229/11520*n^6 - 5/2304*n^5 + 1/36*n^4 - 1/960*n^3 - 1/80*n^2 + Some checks for digamma and polygamma (:trac:`31853`):: + + sage: fricas.digamma(1.0) # optional - fricas + - 0.5772156649_0153286061 + sage: psi(1.0) + -0.577215664901533 + sage: fricas.polygamma(1, 1.0) # optional - fricas + 1.6449340668482269 + sage: psi(1, 1).n() + 1.64493406684823 + + sage: var("w") + w + sage: fricas.laplace(log(x), x, w).sage() # optional - fricas + -(euler_gamma + log(w))/w + sage: fricas(laplace(log(x), x, w)).sage() # optional - fricas + -(euler_gamma + log(w))/w Check that :trac:`25224` is fixed:: @@ -1569,6 +1586,15 @@ def _sage_expression(fricas_InputForm): 0 sage: fricas(A).D(x).sage() - diff(A, x) # optional - fricas 0 + + Check that :trac:`31858` is fixed:: + + sage: fricas.Gamma(3/2).sage() # optional - fricas + 1/2*sqrt(pi) + sage: fricas.Gamma(3/4).sage() # optional - fricas + gamma(3/4) + sage: fricas.Gamma(3, 2).sage() # optional - fricas + gamma(3, 2) """ from sage.libs.pynac.pynac import register_symbol from sage.symbolic.constants import e, pi, I @@ -1577,9 +1603,10 @@ def _sage_expression(fricas_InputForm): from sage.functions.trig import sin, cos, tan, cot, sec, csc from sage.functions.hyperbolic import tanh, sinh, cosh, coth, sech, csch from sage.functions.other import abs + from sage.functions.gamma import gamma from sage.misc.functional import symbolic_sum, symbolic_prod from sage.rings.infinity import infinity - register_symbol(I, {'fricas': '%i'}) + register_symbol(I, {'fricas': '(%i::EXPR Complex INT)'}) register_symbol(e, {'fricas': '%e'}) register_symbol(pi, {'fricas': 'pi'}) # fricas uses both pi and %pi register_symbol(lambda: infinity, {'fricas': 'infinity'}) @@ -1597,6 +1624,7 @@ def _sage_expression(fricas_InputForm): register_symbol(coth, {'fricas': 'coth'}) register_symbol(sech, {'fricas': 'sech'}) register_symbol(csch, {'fricas': 'csch'}) + register_symbol(gamma, {'fricas': 'Gamma'}) register_symbol(lambda x, y: x + y, {'fricas': '+'}) register_symbol(lambda x, y: x - y, {'fricas': '-'}) register_symbol(lambda x, y: x * y, {'fricas': '*'}) @@ -1729,7 +1757,7 @@ def _sage_(self): We can also convert FriCAS's polynomials to Sage polynomials:: - sage: a = fricas(x^2 + 1); a.typeOf() # optional - fricas + sage: a = fricas("x^2 + 1"); a.typeOf() # optional - fricas Polynomial(Integer) sage: a.sage() # optional - fricas x^2 + 1 diff --git a/src/sage/interfaces/frobby.py b/src/sage/interfaces/frobby.py index 69a5a3c2870..ed852f74efe 100644 --- a/src/sage/interfaces/frobby.py +++ b/src/sage/interfaces/frobby.py @@ -396,7 +396,7 @@ def _parse_4ti2_matrix(self, string): for i in range(term_count): exponents.append(ints[:var_count]) ints = ints[var_count:] - return exponents; + return exponents def _ideal_to_string(self, monomial_ideal): r""" @@ -424,10 +424,10 @@ def _ideal_to_string(self, monomial_ideal): if monomial_ideal.is_zero(): gens = [] else: - gens = monomial_ideal.gens(); - var_count = monomial_ideal.ring().ngens(); + gens = monomial_ideal.gens() + var_count = monomial_ideal.ring().ngens() first_row = str(len(gens)) + ' ' + str(var_count) + '\n' - rows = [self._monomial_to_string(_) for _ in gens]; + rows = [self._monomial_to_string(_) for _ in gens] return first_row + "".join(rows) def _monomial_to_string(self, monomial): diff --git a/src/sage/interfaces/gap.py b/src/sage/interfaces/gap.py index 5855b7cf650..9937c8437db 100644 --- a/src/sage/interfaces/gap.py +++ b/src/sage/interfaces/gap.py @@ -630,15 +630,15 @@ def _execute_line(self, line, wait_for_prompt=True, expect_eof=False): elif x == 5: # @c completion, doesn't seem to happen when -p is in use warnings.warn("I didn't think GAP could do this") elif x == 6: # @f GAP error message - current_outputs = error_outputs; + current_outputs = error_outputs elif x == 7: # @h help text, but this stopped happening with new help warnings.warn("I didn't think GAP could do this") elif x == 8: # @i awaiting normal input - break; + break elif x == 9: # @m finished running a child pass # there is no need to do anything elif x==10: #@n normal output line - current_outputs = normal_outputs; + current_outputs = normal_outputs elif x==11: #@r echoing input current_outputs = terminal_echo elif x==12: #@sN shouldn't happen diff --git a/src/sage/interfaces/giac.py b/src/sage/interfaces/giac.py index d90413e5335..e77baa0a106 100644 --- a/src/sage/interfaces/giac.py +++ b/src/sage/interfaces/giac.py @@ -301,8 +301,8 @@ class Giac(Expect): :: sage: R. = QQ[]; f = (2+a+b) - sage: p = giac.gcd(f^3+5*f^5,f^2+f^5); p; R(p) - a^2+2*a*b+4*a+b^2+4*b+4 + sage: p = giac.gcd(f^3+5*f^5,f^2+f^5); p; R(p.sage()) + sageVARa^2+2*sageVARa*sageVARb+4*sageVARa+sageVARb^2+4*sageVARb+4 a^2 + 2*a*b + b^2 + 4*a + 4*b + 4 Variable names in python and giac are independent:: @@ -605,9 +605,12 @@ def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if TESTS:: - sage: h='int(1/x*((-2*x^(1/3)+1)^(1/4))^3,x)' - sage: giac(h) - 12*(...) + sage: h1 = 'int(sin(x)^2, x)' + sage: h2 = 'int(cos(x)^2, x)' + sage: giac_result = giac(h1) + giac(h2) + sage: bool(giac_result.sage() == x) + True + """ with gc_disabled(): z = Expect._eval_line(self, line, allow_use_file=allow_use_file, @@ -1115,9 +1118,17 @@ def _sage_(self, locals={}): sage: giac('true')._sage_(), giac('false')._sage_() (True, False) + + Check that variables and constants are not mixed up (:trac:`30133`):: + + sage: ee, ii, pp = SR.var('e,i,pi') + sage: giac(ee * ii * pp).sage().variables() + (e, i, pi) + sage: giac(e * i * pi).sage().variables() + () """ from sage.libs.pynac.pynac import symbol_table - from sage.calculus.calculus import symbolic_expression_from_string + from sage.calculus.calculus import symbolic_expression_from_string, SR_parser_giac result = repr(self) # string representation @@ -1130,7 +1141,7 @@ def _sage_(self, locals={}): try: return symbolic_expression_from_string(result, lsymbols, - accept_sequence=True) + accept_sequence=True, parser=SR_parser_giac) except Exception: raise NotImplementedError("Unable to parse Giac output: %s" % result) diff --git a/src/sage/interfaces/interface.py b/src/sage/interfaces/interface.py index 66f57641c00..3a0e4e9ff70 100644 --- a/src/sage/interfaces/interface.py +++ b/src/sage/interfaces/interface.py @@ -332,6 +332,16 @@ def _coerce_from_special_method(self, x): return self(x._interface_init_()) def _coerce_impl(self, x, use_special=True): + r""" + Coerce pure Python types via corresponding Sage objects. + + TESTS: + + Check that python type ``complex`` can be converted (:trac:`31775`):: + + sage: giac(complex(I))**2 # should not return `j^2` + -1 + """ if isinstance(x, bool): return self(self._true_symbol() if x else self._false_symbol()) elif isinstance(x, int): @@ -340,6 +350,9 @@ def _coerce_impl(self, x, use_special=True): elif isinstance(x, float): import sage.rings.all return self(sage.rings.all.RDF(x)) + elif isinstance(x, complex): + import sage.rings.all + return self(sage.rings.all.CDF(x)) if use_special: try: return self._coerce_from_special_method(x) @@ -1169,7 +1182,7 @@ def _repr_(self): 2 sage: x = var('x') sage: giac(x) - x + sageVARx sage: giac(5) 5 sage: M = matrix(QQ,2,range(4)) diff --git a/src/sage/interfaces/kenzo.py b/src/sage/interfaces/kenzo.py index b501749cb89..767d7525bd5 100644 --- a/src/sage/interfaces/kenzo.py +++ b/src/sage/interfaces/kenzo.py @@ -35,12 +35,11 @@ from sage.matrix.all import matrix from sage.homology.chain_complex import ChainComplex -from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet +from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet from sage.libs.ecl import EclObject, ecl_eval, EclListIterator from sage.features.kenzo import Kenzo - # defining the auxiliary functions as wrappers over the kenzo ones kenzo_names = ['add', 'array-dimensions', @@ -107,7 +106,12 @@ # example __sphere__ is defined as EclObject("sphere"). Hyphens # are replaced with underscores to get valid Python identifiers. if Kenzo().is_present(): - ecl_eval("(require :kenzo)") + from sage.env import KENZO_FAS + if KENZO_FAS: + ecl_eval("(require :kenzo \"{}\")".format(KENZO_FAS)) + else: + ecl_eval("(require :kenzo)") + ecl_eval("(in-package :cat)") ecl_eval("(setf *HOMOLOGY-VERBOSE* nil)") for s in kenzo_names: @@ -1190,7 +1194,7 @@ def KAbstractSimplex(simplex): EXAMPLES:: - sage: from sage.homology.simplicial_set import AbstractSimplex + sage: from sage.topology.simplicial_set import AbstractSimplex sage: from sage.interfaces.kenzo import KAbstractSimplex,\ ....: SAbstractSimplex # optional - kenzo sage: SAbSm = AbstractSimplex(1, (2,0,3,2,1), name = 'SAbSm') # optional - kenzo @@ -1219,7 +1223,7 @@ def KFiniteSimplicialSet(sset): EXAMPLES:: - sage: from sage.homology.simplicial_set import AbstractSimplex, SimplicialSet + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet sage: from sage.interfaces.kenzo import KFiniteSimplicialSet # optional - kenzo sage: s0 = AbstractSimplex(0, name='s0') sage: s1 = AbstractSimplex(0, name='s1') @@ -1241,7 +1245,7 @@ def KFiniteSimplicialSet(sset): sage: KS1vS3.homology(3) # optional - kenzo Z """ - from sage.homology.simplicial_set_constructions import ProductOfSimplicialSets + from sage.topology.simplicial_set_constructions import ProductOfSimplicialSets if isinstance(sset, ProductOfSimplicialSets): f0 = KFiniteSimplicialSet(sset.factor(0)) for f1 in sset.factors()[1:]: @@ -1287,7 +1291,7 @@ def SFiniteSimplicialSet(ksimpset, limit): EXAMPLES:: - sage: from sage.homology.simplicial_set import SimplicialSet + sage: from sage.topology.simplicial_set import SimplicialSet sage: from sage.interfaces.kenzo import AbstractSimplex,\ ....: KFiniteSimplicialSet, SFiniteSimplicialSet, Sphere # optional - kenzo sage: s0 = AbstractSimplex(0, name='s0') # optional - kenzo diff --git a/src/sage/interfaces/latte.py b/src/sage/interfaces/latte.py index 776518dc388..6b130a32776 100644 --- a/src/sage/interfaces/latte.py +++ b/src/sage/interfaces/latte.py @@ -432,12 +432,21 @@ def to_latte_polynomial(polynomial): sage: to_latte_polynomial(f) '[[3, [2, 4, 6]], [7, [0, 3, 5]]]' + sage: to_latte_polynomial(x.parent().zero()) + '[]' + Testing a univariate polynomial:: sage: x = polygen(QQ, 'x') sage: to_latte_polynomial((x-1)^2) '[[1, [0]], [-2, [1]], [1, [2]]]' + + sage: to_latte_polynomial(x.parent().zero()) + '[]' """ + if polynomial == 0: + return str([]) + from sage.rings.polynomial.polydict import ETuple coefficients_list = polynomial.coefficients() @@ -450,8 +459,8 @@ def to_latte_polynomial(polynomial): exponents_list = [[exponent_vector_i] for exponent_vector_i in polynomial.exponents()] # assuming that the order in coefficients() and exponents() methods match - monomials_list = zip(coefficients_list, exponents_list) - monomials_list = [list(monomial_i) for monomial_i in monomials_list] - monomials_list = str(monomials_list) + monomials_list = [list(monomial_i) + for monomial_i + in zip(coefficients_list, exponents_list)] - return monomials_list + return str(monomials_list) diff --git a/src/sage/interfaces/maxima.py b/src/sage/interfaces/maxima.py index a3192fffa3f..db362ae0997 100644 --- a/src/sage/interfaces/maxima.py +++ b/src/sage/interfaces/maxima.py @@ -839,7 +839,8 @@ def _synchronize(self): 4 """ marker = '__SAGE_SYNCHRO_MARKER_' - if self._expect is None: return + if self._expect is None: + return r = randrange(2147483647) s = marker + str(r+1) diff --git a/src/sage/interfaces/maxima_abstract.py b/src/sage/interfaces/maxima_abstract.py index 1200c2fa0c2..d8102801ba8 100644 --- a/src/sage/interfaces/maxima_abstract.py +++ b/src/sage/interfaces/maxima_abstract.py @@ -1766,7 +1766,7 @@ def _latex_(self): sage: y,d = var('y,d') sage: f = function('f') sage: latex(maxima(derivative(f(x*y), x))) - \left(\left.{{{\it \partial}}\over{{\it \partial}\, {\it t}_{0}}}\,f\left({\it t}_{0}\right) \right|_{{\it t}_{0}={\it x}\, {\it y}}\right)\,{\it y} + \left(\left.{{{\it \partial}}\over{{\it \partial}\, {\it \_symbol}_{0}}}\,f\left( {\it \_symbol}_{0}\right)\right|_{ {\it \_symbol}_{0}={\it x}\, {\it y}}\right)\,{\it y} sage: latex(maxima(derivative(f(x,y,d), d,x,x,y))) {{{\it \partial}^4}\over{{\it \partial}\,{\it d}\, {\it \partial}\,{\it x}^2\,{\it \partial}\, {\it y}}}\,f\left({\it x} , {\it y} , {\it d}\right) sage: latex(maxima(d/(d-2))) diff --git a/src/sage/interfaces/maxima_lib.py b/src/sage/interfaces/maxima_lib.py index 40367c52426..14be2195f5c 100644 --- a/src/sage/interfaces/maxima_lib.py +++ b/src/sage/interfaces/maxima_lib.py @@ -99,7 +99,7 @@ ## We begin here by initializing Maxima in library mode ## i.e. loading it into ECL ecl_eval("(setf *load-verbose* NIL)") -if MAXIMA_FAS is not None: +if MAXIMA_FAS: ecl_eval("(require 'maxima \"{}\")".format(MAXIMA_FAS)) else: ecl_eval("(require 'maxima)") @@ -872,7 +872,7 @@ def sr_sum(self,*args): """ try: - return max_to_sr(maxima_eval([[max_ratsimp],[[max_simplify_sum],([max_sum],[sr_to_max(SR(a)) for a in args])]])); + return max_to_sr(maxima_eval([[max_ratsimp],[[max_simplify_sum],([max_sum],[sr_to_max(SR(a)) for a in args])]])) except RuntimeError as error: s = str(error) if "divergent" in s: @@ -900,17 +900,16 @@ def sr_prod(self,*args): """ try: - return max_to_sr(maxima_eval([[max_ratsimp],[[max_simplify_prod],([max_prod],[sr_to_max(SR(a)) for a in args])]])); + return max_to_sr(maxima_eval([[max_ratsimp],[[max_simplify_prod],([max_prod],[sr_to_max(SR(a)) for a in args])]])) except RuntimeError as error: s = str(error) if "divergent" in s: raise ValueError("Product is divergent.") - elif "Is" in s: # Maxima asked for a condition + elif "Is" in s: # Maxima asked for a condition self._missing_assumption(s) else: raise - def sr_limit(self, expr, v, a, dir=None): """ Helper function to wrap calculus use of Maxima's limits. @@ -1581,10 +1580,12 @@ def sr_to_max(expr): # An evaluated derivative of the form f'(1) is not a # symbolic variable, yet we would like to treat it # like one. So, we replace the argument `1` with a - # temporary variable e.g. `t0` and then evaluate the - # derivative f'(t0) symbolically at t0=1. See trac - # #12796. - temp_args = [SR.var("t%s"%i) for i in range(len(args))] + # temporary variable e.g. `_symbol0` and then evaluate + # the derivative f'(_symbol0) symbolically at + # _symbol0=1. See trac #12796. Note that we cannot use + # SR.temp_var here since two conversions of the same + # expression have to be equal. + temp_args = [SR.symbol("_symbol%s"%i) for i in range(len(args))] f = sr_to_max(op.function()(*temp_args)) params = op.parameter_set() deriv_max = [[mdiff],f] diff --git a/src/sage/interfaces/qepcad.py b/src/sage/interfaces/qepcad.py index 4d61ea1306e..222be41d285 100644 --- a/src/sage/interfaces/qepcad.py +++ b/src/sage/interfaces/qepcad.py @@ -1214,7 +1214,8 @@ def make_cells(self, text): for line in lines: if 'Information about the cell' in line: in_cell = True - if in_cell: cell_lines.append(line) + if in_cell: + cell_lines.append(line) if line == '----------------------------------------------------': cells.append(QepcadCell(self, cell_lines)) cell_lines = [] @@ -1285,10 +1286,12 @@ def _eval_line(self, cmd, restart_if_needed=False): result = self._qex._eval_line(cmd + ' &') nl = result.find('\n') - if nl < 0: nl = len(result) + if nl < 0: + nl = len(result) amp = result.find('&', 0, nl) - if amp > 0: result = result[amp+1:] + if amp > 0: + result = result[amp+1:] result = result.strip() @@ -1802,15 +1805,23 @@ def _normalize_op(self, op): '=' """ import operator - if op == operator.eq: return '=' - if op == operator.ne: return '/=' - if op == operator.lt: return '<' - if op == operator.gt: return '>' - if op == operator.le: return '<=' - if op == operator.ge: return '>=' - - if op == '==': return '=' - if op == '!=': return '/=' + if op == operator.eq: + return '=' + if op == operator.ne: + return '/=' + if op == operator.lt: + return '<' + if op == operator.gt: + return '>' + if op == operator.le: + return '<=' + if op == operator.ge: + return '>=' + + if op == '==': + return '=' + if op == '!=': + return '/=' return op diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py index 49378c689ba..1544af8026b 100644 --- a/src/sage/interfaces/r.py +++ b/src/sage/interfaces/r.py @@ -444,7 +444,7 @@ def _list_vector(vec): '_Names': rpy2py(names), # We don't give the rclass here because the old expect interface # didn't do that either and we want to maintain compatibility. - }; + } rpy2py.register(ListSexpVector, _list_vector) return cv diff --git a/src/sage/interfaces/singular.py b/src/sage/interfaces/singular.py index bc29515ccba..89b687d3182 100644 --- a/src/sage/interfaces/singular.py +++ b/src/sage/interfaces/singular.py @@ -1365,7 +1365,8 @@ def __init__(self, parent, type, value, is_name=False): 2 """ RingElement.__init__(self, parent) - if parent is None: return + if parent is None: + return if not is_name: try: self._name = parent._create(value, type) diff --git a/src/sage/interfaces/sympy.py b/src/sage/interfaces/sympy.py index b7419ab9f47..a64a58c8fa2 100644 --- a/src/sage/interfaces/sympy.py +++ b/src/sage/interfaces/sympy.py @@ -748,6 +748,99 @@ def _sympysage_crootof(self): from sage.symbolic.ring import SR return complex_root_of(self.args[0]._sage_(), SR(self.args[1])) +def _sympysage_matrix(self): + """ + Convert SymPy matrix ``self`` to Sage. + + EXAMPLES:: + + sage: from sympy.matrices import Matrix, SparseMatrix, ImmutableMatrix + sage: from sage.interfaces.sympy import sympy_init + sage: from sympy.abc import x + sage: sympy_init() + sage: sM = Matrix([[1, x + 1], [x - 1, 1]]); sM + Matrix([ + [ 1, x + 1], + [x - 1, 1]]) + sage: M = sM._sage_(); M + [ 1 x + 1] + [x - 1 1] + sage: M.parent() + Full MatrixSpace of 2 by 2 dense matrices over Symbolic Ring + + sage: sN = SparseMatrix.eye(3); sN + Matrix([ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + sage: N = sN._sage_(); N + [1 0 0] + [0 1 0] + [0 0 1] + sage: N.parent() + Full MatrixSpace of 3 by 3 sparse matrices over Integer Ring + + sage: sO = SparseMatrix.zeros(3); sO + Matrix([ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0]]) + sage: O = sO._sage_(); O + [0 0 0] + [0 0 0] + [0 0 0] + sage: O.parent() + Full MatrixSpace of 3 by 3 sparse matrices over Integer Ring + + If ``self`` is immutable, the result is cached:: + + sage: sImmM = ImmutableMatrix([[1, x + 1], [x - 1, 1]]); sImmM + Matrix([ + [ 1, x + 1], + [x - 1, 1]]) + sage: ImmM = sImmM._sage_(); ImmM + [ 1 x + 1] + [x - 1 1] + sage: ImmM is sImmM._sage_() + True + + If ``self`` is mutable, the conversion is redone every time:: + + sage: sM[0, 0] = 1000 + sage: MutatedM = sM._sage_(); MutatedM + [ 1000 x + 1] + [x - 1 1] + sage: M == MutatedM + False + + """ + try: + return self._sage_object + except AttributeError: + from sympy.matrices import SparseMatrix, ImmutableMatrix + from sage.matrix.constructor import matrix + + rows, cols = self.shape + d = {row_col: value._sage_() + for row_col, value in self.todok().items()} + if not d: + from sage.rings.integer_ring import ZZ + base_ring = ZZ + else: + from sage.structure.element import get_coercion_model + from sage.symbolic.ring import SR + coercion_model = get_coercion_model() + try: + base_ring = coercion_model.common_parent(*d.values()) + except TypeError: # no common canonical parent + base_ring = SR + result = matrix(base_ring, rows, cols, d, + sparse=isinstance(self, SparseMatrix), + immutable=True) + if isinstance(self, ImmutableMatrix): + self._sage_object = result + return result + def _sympysage_relational(self): """ EXAMPLES:: @@ -846,6 +939,7 @@ def sympy_init(): from sympy.integrals.integrals import Integral from sympy.polys.rootoftools import CRootOf from sympy.series.order import Order + from sympy.matrices import ImmutableMatrix, ImmutableSparseMatrix, Matrix, SparseMatrix Float._sage_ = _sympysage_float Integer._sage_ = _sympysage_integer @@ -854,6 +948,10 @@ def sympy_init(): NegativeInfinity._sage_ = _sympysage_ninfty ComplexInfinity._sage_ = _sympysage_uinfty sympy_nan._sage_ = _sympysage_nan + ImmutableMatrix._sage_ = _sympysage_matrix + ImmutableSparseMatrix._sage_ = _sympysage_matrix + Matrix._sage_ = _sympysage_matrix + SparseMatrix._sage_ = _sympysage_matrix Relational._sage_ = _sympysage_relational Exp1._sage_ = _sympysage_e Pi._sage_ = _sympysage_pi diff --git a/src/sage/interfaces/sympy_wrapper.py b/src/sage/interfaces/sympy_wrapper.py new file mode 100644 index 00000000000..00f7b65dd27 --- /dev/null +++ b/src/sage/interfaces/sympy_wrapper.py @@ -0,0 +1,175 @@ +""" +Wrapper Class for Sage Sets as SymPy Sets +""" + +# **************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# 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 sympy.core.basic import Basic +from sympy.core.decorators import sympify_method_args +from sympy.core.sympify import sympify +from sympy.sets.sets import Set + + +@sympify_method_args +class SageSet(Set): + r""" + Wrapper for a Sage set providing the SymPy Set API. + + Parents in the category :class:`sage.categories.sets_cat.Sets`, unless + a more specific method is implemented, convert to SymPy by creating + an instance of this class. + + EXAMPLES:: + + sage: F = Family([2, 3, 5, 7]); F + Family (2, 3, 5, 7) + sage: sF = F._sympy_(); sF # indirect doctest + SageSet(Family (2, 3, 5, 7)) + sage: sF._sage_() is F + True + sage: bool(sF) + True + sage: len(sF) + 4 + sage: list(sF) + [2, 3, 5, 7] + sage: sF.is_finite_set + True + """ + + def __new__(cls, sage_set): + r""" + Construct a wrapper for a Sage set. + + TESTS:: + + sage: from sage.interfaces.sympy_wrapper import SageSet + sage: F = Set([1, 2]); F + {1, 2} + sage: sF = SageSet(F); sF + SageSet({1, 2}) + """ + return Basic.__new__(cls, sage_set) + + def _sage_(self): + r""" + Return the underlying Sage set of the wrapper ``self``. + + EXAMPLES:: + + sage: F = Set([1, 2]) + sage: F is Set([1, 2]) + False + sage: sF = F._sympy_(); sF + SageSet({1, 2}) + sage: sF._sage_() is F + True + """ + return self._args[0] + + @property + def is_empty(self): + r""" + Return whether the set ``self`` is empty. + + EXAMPLES:: + + sage: Empty = Set([]) + sage: sEmpty = Empty._sympy_() + sage: sEmpty.is_empty + True + """ + return self._sage_().is_empty() + + @property + def is_finite_set(self): + r""" + Return whether the set ``self`` is finite. + + EXAMPLES:: + + sage: W = WeylGroup(["A",1,1]) + sage: sW = W._sympy_(); sW + SageSet(Weyl Group of type ['A', 1, 1] (as a matrix group acting on the root space)) + sage: sW.is_finite_set + False + """ + return self._sage_().is_finite() + + @property + def is_iterable(self): + r""" + Return whether the set ``self`` is iterable. + + EXAMPLES:: + + sage: W = WeylGroup(["A",1,1]) + sage: sW = W._sympy_(); sW + SageSet(Weyl Group of type ['A', 1, 1] (as a matrix group acting on the root space)) + sage: sW.is_iterable + True + """ + from sage.categories.enumerated_sets import EnumeratedSets + return self._sage_() in EnumeratedSets() + + def __iter__(self): + r""" + Iterator for the set ``self``. + + EXAMPLES:: + + sage: sPrimes = Primes()._sympy_(); sPrimes + SageSet(Set of all prime numbers: 2, 3, 5, 7, ...) + sage: iter_sPrimes = iter(sPrimes) + sage: next(iter_sPrimes), next(iter_sPrimes), next(iter_sPrimes) + (2, 3, 5) + """ + for element in self._sage_(): + yield sympify(element) + + def _contains(self, element): + """ + Return whether ``element`` is an element of the set ``self``. + + EXAMPLES:: + + sage: sPrimes = Primes()._sympy_(); sPrimes + SageSet(Set of all prime numbers: 2, 3, 5, 7, ...) + sage: 91 in sPrimes + False + + sage: from sympy.abc import p + sage: sPrimes.contains(p) + Contains(p, SageSet(Set of all prime numbers: 2, 3, 5, 7, ...)) + + sage: p in sPrimes + Traceback (most recent call last): + ... + TypeError: did not evaluate to a bool: None + + """ + if element.is_symbol: + # keep symbolic + return None + return element in self._sage_() + + def __len__(self): + """ + Return the cardinality of the finite set ``self``. + + EXAMPLES:: + + sage: sB3 = WeylGroup(["B", 3])._sympy_(); sB3 + SageSet(Weyl Group of type ['B', 3] (as a matrix group acting on the ambient space)) + sage: len(sB3) + 48 + """ + return len(self._sage_()) diff --git a/src/sage/interfaces/tests.py b/src/sage/interfaces/tests.py index 7d1ca00182d..7e1fa9d68db 100644 --- a/src/sage/interfaces/tests.py +++ b/src/sage/interfaces/tests.py @@ -23,7 +23,7 @@ Singular Test that write errors to stderr are handled gracefully by GAP -(see :trac:`13211`) and ECL (see :trac:`14426`) and other interfaces:: +(see :trac:`13211`) and other interfaces:: sage: import subprocess sage: try: @@ -31,8 +31,6 @@ ....: except IOError: ....: f = open('/dev/null', 'w') sage: kwds = dict(shell=True, stdout=f, stderr=f) - sage: subprocess.call("echo syntax error | ecl", **kwds) in (0, 255) - True sage: subprocess.call("echo syntax error | gap", **kwds) in (0, 1) True sage: subprocess.call("echo syntax error | gp", **kwds) diff --git a/src/sage/knots/all.py b/src/sage/knots/all.py index a3daecd2fa5..02223efef57 100644 --- a/src/sage/knots/all.py +++ b/src/sage/knots/all.py @@ -1,4 +1,7 @@ from sage.misc.lazy_import import lazy_import +from sage.features.databases import DatabaseKnotInfo lazy_import('sage.knots.knot', ['Knot', 'Knots']) lazy_import('sage.knots.link', 'Link') +if DatabaseKnotInfo().is_present(): + lazy_import('sage.knots.knotinfo', ['KnotInfo', 'KnotInfoSeries']) diff --git a/src/sage/knots/knot.py b/src/sage/knots/knot.py index 9b247894ad7..358868c2565 100644 --- a/src/sage/knots/knot.py +++ b/src/sage/knots/knot.py @@ -386,9 +386,9 @@ def connected_sum(self, other): :: sage: rev_trefoil = Knot(B([-1,-1,-1])) - sage: K = trefoil.connected_sum(rev_trefoil); K + sage: K2 = trefoil.connected_sum(rev_trefoil); K2 Knot represented by 6 crossings - sage: K.braid() + sage: K2.braid() s0^3*s1^-1*s0^-3*s1 .. PLOT:: @@ -397,8 +397,19 @@ def connected_sum(self, other): B = BraidGroup(2) t = Knot(B([1,1,1])) tr = Knot(B([-1,-1,-1])) - K = t.connected_sum(tr) - sphinx_plot(K.plot()) + K2 = t.connected_sum(tr) + sphinx_plot(K2.plot()) + + Observe that both knots have according ``dowker_notation`` (showing that + the constructing from DT-code may not be unique for non prime knots, see + :meth:`from_dowker_code`):: + + sage: K.dowker_notation() + [(4, 1), (2, 5), (6, 3), (10, 7), (8, 11), (12, 9)] + sage: K2.dowker_notation() + [(4, 1), (2, 5), (6, 3), (7, 10), (11, 8), (9, 12)] + sage: K.homfly_polynomial() == K2.homfly_polynomial() + False TESTS:: @@ -534,6 +545,15 @@ def from_dowker_code(self, code): a knot + .. WARNING:: + + In general the Dowker-Thistlethwaite code does not describe a knot + uniquely. It is not only insensitive on mirror images, but may also + mix up non prime knots. For example ``[4, 6, 2, 10, 12, 8]`` describes + the connected sum of two trefoil knots, as well as the connected sum + of a trefoil with its mirror (see the corresponding example in the + documentation of :meth:`connected_sum`). + EXAMPLES:: sage: W = Knots() diff --git a/src/sage/knots/knotinfo.py b/src/sage/knots/knotinfo.py new file mode 100644 index 00000000000..54b7812a3b0 --- /dev/null +++ b/src/sage/knots/knotinfo.py @@ -0,0 +1,2416 @@ +# -*- coding: utf-8 -*- +r""" +Access to the KnotInfo database + +This module contains the class :class:`KnotInfoBase` which is derived from +:class:`Enum` and provides knots and links listed in the databases at the +web-pages `KnotInfo `__ +and `LinkInfo `__ as its items. + +This interface contains a set of about twenty knots and links statically as +demonstration cases. The complete database can be installed as an optional Sage +package using + +- ``sage -i database_knotinfo`` (does not install if the current version is present) +- ``sage -f database_knotinfo`` (installs even if the current version is present) + +To perform all the doctests concerning the usage of the database on the installation +add the option ``-c``. In this case (for instance ``sage -f -c database_knotinfo``) +the installation breaks on failing tests. + +The installation of the complete database will be necessary in order to have +access to all the properties recorded in the databases, as well. + +If the entire database is installed as explained above, the import instructions +for :class:`KnotInfo` and :class:`KnotInfoSeries`, which can be seen in the opening +lines of the examples, are unnecessary. + +Be aware that there are a couple of conventions used differently on KnotInfo as +in Sage, especially concerning the selection of the symmetry version of the link. + +In this context you should note that the PD notation is recorded counter +clockwise in KnotInfo (see note in :meth:`KnotInfoBase.link`). In our transition +to Sage objects this is translated (by default) in order to avoid confusion about +exchanged mirror versions. + +Also, note that the braid notation is used according to Sage, even thought in +the source where it is taken from, the braid generators are assumed to have a +negative crossing which would be opposite to the convention in Sage (see definition +3 of `Gittings, T., "Minimum Braids: A Complete Invariant of Knots and Links +`__). + +For different conventions regarding normalization of the polynomial invariants see +the according documentation of :meth:`KnotInfoBase.homfly_polynomial`, +:meth:`KnotInfoBase.jones_polynomial` and :meth:`KnotInfoBase.alexander_polynomial`. + +EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.pd_notation() + [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]] + sage: L.pd_notation(original=True) + '{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}' + sage: L.is_knot() + False + sage: L.num_components() + 2 + +Items for knots need a leading ``K`` for technical reason:: + + sage: K = KnotInfo.K4_1 + sage: K.is_knot() + True + +Injecting the variable name into the namespace:: + + sage: KnotInfo.K5_1.inject() + Defining K5_1 + sage: K5_1.dt_notation() + [6, 8, 10, 2, 4] + +Defining a link from the original name string:: + + sage: KnotInfo('L6a1{1}').inject() + Defining L6a1_1 + sage: L6a1_1.is_alternating() + True + +Obtaining an instance of :class:`~sage.groups.braid.Braid`:: + + sage: L.braid() + s0*s1^-1*s2*s1^-1*s0^-1*s1^-1*s2^-1*s1^-1 + sage: type(_) + + +Obtaining an instance of :class:`Link`:: + + sage: l = L.link(); l + Link with 2 components represented by 4 crossings + sage: type(l) + + +If you have `SnapPy `__ installed inside +Sage you can obtain an instance of :class:`~spherogram.links.links_base.Link`, +too:: + + sage: L6 = KnotInfo.L6a1_0 + sage: l6s = L6.link(snappy=True); l6s # optional - snappy + Plink failed to import tkinter. + + + sage: type(l6s) # optional - snappy + + sage: l6 = L6.link().mirror_image() + sage: l6 == l6s.sage_link() # optional - snappy + True + sage: L6.link(L6.items.name, snappy=True) # optional - snappy + + sage: l6sn = _ # optional - snappy + sage: l6s == l6sn # optional - snappy + False + sage: l6sn.sage_link().is_isotopic(l6) # optional - snappy + True + +But observe that the name conversion to SnapPy does not distingiush orientation +types:: + + sage: L6b = KnotInfo.L6a1_1 + sage: L6b.link(L6b.items.name, snappy=True) # optional - snappy + + sage: _.PD_code() == l6sn.PD_code() # optional - snappy + True + +Obtaining the HOMFLY-PT polynomial:: + + sage: L.homfly_polynomial() + -v^-1*z - v^-3*z - v^-3*z^-1 + v^-5*z^-1 + sage: _ == l.homfly_polynomial(normalization='vz') + True + + +Obtaining the original string from the database for an arbitrary property:: + + sage: K[K.items.classical_conway_name] # optional - database_knotinfo + '4_1' + +Further methods:: + + sage: K.crossing_number() + 4 + sage: K.gauss_notation() + [-1, 2, -3, 1, -4, 3, -2, 4] + sage: K.dt_notation() + [4, 6, 8, 2] + sage: K.determinant() + 5 + sage: K.symmetry_type() + 'fully amphicheiral' + sage: _ == K[K.items.symmetry_type] + True + sage: K.is_reversible() + True + sage: K.is_amphicheiral() + True + sage: K.jones_polynomial() + t^2 - t - 1/t + 1/t^2 + 1 + sage: K.kauffman_polynomial() + a^2*z^2 + a*z^3 - a^2 - a*z + 2*z^2 + a^-1*z^3 - 1 - a^-1*z + a^-2*z^2 - a^-2 + sage: K.alexander_polynomial() + t^2 - 3*t + 1 + +Using the ``column_type`` of a property:: + + sage: def select_column(i): + ....: return i.column_type() != i.types.OnlyLinks and K[i] == 'Y' + sage: [i.column_name() for i in K.items if select_column(i)] # optional - database_knotinfo + ['Alternating', 'Fibered', 'Quasialternating', 'Adequate'] + +You can launch web-pages attached to the links:: + + sage: K.diagram() # not tested + True + sage: L.diagram(single=True) # not tested + True + sage: L.knot_atlas_webpage() # not tested + True + sage: K.knotilus_webpage() # not tested + True + +and the description web-pages of the properties:: + + sage: K.items.positive.description_webpage() # not tested + True + +To see all the properties available in this interface you can use "tab-completion". +For example type ``K.items.`` and than hit the "tab-key". You can select the item +you want from the list. If you know some first letters type them first to obtain a +reduced selection list. + +In a similar way you may select the knots and links. Here you have to type ``KnotInfo.`` +or ``KnotInfo.L7`` before stroking the "tab-key". In the latter case the selection list +will be reduced to proper links with 7 crossings. + +Finally there is a method :meth:`Link.get_knotinfo` of class :class:`Link` to find an instance +in the KnotInfo database:: + + sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5], + ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], + ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) + sage: L.get_knotinfo() + (, None) + + +REFERENCES: + +- `KnotInfo `__ +- `LinkInfo `__ + + + +AUTHORS: + +- Sebastian Oehms August 2020: initial version + +Thanks to Chuck Livingston and Allison Moore for their support. For further acknowledgments see the correspondig hompages. +""" + + +############################################################################## +# Copyright (C) 2020 Sebastian Oehms +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +############################################################################## + + + +from enum import Enum +from sage.misc.cachefunc import cached_method +from sage.misc.sage_eval import sage_eval +from sage.structure.sage_object import SageObject +from sage.structure.unique_representation import UniqueRepresentation +from sage.rings.integer_ring import ZZ +from sage.groups.braid import BraidGroup +from sage.knots.knot import Knots +from sage.databases.knotinfo_db import KnotInfoColumns, db + + + + +def eval_knotinfo(string, locals={}, to_tuple=True): + r""" + Preparse a string from the KnotInfo database and evaluate it by ``sage_eval``. + + INPUT: + + - ``string`` -- string that gives a value of some database entry + - ``locals`` -- dictionary of locals passed to ``sage_eval`` + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo, eval_knotinfo + sage: L = KnotInfo.L4a1_0 + sage: L.braid_notation(original=True) + '{4, {1, -2, 3, -2, -1, -2, -3, -2}}' + sage: eval_knotinfo(_) + (4, (1, -2, 3, -2, -1, -2, -3, -2)) + """ + if to_tuple: + new_string = string.replace('{', '(') + new_string = new_string.replace('}', ')') + else: + new_string = string.replace('{', '[') + new_string = new_string.replace('}', ']') + new_string = new_string.replace(';', ',') + return sage_eval(new_string, locals=locals) + +def knotinfo_bool(string): + r""" + Preparse a string from the KnotInfo database representing a boolean. + + INPUT: + + - ``string`` -- string that gives a value of some database entry + + EXAMPLES:: + + sage: from sage.knots.knotinfo import knotinfo_bool + sage: knotinfo_bool('Y') + True + """ + if string == 'Y': + return True + elif string == 'N': + return False + raise ValueError('%s is not a KnotInfo boolean') + + + +# --------------------------------------------------------------------------------- +# KnotInfoBase +# --------------------------------------------------------------------------------- +class KnotInfoBase(Enum): + r""" + Enum class to select the knots and links listed in the databases at the web-pages + `KnotInfo `__ and `LinkInfo `__. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: [knot.name for knot in KnotInfo if knot.crossing_number() < 5] + ['K0_1', 'K3_1', 'K4_1', 'L2a1_0', 'L2a1_1', 'L4a1_0', 'L4a1_1'] + + More examples and information can be seen in the module header + :mod:`~sage.knots.knotinfo` (by typing):: + + sage: import sage.knots.knotinfo # not tested + sage: sage.knots.knotinfo? # not tested + + TESTS: + + sage: KnotInfo.K7_1.inject() + Defining K7_1 + sage: TestSuite(K7_1).run() + """ + + def __gt__(self, other): + r""" + Implement comparision of different items in order to have ``sorted`` work. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0 < KnotInfo.L4a1_1 # indirect doctest + True + sage: KnotInfo.L2a1_0 < KnotInfo.K3_1 # indirect doctest + False + sage: KnotInfo.K10_3 > KnotInfo.K3_1 # optional - database_knotinfo + True + """ + if self.__class__ is other.__class__: + tups = (not self.is_knot(), self.crossing_number(), self.value) + tupo = (not other.is_knot(), other.crossing_number(), other.value) + return tups > tupo + return NotImplemented + + @property + def items(self): + r""" + Return an Enum class to select a column item of the KnotInfo database. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: it = L.items + sage: [i.name for i in it if i.name.startswith('braid')] + ['braid_index', 'braid_length', 'braid_notation'] + sage: L.items.dt_notation.column_name() + 'DT Notation' + + To check if the item is available for proper links or only knots type:: + + sage: it.gauss_notation.column_type() + + sage: it.dt_notation.column_type() + + + To see the description of the item in your web browser type:: + + sage: it.gauss_notation.description_webpage() # not tested + True + """ + return db.columns() + + @cached_method + def __getitem__(self, item): + r""" + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L[L.items.alternating] + 'Y' + sage: L[L.items.arc_notation] + '{{6, 4}, {3, 5}, {4, 2}, {1, 3}, {2, 6}, {5, 1}}' + sage: L[L.items.braid_notation] + '{4, {1, -2, 3, -2, -1, -2, -3, -2}}' + sage: L[0] + Traceback (most recent call last): + ... + KeyError: "Item must be an instance of " + """ + if not isinstance(item, KnotInfoColumns): + raise KeyError('Item must be an instance of %s' %(KnotInfoColumns)) + if item.column_type() == item.types.OnlyLinks and self.is_knot(): + raise KeyError('Item not available for knots' %(KnotInfoColumns)) + if item.column_type() == item.types.OnlyKnots and not self.is_knot(): + raise KeyError('Item not available for links' %(KnotInfoColumns)) + + l = db.read(item) + ind = db.read_row_dict()[self.name][0] + offset = 0 + if item.column_type() == item.types.OnlyLinks: + offset = self._offset_knots() + + return l[ind-offset] + + def _offset_knots(self): + r""" + Return the list index of the first proper link in a combined + list containing knots and proper links together which is the + case for columns used for KnotInfo and LinkInfo in common. + This index is exactly the total number of knots recorded + in KnotInfo. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L._offset_knots() # optional - database_knotinfo + 2978 + """ + return db.read_num_knots() + + @cached_method + def _braid_group(self): + r""" + Return the braid group corresponding to the braid index + of ``self``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L._braid_group() + Braid group on 4 strands + """ + n = self.braid_index() + if n == 1: + return BraidGroup(2) + else: + return BraidGroup(n) + + + @cached_method + def _homfly_pol_ring(self, var1, var2): + r""" + Return the parent Laurent polynomial ring for the HOMFLY-PT + polynomial according to Sage's internal one. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_1 + sage: L._homfly_pol_ring('u', 'v') + Multivariate Laurent Polynomial Ring in u, v over Integer Ring + """ + K3_1 = Knots().from_table(3,1) + return K3_1.homfly_polynomial(var1=var1, var2=var2).parent() + + @cached_method + def pd_notation(self, original=False): + r""" + Return the value of column ``pd_notation`` for this + link as a Python list of Python lists. For more information + type ``KnotInfo.K0_1.items.pd_notation.description_webpage()``. + + INPUT: + + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT: + + Python list of python lists each entry of the outer list + representing a crossing. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.pd_notation() + [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]] + sage: L.pd_notation(original=True) + '{{6, 1, 7, 2}, {8, 3, 5, 4}, {2, 5, 3, 6}, {4, 7, 1, 8}}' + sage: K = KnotInfo.K4_1 + sage: K.pd_notation() + [[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]] + """ + if self.is_knot(): + pd_notation = self[self.items.pd_notation] + else: + pd_notation = self[self.items.pd_notation_vector] + + if original: + return pd_notation + + if not pd_notation: + # don't forget the unknot + return [] + + return eval_knotinfo(pd_notation, to_tuple=False) + + @cached_method + def dt_notation(self, original=False): + r""" + Return the value of column ``dt_notation`` for this + link as a Python list of Python lists. For more information + type ``KnotInfo.K0_1.items.dt_notation.description_webpage()``. + + INPUT: + + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT: + + Python list of python lists each entry of the outer list + representing a crossing. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.dt_notation() + [[6, 8], [2, 4]] + sage: L.dt_notation(original=True) + '[{6, 8}, {2, 4}]' + sage: L = KnotInfo.L4a1_0 + sage: K = KnotInfo.K4_1 + sage: K.dt_notation() + [4, 6, 8, 2] + """ + if self.is_knot(): + dt_notation = self[self.items.dt_notation] + else: + dt_notation = self[self.items.dt_code] + + if original: + return dt_notation + + if not dt_notation: + # don't forget the unknot + return [] + + return eval_knotinfo(dt_notation, to_tuple=False) + + @cached_method + def gauss_notation(self, original=False): + r""" + Return the value of column ``gauss_notation`` for this + link as a Python list of Python lists. For more information + type ``KnotInfo.K0_1.items.gauss_notation.description_webpage()``. + + INPUT: + + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT: + + Python list of + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.gauss_notation() + [[1, -3, 2, -4], [3, -1, 4, -2]] + sage: L.gauss_notation(original=True) + '{{1, -3, 2, -4}, {3, -1, 4, -2}}' + """ + gauss_notation = self[self.items.gauss_notation] + if original: + return gauss_notation + + if not gauss_notation: + # don't forget the unknot + return [] + + return eval_knotinfo(gauss_notation, to_tuple=False) + + @cached_method + def braid_notation(self, original=False): + r""" + Return the value of column ``braid_notation`` for this + link as a Python tuple (Tietze form). For more information + type ``KnotInfo.K0_1.items.braid_notation.description_webpage()``. + + INPUT: + + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT: + + Python tuple representing the braid whose closure is ``self`` + in Tietze form. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.braid_notation() + (1, -2, 3, -2, -1, -2, -3, -2) + sage: L.braid_notation(original=True) + '{4, {1, -2, 3, -2, -1, -2, -3, -2}}' + """ + braid_notation = self[self.items.braid_notation] + if original: + return braid_notation + + if not braid_notation: + # don't forget the unknot + return (1, -1) + + braid_notation = eval_knotinfo(braid_notation) + if type(braid_notation) is list: + # in some cases there are a pair of braid representations + # in the database. If this is the case we select the + # corresponding to the braid index. + if type(braid_notation[0]) is tuple: + i = self.braid_index() + for b in braid_notation: + if -i < min(b) and max(b) < i: + braid_notation = b + break + + if not self.is_knot(): + # in linkinfo the braid_notation includes the braid_index as + # first item of a pair + braid_notation = braid_notation[1] + return braid_notation + + @cached_method + def braid_index(self): + r""" + Return the value of column ``braid_index`` for this + link as a Python int. + + OUTPUT: + + Python int giving the minimum of strands needed to + represent ``self`` as closure of a braid. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L4a1_0 + sage: L.braid_index() + 4 + """ + if self.is_knot(): + return int(self[self.items.braid_index]) + else: + braid_notation = self[self.items.braid_notation] + braid_notation = eval_knotinfo(braid_notation) + return int(braid_notation[0]) + + @cached_method + def braid_length(self): + r""" + Return the value of column ``braid_length`` for this + link as a Python int. + + OUTPUT: + + Python int giving the minimum length of a braid word + needed to represent ``self`` as closure of a braid. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.braid_length() + 3 + """ + return int(self[self.items.braid_length]) + + @cached_method + def braid(self): + r""" + Return the braid notation of self as an instance of :class:`~sage.groups.braid.Braid`. + + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.braid() + s^3 + sage: K.braid_notation() + (1, 1, 1) + """ + return self._braid_group()(self.braid_notation()) + + @cached_method + def num_components(self): + r""" + Return the number of components of ``self``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L6a1_0.num_components() + 2 + """ + return db.read_row_dict()[self.name][1] + + @cached_method + def crossing_number(self): + r""" + Return the minimal number of crossings of ``self``. + + .. NOTE:: + + In contrast to the number of crossings displayed for instances + of :class:`Link` this number is the minimum over all possible + diagrams of the link. The number of crossings displayed in + the representation string of :class:`Link` refers to the + special diagram which could be larger. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0.crossing_number() + 4 + sage: KnotInfo.K3_1.crossing_number() + 3 + sage: Link(KnotInfo.L4a1_0.braid()) + Link with 2 components represented by 8 crossings + """ + return int(self[self.items.crossing_number]) + + @cached_method + def determinant(self): + r""" + Return the determinant of ``self``. + + From the KnotInfo description page: + + The determinant of a knot is `\det(V + V^t)`, where `V` is a Seifert + matrix for the knot. + + To read the complete description type + ``KnotInfo.K0_1.items.determinant.description_webpage()``. + + .. NOTE:: + + KnotInfo's value for the unknot ``0_1`` is zero. This is not + compatible whith Sage's result (the value of the Alexander + polynomial at -1). Since this method is needed to identify + Sage links we take the according value in that case. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0.determinant() + 4 + sage: KnotInfo.K3_1.determinant() + 3 + sage: KnotInfo.K0_1.determinant() + 1 + """ + if self.crossing_number() == 0: + # see note above + return 1 + return int(self[self.items.determinant]) + + @cached_method + def three_genus(self): + r""" + Return the three genus of ``self``. + + From the KnotInfo description page: + + The three-genus of a knot is defined to be the minimal genus of + a Seifert surface for a knot. + + To read the complete description type + ``KnotInfo.K0_1.items.three_genus.description_webpage()``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.three_genus() # optional - databsase_knotinfo + 1 + + Note that this differs from the corresponding result in Sage + since the latter is obtained for a Seifert surface that does not + have the minimal genus:: + + sage: KnotInfo.K5_2.link().genus() + 3 + """ + return int(self[self.items.three_genus]) + + @cached_method + def signature(self): + r""" + Return the signature of ``self``. + + From the KnotInfo description page: + + The signature of a knot, `\sigma (K)`, is equal to `\sigma (V + V^t)`, + the signature of `V + V^t` where `V` is a Seifert matrix for the knot + and `V^t` is its transpose. + + To read the complete description type + ``KnotInfo.K0_1.items.signatur.description_webpage()``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.signature() # optional - databsase_knotinfo + 1 + """ + return int(self[self.items.signature]) + + @cached_method + def is_knot(self): + r""" + Return whether ``self`` is a knot or a proper link. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L7a1_0.is_knot() # optional - database_knotinfo + False + sage: KnotInfo.K6_3.is_knot() + True + """ + return self.num_components() == 1 + + @cached_method + def name_unoriented(self): + r""" + Return the the part of the name of ``self`` which is independent on the + orientation. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L10a122_1_0.name_unoriented() # optional - database_knotinfo + 'L10a122' + """ + return self[self.items.name_unoriented] + + @cached_method + def symmetry_type(self): + r""" + Return the symmetry type of ``self``. + + From the KnotInfo description page: + + If a knot is viewed as the oriented diffeomorphism + class of an oriented pair, `K = (S_3, S_1)`, with `S_i` + diffeomorphic to `S^i`, there are four oriented knots + associated to any particular knot `K`. In addition to + `K` itself, there is the reverse, `K^r = (S_3, -S_1)`, + the concordance inverse, `-K = (-S_3, -S_1)`, and the + mirror image, `K^m = (-S_3, S_1)`. A knot is called + reversible if `K = K^r`, negative amphicheiral if + `K = -K`, and positive amphicheiral if `K = K^m`. + + A knot possessing any two of these types of symmetry + has all three. Thus, in the table, a knot is called + reversible if that is the only type of symmetry it has, + and likewise for negative amphicheiral. If it has none + of these types of symmetry it is called chiral, and if + it has all three it is called fully amphicheiral. + + For prime knots with fewer than 12 crossings, all + amphicheiral knots are negative amphicheiral. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K6_1.series().inject() + Defining K6 + sage: [(K.name, K.symmetry_type()) for K in K6] + [('K6_1', 'reversible'), + ('K6_2', 'reversible'), + ('K6_3', 'fully amphicheiral')] + """ + if not self.is_knot(): + raise NotImplementedError('This is only available for knots') + + symmetry_type = self[self.items.symmetry_type].strip() # for example K10_88 is a case with trailing whitespaces + if not symmetry_type and self.crossing_number() == 0: + return 'fully amphicheiral' + return symmetry_type + + @cached_method + def is_reversible(self): + r""" + Return whether ``self`` is reversible. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K6_3.is_reversible() + True + """ + symmetry_type = self.symmetry_type() + if symmetry_type == 'reversible': + return True + if symmetry_type == 'fully amphicheiral': + return True + return False + + @cached_method + def is_amphicheiral(self, positive=False): + r""" + Return whether ``self`` is amphicheiral. + + INPUT: + + - ``positive`` -- boolean (optional, default False) whether to check + if ``self`` is positive or negative amphicheiral (see documentation + of :meth:`symmetry_type`) + + OUTPUT: + + Boolean or ``None`` if this cannot be determined. + + ``True`` if ``self`` is fully or negative amphicheiral per default. If + ``positive`` is set to ``True`` than fully and positive amphicheiral + links give ``True``. + + .. NOTE:: + + For proper links this property is not provided in the database. + Anyway, we support it here in this case, as well, except for a few + items where it cannot be determined easily and where ``None`` + is returned as answer. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: Kp = KnotInfo.K12a_427 # optional - database_knotinfo + sage: Kp.is_amphicheiral() # optional - database_knotinfo + False + sage: Kp.is_amphicheiral(positive=True) # optional - database_knotinfo + True + + sage: Kn = KnotInfo.K10_88 # optional - database_knotinfo + sage: Kn.is_amphicheiral() # optional - database_knotinfo + True + sage: Kn.is_amphicheiral(positive=True) # optional - database_knotinfo + False + + sage: KnotInfo.L4a1_0.is_amphicheiral() + False + sage: KnotInfo.L10n59_1.is_amphicheiral() # optional - database_knotinfo + True + sage: KnotInfo.L10n59_0.inject() # optional - database_knotinfo + Defining L10n59_0 + sage: L10n59_0.is_amphicheiral() is None # optional - database_knotinfo + True + """ + if self.is_knot(): + symmetry_type = self.symmetry_type() + if positive: + if symmetry_type == 'positive amphicheiral': + return True + else: + if symmetry_type == 'negative amphicheiral': + return True + + if symmetry_type == 'fully amphicheiral': + return True + return False + + h = self.homfly_polynomial() + v, z = h.parent().gens() + hm = h.subs(v=~v, z=-z) + if h != hm: + return False + + k = self.kauffman_polynomial() + a, z = k.parent().gens() + km = k.subs(a=~a) + if k != km: + return False + + b = self.braid() + bi = ~b + if b.is_conjugated(bi): + # at least negative amphicheiral + if not positive: + return True + + # revert orientation (back) + bit = list(bi.Tietze()) + bit.reverse() + bm = b.parent()(tuple(bit)) + if b.is_conjugated(bm): + if positive: + return True + + return None + + @cached_method + def is_alternating(self): + r""" + Return whether ``self`` is alternating. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_alternating() + True + """ + return knotinfo_bool(self[self.items.alternating]) + + @cached_method + def is_almost_alternating(self): + r""" + Return whether ``self`` is almost alternating. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_almost_alternating() # optional - database_knotinfo + False + """ + db._feature.require() # column not available in demo-version + return knotinfo_bool(self[self.items.almost_alternating]) + + @cached_method + def is_quasi_alternating(self): + r""" + Return whether ``self`` is quasi alternating. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_quasi_alternating() # optional - database_knotinfo + True + """ + db._feature.require() # column not available in demo-version + return knotinfo_bool(self[self.items.quasi_alternating]) + + @cached_method + def is_adequate(self): + r""" + Return whether ``self`` is adequate. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_adequate() # optional - database_knotinfo + True + """ + db._feature.require() # column not available in demo-version + return knotinfo_bool(self[self.items.adequate]) + + @cached_method + def is_positive(self): + r""" + Return whether ``self`` is positive. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_positive() + True + """ + return knotinfo_bool(self[self.items.positive]) + + @cached_method + def is_quasipositive(self): + r""" + Return whether ``self`` is quasi-positive. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_quasipositive() # optional - database_knotinfo + True + """ + db._feature.require() # column not available in demo-version + return knotinfo_bool(self[self.items.quasipositive]) + + @cached_method + def is_strongly_quasipositive(self): + r""" + Return whether ``self`` is strongly quasi-positive. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_strongly_quasipositive() # optional - database_knotinfo + True + """ + db._feature.require() # column not available in demo-version + return knotinfo_bool(self[self.items.strongly_quasipositive]) + + @cached_method + def is_positive_braid(self): + r""" + Return whether ``self`` is a positive braid. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.is_positive_braid() # optional - database_knotinfo + False + """ + db._feature.require() # column not available in demo-version + return knotinfo_bool(self[self.items.positive_braid]) + + @cached_method + def is_fibered(self): + r""" + Return whether ``self`` is fibered. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K6_3.is_fibered() + True + """ + return knotinfo_bool(self[self.items.fibered]) + + @cached_method + def is_oriented(self): + r""" + Return whether ``self`` is oriented. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L6a2_1.is_oriented() + True + """ + return not knotinfo_bool(self[self.items.unoriented]) + + + @cached_method + def homfly_polynomial(self, var1='v', var2='z', original=False): + r""" + Return the HOMFLY-PT polynomial according to the value of column + ``homfly_polynomial`` for this knot or link (in the latter case the + column ``homflypt_polynomial`` is used) as an instance of the + element class according to the output of :meth:`Link.homfly_polynomial` + of :class:`Link`. + + The HOMFLY-PT polynomial `P(L)` of a link `L` satisfies the following skein + relation (see the corresponding `KnotInfo description page + `__): + + .. MATH:: + + P(O) = 1,\,\,\, v^{-1} P(L_+) - v P(L_-) = z P(L_0) + + INPUT: + + - ``var1`` -- string (default ``v``) for the name of the first variable + - ``var2`` -- string (default ``z``) for the name of the second variable + - ``original`` -- boolean (default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT: + + A Laurent polynomial over the integers, more precisely an instance of + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`. + If ``original`` is set to ``True`` then a string is returned. + + .. NOTE:: + + The skein-relation for the HOMFLY-PT polynomial given on KnotInfo + does not match the default used in Sage. For comparison you have + to use the keyword argument ``normalization='vz'`` on the side + of Sage. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K3_1 = KnotInfo.K3_1 + sage: PK3_1 = K3_1.homfly_polynomial(); PK3_1 + -v^4 + v^2*z^2 + 2*v^2 + sage: K3_1.homfly_polynomial(original=True) + '(2*v^2-v^4)+(v^2)*z^2' + sage: PK3_1 == K3_1.link().homfly_polynomial(normalization='vz') + True + + for proper links:: + + sage: L4a1_1 = KnotInfo.L4a1_1 + sage: PL4a1_1 = L4a1_1.homfly_polynomial(var1='x', var2='y'); PL4a1_1 + -x^5*y + x^3*y^3 - x^5*y^-1 + 3*x^3*y + x^3*y^-1 + sage: _ == L4a1_1.link().homfly_polynomial('x', 'y', 'vz') + True + + check the skein-relation from the KnotInfo description page (applied to one + of the positive crossings of the right-handed trefoil):: + + sage: R = PK3_1.parent() + sage: PO = R.one() + sage: L2a1_1 = KnotInfo.L2a1_1 + sage: PL2a1_1 = L2a1_1.homfly_polynomial() + sage: v, z = R.gens() + sage: ~v*PK3_1 -v*PO == z*PL2a1_1 + True + + TESTS:: + + sage: H = KnotInfo.L11n459_1_1_1.homfly_polynomial() # optional - database_knotinfo + sage: all(L.homfly_polynomial() == L.link().homfly_polynomial(normalization='vz')\ + for L in KnotInfo if L.crossing_number() < 7) + True + + REFERENCES: + + - :wikipedia:`HOMFLY_polynomial` + """ + if self.is_knot(): + homfly_polynomial = self[self.items.homfly_polynomial] + else: + homfly_polynomial = self[self.items.homflypt_polynomial] + + if original: + return homfly_polynomial + + R = self._homfly_pol_ring(var1, var2) + if not homfly_polynomial and self.crossing_number() == 0: + return R.one() + + # As of February 2021 there is a wrong character for the link in the + # last row of the database. This is removed here (see SPKG.rst and + # the test above). Once this is fixed upstream, the following three + # lines of code can be removed again: + if self.value == 'L11n459{1,1,1}': + if homfly_polynomial.endswith('}'): + homfly_polynomial = homfly_polynomial.strip('}') + + L, M = R.gens() + lc = {'v': L, 'z':M} + return eval_knotinfo(homfly_polynomial, locals=lc) + + @cached_method + def kauffman_polynomial(self, var1='a', var2='z', original=False): + r""" + Return the Kauffman polynomial according to the value of column + ``kauffman_polynomial`` for this knot or link as an instance of + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`. + + The Kauffman polynomial `F(L)` respectivlely its corresponding invariant + under regular isotopy `\Delta (L) = a^{w(L)} F(L)` where `w(L)` is the + writhe of the link `L` satisfies the following skein relation + (see the corresponding `KnotInfo description page + `__): + + .. MATH:: + + \Delta(O) = 1,\,\,\, \Delta(L_+) - \Delta(L_-) = z (\Delta(L_0 + \Delta(L_{\infty})) + + Furthermore, removing a curl of sign `\epsilon` leads to a multiplication + of `\Delta(L)` with `a^{\epsilon}`. + + INPUT: + + - ``var1`` -- (default: ``'a'``) the first variable + - ``var2`` -- (default: ``'z'``) the second variable + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + + OUTPUT: + + A Laurent polynomial over the integers, more precisely an instance of + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial_mpair`. + If ``original`` is set to ``False`` then a string is returned. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L2a1_1 + sage: L.kauffman_polynomial() + a^-1*z - a^-1*z^-1 + a^-2 + a^-3*z - a^-3*z^-1 + sage: K = KnotInfo.K4_1 + sage: K.kauffman_polynomial() + a^2*z^2 + a*z^3 - a^2 - a*z + 2*z^2 + a^-1*z^3 - 1 - a^-1*z + a^-2*z^2 - a^-2 + + Comparison with Jones polynomial:: + + sage: k = _ + sage: a, z = k.variables() + sage: j = K.jones_polynomial(skein_normalization=True) + sage: t, = j.variables() + sage: k.subs(a=-t^3, z=~t+t) == j.subs(t=t^4) + True + + Check the skein relation:: + + sage: K3_1 = KnotInfo.K3_1 + sage: FK3_1 = K3_1.kauffman_polynomial() + sage: FL2a1_1 = L.kauffman_polynomial() + sage: z, a = FK3_1.variables() + sage: ΔK3_1 = FK3_1 * a**K3_1.link().writhe() + sage: ΔL2a1_1 = FL2a1_1 * a**L.link().writhe() + sage: ΔO1p = a # unknot with one positive curl + sage: ΔO2n = a**-2 # unknot with two negative curls + sage: ΔK3_1 + ΔO1p == z*(ΔL2a1_1 + ΔO2n) + True + + REFERENCES: + + - :wikipedia:`Kauffman_polynomial` + """ + kauffman_polynomial = self[self.items.kauffman_polynomial] + + if original: + return kauffman_polynomial + + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + R = LaurentPolynomialRing(ZZ, (var1, var2)) + if not kauffman_polynomial and self.crossing_number() == 0: + return R.one() + + a, z = R.gens() + lc = {'a': a, 'z': z} + return R(eval_knotinfo(kauffman_polynomial, locals=lc)) + + + @cached_method + def jones_polynomial(self, variab=None, skein_normalization=False, puiseux=False, original=False, use_sqrt=False): + r""" + Return the Jones polynomial according to the value of column + ``jones_polynomial`` for this knot or link as an element of the symbolic + ring :class:`~sage.symbolic.ring.SR` or an instance of + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` + depending on the keyword ``skein_normalization``. Using the keyword + ``puiseux`` instead of an element of the symbolic ring an instance of + :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` can be + returned. + + The Jones polynomial `V(L)` of a link `L` satisfies the following skein + relation (see the corresponding `KnotInfo description page + `__): + + .. MATH:: + + V(O) = 1,\,\,\, t^{-1} V(L_+) - t V(L_-) = (t^{\frac{1}{2}} - t^{-\frac{1}{2}}) V(L_0) + + INPUT: + + - ``variab`` -- variable (default: ``None``) used according to :meth:`Link.jones_polynomial` + - ``skein_normalization`` -- boolean (default: ``False``) used according + to :meth:`Link.jones_polynomial` + - ``puiseux`` -- boolean (default ``True``) only used in case + ``skein_normalization=False``. If set to ``True`` instead of an element + of the symbolic ring an instance of :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` + is returned + - ``original`` -- boolean (default ``False``) if set to + ``True`` the original table entry is returned as a string + - ``use_sqrt`` -- boolean (default ``False``) see the note below + + + OUTPUT: + + Depends on the keywords (in excluding order): + + - ``original=True`` a string according to the original value from the + database + - ``skein_normalization=True`` a Laurent polynomial over the integers, + more precisely an instance of :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` + - ``puiseux=True`` a puiseux series over the integers, more precisely an + instance of :class:`~sage.rings.puiseux_series_ring_element.PuiseuxSeries` + + In all other cases an element of the symbolic ring :class:`~sage.symbolic.ring.SR`. + + .. NOTE:: + + There is a difference to Sage's conventions concerning the Jones + polynomial in the case of proper links. KnotInfo does not display + these polynomials in the indeterminate `t` used in the skein relation. + Instead a variable `x` is used defined by `x^2 = t`. Sage uses `t` in + both cases, knots and proper links. Thus, to obtain the Jones polynomial + for a proper link in `t` you have to set the keyword ``use_sqrt`` + to ``True``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K4_1 + sage: Kj = K.jones_polynomial(); Kj + t^2 - t - 1/t + 1/t^2 + 1 + sage: Kjs = K.jones_polynomial(skein_normalization=True); Kjs + A^-8 - A^-4 + 1 - A^4 + A^8 + sage: Kjp = K.jones_polynomial(puiseux=True); Kjp + t^-2 - t^-1 + 1 - t + t^2 + + for proper links:: + + sage: L = KnotInfo.L2a1_1 + sage: Lj = L.jones_polynomial(); Lj + -x^5 - x + sage: Ljt = L.jones_polynomial(use_sqrt=True); Ljt + -t^(5/2) - sqrt(t) + sage: Ljp = L.jones_polynomial(puiseux=True); Ljp + -t^(1/2) - t^(5/2) + sage: Ljs = L.jones_polynomial(skein_normalization=True); Ljs + -A^2 - A^10 + sage: Lj.parent() + Symbolic Ring + sage: Ljt.parent() + Symbolic Ring + sage: Ljp.parent() + Puiseux Series Ring in t over Integer Ring + sage: Ljs.parent() + Univariate Laurent Polynomial Ring in A over Integer Ring + + Comparison with Sage's results:: + + sage: k = K.link() + sage: kj = k.jones_polynomial() + sage: bool(Kj == kj) + True + sage: kjs = k.jones_polynomial(skein_normalization=True) + sage: Kjs == kjs + True + sage: l = L.link() + sage: lj = l.jones_polynomial() + sage: bool(Lj == lj) + False + sage: bool(Ljt == lj) # see note above + True + sage: ljs = l.jones_polynomial(skein_normalization=True) + sage: Ljs == ljs + True + + Check the skein-relation from the KnotInfo description page (applied to one + of the positive crossings of the right-handed trefoil):: + + sage: K3_1 = KnotInfo.K3_1 + sage: K3_1j = K3_1.jones_polynomial() + sage: L2a1_1j = Ljt # see note above + sage: R = L2a1_1j.parent() + sage: Oj = R(1) + sage: t = R('t') + sage: lhs = expand(~t*K3_1j - t*Oj) + sage: rhs = expand((sqrt(t) - ~sqrt(t))*L2a1_1j) + sage: bool(lhs == rhs) + True + + The same with the Puiseux series version:: + + sage: K3_1jp = K3_1.jones_polynomial(puiseux=True) + sage: L2a1_1jp = Ljp + sage: R = L2a1_1jp.parent() + sage: Ojp = R(1) + sage: t = R('t') + sage: ~t*K3_1jp - t*Ojp == (t^(1/2)-~t^(1/2))*L2a1_1jp + True + + The same in the case of skein normalization (using `t = A^4`):: + + sage: K3_1js = K3_1.jones_polynomial(skein_normalization=True) + sage: L2a1_1js = L.jones_polynomial(skein_normalization=True) + sage: Rs = K3_1js.parent() + sage: Ojs = Rs.one() + sage: A, = Rs.gens() + sage: ~A^4*K3_1js - A^4*Ojs == (A^2-~A^2)*L2a1_1js + True + + REFERENCES: + + - :wikipedia:`Jones_polynomial` + """ + jones_polynomial = self[self.items.jones_polynomial] + + if original: + return jones_polynomial + + if skein_normalization: + if not variab: + variab='A' + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + R = LaurentPolynomialRing(ZZ, variab) + else: + if not variab: + if use_sqrt or self.is_knot() or puiseux: + variab='t' + else: + variab='x' + if puiseux: + from sage.rings.puiseux_series_ring import PuiseuxSeriesRing # since PuiseuxPolynomial is not available, so far + R = PuiseuxSeriesRing(ZZ, variab) + else: + from sage.symbolic.ring import SR + R = SR + + if not jones_polynomial and self.crossing_number() == 0: + return R(1) + + t = R(variab) + if skein_normalization: + if self.is_knot(): + lc = {'t': t**4} + else: + lc = {'x': t**2} + else: + if self.is_knot(): + lc = {'t': t} + elif puiseux: + lc = {'x': t**(1/2)} + elif use_sqrt: + from sage.functions.other import sqrt + lc = {'x': sqrt(t)} + else: + lc = {'x': t} + + + return R(eval_knotinfo(jones_polynomial, locals=lc)) + + + @cached_method + def alexander_polynomial(self, var='t', original=False, laurent_poly=False): + r""" + Return the Alexander polynomial according to the value of column + ``alexander_polynomial`` for this knot as an instance of + :class:`~sage.rings.polynomial.polynomial_element.Polynomial`. + + It is obtained from the Seifert matrix `V` of ``self`` by the following + formula (see the KnotInfo description web-page; to launch it see the + example below): + + .. MATH:: + + A(L) = \det(V -t V^t) + + Here `V^t` stands for the transpose of `V`. + + + INPUT: + + - ``var`` -- (default: ``'t'``) the variable + - ``original`` -- boolean (optional, default ``False``) if set to + ``True`` the original table entry is returned as a string + - ``laurent_poly`` -- boolean (default ``False``) see the note below + + OUTPUT: + + A polynomial over the integers, more precisely an instance of + :class:`~sage.rings.polynomial.polynomial_element.Polynomial`. + If ``laurent_poly`` is set to ``True`` a Laurent polynomial + over the integers, more precisely an instance of + :class:`~sage.rings.polynomial.laurent_polynomial.LaurentPolynomial` + is returned. If ``original`` is set to ``True`` then a string + is returned. + + .. NOTE:: + + As an invariant the Alexander polynomial is only unique up to + a unit factor in the Laurent polynomial ring over the integers + in the indeterminate `t`. While the normalization of the exponents + in KnotInfo guarantees it to be a proper polynomial, this is + not the case for the implementation in Sage. Use the keyword + ``laurent_poly`` to achiev a normalization according to Sage. + But, still there may be a difference in sign (see the example below). + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K4_1 + sage: Ka = K.alexander_polynomial(); Ka + t^2 - 3*t + 1 + + Comparison with Sage's results:: + + sage: k = K.link() + sage: ka = k.alexander_polynomial(); ka + -t^-1 + 3 - t + sage: K.alexander_polynomial(laurent_poly=True) + t^-1 - 3 + t + sage: _ == -ka + True + + Launch the KnotInfo description web-page:: + + sage: K.items.alexander_polynomial.description_webpage() # not tested + True + """ + alexander_polynomial = self[self.items.alexander_polynomial] + + if original: + return alexander_polynomial + + if laurent_poly: + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + R = LaurentPolynomialRing(ZZ, var) + else: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(ZZ, var) + + if not alexander_polynomial and self.crossing_number() == 0: + return R.one() + + t, = R.gens() + lc = {'t': t} + ap = R(eval_knotinfo(alexander_polynomial, locals=lc)) + if not laurent_poly or ap.is_constant(): + return ap + + exp = ap.exponents() + return t ** ((-max(exp) - min(exp)) // 2) * ap + + + @cached_method + def link(self, use_item=db.columns().pd_notation, snappy=False): + r""" + Return ``self`` as an instance of :class:`Link` or optional + ``spherogram.links.invariants.Link`` (SnapPy). + + INPUT: + + - ``use_item`` -- (optional, default ``self.items.pd_notation``) + instance of :class:`KnotInfoColumns` to choose the column + that should be used to construct the link. Allowed values + are: + + - ``self.items.pd_notation`` + - ``self.items.braid_notation`` + - ``self.items.name`` (only for ``snappy=True``) + - ``self.items.dt_notation`` (only for knots and ``snappy=False``) + - ``self.items.gauss_notation`` (only for knots and ``snappy=False``) + + - ``snappy`` boolean (default ``False``) if set to ``True`` + the target of the conversion is the ``pip`` installable + package `SnapPy `__ + (explicitely, ``spherogram.links.invariants.Link``). + If SnapPy is not installed an ``ImportError`` is raised. To + install SnapPy use ``sage -pip install snappy``. + + .. NOTE:: + + We use the PD-notation to construct ``self`` as + default. This ensures that the number of crossings + displayed in the representation string of the link + coincides with the crossing number as a topological + invariant. + + But attention: The convention on how the edges are + listed are opposite to each other + + - KnotInfo: counter clockwise + - Sage: clockwise + + Therefore, we take the mirror version of the ``pd_notation``! + + Furthermore, note that the mirror version may depend + on the used KnotInfo-notation. For instance, regarding to + the knot ``5_1`` the Gauss- and the DT-notation refer to + the mirror image (see example below). + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.link() + Knot represented by 3 crossings + sage: _.braid() + s^3 + sage: _ == K.braid() + True + + using ``dt_notation``:: + + sage: K.link(use_item=K.items.dt_notation) + Knot represented by 3 crossings + sage: _.braid() + s^3 + + sage: L = KnotInfo.L4a1_0 + sage: L.link() + Link with 2 components represented by 4 crossings + + sage: L.link(use_item=L.items.dt_notation) + Traceback (most recent call last): + ... + ValueError: Link construction using Columns.dt_notation not possible + + using ``snappy``:: + + sage: K7 = KnotInfo.K7_2 + sage: k7s = K7.link(snappy=True); k7s # optional - snappy + + sage: K7.link(K7.items.name, snappy=True) # optional - snappy + + sage: k7sn = _ # optional - snappy + sage: k7s == k7sn # optional - snappy + False + sage: k7s.sage_link().is_isotopic(k7sn.sage_link()) # optional - snappy + True + + but observe:: + + sage: L2 = KnotInfo.L2a1_1 + sage: l2 = L2.link() + sage: l2s = L2.link(snappy=True).sage_link() # optional - snappy + sage: l2 == l2s # optional - snappy + False + sage: l2 == l2s.mirror_image() # optional - snappy + True + + using ``braid_notation``:: + + sage: L2.link(use_item=L.items.braid_notation) == l2 + True + + observe:: + + sage: L.link(use_item=L.items.braid_notation) + Link with 2 components represented by 8 crossings + + sage: K6_1 = KnotInfo.K6_1 + sage: K6_1.link().braid() == K6_1.braid() + False + + also observe:: + + sage: K4_1 = KnotInfo.K4_1 + sage: K4_1.link().pd_code() + [[4, 1, 5, 2], [8, 5, 1, 6], [6, 4, 7, 3], [2, 8, 3, 7]] + sage: K4_1.pd_notation() + [[4, 2, 5, 1], [8, 6, 1, 5], [6, 3, 7, 4], [2, 7, 3, 8]] + + sage: K5_1 = KnotInfo.K5_1 + sage: K5_1.link().braid() + s^5 + sage: K5_1.link(K5_1.items.dt_notation).braid() + s^-5 + sage: K5_1.link(K5_1.items.gauss_notation).braid() + s^-5 + """ + if not isinstance(use_item, KnotInfoColumns): + raise TypeError('%s must be an instance of %s' %(use_item, KnotInfoColumns)) + + if snappy: + try: + from snappy import Link + except ImportError: + raise ImportError('This option demands snappy to be installed') + elif self.is_knot(): + from sage.knots.knot import Knot as Link + else: + from sage.knots.link import Link + + if use_item == self.items.pd_notation: + pd_code = [[a[0], a[3], a[2], a[1]] for a in self.pd_notation()] # take mirror version, see note above + return Link(pd_code) + elif use_item == self.items.braid_notation: + return Link(self.braid()) + elif use_item == self.items.name and snappy: + if not self.is_knot(): + use_item = self.items.name_unoriented + return Link(self[use_item]) + elif self.is_knot() and not snappy: + # Construction via Gauss and DT-Code only possible for knots + from sage.knots.knot import Knots + if use_item == self.items.dt_notation: + return Knots().from_dowker_code(self.dt_notation()) + elif use_item == self.items.gauss_notation: + return Knots().from_gauss_code(self.gauss_notation()) + + raise ValueError('Link construction using %s not possible' %use_item) + + + @cached_method + def is_unique(self): + r""" + Return whether there is no other isotopic link in the database or not. + + OUTPUT: + + Boolean or ``None`` if this cannot be determined. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0.is_unique() + True + sage: KnotInfo.L5a1_0.is_unique() + False + sage: L = KnotInfo.L7a7_0_0 # optional - database_knotinfo + sage: L.series(oriented=True).inject() # optional - database_knotinfo + Defining L7a7 + sage: [(L,L.is_unique()) for L in L7a7] # optional - database_knotinfo + [(, False), + (, None), + (, False), + (, True)] + """ + # an isotopic pair must have the same unoriented name. So, we can focus + # on such series + if self.is_knot(): + return True + S = self.series(oriented=True) + hp = self.homfly_polynomial() + Sl = S.list(homfly=hp) + if len(Sl) == 1: + return True + kp = self.kauffman_polynomial() + Sl = [L for L in Sl if L != self and L.kauffman_polynomial() == kp] + if not Sl: + return True + + b = self.braid() + for L in Sl: + Lb = L.braid() + if L.braid() == b: + return False + if Lb.is_conjugated(b): + return False + + return None + + @cached_method + def is_recoverable(self, unique=True): + r""" + Return if ``self`` can be recovered from its conversion to Sage links + using the ``pd_notation`` and the ``braid_notation`` and their + mirror images. + + The method is indirectly used by the ``TestSuite`` of the series of ``self``. + + INPUT: + + - ``unique`` -- boolean (optional, default=``True``) if set to ``False`` + it is only checked if ``self`` is among the recovered items + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0.inject() + Defining L4a1_0 + sage: L4a1_0.is_recoverable() + True + sage: L4a1_0.is_recoverable(unique=False) + True + sage: KnotInfo.L5a1_0.inject() + Defining L5a1_0 + sage: L5a1_0.is_recoverable() + False + sage: L5a1_0.is_recoverable(unique=False) + True + """ + def recover(mirror, braid): + r""" + Check if ``self`` can be recovered form its associated + Sage link. + """ + if braid: + l = self.link(self.items.braid_notation) + else: + l = self.link() + if mirror: + l = l.mirror_image() + + def check_result(L, m): + r""" + Check a single result from ``get_knotinfo``. + """ + if L != self: + return False + if m is None or m == '?': + return True + if mirror: + return m + else: + return not m + + try: + L, m = l.get_knotinfo() + if isinstance(L, KnotInfoBase): + return check_result(L,m) + elif unique: + return False + except NotImplementedError: + if unique: + return False + Llist = l.get_knotinfo(unique=False) + return any(check_result(L, m) for (L, m) in Llist) + + from sage.misc.misc import some_tuples + return all(recover(mirror, braid) for mirror, braid in some_tuples([True, False], 2, 4)) + + def inject(self, verbose=True): + """ + Inject ``self`` with its name into the namespace of the + Python code from which this function is called. + + INPUT: + + - ``verbose`` -- boolean (optional, default ``True``) to suppress + the message printed on the invocation + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.K5_2.inject() + Defining K5_2 + sage: K5_2.is_alternating() + True + """ + name = self.name + if verbose: + print("Defining %s" % (name)) + from sage.repl.user_globals import set_global + set_global(name, self) + + @cached_method + def series(self, oriented=False): + r""" + Return the series of links ``self`` belongs to. + + INPUT: + + - ``oriented`` -- boolean (default False) it only affects proper links. + By default the items of the series will be again series of links + collecting all orientation mutants of an unoriented name. To obtain + the series of the individual links this keyword has to be set to + ``True``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K5 = KnotInfo.K5_2.series() + sage: K5(1) + + sage: KnotInfo.L4a1_1.series().inject() + Defining L4a + sage: L4a(1) + Series of links L4a1 + sage: KnotInfo.L4a1_1.series(oriented=True).inject() + Defining L4a1 + sage: L4a(1) == L4a1 + True + sage: L4a1(1) + + """ + if oriented: + S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating(), self.name_unoriented()) + else: + S = KnotInfoSeries(self.crossing_number(), self.is_knot(), self.is_alternating()) + return S + + def diagram(self, single=False, new=0, autoraise=True): + r""" + Launch the diagram of ``self`` given on the KnotInfo web-page. + + INPUT: + + - ``single`` -- boolean (default ``False``) if set to ``True`` only one + diagram is shown. + - ``new`` -- ``int`` according to :func:`open` of :mod:`webbrowser` + (``0`` default, ``1`` new window, ``2`` new tab) + - ``autoraise`` -- boolean (default ``True``) + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.diagram() # not tested + True + sage: K.diagram(single=True) # not tested + True + """ + import webbrowser + if self.is_knot(): + filename = db.filename.knots + else: + filename = db.filename.links + + if single: + return webbrowser.open(filename.diagram_url(self[self.items.diagram], single=single), new=new, autoraise=autoraise) + else: + return webbrowser.open(filename.diagram_url(self[self.items.name]), new=new, autoraise=autoraise) + + + def knot_atlas_webpage(self, new=0, autoraise=True): + r""" + Launch the Knot Atlas web-page for ``self``. + + INPUT: + + - ``new`` -- ``int`` according to :func:`open` of :mod:`webbrowser` + (``0`` default, ``1`` new window, ``2`` new tab) + - ``autoraise`` -- boolean (default ``True``) + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.knot_atlas_webpage() # not tested + True + """ + import webbrowser + return webbrowser.open(self[self.items.knot_atlas_anon], new=new, autoraise=autoraise) + + def knotilus_webpage(self, new=0, autoraise=True): + r""" + Launch the Knotilus web-page for ``self``. + + INPUT: + + - ``new`` -- ``int`` according to :func:`open` of :mod:`webbrowser` + (``0`` default, ``1`` new window, ``2`` new tab) + - ``autoraise`` -- boolean (default ``True``) + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: K = KnotInfo.K3_1 + sage: K.knotilus_webpage(new=1) # not tested + True + """ + import webbrowser + return webbrowser.open(self[self.items.knotilus_page_anon], new=new, autoraise=autoraise) + + + +# -------------------------------------------------------------------------------------------- +# KnotInfoSeries +# -------------------------------------------------------------------------------------------- +class KnotInfoSeries(UniqueRepresentation, SageObject): + r""" + This class can be used to access knots and links via their index + according to the series they belong to. + + INPUT: + + - ``crossing_number`` -- integer giving the crossing numer of this series + of links + - ``is_knot`` -- boolean whether this series is a series of knots + or proper links + - ``is_alternating`` -- boolean whether this series is restriced to + alternating links or not + This is not relevant for knots with less than 11 crossings + - ``name_unoriented`` -- string restricting the series to all links with + that ``name_unoriented`` + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: K6 = KnotInfoSeries(6, True, True); K6 + Series of knots K6 + sage: K6(3) + + sage: list(K6) + [, , ] + sage: L6a = KnotInfoSeries(6, False, True); L6a + Series of links L6a + sage: L6a(2) + Series of links L6a2 + sage: _.inject() + Defining L6a2 + sage: list(L6a2) + [, ] + sage: L6a2(0).series() == L6a + True + sage: L6a2(0) == L6a2('0') + True + """ + + + def __init__(self, crossing_number, is_knot, is_alternating, name_unoriented=None): + r""" + Python constructor. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: L6a = KnotInfoSeries(6, False, True); L6a + Series of links L6a + """ + self._crossing_number = crossing_number + self._is_knot = is_knot + self._is_alternating = is_alternating + self._name_unoriented = name_unoriented + + @cached_method + def list(self, oriented=False, comp=None, det=None, homfly=None): + r""" + Return this series as a Python list. + + INPUT: + + - ``oriented`` -- boolean (optional, default ``False``) it only affects + series of proper links. By default the list items of a series of proper + links are again series of links collecting all orientation types of an + unoriented name. To obtain the list of the individual links this + keyword has to be set to ``True`` + + - ``comp`` (optional, default ``None``) if given an integer for this + keyword the list is restriced to links having the according number + of components. This keyword implies ``oriented=True`` + + - ``det`` (optional, default ``None``) if given an integer for this + keyword the list is restriced to links having the according value + for its determinant. This keyword implies ``oriented=True`` + + - ``homfly`` (optional, default ``None``) if given a HOMFLY-PT polynomial + having ``normalization='vz'`` for this keyword the list is restriced to + links having the according value for its HOMFLY-PT polynomial. This + keyword implies ``oriented=True`` + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: K6 = KnotInfoSeries(6, True, True); K6 + Series of knots K6 + sage: K6.list() + [, , ] + sage: KnotInfoSeries(2, False, True).inject() + Defining L2a + sage: L2a.list() + [Series of links L2a1] + sage: L2a.list(oriented=True) + [, ] + """ + if homfly is not None: + # additional restriction to number of components, determinant and + # HOMFLY-PT polynomial + l = self.list(oriented=True, comp=comp, det=det) + return [L for L in l if L.homfly_polynomial() == homfly] + + if det is not None: + # additional restriction to number of components and determinant + l = self.list(oriented=True, comp=comp) + return [L for L in l if L.determinant() == det] + + if comp is not None: + # additional restriction to number of components + l = self.list(oriented=True) + return [L for L in l if L.num_components() == comp] + + # default case + is_knot = self._is_knot + cross_nr = self._crossing_number + is_alt = self._is_alternating + n_unori = self._name_unoriented + + res = [] + curr_n_unori = None + for K in KnotInfo: + if K.is_knot() != is_knot: + continue + if K.crossing_number() != cross_nr: + continue + if not is_knot or cross_nr > 10: + if K.is_alternating() != is_alt: + continue + if is_knot or oriented: + res.append(K) + else: + this_n_unori = K.name_unoriented() + if n_unori: + if this_n_unori != n_unori: + continue + res.append(K) + elif this_n_unori != curr_n_unori: + if curr_n_unori: + res.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori)) + curr_n_unori = this_n_unori + else: + continue + + if curr_n_unori: + res.append(KnotInfoSeries(cross_nr, is_knot, is_alt, curr_n_unori)) + return res + + + @cached_method + def lower_list(self, oriented=False, comp=None, det=None, homfly=None): + r""" + Return this series together with all series with smaller crossing number + as a Python list. + + INPUT: + + - ``oriented`` -- boolean (optional, default ``False``) see the + description for :meth:`list` + + - ``comp`` (optional, default ``None``) see the description for + :meth:`list` + + - ``det`` (optional, default ``None``) see the description for + :meth:`list` + + - ``homfly`` (optional, default ``None``) see the description for + :meth:`list` + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(5, True, True).lower_list() + [, + , + , + , + ] + sage: KnotInfoSeries(4, False, True).lower_list() + [Series of links L2a1, Series of links L4a1] + sage: KnotInfoSeries(4, False, True).lower_list(oriented=True) + [, + , + , + ] + """ + l = [] + cr = self._crossing_number + if cr > 0: + LS = type(self)(cr - 1, self._is_knot, self._is_alternating, self._name_unoriented ) + l = LS.lower_list(oriented=oriented, comp=comp, det=det, homfly=homfly) + return l + self.list(oriented=oriented, comp=comp, det=det, homfly=homfly) + + + def __repr__(self): + r""" + Return the representation string of ``self``. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True) + Series of knots K6 + sage: _.__repr__() + 'Series of knots K6' + """ + if self._is_knot: + return 'Series of knots %s' %(self._name()) + else: + return 'Series of links %s' %(self._name()) + + + def __getitem__(self, item): + r""" + Return the given ``item`` from the list of ``self`` + (making the Python build-in ``list`` work). + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True).inject() + Defining K6 + sage: list(K6) # indirect doctest + [, , ] + """ + from sage.rings.integer import Integer + if not type(item) in (int, Integer): + raise ValueError('Item must be an integer') + l = self.list() + max_item = len(l) + if item < 0 or item > max_item: + raise ValueError('Item must be non negative and smaller than %s' %(max_item)) + + return l[item] + + def __call__(self, item): + r""" + Return the given ``item`` from the list of ``self`` + (making the function call for ``self`` work). + In contrast to ``__getitem__`` the first ``item`` + has to be ``1`` (not ``0``). + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True).inject() + Defining K6 + sage: K6(2) # indirect doctest + + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L8a21_0_1_0.inject() # optional - database_knotinfo + Defining L8a21_0_1_0 + sage: L8a21_0_1_0.series().inject() # optional - database_knotinfo + Defining L8a + sage: L8a(1) # optional - database_knotinfo + Series of links L8a1 + sage: L8a(21)(2) == L8a21_0_1_0 # optional - database_knotinfo + True + sage: L8a(21)('010') == L8a21_0_1_0 # optional - database_knotinfo + True + """ + if self._name_unoriented: + if type(item) == str: + # allow input as dual number according to naming + item = int(item, 2) + return self[item] + + from sage.rings.integer import Integer + if not type(item) in (int, Integer): + raise ValueError('Item must be an integer') + l =self.list() + max_item = len(l)+1 + if item < 1 or item > max_item: + raise ValueError('Item must be positive and smaller than %s' %(max_item)) + + return l[item-1] + + def _name(self): + r""" + Return the name of the series. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True)._name() + 'K6' + """ + is_knot = self._is_knot + cross_nr = self._crossing_number + is_alt = self._is_alternating + n_unori = self._name_unoriented + + alt = 'a' + if not is_alt: + alt = 'n' + + if is_knot: + if cross_nr > 10: + res = 'K%s%s' %(cross_nr, alt) + else: + res = 'K%s' %(cross_nr) + elif n_unori: + res = '%s' %(n_unori) + else: + res = 'L%s%s' %(cross_nr, alt) + return res + + def is_recoverable(self, unique=True, max_samples=8): + r""" + Return if all items of ``self`` can be recovered from its conversion to + Sage links using the ``pd_notation`` and the ``braid_notation`` and their + mirror images. + + The method is indirectly used by the ``TestSuite``. + + INPUT: + + - ``unique`` -- boolean (optional, default=``True``) see + :meth:`KnotInfoBase.is_recoverable` + - ``max_samples`` -- non negative integer or ``infinity`` (optional, + default ``8``) limits the number of items to check (random sample). + If set to ``infinity`` then no limit is set. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L4a1_0.series().inject() + Defining L4a + sage: L4a.is_recoverable() + True + sage: L4a.is_recoverable(unique=False) + True + sage: KnotInfo.L5a1_0.series().inject() + Defining L5a + sage: L5a.is_recoverable() + False + sage: L5a.is_recoverable(unique=False) + True + """ + from sage.misc.misc import some_tuples + l = self.list(oriented=True) + bound = len(l) + return all(L.is_recoverable(unique=unique) for L, in some_tuples(l, 1, bound, max_samples=max_samples)) + + def _test_recover(self, **options): + r""" + Method used by ``TestSuite``. Tests if all links of the series can be + recovered from their conversion to Sage links. It uses :meth:`is_recoverable`. + Thus, per default maximal `8` items (random sample) are tested. Use the + option ``max_samples`` to choose another limit or test all + (``max_samples=infinity``) + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: TestSuite(KnotInfo.L5a1_0.series()).run(verbose=True) # indirec doctest + running ._test_category() . . . pass + running ._test_new() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + running ._test_recover() . . . pass + sage: TestSuite(KnotInfo.K6_1.series()).run(max_samples=infinity) # indirec doctest + """ + tester = options['tester'] + max_samples = tester._max_samples + if max_samples: + tester.assertTrue(self.is_recoverable(unique=False, max_samples=max_samples)) + else: + tester.assertTrue(self.is_recoverable(unique=False)) + + + def inject(self, verbose=True): + r""" + Inject ``self`` with its name into the namespace of the + Python code from which this function is called. + + INPUT: + + - ``verbose`` -- boolean (optional, default ``True``) to suppress + the message printed on the invocation + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(6, True, True).inject() + Defining K6 + sage: K6(2) + + """ + name = self._name() + if verbose: + print("Defining %s" % (name)) + from sage.repl.user_globals import set_global + set_global(name, self) + + +KnotInfo = KnotInfoBase('KnotInfo', db.row_names()) diff --git a/src/sage/knots/link.py b/src/sage/knots/link.py index 1587990aaf3..38ba588d882 100644 --- a/src/sage/knots/link.py +++ b/src/sage/knots/link.py @@ -26,14 +26,18 @@ .. SEEALSO:: - There are also tables of link and knot invariants at - https://www.indiana.edu/~knotinfo/ - and https://www.indiana.edu/~linkinfo/. + There are also tables of link and knot invariants at web-pages + `KnotInfo `__ and + `LinkInfo `__. These can be + used inside Sage after installing the optional package + ``database_knotinfo`` (type ``sage -i database_knotinfo`` in a command shell, + see :mod:`~sage.knots.knotinfo`). AUTHORS: - Miguel Angel Marco Buzunariz - Amit Jamadagni +- Sebastian Oehms (October 2020, add :meth:`get_knotinfo` and meth:`is_isotopic`) """ # **************************************************************************** @@ -44,6 +48,7 @@ # 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/ # **************************************************************************** @@ -273,7 +278,7 @@ class Link(SageObject): """ def __init__(self, data): - """ + r""" Initialize ``self``. TESTS:: @@ -505,7 +510,7 @@ def fundamental_group(self, presentation='wirtinger'): return F.quotient(rels) def _repr_(self): - """ + r""" Return a string representation. OUTPUT: string representation @@ -529,7 +534,7 @@ def _repr_(self): return 'Link with {} component{} represented by {} crossings'.format(number_of_components, plural, pd_len) def __eq__(self, other): - """ + r""" Check equality. TESTS:: @@ -554,7 +559,7 @@ def __eq__(self, other): return self.braid() == other.braid() def __hash__(self): - """ + r""" Return the hash of ``self``. EXAMPLES:: @@ -566,7 +571,7 @@ def __hash__(self): return hash(self.braid()) def __ne__(self, other): - """ + r""" Check inequality. TESTS:: @@ -582,8 +587,9 @@ def __ne__(self, other): """ return not self.__eq__(other) + def braid(self): - """ + r""" Return a braid representation of ``self``. OUTPUT: an element in the braid group @@ -1209,7 +1215,7 @@ def khovanov_homology(self, ring=ZZ, height=None, degree=None): return homologies def oriented_gauss_code(self): - """ + r""" Return the oriented Gauss code of ``self``. The oriented Gauss code has two parts: @@ -1296,7 +1302,7 @@ def oriented_gauss_code(self): return self._oriented_gauss_code def pd_code(self): - """ + r""" Return the planar diagram code of ``self``. The planar diagram is returned in the following format. @@ -1404,7 +1410,7 @@ def pd_code(self): raise AssertionError("invalid state") def gauss_code(self): - """ + r""" Return the Gauss code of ``self``. The Gauss code is generated by the following procedure: @@ -1431,7 +1437,7 @@ def gauss_code(self): return self.oriented_gauss_code()[0] def dowker_notation(self): - """ + r""" Return the Dowker notation of ``self``. Similar to the PD code we number the components, so every crossing @@ -1465,7 +1471,7 @@ def dowker_notation(self): return dn def _braid_word_components(self): - """ + r""" Return the disjoint braid components, if any, else return the braid of ``self``. @@ -1514,7 +1520,7 @@ def _braid_word_components(self): return tuple([a for a in x if a]) def _braid_word_components_vector(self): - """ + r""" The list from the :meth:`_braid_word_components` is flattened to give out the vector form. @@ -1581,7 +1587,7 @@ def _homology_generators(self): @cached_method def seifert_matrix(self): - """ + r""" Return the Seifert matrix associated with ``self``. ALGORITHM: @@ -1636,7 +1642,7 @@ def seifert_matrix(self): @cached_method def number_of_components(self): - """ + r""" Return the number of connected components of ``self``. OUTPUT: number of connected components @@ -1669,7 +1675,7 @@ def number_of_components(self): return G.connected_components_number() def is_knot(self): - """ + r""" Return ``True`` if ``self`` is a knot. Every knot is a link but the converse is not true. @@ -1688,7 +1694,7 @@ def is_knot(self): return self.number_of_components() == 1 def genus(self): - """ + r""" Return the genus of ``self``. EXAMPLES:: @@ -1741,7 +1747,7 @@ def genus(self): return sum(g, ZZ.zero()) def signature(self): - """ + r""" Return the signature of ``self``. This is defined as the signature of the symmetric matrix @@ -1811,7 +1817,7 @@ def omega_signature(self, omega): return ZZ.sum(j.real().sign() for j in m.eigenvalues()) def alexander_polynomial(self, var='t'): - """ + r""" Return the Alexander polynomial of ``self``. INPUT: @@ -1886,7 +1892,7 @@ def alexander_polynomial(self, var='t'): return f def determinant(self): - """ + r""" Return the determinant of ``self``. EXAMPLES:: @@ -1917,9 +1923,8 @@ def determinant(self): raise NotImplementedError("determinant implemented only for knots") def is_alternating(self): - """ - Return ``True`` if the given knot diagram is alternating else - returns ``False``. + r""" + Return whether the given knot diagram is alternating. Alternating diagram implies every overcross is followed by an undercross or the vice-versa. @@ -2017,7 +2022,7 @@ def orientation(self): return orientation def seifert_circles(self): - """ + r""" Return the Seifert circles from the link diagram of ``self``. Seifert circles are the circles obtained by smoothing all crossings @@ -2087,7 +2092,7 @@ def seifert_circles(self): return result def regions(self): - """ + r""" Return the regions from the link diagram of ``self``. Regions are obtained always turning left at each crossing. @@ -2185,6 +2190,7 @@ def regions(self): regions.append(region) return regions + @cached_method def mirror_image(self): r""" Return the mirror image of ``self``. @@ -2258,7 +2264,7 @@ def mirror_image(self): return type(self)(pd) def writhe(self): - """ + r""" Return the writhe of ``self``. EXAMPLES:: @@ -2545,6 +2551,7 @@ def _bracket(self): cross[cross.index(b)] = a return t * Link(rest)._bracket() + ~t * Link(rest_2)._bracket() + @cached_method def _isolated_components(self): r""" Return the PD codes of the isolated components of ``self``. @@ -2570,7 +2577,8 @@ def _isolated_components(self): return [[list(i) for i in j] for j in G.connected_components(sort=False)] - def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): + @cached_method + def homfly_polynomial(self, var1=None, var2=None, normalization='lm'): r""" Return the HOMFLY polynomial of ``self``. @@ -2580,8 +2588,10 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): INPUT: - - ``var1`` -- (default: ``'L'``) the first variable - - ``var2`` -- (default: ``'M'``) the second variable + - ``var1`` -- (default: ``'L'``) the first variable. If ``normalization`` + is set to ``az`` resp. ``vz`` the default is ``a`` resp. ``v`` + - ``var2`` -- (default: ``'M'``) the second variable. If ``normalization`` + is set to ``az`` resp. ``vz`` the default is ``z`` - ``normalization`` -- (default: ``lm``) the system of coordinates and can be one of the following: @@ -2591,6 +2601,9 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): * ``'az'`` -- corresponding to the Skein relation `a\cdot P(K _+) - a^{-1}\cdot P(K _-) = z \cdot P(K _0)` + * ``'vz'`` -- corresponding to the Skein relation + `v^{-1}\cdot P(K _+) - v\cdot P(K _-) = z \cdot P(K _0)` + where `P(K _+)`, `P(K _-)` and `P(K _0)` represent the HOMFLY polynomials of three links that vary only in one crossing; that is the positive, negative, or smoothed links respectively @@ -2602,7 +2615,10 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): .. NOTE:: Use the ``'az'`` normalization to agree with the data - in [KnotAtlas]_ and http://www.indiana.edu/~knotinfo/. + in [KnotAtlas]_ + + Use the ``'vz'`` normalization to agree with the data + `KnotInfo `__. EXAMPLES: @@ -2627,7 +2643,7 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): sage: L.homfly_polynomial() L^3*M^-1 - L*M + L*M^-1 sage: L = Link([[1,4,2,3], [4,1,3,2]]) - sage: L.homfly_polynomial('a', 'z', 'az') + sage: L.homfly_polynomial(normalization='az') a^3*z^-1 - a*z - a*z^-1 The figure-eight knot:: @@ -2646,6 +2662,13 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): sage: L.homfly_polynomial() 1 + Comparison with KnotInfo:: + + sage: KI, m = K.get_knotinfo(); KI, m + (, False) + sage: K.homfly_polynomial(normalization='vz') == KI.homfly_polynomial() + True + The knot `9_6`:: sage: B = BraidGroup(3) @@ -2667,10 +2690,14 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): -L*M^-1 - L^-1*M^-1 sage: L.homfly_polynomial() -L*M^-1 - L^-1*M^-1 - sage: L.homfly_polynomial('a', 'z', 'az') - a*z^-1 - a^-1*z^-1 - sage: L2.homfly_polynomial('a', 'z', 'az') + sage: L.homfly_polynomial(normalization='az') a*z^-1 - a^-1*z^-1 + sage: L2.homfly_polynomial('α', 'ζ', 'az') + α*ζ^-1 - α^-1*ζ^-1 + sage: L.homfly_polynomial(normalization='vz') + -v*z^-1 + v^-1*z^-1 + sage: L2.homfly_polynomial('ν', 'ζ', 'vz') + -ν*ζ^-1 + ν^-1*ζ^-1 Check that :trac:`30346` is fixed:: @@ -2683,14 +2710,29 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): - :wikipedia:`HOMFLY_polynomial` - http://mathworld.wolfram.com/HOMFLYPolynomial.html """ + if not var1: + if normalization == 'az': + var1 = 'a' + elif normalization == 'vz': + var1 = 'v' + else: + var1 = 'L' + if not var2: + if normalization == 'lm': + var2 = 'M' + else: + var2 = 'z' + L = LaurentPolynomialRing(ZZ, [var1, var2]) if len(self._isolated_components()) > 1: if normalization == 'lm': fact = L({(1, -1):-1, (-1, -1):-1}) elif normalization == 'az': fact = L({(1, -1):1, (-1, -1):-1}) + elif normalization == 'vz': + fact = L({(1, -1):-1, (-1, -1):1}) else: - raise ValueError('normalization must be either `lm` or `az`') + raise ValueError('normalization must be either `lm`, `az` or `vz`') fact = fact ** (len(self._isolated_components())-1) for i in self._isolated_components(): fact = fact * Link(i).homfly_polynomial(var1, var2, normalization) @@ -2720,8 +2762,13 @@ def homfly_polynomial(self, var1='L', var2='M', normalization='lm'): return L(auxdic) else: return -L(auxdic) + elif normalization == 'vz': + h_az = self.homfly_polynomial(var1=var1, var2=var2, normalization='az') + a, z = h_az.parent().gens() + v = ~a + return h_az.subs({a:v}) else: - raise ValueError('normalization must be either `lm` or `az`') + raise ValueError('normalization must be either `lm`, `az` or `vz`') def _coloring_matrix(self, n): r""" @@ -3340,3 +3387,598 @@ def delta(u, v): image += l ims += sum(line(a[0], **kwargs) for a in im) return image + + + def _markov_move_cmp(self, braid): + r""" + Return whether ``self`` can be transformed to the closure of ``braid`` + by a sequence of Markov moves. + + More precisely it is checked whether the braid of ``self`` is conjugated + to the given braid in the following sense. If both braids have the same + number of strands it is checked if they are conjugated to each other in + their common braid group (Markov move I). If the number of strands differs, + the braid having less strands is extended by Markov moves II (appendening + the largest generator or its inverse recursively) until a common braid + group can be achieved, where conjugation is tested. + + Be aware, that a negative result does not ensure that ``self`` is not + isotopic to the closure of ``braid``. + + EXAMPLES:: + + sage: b = BraidGroup(4)((1, 2, -3, 2, 2, 2, 2, 2, 2, -1, 2, 3, 2)) + sage: L = Link([[2, 1, 4, 5], [5, 4, 6, 7], [7, 6, 8, 9], [9, 8, 10, 11], + ....: [11, 10, 12, 13], [13, 12, 14, 15], [15, 14, 16, 17], + ....: [3, 17, 18, 19], [16, 1, 21, 18], [19, 21, 2, 3]]) + sage: L._markov_move_cmp(b) # both are isotopic to ``9_3`` + True + sage: bL = L.braid(); bL + s0^7*s1*s0^-1*s1 + sage: Lb = Link(b); Lb + Link with 1 component represented by 13 crossings + sage: Lb._markov_move_cmp(bL) + True + sage: L == Lb + False + sage: b.strands() > bL.strands() + True + + REFERENCES: + + - :wikipedia:`Markov_theorem` + """ + sb = self.braid() + sb_ind = sb.strands() + + ob = braid + ob_ind = ob.strands() + + if sb_ind == ob_ind: + return sb.is_conjugated(ob) + + if sb_ind > ob_ind: + # if the braid of self has more strands we have to perfom + # Markov II moves + B = sb.parent() + g = B.gen(ob_ind-1) + ob = B(ob) + if sb_ind > ob_ind+1: + # proceed by recursion + res = self._markov_move_cmp(ob*g) + if not res: + res = self._markov_move_cmp(ob*~g) + else: + res = sb.is_conjugated(ob*g) + if not res: + res = sb.is_conjugated(ob*~g) + return res + else: + L = Link(ob) + return L._markov_move_cmp(sb) + + @cached_method + def _knotinfo_matching_list(self): + r""" + Return a list of links from the KnotInfo and LinkInfo databases which match + the properties of ``self`` as much as possible. + + OUTPUT: + + A tuple ``(l, proved)`` where ``l`` is the matching list and ``proved`` a boolean + telling if the entries of ``l`` are checked to be isotopic to self or not. + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: KnotInfo.L5a1_0.inject() + Defining L5a1_0 + sage: L5a1_0.link()._knotinfo_matching_list() + ([], True) + sage: Link(L5a1_0.braid())._knotinfo_matching_list() + ([, ], True) + + Care is needed for links having non irreducible HOMFLY-PT polynomials:: + + sage: k4_1 = KnotInfo.K4_1.link() + sage: k5_2 = KnotInfo.K5_2.link() + sage: k = k4_1.connected_sum(k5_2) + sage: k._knotinfo_matching_list() # optional - database_knotinfo + ([], False) + + """ + from sage.knots.knotinfo import KnotInfoSeries + cr = len(self.pd_code()) + co = self.number_of_components() + + # set the limits for the KnotInfoSeries + if cr > 11 and co > 1: + cr = 11 + if cr > 12: + cr = 12 + + Hp = self.homfly_polynomial(normalization='vz') + + det = None + if cr > 6: + # for larger crossing numbers the KnotInfoSeries become very + # large, as well. For better performance we restrict the cached + # lists by the determinant and number of components. + # + # Since :meth:`determinant` is not implemented for proper links + # we have to go back to the roots. + ap = self.alexander_polynomial() + det = Integer(abs(ap(-1))) + + is_knot = self.is_knot() + if is_knot and cr < 11: + S = KnotInfoSeries(cr, True, None) + l = S.lower_list(oriented=True, comp=co, det=det, homfly=Hp) + else: + # the result of :meth:`is_alternating` depends on the specific + # diagram of the link. For example ``K11a_2`` is an alternating + # knot but ``Link(KnotInfo.K11a_2.braid()).is_alternating()`` + # gives ``False``. Therefore, we have to take both series + # into consideration. + Sa = KnotInfoSeries(cr, is_knot, True) + Sn = KnotInfoSeries(cr, is_knot, False) + la = Sa.lower_list(oriented=True, comp=co, det=det, homfly=Hp) + ln = Sn.lower_list(oriented=True, comp=co, det=det, homfly=Hp) + l = sorted(list(set(la + ln))) + + pdm = [[a[0], a[3], a[2], a[1]] for a in self.pd_code() ] + br = self.braid() + br_ind = br.strands() + + res = [] + for L in l: + if L.pd_notation() == pdm: + # note that KnotInfo pd_notation works counter clockwise. Therefore, + # to compensate this we compare with the mirrored pd_code. See also, + # docstring of :meth:`link` of :class:`~sage.knots.knotinfo.KnotInfoBase`. + return[L], True # pd_notation is unique in the KnotInfo database + + if L.braid_index() <= br_ind: + if self._markov_move_cmp(L.braid()): + res.append(L) + + if res: + if len(res) > 1 or res[0].is_unique(): + return res, True + return l, False + + def get_knotinfo(self, mirror_version=True, unique=True): + r""" + Identify this link as an item of the KnotInfo database (if possible). + + INPUT: + + - ``mirror_version`` -- boolean (default is ``True``). If set to ``False`` + the result of the method will be just the instance of :class:`~sage.knots.knotinfo.KnotInfoBase` + (by default the result is a tuple of the instance and a boolean, see + explanation of the output below) + + - ``unique`` -- boolean (default is ``True``). This only affects the case + where a unique identification is not possible. If set to ``False`` you + can obtain a matching list (see explanation of the output below) + + OUTPUT: + + A tuple ``(K, m)`` where ``K`` is an instance of :class:`~sage.knots.knotinfo.KnotInfoBase` + and ``m`` a boolean (for chiral links) telling if ``self`` corresponds + to the mirrored version of ``K`` or not. The value of ``m`` is ``None`` + for amphicheiral links and ``?`` if it cannot be determined uniquely + and the keyword option ``unique=False`` is given. + + For proper links, if the orientation mutant cannot be uniquely determined, + K will be a series of links gathering all links having the same unoriented + name, that is an instance of :class:`~sage.knots.knotinfo.KnotInfoSeries`. + + If ``mirror_version`` is set to ``False`` then the result is just ``K`` + (that is: ``m`` is suppressed). + + If it is not possible to determine a unique result a ``NotImplementedError`` + will be raised. To avoid this you can set ``unique`` to ``False``. You + will get a list of matching candidates instead. + + .. NOTE:: + + The identification of proper links may fail to be unique due to the + following fact: In opposite to the database for knots, there are pairs + of oriented mutants of an unoriented link which are isotopic to each + other. For example ``L5a1_0`` and ``L5a1_1`` is such a pair. + + This is because all combinatorial possible oriented mutants are + listed with individual names regardless whether they are pairwise + non isotopic or not. In such a case the identification is not + unique and therefore a series of the links will be returned which + gathers all having the same unoriented name. + + To obtain the individual oriented links being isotopic to ``self`` + use the keyword ``unique`` (see the examples for ``L2a1_1`` and + ``L5a1_0`` below). + + EXAMPLES:: + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = Link([[4,2,5,1], [10,3,11,4], [5,16,6,17], [7,12,8,13], + ....: [18,9,19,10], [2,11,3,12], [13,20,14,21], [15,6,16,7], + ....: [22,18,1,17], [8,19,9,20], [21,14,22,15]]) + sage: L.get_knotinfo() # optional - database_knotinfo + (, True) + + sage: K = KnotInfo.K10_25 # optional - database_knotinfo + sage: l = K.link() # optional - database_knotinfo + sage: l.get_knotinfo() # optional - database_knotinfo + (, False) + + Knots with more than 12 and proper links having more than 11 crossings + cannot be identified. In addition non prime links or even links whose + HOMFLY-PT polynomial is not irreducible cannot be identified:: + + sage: b, = BraidGroup(2).gens() + sage: Link(b**13).get_knotinfo() + Traceback (most recent call last): + ... + NotImplementedError: this knot having more than 12 crossings cannot be determined + + sage: Link([[1, 5, 2, 4], [3, 1, 4, 8], [5, 3, 6, 2], [6, 9, 7, 10], [10, 7, 9, 8]]) + Link with 2 components represented by 5 crossings + sage: _.get_knotinfo() + Traceback (most recent call last): + ... + NotImplementedError: this (possibly non prime) link cannot be determined + + Lets identify the monster unknot:: + + sage: L = Link([[3,1,2,4], [8,9,1,7], [5,6,7,3], [4,18,6,5], + ....: [17,19,8,18], [9,10,11,14], [10,12,13,11], + ....: [12,19,15,13], [20,16,14,15], [16,20,17,2]]) + sage: L.get_knotinfo() + (, None) + + Usage of option ``mirror_version``:: + + sage: L.get_knotinfo(mirror_version=False) == KnotInfo.K0_1 + True + + Usage of option ``unique``:: + + sage: l = K.link(K.items.gauss_notation) # optional - database_knotinfo + sage: l.get_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: this link cannot be uniquely determined + use keyword argument `unique` to obtain more details + + sage: l.get_knotinfo(unique=False) # optional - database_knotinfo + [(, False), (, False)] + + sage: k11 = KnotInfo.K11n_82.link() # optional - database_knotinfo + sage: k11m = k11.mirror_image() # optional - database_knotinfo + sage: k11m.braid() # optional - database_knotinfo + s0*s1^-1*s0*s2^-1*s1*(s1*s2^-1)^2*s2^-2 + sage: k11m.get_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: mirror type of this link cannot be uniquely determined + use keyword argument `unique` to obtain more details + + sage: k11m.get_knotinfo(unique=False) # optional - database_knotinfo + [(, '?')] + + sage: t = (-1, 2, -1, 2, -1, 3, -2, 3, -2) + sage: l9 = Link(BraidGroup(4)(t)) + sage: l9.get_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: this link cannot be uniquely determined + use keyword argument `unique` to obtain more details + + sage: l9.get_knotinfo(unique=False) # optional - database_knotinfo + [(, False), + (, False)] + + sage: t = (1, 2, 3, -4, 3, -2, -1, 3, -2, 3, -2, 3, -4, 3, -2) + sage: l15 = Link(BraidGroup(5)(t)) + sage: l15.get_knotinfo() # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: this link having more than 11 crossings cannot be uniquely determined + use keyword argument `unique` to obtain more details + + sage:l15.get_knotinfo(unique=False) # optional - database_knotinfo + [(, False), + (, False)] + + Furthermore, if the result is a complete series of oriented links having + the same unoriented name (according to the note above) the option can be + used to achieve more detailed information:: + + sage: L2a1 = Link(b**2) + sage: L2a1.get_knotinfo() + (Series of links L2a1, None) + sage: L2a1.get_knotinfo(unique=False) + [(, True), (, False)] + + sage: KnotInfo.L5a1_0.inject() + Defining L5a1_0 + sage: l5 = Link(L5a1_0.braid()) + sage: l5.get_knotinfo() + (Series of links L5a1, False) + sage: _[0].inject() + Defining L5a1 + sage: list(L5a1) + [, ] + sage: l5.get_knotinfo(unique=False) + [(, False), (, False)] + + Clarifying the series around the Perko pair (:wikipedia:`Perko_pair`):: + + sage: for i in range(160, 166): # optional - database_knotinfo + ....: K = Knots().from_table(10, i) + ....: print('%s_%s' %(10, i), '--->', K.get_knotinfo()) + 10_160 ---> (, False) + 10_161 ---> (, True) + 10_162 ---> (, False) + 10_163 ---> (, False) + 10_164 ---> (, False) + 10_165 ---> (, True) + + Clarifying ther Perko series against `SnapPy + `__:: + + sage: import snappy # optional - snappy + Plink failed to import tkinter. + + sage: from sage.knots.knotinfo import KnotInfoSeries + sage: KnotInfoSeries(10, True, True) # optional - database_knotinfo + Series of knots K10 + sage: _.inject() # optional - database_knotinfo + Defining K10 + sage: for i in range(160, 166): # optional - database_knotinfo snappy + ....: K = K10(i) + ....: k = K.link(K.items.name, snappy=True) + ....: print(k, '--->', k.sage_link().get_knotinfo()) + ---> (, False) + ---> (, True) + ---> (, False) + ---> (, False) + ---> (, False) + ---> (, False) + + sage: snappy.Link('10_166') # optional - snappy + + sage: _.sage_link().get_knotinfo() # optional - database_knotinfo snappy + (, True) + + Another pair of confusion (see the corresponding `Warning + `__):: + + sage: Ks10_86 = snappy.Link('10_86') # optional - snappy + sage: Ks10_83 = snappy.Link('10_83') # optional - snappy + sage: Ks10_86.sage_link().get_knotinfo() # optional - snappy + (, True) + sage: Ks10_83.sage_link().get_knotinfo() # optional - snappy + (, False) + + TESTS: + + sage: L = KnotInfo.L10a171_1_1_0 # optional - database_knotinfo + sage: l = L.link(L.items.braid_notation) # optional - database_knotinfo + sage: l.get_knotinfo(unique=False) # optional - database_knotinfo + [(, True), + (, True), + (, False), + (, False)] + """ + # ToDo: extension to non prime links in which case an element of the monoid + # over :class:`KnotInfo` should be returned + + non_unique_hint = '\nuse keyword argument `unique` to obtain more details' + def answer(L): + r""" + Return a single item of the KnotInfo database according to the keyword + arguments ``mirror_version``. + """ + if not mirror_version: + return L + + chiral = True + ach = L.is_amphicheiral() + achp = L.is_amphicheiral(positive=True) + if ach is None and achp is None: + if unique: + raise NotImplementedError('this link cannot be uniquely determined (unknown chirality)%s' %non_unique_hint) + elif L.is_amphicheiral() or L.is_amphicheiral(positive=True): + chiral = False + + if not chiral: + mirrored = None + elif proved_m and not proved_s and L in lm: + mirrored = True + elif proved_s and not proved_m and L in l: + mirrored = False + else: + # nothing proved + if L in ls and L in lm: + # In case of a chiral link this means that the HOMFLY-PT + # polynomial does not distinguish mirror images (see the above + # example ``k11m``). + if unique: + raise NotImplementedError('mirror type of this link cannot be uniquely determined%s' %non_unique_hint) + mirrored = '?' + elif L in lm: + mirrored = True + else: + mirrored = False + + return L, mirrored + + def answer_unori(S): + r""" + Return a series of oriented links having the same unoriented name + according to the keyword ``mirror_version``. + """ + if not mirror_version: + return S + + mirrored = [answer(L)[1] for L in S] + if all(mirrored): + # all matching links are mirrored to self + return S, True + if any(i == '?' for i in mirrored): + # unknown chirality for a matching link + return S, '?' + if any(i is None for i in mirrored): + # an amphicheiral link matches + return S, None + if not any(mirrored): + # no matching link is mirrored to self + return S, False + # finally both mirror types match + return S, None + + def answer_list(l): + r""" + Return a list of items of the KnotInfo database according to the keyword + argument ``unique``. + """ + if not unique: + return sorted([answer(L) for L in l]) + + if len(l) == 1: + return answer(l[0]) + + if not l[0].is_knot(): + S = l[0].series(oriented=True) + if set(list(S)) == set(l): + return answer_unori(S) + + raise NotImplementedError('this link cannot be uniquely determined%s' %non_unique_hint) + + + self_m = self.mirror_image() + ls, proved_s = self._knotinfo_matching_list() + lm, proved_m = self_m._knotinfo_matching_list() + l = list(set(ls + lm)) + + if l and not unique: + return answer_list(l) + + if proved_s and proved_m: + return answer_list(l) + + if proved_s: + return answer_list(ls) + + if proved_m: + return answer_list(lm) + + # here we come if we cannot be sure about the found result + + uniq_txt = ('', '') + if l: + uniq_txt = (' uniquely', non_unique_hint) + + cr = len(self.pd_code()) + if self.is_knot() and cr > 12: + # we cannot not be sure if this link is recorded in the KnotInfo database + raise NotImplementedError('this knot having more than 12 crossings cannot be%s determined%s' %uniq_txt) + + if not self.is_knot() and cr > 11: + # we cannot not be sure if this link is recorded in the KnotInfo database + raise NotImplementedError('this link having more than 11 crossings cannot be%s determined%s' %uniq_txt) + + H = self.homfly_polynomial(normalization='vz') + + if sum(exp for f, exp in H.factor()) > 1: + # we cannot be sure if this is a prime link (see the example for the connected + # sum of K4_1 and K5_2 in the doctest of :meth:`_knotinfo_matching_list`) + raise NotImplementedError('this (possibly non prime) link cannot be%s determined%s' %uniq_txt) + + if not l: + from sage.features.databases import DatabaseKnotInfo + DatabaseKnotInfo().require() + return l + + return answer_list(l) + + + def is_isotopic(self, other): + r""" + Check whether ``self`` is isotopic to ``other``. + + INPUT: + + - ``other`` -- another instance of :class:`Link` + + EXAMPLES:: + + sage: l1 = Link([[2, 9, 3, 10], [4, 13, 5, 14], [6, 11, 7, 12], + ....: [8, 1, 9, 2], [10, 7, 11, 8], [12, 5, 13, 6], + ....: [14, 3, 1, 4]]) + sage: l2 = Link([[1, 8, 2, 9], [9, 2, 10, 3], [3, 14, 4, 1], + ....: [13, 4, 14, 5], [5, 12, 6, 13], [11, 6, 12, 7], + ....: [7, 10, 8, 11]]) + sage: l1.is_isotopic(l2) + True + + sage: l3 = l2.mirror_image() + sage: l1.is_isotopic(l3) + False + + sage: from sage.knots.knotinfo import KnotInfo + sage: L = KnotInfo.L7a7_0_0 # optional - database_knotinfo + sage: L.series(oriented=True).inject() # optional - database_knotinfo + Defining L7a7 + sage: L == L7a7(0) # optional - database_knotinfo + True + sage: l = L.link() # optional - database_knotinfo + sage: l.is_isotopic(L7a7(1).link()) # optional - database_knotinfo + Traceback (most recent call last): + ... + NotImplementedError: comparison not possible! + sage: l.is_isotopic(L7a7(2).link()) # optional - database_knotinfo + True + sage: l.is_isotopic(L7a7(3).link()) # optional - database_knotinfo + False + """ + from sage.misc.verbose import verbose + if not isinstance(other, Link): + verbose('other is not a link') + return False + + if self == other: + # surely isotopic + verbose('identified by representation') + return True + + if self.homfly_polynomial() != other.homfly_polynomial(): + # surely non isotopic + verbose('different Homfly-PT polynomials') + return False + + if self._markov_move_cmp(other.braid()): + # surely isotopic + verbose('identified via Markov moves') + return True + + try: + ki, m = self.get_knotinfo() + verbose('KnotInfo self: %s mirrored %s' %(ki, m)) + try: + if ki.is_unique(): + try: + kio = other.get_knotinfo() + verbose('KnotInfo other: %s mirrored %s' %kio) + return (ki, m) == kio + except NotImplementedError: + pass + except AttributeError: + # ki is a series + pass + except NotImplementedError: + pass + + raise NotImplementedError('comparison not possible!') diff --git a/src/sage/lfunctions/pari.py b/src/sage/lfunctions/pari.py index f810157b3e4..f621b57c67c 100644 --- a/src/sage/lfunctions/pari.py +++ b/src/sage/lfunctions/pari.py @@ -421,7 +421,7 @@ class LFunction(SageObject): sage: L.derivative(1,E.rank()) 1.51863300057685 sage: L.taylor_series(1,4) - -3...e-19 + (...e-19)*z + 0.759316500288427*z^2 - 0.430302337583362*z^3 + O(z^4) + ...e-19 + (...e-19)*z + 0.759316500288427*z^2 - 0.430302337583362*z^3 + O(z^4) .. RUBRIC:: Number field diff --git a/src/sage/libs/all.py b/src/sage/libs/all.py index cb00bd33b4f..9fd69e148ff 100644 --- a/src/sage/libs/all.py +++ b/src/sage/libs/all.py @@ -8,9 +8,10 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.libs.gap.libgap', 'libgap') -lazy_import('sage.libs.eclib.all', ('mwrank_EllipticCurve', - 'mwrank_MordellWeil', 'mwrank_initprimes', 'CremonaModularSymbols')) -lazy_import('sage.libs.eclib.all', 'get_precision', 'mwrank_get_precision') -lazy_import('sage.libs.eclib.all', 'set_precision', 'mwrank_set_precision') +lazy_import('sage.libs.eclib.constructor', 'CremonaModularSymbols') +lazy_import('sage.libs.eclib.interface', ['mwrank_EllipticCurve', 'mwrank_MordellWeil']) +lazy_import('sage.libs.eclib.mwrank', 'get_precision', 'mwrank_get_precision') +lazy_import('sage.libs.eclib.mwrank', 'set_precision', 'mwrank_set_precision') +lazy_import('sage.libs.eclib.mwrank', 'initprimes', 'mwrank_initprimes') lazy_import('sage.libs.giac.giac', 'libgiac') diff --git a/src/sage/libs/eclib/__init__.pxd b/src/sage/libs/eclib/__init__.pxd index 3f99f998a50..d44d4fba865 100644 --- a/src/sage/libs/eclib/__init__.pxd +++ b/src/sage/libs/eclib/__init__.pxd @@ -12,9 +12,11 @@ from libcpp.pair cimport pair from sage.libs.ntl.types cimport ZZ_c -# NOTE: eclib includes have specific dependencies and must be included -# in a specific order. So we start by listing all relevant include files -# in the correct order. +# NOTE: eclib used to have specific dependencies, so that they had to +# be included in a specific order. Although this is no longer the +# case, we start by listing all relevant include files in the correct +# order. + cdef extern from "eclib/vector.h": pass cdef extern from "eclib/xmod.h": pass cdef extern from "eclib/svector.h": pass diff --git a/src/sage/libs/eclib/interface.py b/src/sage/libs/eclib/interface.py index e8984567205..a163cc31093 100644 --- a/src/sage/libs/eclib/interface.py +++ b/src/sage/libs/eclib/interface.py @@ -21,17 +21,16 @@ sage: [k for k in sys.modules if k.startswith("sage.libs.eclib")] [] sage: EllipticCurve('11a1').mwrank_curve() - y^2+ y = x^3 - x^2 - 10*x - 20 + y^2 + y = x^3 - x^2 - 10 x - 20 sage: [k for k in sys.modules if k.startswith("sage.libs.eclib")] ['...'] """ - +import sys from sage.structure.sage_object import SageObject from sage.rings.all import Integer from sage.rings.integer_ring import IntegerRing -from .mwrank import _Curvedata, _two_descent, _mw - +from .mwrank import _Curvedata, _two_descent, _mw, parse_point_list class mwrank_EllipticCurve(SageObject): r""" @@ -67,7 +66,7 @@ class mwrank_EllipticCurve(SageObject): sage: e = mwrank_EllipticCurve([3, -4]) sage: e - y^2 = x^3 + 3*x - 4 + y^2 = x^3 + 3 x - 4 sage: e.ainvs() [0, 0, 0, 3, -4] @@ -127,6 +126,7 @@ def __init__(self, ainvs, verbose=False): # place holders self.__saturate = -2 # not yet saturated + self.__descent = None def __reduce__(self): r""" @@ -137,12 +137,9 @@ def __reduce__(self): sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: E.__reduce__() (, ([0, 0, 1, -7, 6], False)) - - """ return mwrank_EllipticCurve, (self.__ainvs, self.__verbose) - def set_verbose(self, verbose): """ Set the verbosity of printing of output by the :meth:`two_descent()` and @@ -247,53 +244,27 @@ def __repr__(self): sage: E = mwrank_EllipticCurve([0,-1,1,0,0]) sage: E.__repr__() - 'y^2+ y = x^3 - x^2 ' + 'y^2 + y = x^3 - x^2' """ - # TODO: Is the use (or omission) of spaces here intentional? - a = self.ainvs() - s = "y^2" - if a[0] == -1: - s += "- x*y " - elif a[0] == 1: - s += "+ x*y " - elif a[0] != 0: - s += "+ %s*x*y "%a[0] - if a[2] == -1: - s += " - y" - elif a[2] == 1: - s += "+ y" - elif a[2] != 0: - s += "+ %s*y"%a[2] - s += " = x^3 " - if a[1] == -1: - s += "- x^2 " - elif a[1] == 1: - s += "+ x^2 " - elif a[1] != 0: - s += "+ %s*x^2 "%a[1] - if a[3] == -1: - s += "- x " - elif a[3] == 1: - s += "+ x " - elif a[3] != 0: - s += "+ %s*x "%a[3] - if a[4] == -1: - s += "-1" - elif a[4] == 1: - s += "+1" - elif a[4] != 0: - s += "+ %s"%a[4] - s = s.replace("+ -","- ") - return s - + a1, a2, a3, a4, a6 = self.__ainvs + # we do not assume a1, a2, a3 are reduced to {0,1}, {-1,0,1}, {0,1} + coeff = lambda a: ''.join([" +" if a > 0 else " -", + " " + str(abs(a)) if abs(a) > 1 else ""]) + return ''.join(['y^2', + ' '.join([coeff(a1), 'xy']) if a1 else '', + ' '.join([coeff(a3), 'y']) if a3 else '', + ' = x^3', + ' '.join([coeff(a2), 'x^2']) if a2 else '', + ' '.join([coeff(a4), 'x']) if a4 else '', + ' '.join([" +" if a6 > 0 else " -", str(abs(a6))]) if a6 else '']) def two_descent(self, - verbose = True, - selmer_only = False, - first_limit = 20, - second_limit = 8, - n_aux = -1, - second_descent = True): + verbose=True, + selmer_only=False, + first_limit=20, + second_limit=8, + n_aux=-1, + second_descent=True): r""" Compute 2-descent data for this curve. @@ -374,16 +345,14 @@ def two_descent(self, second_limit = int(second_limit) n_aux = int(n_aux) second_descent = int(second_descent) # convert from bool to (int) 0 or 1 - # TODO: Don't allow limits above some value...??? - # (since otherwise mwrank just sets limit tiny) self.__descent = _two_descent() self.__descent.do_descent(self.__curve, - verbose, - selmer_only, - first_limit, - second_limit, - n_aux, - second_descent) + verbose, + selmer_only, + first_limit, + second_limit, + n_aux, + second_descent) if not self.__descent.ok(): raise RuntimeError("A 2-descent did not complete successfully.") self.__saturate = -2 # not yet saturated @@ -398,11 +367,9 @@ def __two_descent_data(self): sage: E._mwrank_EllipticCurve__two_descent_data() """ - try: - return self.__descent - except AttributeError: + if self.__descent is None: self.two_descent(self.__verbose) - return self.__descent + return self.__descent def conductor(self): """ @@ -565,22 +532,24 @@ def regulator(self): R = self.__two_descent_data().regulator() return float(R) - def saturate(self, bound=-1): + def saturate(self, bound=-1, lower=2): """ - Compute the saturation of the Mordell-Weil group at all - primes up to ``bound``. + Compute the saturation of the Mordell-Weil group. INPUT: - - ``bound`` (int, default -1) -- Use `-1` (the default) to - saturate at *all* primes, `0` for no saturation, or `n` (a - positive integer) to saturate at all primes up to `n`. + - ``bound`` (int, default -1) -- If `-1`, saturate at *all* + primes by computing a bound on the saturation index, + otherwise saturate at all primes up to the minimum of + ``bound`` and the saturation index bound. + + - ``lower`` (int, default 2) -- Only saturate at primes not + less than this. EXAMPLES: Since the 2-descent automatically saturates at primes up to - 20, it is not easy to come up with an example where saturation - has any effect:: + 20, further saturation often has no effect:: sage: E = mwrank_EllipticCurve([0, 0, 0, -1002231243161, 0]) sage: E.gens() @@ -599,7 +568,7 @@ def saturate(self, bound=-1): """ bound = int(bound) if self.__saturate < bound: - self.__two_descent_data().saturate(bound) + self.__two_descent_data().saturate(bound, lower) self.__saturate = bound def gens(self): @@ -613,8 +582,7 @@ def gens(self): [[0, -1, 1]] """ self.saturate() - L = eval(self.__two_descent_data().getbasis().replace(":",",")) - return [[Integer(x), Integer(y), Integer(z)] for (x,y,z) in L] + return parse_point_list(self.__two_descent_data().getbasis()) def certain(self): r""" @@ -760,65 +728,37 @@ class mwrank_MordellWeil(SageObject): sage: EQ.search(1) P1 = [0:1:0] is torsion point, order 1 P1 = [-3:0:1] is generator number 1 - saturating up to 20...Checking 2-saturation - Points have successfully been 2-saturated (max q used = 7) - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 7) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 23) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 41) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 17) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 43) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 31) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 37) + saturating up to 20...Saturation index bound (for points of good reduction) = 3 + Reducing saturation bound from given value 20 to computed index bound 3 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 7) + Checking 3-saturation + Points were proved 3-saturated (max q used = 7) done P2 = [-2:3:1] is generator number 2 - saturating up to 20...Checking 2-saturation + saturating up to 20...Saturation index bound (for points of good reduction) = 4 + Reducing saturation bound from given value 20 to computed index bound 4 + Checking saturation at [ 2 3 ] + Checking 2-saturation possible kernel vector = [1,1] This point may be in 2E(Q): [14:-52:1] - ...and it is! + ...and it is! Replacing old generator #1 with new generator [1:-1:1] + Reducing index bound from 4 to 2 Points have successfully been 2-saturated (max q used = 7) Index gain = 2^1 - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 13) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 67) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 53) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 73) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 103) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 113) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 47) - done (index = 2). + done, index = 2. Gained index 2, new generators = [ [1:-1:1] [-2:3:1] ] P3 = [-14:25:8] is generator number 3 - saturating up to 20...Checking 2-saturation - Points have successfully been 2-saturated (max q used = 11) - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 13) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 71) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 101) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 127) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 151) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 139) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 179) - done (index = 1). + saturating up to 20...Saturation index bound (for points of good reduction) = 3 + Reducing saturation bound from given value 20 to computed index bound 3 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 11) + Checking 3-saturation + Points were proved 3-saturated (max q used = 13) + done, index = 1. P4 = [-1:3:1] = -1*P1 + -1*P2 + -1*P3 (mod torsion) P4 = [0:2:1] = 2*P1 + 0*P2 + 1*P3 (mod torsion) P4 = [2:13:8] = -3*P1 + 1*P2 + -1*P3 (mod torsion) @@ -878,7 +818,7 @@ def __reduce__(self): sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: EQ = mwrank_MordellWeil(E) sage: EQ.__reduce__() - (, (y^2+ y = x^3 - 7*x + 6, True, 1, 999)) + (, (y^2 + y = x^3 - 7 x + 6, True, 1, 999)) """ return mwrank_MordellWeil, (self.__curve, self.__verbose, self.__pp, self.__maxr) @@ -902,12 +842,10 @@ def __repr__(self): """ return "Subgroup of Mordell-Weil group: %s"%self.__mw - def process(self, v, sat=0): - """ - This function allows one to add points to a :class:`mwrank_MordellWeil` object. + def process(self, v, saturation_bound=0): + """Process points in the list ``v``. - Process points in the list ``v``, with saturation at primes up to - ``sat``. If ``sat`` is zero (the default), do no saturation. + This function allows one to add points to a :class:`mwrank_MordellWeil` object. INPUT: @@ -915,8 +853,9 @@ def process(self, v, sat=0): list of triples of integers, which define points on the curve. - - ``sat`` (int, default 0) -- saturate at primes up to ``sat``, or at - *all* primes if ``sat`` is zero. + - ``saturation_bound`` (int, default 0) -- saturate at primes up to + ``saturation_bound``, or at *all* primes if ``saturation_bound`` is -1; when ``saturation_bound`` + is 0 (the default), do no saturation.. OUTPUT: @@ -939,11 +878,11 @@ def process(self, v, sat=0): sage: EQ.points() [[1, -1, 1], [-2, 3, 1], [-14, 25, 8]] - Example to illustrate the saturation parameter ``sat``:: + Example to illustrate the saturation parameter ``saturation_bound``:: sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: EQ = mwrank_MordellWeil(E) - sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], sat=20) + sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], saturation_bound=20) P1 = [1547:-2967:343] is generator number 1 ... Gained index 5, new generators = [ [-2:3:1] [-14:25:8] [1:-1:1] ] @@ -956,7 +895,7 @@ def process(self, v, sat=0): sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: EQ = mwrank_MordellWeil(E) - sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], sat=0) + sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], saturation_bound=0) P1 = [1547:-2967:343] is generator number 1 P2 = [2707496766203306:864581029138191:2969715140223272] is generator number 2 P3 = [-13422227300:-49322830557:12167000000] is generator number 3 @@ -965,55 +904,92 @@ def process(self, v, sat=0): sage: EQ.regulator() 375.42920288254555 sage: EQ.saturate(2) # points were not 2-saturated - saturating basis...Saturation index bound = 93 - WARNING: saturation at primes p > 2 will not be done; - ... + saturating basis...Saturation index bound (for points of good reduction) = 93 + Only p-saturating for p up to given value 2. + The resulting points may not be p-saturated for p between this and the computed index bound 93 + Checking saturation at [ 2 ] + Checking 2-saturation + possible kernel vector = [1,0,0] + This point may be in 2E(Q): [1547:-2967:343] + ...and it is! + Replacing old generator #1 with new generator [-2:3:1] + Reducing index bound from 93 to 46 + Points have successfully been 2-saturated (max q used = 11) + Index gain = 2^1 + done Gained index 2 - New regulator = 93.857... - (False, 2, '[ ]') + New regulator = 93.85730072 + (True, 2, '[ ]') sage: EQ.points() [[-2, 3, 1], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]] sage: EQ.regulator() 93.85730072063639 sage: EQ.saturate(3) # points were not 3-saturated - saturating basis...Saturation index bound = 46 - WARNING: saturation at primes p > 3 will not be done; - ... + saturating basis...Saturation index bound (for points of good reduction) = 46 + Only p-saturating for p up to given value 3. + The resulting points may not be p-saturated for p between this and the computed index bound 46 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 11) + Checking 3-saturation + possible kernel vector = [0,1,0] + This point may be in 3E(Q): [2707496766203306:864581029138191:2969715140223272] + ...and it is! + Replacing old generator #2 with new generator [-14:25:8] + Reducing index bound from 46 to 15 + Points have successfully been 3-saturated (max q used = 13) + Index gain = 3^1 + done Gained index 3 - New regulator = 10.428... - (False, 3, '[ ]') + New regulator = 10.42858897 + (True, 3, '[ ]') sage: EQ.points() [[-2, 3, 1], [-14, 25, 8], [-13422227300, -49322830557, 12167000000]] sage: EQ.regulator() 10.4285889689596 sage: EQ.saturate(5) # points were not 5-saturated - saturating basis...Saturation index bound = 15 - WARNING: saturation at primes p > 5 will not be done; - ... + saturating basis...Saturation index bound (for points of good reduction) = 15 + Only p-saturating for p up to given value 5. + The resulting points may not be p-saturated for p between this and the computed index bound 15 + Checking saturation at [ 2 3 5 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 11) + Checking 3-saturation + Points were proved 3-saturated (max q used = 13) + Checking 5-saturation + possible kernel vector = [0,0,1] + This point may be in 5E(Q): [-13422227300:-49322830557:12167000000] + ...and it is! + Replacing old generator #3 with new generator [1:-1:1] + Reducing index bound from 15 to 3 + Points have successfully been 5-saturated (max q used = 71) + Index gain = 5^1 + done Gained index 5 - New regulator = 0.417... - (False, 5, '[ ]') + New regulator = 0.4171435588 + (True, 5, '[ ]') sage: EQ.points() [[-2, 3, 1], [-14, 25, 8], [1, -1, 1]] sage: EQ.regulator() 0.417143558758384 sage: EQ.saturate() # points are now saturated - saturating basis...Saturation index bound = 3 + saturating basis...Saturation index bound (for points of good reduction) = 3 + Tamagawa index primes are [ ] Checking saturation at [ 2 3 ] - Checking 2-saturation + Checking 2-saturation Points were proved 2-saturated (max q used = 11) - Checking 3-saturation + Checking 3-saturation Points were proved 3-saturated (max q used = 13) done (True, 1, '[ ]') """ if not isinstance(v, list): raise TypeError("v (=%s) must be a list"%v) - sat = int(sat) + saturation_bound = int(saturation_bound) for P in v: - if not isinstance(P, (list,tuple)) or len(P) != 3: + if not isinstance(P, (list, tuple)) or len(P) != 3: raise TypeError("v (=%s) must be a list of 3-tuples (or 3-element lists) of ints"%v) - self.__mw.process(P, sat) + self.__mw.process(P, saturation_bound) def regulator(self): """ @@ -1091,23 +1067,21 @@ def rank(self): """ return self.__mw.rank() - def saturate(self, max_prime=-1, odd_primes_only=False): - r""" - Saturate this subgroup of the Mordell-Weil group. + def saturate(self, max_prime=-1, min_prime=2): + r"""Saturate this subgroup of the Mordell-Weil group. INPUT: - - ``max_prime`` (int, default -1) -- saturation is performed for - all primes up to ``max_prime``. If `-1` (the default), an + - ``max_prime`` (int, default -1) -- If `-1` (the default), an upper bound is computed for the primes at which the subgroup - may not be saturated, and this is used; however, if the - computed bound is greater than a value set by the ``eclib`` - library (currently 97) then no saturation will be attempted - at primes above this. + may not be saturated, and saturation is performed for all + primes up to this bound. Otherwise, the bound used is the + minimum of ``max_prime`` and the computed bound. - - ``odd_primes_only`` (bool, default ``False``) -- only do - saturation at odd primes. (If the points have been found - via :meth:`two_descent` they should already be 2-saturated.) + - ``min_prime`` (int, default 2) -- only do saturation at + primes no less than this. (For example, if the points have + been found via :meth:`two_descent` they should already be + 2-saturated so a value of 3 is appropriate.) OUTPUT: @@ -1115,40 +1089,35 @@ def saturate(self, max_prime=-1, odd_primes_only=False): - ``ok`` (bool) -- ``True`` if and only if the saturation was provably successful at all primes attempted. If the default - was used for ``max_prime`` and no warning was output about - the computed saturation bound being too high, then ``True`` - indicates that the subgroup is saturated at *all* - primes. + was used for ``max_prime``, then ``True`` indicates that the + subgroup is saturated at *all* primes. - ``index`` (int) -- the index of the group generated by the original points in their saturation. - ``unsatlist`` (list of ints) -- list of primes at which - saturation could not be proved or achieved. Increasing the - precision should correct this, since it happens when - a linear combination of the points appears to be a multiple - of `p` but cannot be divided by `p`. (Note that ``eclib`` - uses floating point methods based on elliptic logarithms to - divide points.) + saturation could not be proved or achieved. .. note:: - We emphasize that if this function returns ``True`` as the - first return argument (``ok``), and if the default was used for the - parameter ``max_prime``, then the points in the basis after - calling this function are saturated at *all* primes, - i.e., saturating at the primes up to ``max_prime`` are - sufficient to saturate at all primes. Note that the - function might not have needed to saturate at all primes up - to ``max_prime``. It has worked out what prime you need to - saturate up to, and that prime might be smaller than ``max_prime``. + In versions up to v20190909, ``eclib`` used floating point + methods based on elliptic logarithms to divide points, and + did not compute the precision necessary, which could cause + failures. Since v20210310, ``eclib`` uses exact method based + on division polynomials, which should mean that such + failures does not happen. .. note:: - Currently (May 2010), this does not remember the result of - calling :meth:`search()`. So calling :meth:`search()` up - to height 20 then calling :meth:`saturate()` results in - another search up to height 18. + We emphasize that if this function returns ``True`` as the + first return argument (``ok``), and if the default was used + for the parameter ``max_prime``, then the points in the + basis after calling this function are saturated at *all* + primes, i.e., saturating at the primes up to ``max_prime`` + are sufficient to saturate at all primes. Note that the + function computes an upper bound for the index of + saturation, and does no work for primes greater than this + even if ``max_prime`` is larger. EXAMPLES:: @@ -1160,7 +1129,7 @@ def saturate(self, max_prime=-1, odd_primes_only=False): automatic saturation at this stage we set the parameter ``sat`` to 0 (which is in fact the default):: - sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], sat=0) + sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], saturation_bound=0) P1 = [1547:-2967:343] is generator number 1 P2 = [2707496766203306:864581029138191:2969715140223272] is generator number 2 P3 = [-13422227300:-49322830557:12167000000] is generator number 3 @@ -1172,12 +1141,12 @@ def saturate(self, max_prime=-1, odd_primes_only=False): Now we saturate at `p=2`, and gain index 2:: sage: EQ.saturate(2) # points were not 2-saturated - saturating basis...Saturation index bound = 93 - WARNING: saturation at primes p > 2 will not be done; + saturating basis...Saturation index bound (for points of good reduction) = 93 + Only p-saturating for p up to given value 2. ... Gained index 2 New regulator = 93.857... - (False, 2, '[ ]') + (True, 2, '[ ]') sage: EQ Subgroup of Mordell-Weil group: [[-2:3:1], [2707496766203306:864581029138191:2969715140223272], [-13422227300:-49322830557:12167000000]] sage: EQ.regulator() @@ -1186,12 +1155,12 @@ def saturate(self, max_prime=-1, odd_primes_only=False): Now we saturate at `p=3`, and gain index 3:: sage: EQ.saturate(3) # points were not 3-saturated - saturating basis...Saturation index bound = 46 - WARNING: saturation at primes p > 3 will not be done; + saturating basis...Saturation index bound (for points of good reduction) = 46 + Only p-saturating for p up to given value 3. ... Gained index 3 New regulator = 10.428... - (False, 3, '[ ]') + (True, 3, '[ ]') sage: EQ Subgroup of Mordell-Weil group: [[-2:3:1], [-14:25:8], [-13422227300:-49322830557:12167000000]] sage: EQ.regulator() @@ -1200,12 +1169,12 @@ def saturate(self, max_prime=-1, odd_primes_only=False): Now we saturate at `p=5`, and gain index 5:: sage: EQ.saturate(5) # points were not 5-saturated - saturating basis...Saturation index bound = 15 - WARNING: saturation at primes p > 5 will not be done; + saturating basis...Saturation index bound (for points of good reduction) = 15 + Only p-saturating for p up to given value 5. ... Gained index 5 New regulator = 0.417... - (False, 5, '[ ]') + (True, 5, '[ ]') sage: EQ Subgroup of Mordell-Weil group: [[-2:3:1], [-14:25:8], [1:-1:1]] sage: EQ.regulator() @@ -1215,7 +1184,8 @@ def saturate(self, max_prime=-1, odd_primes_only=False): the points are now provably saturated at all primes:: sage: EQ.saturate() # points are now saturated - saturating basis...Saturation index bound = 3 + saturating basis...Saturation index bound (for points of good reduction) = 3 + Tamagawa index primes are [ ] Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) @@ -1229,7 +1199,7 @@ def saturate(self, max_prime=-1, odd_primes_only=False): sage: E = mwrank_EllipticCurve([0,0,1,-7,6]) sage: EQ = mwrank_MordellWeil(E) - sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], sat=5) + sage: EQ.process([[1547, -2967, 343], [2707496766203306, 864581029138191, 2969715140223272], [-13422227300, -49322830557, 12167000000]], saturation_bound=5) P1 = [1547:-2967:343] is generator number 1 ... Gained index 5, new generators = [ [-2:3:1] [-14:25:8] [1:-1:1] ] @@ -1242,7 +1212,8 @@ def saturate(self, max_prime=-1, odd_primes_only=False): verify that full saturation has been done:: sage: EQ.saturate() - saturating basis...Saturation index bound = 3 + saturating basis...Saturation index bound (for points of good reduction) = 3 + Tamagawa index primes are [ ] Checking saturation at [ 2 3 ] Checking 2-saturation Points were proved 2-saturated (max q used = 11) @@ -1255,8 +1226,9 @@ def saturate(self, max_prime=-1, odd_primes_only=False): index of the points in their saturation is at most 3, then proves saturation at 2 and at 3, by reducing the points modulo all primes of good reduction up to 11, respectively 13. + """ - ok, index, unsat = self.__mw.saturate(int(max_prime), odd_primes_only) + ok, index, unsat = self.__mw.saturate(int(max_prime), int(min_prime)) return bool(ok), int(str(index)), unsat def search(self, height_limit=18, verbose=False): @@ -1271,9 +1243,9 @@ def search(self, height_limit=18, verbose=False): .. note:: - On 32-bit machines, this *must* be < 21.48 else + On 32-bit machines, this *must* be < 21.48 (`31\log(2)`) else `\exp(h_{\text{lim}}) > 2^{31}` and overflows. On 64-bit machines, it - must be *at most* 43.668. However, this bound is a logarithmic + must be *at most* 43.668 (`63\log(2)`) . However, this bound is a logarithmic bound and increasing it by just 1 increases the running time by (roughly) `\exp(1.5)=4.5`, so searching up to even 20 takes a very long time. @@ -1320,8 +1292,10 @@ def search(self, height_limit=18, verbose=False): Subgroup of Mordell-Weil group: [[4413270:10381877:27000]] """ height_limit = float(height_limit) - if height_limit >= 21.4: # TODO: docstring says 21.48 (for 32-bit machines; what about 64-bit...?) - raise ValueError("The height limit must be < 21.4.") + int_bits = sys.maxsize.bit_length() + max_height_limit = int_bits * 0.693147 # log(2.0) = 0.693147 approx + if height_limit >= max_height_limit: + raise ValueError("The height limit must be < {} = {}log(2) on a {}-bit machine.".format(max_height_limit, int_bits, int_bits+1)) moduli_option = 0 # Use Stoll's sieving program... see strategies in ratpoints-1.4.c @@ -1352,5 +1326,4 @@ def points(self): [[1, -1, 1], [-2, 3, 1], [-14, 25, 8]] """ - L = eval(self.__mw.getbasis().replace(":",",")) - return [[Integer(x), Integer(y), Integer(z)] for (x,y,z) in L] + return self.__mw.getbasis() diff --git a/src/sage/libs/eclib/mwrank.pyx b/src/sage/libs/eclib/mwrank.pyx index b82831dc5fd..ce5090c80d5 100644 --- a/src/sage/libs/eclib/mwrank.pyx +++ b/src/sage/libs/eclib/mwrank.pyx @@ -28,6 +28,7 @@ from cysignals.signals cimport sig_on, sig_off from sage.cpython.string cimport char_to_str, str_to_bytes from sage.cpython.string import FS_ENCODING from sage.libs.eclib cimport bigint, Curvedata, mw, two_descent +from sage.rings.all import Integer cdef extern from "wrap.cpp": ### misc functions ### @@ -55,8 +56,8 @@ cdef extern from "wrap.cpp": char* mw_getbasis(mw* m) double mw_regulator(mw* m) int mw_rank(mw* m) - int mw_saturate(mw* m, bigint* index, char** unsat, - long sat_bd, int odd_primes_only) + int mw_saturate(mw* m, long* index, char** unsat, + long sat_bd, long sat_low_bd) void mw_search(mw* m, char* h_lim, int moduli_option, int verb) ### two_descent ### @@ -67,8 +68,7 @@ cdef extern from "wrap.cpp": long two_descent_get_rank(two_descent* t) long two_descent_get_rank_bound(two_descent* t) long two_descent_get_selmer_rank(two_descent* t) - void two_descent_saturate(two_descent* t, long sat_bd) - + void two_descent_saturate(two_descent* t, long sat_bd, long sat_low_bd) cdef object string_sigoff(char* s): sig_off() @@ -445,7 +445,6 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class -1269581104000000 """ sig_on() - from sage.rings.all import Integer return Integer(string_sigoff(Curvedata_getdiscr(self.x))) def conductor(self): @@ -467,7 +466,6 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class 126958110400 """ sig_on() - from sage.rings.all import Integer return Integer(string_sigoff(Curvedata_conductor(self.x))) def isogeny_class(self, verbose=False): @@ -503,6 +501,36 @@ cdef class _Curvedata: # cython class wrapping eclib's Curvedata class ############# _mw ################# +def parse_point_list(s): + r""" + Parse a string representing a list of points. + + INPUT: + + - ``s`` (string) -- string representation of a list of points, for + example '[]', '[[1:2:3]]', or '[[1:2:3],[4:5:6]]'. + + OUTPUT: + + (list) a list of triples of integers, for example [], [[1,2,3]], [[1,2,3],[4,5,6]]. + + EXAMPLES:: + + sage: from sage.libs.eclib.mwrank import parse_point_list + sage: parse_point_list('[]') + [] + sage: parse_point_list('[[1:2:3]]') + [[1, 2, 3]] + sage: parse_point_list('[[1:2:3],[4:5:6]]') + [[1, 2, 3], [4, 5, 6]] + + """ + s = s.replace(":", ",").replace(" ", "") + if s == '[]': + return [] + pts = s[2:-2].split('],[') + return [[Integer(x) for x in pt.split(",")] for pt in pts] + cdef class _mw: """ Cython class wrapping eclib's mw class. @@ -561,72 +589,37 @@ cdef class _mw: sage: EQ.search(1) P1 = [0:1:0] is torsion point, order 1 P1 = [-3:0:1] is generator number 1 - ... - P4 = [12:35:27] = 1*P1 + -1*P2 + -1*P3 (mod torsion) - - The previous command produces the following output:: - - P1 = [0:1:0] is torsion point, order 1 - P1 = [-3:0:1] is generator number 1 - saturating up to 20...Checking 2-saturation - Points have successfully been 2-saturated (max q used = 7) - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 7) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 23) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 41) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 17) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 43) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 31) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 37) + saturating up to 20...Saturation index bound (for points of good reduction) = 3 + Reducing saturation bound from given value 20 to computed index bound 3 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 7) + Checking 3-saturation + Points were proved 3-saturated (max q used = 7) done P2 = [-2:3:1] is generator number 2 - saturating up to 20...Checking 2-saturation + saturating up to 20...Saturation index bound (for points of good reduction) = 4 + Reducing saturation bound from given value 20 to computed index bound 4 + Checking saturation at [ 2 3 ] + Checking 2-saturation possible kernel vector = [1,1] This point may be in 2E(Q): [14:-52:1] - ...and it is! + ...and it is! Replacing old generator #1 with new generator [1:-1:1] + Reducing index bound from 4 to 2 Points have successfully been 2-saturated (max q used = 7) Index gain = 2^1 - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 13) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 67) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 53) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 73) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 103) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 113) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 47) - done (index = 2). + done, index = 2. Gained index 2, new generators = [ [1:-1:1] [-2:3:1] ] P3 = [-14:25:8] is generator number 3 - saturating up to 20...Checking 2-saturation - Points have successfully been 2-saturated (max q used = 11) - Checking 3-saturation - Points have successfully been 3-saturated (max q used = 13) - Checking 5-saturation - Points have successfully been 5-saturated (max q used = 71) - Checking 7-saturation - Points have successfully been 7-saturated (max q used = 101) - Checking 11-saturation - Points have successfully been 11-saturated (max q used = 127) - Checking 13-saturation - Points have successfully been 13-saturated (max q used = 151) - Checking 17-saturation - Points have successfully been 17-saturated (max q used = 139) - Checking 19-saturation - Points have successfully been 19-saturated (max q used = 179) - done (index = 1). + saturating up to 20...Saturation index bound (for points of good reduction) = 3 + Reducing saturation bound from given value 20 to computed index bound 3 + Checking saturation at [ 2 3 ] + Checking 2-saturation + Points were proved 2-saturated (max q used = 11) + Checking 3-saturation + Points were proved 3-saturated (max q used = 13) + done, index = 1. P4 = [-1:3:1] = -1*P1 + -1*P2 + -1*P3 (mod torsion) P4 = [0:2:1] = 2*P1 + 0*P2 + 1*P3 (mod torsion) P4 = [2:13:8] = -3*P1 + 1*P2 + -1*P3 (mod torsion) @@ -687,7 +680,7 @@ cdef class _mw: sig_on() return string_sigoff(mw_getbasis(self.x)) - def process(self, point, sat=0): + def process(self, point, saturation_bound=0): """ Processes the given point, adding it to the mw group. @@ -697,10 +690,12 @@ cdef class _mw: An ``ArithmeticError`` is raised if the point is not on the curve. - - ``sat`` (int, default 0) --saturate at primes up to ``sat``. - No saturation is done if ``sat=0``. (Note that it is more - efficient to add several points at once and then saturate - just once at the end). + - ``saturation_bound`` (int, default 0) --saturate at primes up to ``saturation_bound``. + No saturation is done if ``saturation_bound=0``. If ``saturation_bound=-1`` then + saturation is done at all primes, by computing a bound on + the saturation index. Note that it is more efficient to add + several points at once and then saturate just once at the + end. .. NOTE:: @@ -746,7 +741,7 @@ cdef class _mw: cdef _bigint x,y,z sig_on() x,y,z = _bigint(point[0]), _bigint(point[1]), _bigint(point[2]) - r = mw_process(self.curve, self.x, x.x, y.x, z.x, sat) + r = mw_process(self.curve, self.x, x.x, y.x, z.x, saturation_bound) sig_off() if r != 0: raise ArithmeticError("point (=%s) not on curve." % point) @@ -757,8 +752,8 @@ cdef class _mw: OUTPUT: - (string) String representation of the points in the basis of - the mw group. + (list) list of integer triples giving the projective + coordinates of the points in the basis. EXAMPLES:: @@ -768,13 +763,13 @@ cdef class _mw: sage: EQ = _mw(E) sage: EQ.search(3) sage: EQ.getbasis() - '[[0:-1:1], [-1:1:1]]' + [[0, -1, 1], [-1, 1, 1]] sage: EQ.rank() 2 """ sig_on() s = string_sigoff(mw_getbasis(self.x)) - return s + return parse_point_list(s) def regulator(self): """ @@ -797,7 +792,7 @@ cdef class _mw: sage: EQ = _mw(E) sage: EQ.search(3) sage: EQ.getbasis() - '[[0:-1:1], [-1:1:1]]' + [[0, -1, 1], [-1, 1, 1]] sage: EQ.rank() 2 sage: EQ.regulator() @@ -824,39 +819,54 @@ cdef class _mw: sage: EQ = _mw(E) sage: EQ.search(3) sage: EQ.getbasis() - '[[0:-1:1], [-1:1:1]]' + [[0, -1, 1], [-1, 1, 1]] sage: EQ.rank() 2 """ sig_on() r = mw_rank(self.x) sig_off() - from sage.rings.all import Integer return Integer(r) - def saturate(self, int sat_bd=-1, int odd_primes_only=0): + def saturate(self, int sat_bd=-1, int sat_low_bd=2): """ Saturates the current subgroup of the mw group. INPUT: - - ``sat_bnd`` (int, default -1) -- bound on primes at which to - saturate. If -1 (default), compute a bound for the primes - which may not be saturated, and use that. + - ``sat_bnd`` (int, default -1) -- upper bound on primes at + which to saturate. If -1 (default), compute a bound for the + primes which may not be saturated, and use that. Otherwise, + the bound used is the minumum of the value of ``sat_bnd`` + and the computed bound. - - ``odd_primes_only`` (bool, default ``False``) -- only do - saturation at odd primes. (If the points have been found - via 2-descent they should already be 2-saturated.) + - ``sat_low_bd`` (int, default 2) -- only do saturation at + prime not less than this. For exampe, if the points have + been found via 2-descent they should already be 2-saturated, + and ``sat_low_bd=3`` is appropriate. OUTPUT: (tuple) (success flag, index, list) The success flag will be 1 unless something failed (usually an indication that the points - were not saturated but the precision is not high enough to - divide out successfully). The index is the index of the mw - group before saturation in the mw group after. The list is a - string representation of the primes at which saturation was - not proved or achieved. + were not saturated but eclib was not able to divide out + successfully). The index is the index of the mw group before + saturation in the mw group after. The list is a string + representation of the primes at which saturation was not + proved or achieved. + + .. NOTE:: + + ``eclib`` will compute a bound on the saturation index. If + the computed saturation bound is very large and ``sat_bnd`` is + -1, ``eclib`` may output a warning, but will still attempt to + saturate up to the computed bound. If a positive value of + ``sat_bnd`` is given which is greater than the computed bound, + `p`-saturation will only be carried out for primes up to the + compated bound. Setting ``sat_low_bnd`` to a value greater + than 2 allows for saturation to be done incrementally, or for + exactly one prime `p` by setting both ``sat_bd`` and + ``sat_low_bd`` to `p`. EXAMPLES:: @@ -872,34 +882,23 @@ cdef class _mw: sage: EQ [[-1:1:1]] - If we set the saturation bound at 2, then saturation will fail:: + If we set the saturation bound at 2, then saturation will not + enlarge the basis, but the success flag is still 1 (True) + since we did not ask to check 3-saturation:: sage: EQ = _mw(E) sage: EQ.process([494, -5720, 6859]) # 3 times another point sage: EQ.saturate(sat_bd=2) - Saturation index bound = 10 - WARNING: saturation at primes p > 2 will not be done; - points may be unsaturated at primes between 2 and index bound - Failed to saturate MW basis at primes [ ] - (0, 1, '[ ]') + (1, 1, '[ ]') sage: EQ [[494:-5720:6859]] - The following output is also seen in the preceding example:: - - Saturation index bound = 10 - WARNING: saturation at primes p > 2 will not be done; - points may be unsaturated at primes between 2 and index bound - Failed to saturate MW basis at primes [ ] - - """ - cdef _bigint index + cdef long index cdef char* s cdef int ok sig_on() - index = _bigint() - ok = mw_saturate(self.x, index.x, &s, sat_bd, odd_primes_only) + ok = mw_saturate(self.x, &index, &s, sat_bd, sat_low_bd) unsat = string_sigoff(s) return ok, index, unsat @@ -1094,7 +1093,6 @@ cdef class _two_descent: sig_on() r = two_descent_get_rank(self.x) sig_off() - from sage.rings.all import Integer return Integer(r) def getrankbound(self): @@ -1128,7 +1126,6 @@ cdef class _two_descent: sig_on() r = two_descent_get_rank_bound(self.x) sig_off() - from sage.rings.all import Integer return Integer(r) def getselmer(self): @@ -1161,7 +1158,6 @@ cdef class _two_descent: sig_on() r = two_descent_get_selmer_rank(self.x) sig_off() - from sage.rings.all import Integer return Integer(r) def ok(self): @@ -1222,10 +1218,21 @@ cdef class _two_descent: """ return two_descent_get_certain(self.x) - def saturate(self, saturation_bound=0): + def saturate(self, saturation_bound=0, lower=3): """ Carries out saturation of the points found by a 2-descent. + INPUT: + + - ``saturation_bound`` (int) -- an upper bound on the primes + `p` at which `p`-saturation will be carried out, or -1, in + which case ``eclib`` will compute an upper bound on the + saturation index. + + - ``lower`` (int, default 3) -- do no `p`-saturation for `p` + less than this. The default is 3 since the points found + during 2-descent will be 2-saturated. + OUTPUT: None. @@ -1257,7 +1264,7 @@ cdef class _two_descent: '[[1:-1:1], [-2:3:1], [-14:25:8]]' """ sig_on() - two_descent_saturate(self.x, saturation_bound) + two_descent_saturate(self.x, saturation_bound, 3) sig_off() def getbasis(self): diff --git a/src/sage/libs/eclib/newforms.pyx b/src/sage/libs/eclib/newforms.pyx index b50b6061aa2..96263cd0be9 100644 --- a/src/sage/libs/eclib/newforms.pyx +++ b/src/sage/libs/eclib/newforms.pyx @@ -140,6 +140,7 @@ cdef class ECModularSymbol: - ``nap`` - (int, default 1000): the number of ap of E to use in determining the normalisation of the modular symbols. + Note that eclib will increase this to 100*sqrt(N) if necessary. EXAMPLES:: diff --git a/src/sage/libs/eclib/wrap.cpp b/src/sage/libs/eclib/wrap.cpp index 58c18ab67b6..28e6da869b4 100644 --- a/src/sage/libs/eclib/wrap.cpp +++ b/src/sage/libs/eclib/wrap.cpp @@ -178,11 +178,11 @@ int mw_rank(struct mw* m) } /* Returns index and unsat long array, which user must deallocate */ -int mw_saturate(struct mw* m, bigint* index, char** unsat, - long sat_bd, int odd_primes_only) +int mw_saturate(struct mw* m, long* index, char** unsat, + long sat_bd, long sat_low_bd) { vector v; - int s = m->saturate(*index, v, sat_bd, odd_primes_only); + int s = m->saturate(*index, v, sat_bd, sat_low_bd); ostringstream instore; instore << v; *unsat = stringstream_to_char(instore); @@ -236,9 +236,9 @@ long two_descent_get_certain(const two_descent* t) return t->getcertain(); } -void two_descent_saturate(struct two_descent* t, long sat_bd) +void two_descent_saturate(struct two_descent* t, long sat_bd, long sat_low_bd) { - t->saturate(sat_bd); + t->saturate(sat_bd, sat_low_bd); } double two_descent_regulator(struct two_descent* t) diff --git a/src/sage/libs/giac/giac.pyx b/src/sage/libs/giac/giac.pyx index 203acd318fe..ca65c359c30 100644 --- a/src/sage/libs/giac/giac.pyx +++ b/src/sage/libs/giac/giac.pyx @@ -163,7 +163,7 @@ from sage.plot.line import line from sage.plot.scatter_plot import scatter_plot from sage.libs.pynac.pynac import symbol_table -from sage.calculus.calculus import symbolic_expression_from_string +from sage.calculus.calculus import symbolic_expression_from_string, SR_parser_giac from sage.symbolic.ring import SR from sage.symbolic.expression import Expression from sage.symbolic.expression_conversions import InterfaceInit @@ -1012,7 +1012,7 @@ cdef class Pygen(GiacMethods_base): except: raise RuntimeError else: - raise IndexError,'list index %s out of range'%(i) + raise IndexError('list index %s out of range'%(i)) else: if isinstance(i,slice): sig_on() @@ -1029,14 +1029,14 @@ cdef class Pygen(GiacMethods_base): else: return self[i[0],i[1]][tuple(i[2:])] else: - raise TypeError,'gen indexes are not yet implemented' + raise TypeError('gen indexes are not yet implemented') # Here we add support to formal variable indexes: else: cmd='%s[%s]'%(self,i) ans=Pygen(cmd).eval() # if the answer is a string, it must be an error message because self is not a list or a string if (ans._type == 12): - raise TypeError, "Error executing code in Giac\nCODE:\n\t%s\nGiac ERROR:\n\t%s"%(cmd, ans) + raise TypeError("Error executing code in Giac\nCODE:\n\t%s\nGiac ERROR:\n\t%s"%(cmd, ans)) return ans @@ -1380,7 +1380,7 @@ cdef class Pygen(GiacMethods_base): else: return self else: - raise TypeError, "self is not a giac List" + raise TypeError("self is not a giac List") # def htmlhelp(self, str lang='en'): @@ -1397,7 +1397,7 @@ cdef class Pygen(GiacMethods_base): # url=decstring23(browser_help(self.gptr[0],l[lang])) #python3 # giacbasedir=decstring23(GIAC_giac_aide_dir()) # python3 # except: - # raise RuntimeError,'giac docs dir not found' + # raise RuntimeError('giac docs dir not found') # print(url) # if os.access(giacbasedir,os.F_OK): # url='file:'+url @@ -1494,7 +1494,7 @@ cdef class Pygen(GiacMethods_base): return result else: - raise TypeError, "Cannot convert non giac integers to Integer" + raise TypeError("Cannot convert non giac integers to Integer") def _rational_(self,Z=None): @@ -1522,9 +1522,9 @@ cdef class Pygen(GiacMethods_base): result=ZZ(self.numer())/ZZ(self.denom()) return result except: - RuntimeError, "Failed to convert to QQ" + RuntimeError("Failed to convert to QQ") else: - raise TypeError, "Cannot convert non giac _FRAC_ to QQ" + raise TypeError("Cannot convert non giac _FRAC_ to QQ") def sage(self): @@ -1573,7 +1573,7 @@ cdef class Pygen(GiacMethods_base): sage: f(4) 6 sage: f(x) - Gamma(x) + Gamma(sageVARx) sage: (f(x)).sage() gamma(x) @@ -1633,13 +1633,30 @@ cdef class Pygen(GiacMethods_base): sage: u,v=var('u,v');a=libgiac('cos(u+v)').texpand() sage: simplify(SR(a)+sin(u)*sin(v)) cos(u)*cos(v) + + TESTS: + + Check that variables and constants are not mixed up (:trac:`30133`):: + + sage: ee, ii, pp = SR.var('e,i,pi') + sage: libgiac(ee * ii * pp).sage().variables() + (e, i, pi) + sage: libgiac(e * i * pi).sage().variables() + () + sage: libgiac.integrate(ee^x, x).sage() + e^x/log(e) + sage: y = SR.var('π') + sage: libgiac.integrate(cos(y), y).sage() + sin(π) """ if isinstance(R,SR.__class__): # Try to convert some functions names to the symbolic ring lsymbols = symbol_table['giac'].copy() #lsymbols.update(locals) try: - result=symbolic_expression_from_string(self.__str__(),lsymbols,accept_sequence=True) + result = symbolic_expression_from_string(self.__str__(), lsymbols, + accept_sequence=True, + parser=SR_parser_giac) return result except Exception: @@ -1711,7 +1728,7 @@ cdef class Pygen(GiacMethods_base): try: n = v._val except: - raise TypeError, "Entry is not a giac vector" + raise TypeError("Entry is not a giac vector") from sage.modules.free_module_element import vector sig_on() entries = [R(self[c]) for c in range(n)] @@ -1816,7 +1833,7 @@ cdef class Pygen(GiacMethods_base): sig_off() return result else: - raise TypeError,"Cannot convert non _INT_ giac gen" + raise TypeError("Cannot convert non _INT_ giac gen") property _double: # immediate double (type _DOUBLE_) @@ -1830,7 +1847,7 @@ cdef class Pygen(GiacMethods_base): sig_off() return result else: - raise TypeError,"Cannot convert non _DOUBLE_ giac gen" + raise TypeError("Cannot convert non _DOUBLE_ giac gen") property help: def __get__(self): @@ -1903,7 +1920,7 @@ cdef inline _wrap_gen(gen g)except +: # if(pyg.gptr !=NULL): # return pyg # else: -# raise MemoryError,"empty gen" +# raise MemoryError("empty gen") @@ -1925,7 +1942,7 @@ cdef vecteur _wrap_pylist(L) except +: sig_off() return V[0] else: - raise TypeError,"argument must be a tuple or a list" + raise TypeError("argument must be a tuple or a list") ################################# @@ -1947,7 +1964,7 @@ cdef vecteur _getgiacslice(Pygen L,slice sl) except +: sig_off() return V[0] else: - raise TypeError,"argument must be a Pygen list and a slice" + raise TypeError("argument must be a Pygen list and a slice") @@ -2057,8 +2074,8 @@ class GiacFunction(Pygen): EXAMPLES:: sage: from sage.libs.giac.giac import * - sage: libgiac.simplify(exp(I*pi/5)^3) # simplify is a GiacFunction - rootof([[-1,-1+2*i,25+4*i,-7-30*i],[1,0,-30,40,5]])/32 + sage: libgiac.simplify(exp(I*pi)) # simplify is a GiacFunction + -1 sage: libgiac('a:=1') 1 sage: libgiac.purge('a') # purge is not a GiacFunction @@ -2114,8 +2131,6 @@ class GiacFunctionNoEV(Pygen): EXAMPLES:: sage: from sage.libs.giac.giac import * - sage: libgiac.simplify(exp(I*pi/5)^3) # simplify is a GiacFunction - rootof([[-1,-1+2*i,25+4*i,-7-30*i],[1,0,-30,40,5]])/32 sage: libgiac('a:=1') 1 sage: libgiac.purge('a') # purge is a GiacFunctionNoEV diff --git a/src/sage/libs/linkages/padics/relaxed/__init__.py b/src/sage/libs/linkages/padics/relaxed/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/sage/libs/linkages/padics/relaxed/flint_helper.c b/src/sage/libs/linkages/padics/relaxed/flint_helper.c index f8221519d29..aaa7e1a657f 100644 --- a/src/sage/libs/linkages/padics/relaxed/flint_helper.c +++ b/src/sage/libs/linkages/padics/relaxed/flint_helper.c @@ -26,7 +26,7 @@ fmpz* get_coeff (fmpz_poly_t poly, slong i) if (zero == NULL) { zero = malloc(sizeof(fmpz)); - fmpz_zero(zero); + fmpz_init(zero); } return zero; } diff --git a/src/sage/libs/linkages/padics/unram_shared.pxi b/src/sage/libs/linkages/padics/unram_shared.pxi index 901514ec168..bcd76ad3123 100644 --- a/src/sage/libs/linkages/padics/unram_shared.pxi +++ b/src/sage/libs/linkages/padics/unram_shared.pxi @@ -129,6 +129,12 @@ def norm_unram(self, base = None): 4*7^2 + 7^3 + O(7^22) sage: b*b.frobenius() 4*7^2 + 7^3 + O(7^22) + + Check that :trac:`31845` is fixed:: + + sage: R.
= Zq(4) + sage: (a - a).norm() + O(2^20) """ if base is not None: if base is self.parent(): @@ -138,7 +144,7 @@ def norm_unram(self, base = None): if self._is_exact_zero(): return self.parent().ground_ring()(0) elif self._is_inexact_zero(): - return self.ground_ring(0, self.valuation()) + return self.parent().ground_ring()(0, self.valuation()) if self.valuation() == 0: return self.parent().ground_ring()(self.matrix_mod_pn().det()) else: @@ -155,10 +161,10 @@ def trace_unram(self, base = None): Return the absolute or relative trace of this element. If ``base`` is given then ``base`` must be a subfield of the - parent `L` of ``self``, in which case the norm is the relative - norm from `L` to ``base``. + parent `L` of ``self``, in which case the trace is the relative + trace from `L` to ``base``. - In all other cases, the norm is the absolute norm down to + In all other cases, the trace is the absolute trace down to `\QQ_p` or `\ZZ_p`. EXAMPLES:: @@ -202,6 +208,12 @@ def trace_unram(self, base = None): 4*5 + 5^2 + 5^3 + 2*5^4 sage: (a+b).trace() 4*5 + 5^2 + 5^3 + 2*5^4 + + Check that :trac:`31845` is fixed:: + + sage: R. = Zq(4) + sage: (a - a).trace() + O(2^20) """ if base is not None: if base is self.parent(): @@ -211,7 +223,7 @@ def trace_unram(self, base = None): if self._is_exact_zero(): return self.parent().ground_ring()(0) elif self._is_inexact_zero(): - return self.ground_ring(0, self.precision_absolute()) + return self.parent().ground_ring()(0, self.precision_absolute()) if self.valuation() >= 0: return self.parent().ground_ring()(self.matrix_mod_pn().trace()) else: diff --git a/src/sage/libs/mpmath/ext_main.pyx b/src/sage/libs/mpmath/ext_main.pyx index 694cc089e7c..5821be79073 100644 --- a/src/sage/libs/mpmath/ext_main.pyx +++ b/src/sage/libs/mpmath/ext_main.pyx @@ -2491,11 +2491,21 @@ cdef class mpc(mpnumber): """ Returns the hash value of self :: + EXAMPLES:: + sage: from mpmath import mp sage: hash(mp.mpc(2,3)) == hash(complex(2,3)) True + + TESTS: + + Check that :trac:`31676` is fixed:: + + sage: from mpmath import mpc + sage: hash(mpc(1, -1)) == hash(mpc(-1, -1)) # should not return OverflowError: Python int too large to convert to C ssize_t + False """ - return libmp.mpc_hash(self._mpc_) + return hash(libmp.mpc_hash(self._mpc_)) def __neg__(s): """ diff --git a/src/sage/libs/pari/__init__.py b/src/sage/libs/pari/__init__.py index 77eda66097d..507b98c5098 100644 --- a/src/sage/libs/pari/__init__.py +++ b/src/sage/libs/pari/__init__.py @@ -161,12 +161,14 @@ call individually:: sage: e = pari([0,0,0,-82,0]).ellinit() - sage: eta1 = e.elleta(precision=100)[0] + sage: eta1 = e.elleta(precision=50)[0] sage: eta1.sage() - 3.6054636014326520859158205642077267748 - sage: eta1 = e.elleta(precision=180)[0] + 3.6054636014326520859158205642077267748 # 64-bit + 3.605463601432652085915820564 # 32-bit + sage: eta1 = e.elleta(precision=150)[0] sage: eta1.sage() - 3.60546360143265208591582056420772677481026899659802474544 + 3.605463601432652085915820564207726774810268996598024745444380641429820491740 # 64-bit + 3.60546360143265208591582056420772677481026899659802474544 # 32-bit """ diff --git a/src/sage/libs/pari/tests.py b/src/sage/libs/pari/tests.py index dd7f8e9bf86..f34b30146d4 100644 --- a/src/sage/libs/pari/tests.py +++ b/src/sage/libs/pari/tests.py @@ -135,7 +135,7 @@ sage: K. = NumberField(polygen(QQ)^3 - 2) sage: pari(K) - [y^3 - 2, [1, 1], -108, 1, [[1, 1.25992104989487, 1.58740105196820; 1, -0.629960524947437 + 1.09112363597172*I, -0.793700525984100 - 1.37472963699860*I], [1, 1.25992104989487, 1.58740105196820; 1, 0.461163111024285, -2.16843016298270; 1, -1.72108416091916, 0.581029111014503], [1, 1, 2; 1, 0, -2; 1, -2, 1], [3, 0, 0; 0, 0, 6; 0, 6, 0], [6, 0, 0; 0, 6, 0; 0, 0, 3], [2, 0, 0; 0, 0, 1; 0, 1, 0], [2, [0, 0, 2; 1, 0, 0; 0, 1, 0]], []], [1.25992104989487, -0.629960524947437 + 1.09112363597172*I], [1, y, y^2], [1, 0, 0; 0, 1, 0; 0, 0, 1], [1, 0, 0, 0, 0, 2, 0, 2, 0; 0, 1, 0, 1, 0, 0, 0, 0, 2; 0, 0, 1, 0, 1, 0, 1, 0, 0]] + [y^3 - 2, [1, 1], -108, 1, [[1, 1.25992104989487, 1.58740105196820; 1, -0.629960524947437 + 1.09112363597172*I, -0.793700525984100 - 1.37472963699860*I], [1, 1.25992104989487, 1.58740105196820; 1, 0.461163111024285, -2.16843016298270; 1, -1.72108416091916, 0.581029111014503], [16, 20, 25; 16, 7, -35; 16, -28, 9], [3, 0, 0; 0, 0, 6; 0, 6, 0], [6, 0, 0; 0, 6, 0; 0, 0, 3], [2, 0, 0; 0, 0, 1; 0, 1, 0], [2, [0, 0, 2; 1, 0, 0; 0, 1, 0]], [2, 3]], [1.25992104989487, -0.629960524947437 + 1.09112363597172*I], [1, y, y^2], [1, 0, 0; 0, 1, 0; 0, 0, 1], [1, 0, 0, 0, 0, 2, 0, 2, 0; 0, 1, 0, 1, 0, 0, 0, 0, 2; 0, 0, 1, 0, 1, 0, 1, 0, 0]] sage: E = EllipticCurve('37a1') sage: pari(E) @@ -375,13 +375,13 @@ sage: pari('["bc","ab","bc"]').Set() ["ab", "bc"] - sage: pari([65,66,123]).Strchr() + sage: pari([65,66,123]).strchr() "AB{" sage: pari('"Sage"').Vecsmall() Vecsmall([83, 97, 103, 101]) - sage: _.Strchr() + sage: _.strchr() "Sage" - sage: pari([83, 97, 103, 101]).Strchr() + sage: pari([83, 97, 103, 101]).strchr() "Sage" Basic functions:: @@ -448,7 +448,7 @@ sage: pari('x').component(0) Traceback (most recent call last): ... - PariError: non-existent component: index < 1 + PariError: nonexistent component: index < 1 sage: pari('x+1').conj() x + 1 @@ -767,7 +767,7 @@ sage: pari(2).besseli(3+i) 1.12539407613913 + 2.08313822670661*I sage: C. = ComplexField() - sage: pari(2+i).besseln(3) + sage: pari(2+i).bessely(3) -0.280775566958244 - 0.486708533223726*I sage: pari(1.5).cos() @@ -822,7 +822,7 @@ sage: pari(-1).gamma() Traceback (most recent call last): ... - PariError: domain error in gamma: argument = non-positive integer + PariError: domain error in gamma: argument = nonpositive integer sage: pari(2).gammah() 1.32934038817914 @@ -1633,7 +1633,7 @@ sage: x = QQ['x'].0; nf = pari(x^2 + 2).nfinit() sage: nf.nfgaloisconj() - [x, -x]~ + [-x, x]~ sage: nf = pari(x^3 + 2).nfinit() sage: nf.nfgaloisconj() [x]~ @@ -1676,7 +1676,7 @@ [[1, [7605, 4]~, [5610, 5]~, [7913, -6]~; 0, 1, 0, -1; 0, 0, 1, 0; 0, 0, 0, 1], [[19320, 13720; 0, 56], [2, 1; 0, 1], 1, 1]] sage: pari('x^3 - 17').nfinit() - [x^3 - 17, [1, 1], -867, 3, [[1, 1.68006914259990, 2.57128159065824; 1, -0.340034571299952 - 2.65083754153991*I, -1.28564079532912 + 2.22679517779329*I], [1, 1.68006914259990, 2.57128159065824; 1, -2.99087211283986, 0.941154382464174; 1, 2.31080297023995, -3.51243597312241], [1, 2, 3; 1, -3, 1; 1, 2, -4], [3, 1, 0; 1, -11, 17; 0, 17, 0], [51, 0, 16; 0, 17, 3; 0, 0, 1], [17, 0, -1; 0, 0, 3; -1, 3, 2], [51, [-17, 6, -1; 0, -18, 3; 1, 0, -16]], [3, 17]], [2.57128159065824, -1.28564079532912 + 2.22679517779329*I], [3, x^2 - x + 1, 3*x], [1, 0, -1; 0, 0, 3; 0, 1, 1], [1, 0, 0, 0, -4, 6, 0, 6, -1; 0, 1, 0, 1, 1, -1, 0, -1, 3; 0, 0, 1, 0, 2, 0, 1, 0, 1]] + [x^3 - 17, [1, 1], -867, 3, [[1, 1.68006914259990, 2.57128159065824; 1, -0.340034571299952 - 2.65083754153991*I, -1.28564079532912 + 2.22679517779329*I], [1, 1.68006914259990, 2.57128159065824; 1, -2.99087211283986, 0.941154382464174; 1, 2.31080297023995, -3.51243597312241], [16, 27, 41; 16, -48, 15; 16, 37, -56], [3, 1, 0; 1, -11, 17; 0, 17, 0], [51, 0, 16; 0, 17, 3; 0, 0, 1], [17, 0, -1; 0, 0, 3; -1, 3, 2], [51, [-17, 6, -1; 0, -18, 3; 1, 0, -16]], [3, 17]], [2.57128159065824, -1.28564079532912 + 2.22679517779329*I], [3, x^2 - x + 1, 3*x], [1, 0, -1; 0, 0, 3; 0, 1, 1], [1, 0, 0, 0, -4, 6, 0, 6, -1; 0, 1, 0, 1, 1, -1, 0, -1, 3; 0, 0, 1, 0, 2, 0, 1, 0, 1]] sage: pari('x^2 + 10^100 + 1').nfinit() [...] sage: pari('1.0').nfinit() @@ -1737,7 +1737,7 @@ sage: pari(-23).quadhilbert() x^3 - x^2 + 1 sage: pari(145).quadhilbert() - x^4 - 6*x^2 - 5*x - 1 + x^4 - x^3 - 5*x^2 - x + 1 sage: pari(-12).quadhilbert() # Not fundamental Traceback (most recent call last): ... @@ -1760,13 +1760,14 @@ library:: sage: e = pari([0,0,0,-82,0]).ellinit() - sage: eta1 = e.elleta(precision=100)[0] + sage: eta1 = e.elleta(precision=50)[0] sage: eta1.sage() - 3.6054636014326520859158205642077267748 - sage: eta1 = e.elleta(precision=180)[0] + 3.6054636014326520859158205642077267748 # 64-bit + 3.605463601432652085915820564 # 32-bit + sage: eta1 = e.elleta(precision=150)[0] sage: eta1.sage() - 3.60546360143265208591582056420772677481026899659802474544 - + 3.605463601432652085915820564207726774810268996598024745444380641429820491740 # 64-bit + 3.60546360143265208591582056420772677481026899659802474544 # 32-bit sage: from cypari2 import Pari sage: pari = Pari() diff --git a/src/sage/libs/pynac/pynac.pxd b/src/sage/libs/pynac/pynac.pxd index 577401c5b18..5589fc27d34 100644 --- a/src/sage/libs/pynac/pynac.pxd +++ b/src/sage/libs/pynac/pynac.pxd @@ -71,6 +71,7 @@ cdef extern from "pynac_wrap.h": unsigned get_domain() void set_domain(unsigned d) void set_texname(char* t) + const char* get_name "get_name().c_str" () cdef cppclass GExPair "std::pair": pass diff --git a/src/sage/libs/pynac/pynac.pyx b/src/sage/libs/pynac/pynac.pyx index 4c645545ac5..c9f59e2dfab 100644 --- a/src/sage/libs/pynac/pynac.pyx +++ b/src/sage/libs/pynac/pynac.pyx @@ -1190,7 +1190,7 @@ cdef bint py_is_real(a): return False except NotImplementedError: return False - except AttributeError: + except (TypeError, AttributeError): pass return py_imag(a) == 0 @@ -2427,6 +2427,11 @@ def init_pynac_I(): False sage: bool(z == y) True + + Check that :trac:`31869` is fixed:: + + sage: x * ((3*I + 4)*x - 5) + ((3*I + 4)*x - 5)*x """ global pynac_I, I from sage.rings.number_field.number_field import GaussianField diff --git a/src/sage/libs/singular/singular.pyx b/src/sage/libs/singular/singular.pyx index fc01a6f3061..674a36cba81 100644 --- a/src/sage/libs/singular/singular.pyx +++ b/src/sage/libs/singular/singular.pyx @@ -255,7 +255,7 @@ cdef object si2sa_NF(number *n, ring *_ring, object base): sage: f.lc() 1024*a sage: type(f.lc()) - + """ cdef poly *z cdef number *c diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py index 5a2b7070867..1994182cb71 100644 --- a/src/sage/manifolds/chart.py +++ b/src/sage/manifolds/chart.py @@ -318,9 +318,9 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): self._restrictions = [] # to be set with method add_restrictions() # # The chart is added to the domain's atlas, as well as to all the - # atlases of the domain's supersets; moreover the fist defined chart + # atlases of the domain's supersets; moreover the first defined chart # is considered as the default chart - for sd in self._domain._supersets: + for sd in self._domain.open_supersets(): # the chart is added in the top charts only if its coordinates have # not been used: for chart in sd._atlas: @@ -346,13 +346,9 @@ def __init__(self, domain, coordinates='', names=None, calc_method=None): # The null and one functions of the coordinates: # Expression in self of the zero and one scalar fields of open sets # containing the domain of self: - for dom in self._domain._supersets: - if hasattr(dom, '_zero_scalar_field'): - # dom is an open set - dom._zero_scalar_field._express[self] = self.function_ring().zero() - if hasattr(dom, '_one_scalar_field'): - # dom is an open set - dom._one_scalar_field._express[self] = self.function_ring().one() + for dom in self._domain.open_supersets(): + dom._zero_scalar_field._express[self] = self.function_ring().zero() + dom._one_scalar_field._express[self] = self.function_ring().one() def _init_coordinates(self, coord_list): r""" @@ -932,12 +928,9 @@ def transition_map(self, other, transformations, intersection_name=None, The subset `W`, intersection of `U` and `V`, has been created by ``transition_map()``:: - sage: M.list_of_subsets() - [1-dimensional topological manifold S^1, - Open subset U of the 1-dimensional topological manifold S^1, - Open subset V of the 1-dimensional topological manifold S^1, - Open subset W of the 1-dimensional topological manifold S^1] - sage: W = M.list_of_subsets()[3] + sage: F = M.subset_family(); F + Set {S^1, U, V, W} of open subsets of the 1-dimensional topological manifold S^1 + sage: W = F['W'] sage: W is U.intersection(V) True sage: M.atlas() @@ -960,9 +953,8 @@ def transition_map(self, other, transformations, intersection_name=None, In this case, no new subset has been created since `U \cap M = U`:: - sage: M.list_of_subsets() - [2-dimensional topological manifold R^2, - Open subset U of the 2-dimensional topological manifold R^2] + sage: M.subset_family() + Set {R^2, U} of open subsets of the 2-dimensional topological manifold R^2 but a new chart has been created: `(U, (x, y))`:: @@ -3010,7 +3002,7 @@ def __init__(self, chart1, chart2, *transformations): # is added to the subset (and supersets) dictionary: if chart1._domain == chart2._domain: domain = chart1._domain - for sdom in domain._supersets: + for sdom in domain.open_supersets(): sdom._coord_changes[(chart1, chart2)] = self def _repr_(self): @@ -3127,7 +3119,11 @@ def __call__(self, *coords): def inverse(self): r""" - Compute the inverse coordinate transformation. + Return the inverse coordinate transformation. + + If the inverse is not already known, it is computed here. If the + computation fails, the inverse can be set by hand via the method + :meth:`set_inverse`. OUTPUT: @@ -3160,9 +3156,18 @@ def inverse(self): (Chart (M, (x, y)), Chart (M, (u, v))): Change of coordinates from Chart (M, (x, y)) to Chart (M, (u, v))} + The result is cached:: + + sage: xy_to_uv.inverse() is uv_to_xy + True + + We have as well:: + + sage: uv_to_xy.inverse() is xy_to_uv + True + """ from sage.symbolic.relation import solve - from sage.symbolic.assumptions import assumptions if self._inverse is not None: return self._inverse # The computation is necessary: @@ -3186,8 +3191,7 @@ def inverse(self): for i in range(n2): if x2[i].is_positive(): coord_domain[i] = 'positive' - xp2 = [ SR.var('xxxx' + str(i), domain=coord_domain[i]) - for i in range(n2) ] + xp2 = [ SR.temp_var(domain=coord_domain[i]) for i in range(n2) ] xx2 = self._transf.expr() equations = [xp2[i] == xx2[i] for i in range(n2)] try: @@ -3240,12 +3244,8 @@ def inverse(self): "manually") x2_to_x1 = list_x2_to_x1[0] self._inverse = type(self)(self._chart2, self._chart1, *x2_to_x1) - # Some cleaning: the local symbolic variables (xxxx0, xxxx1, ...) are - # removed from the list of assumptions - for asm in assumptions(): - for xxxx in xp2: - if asm.has(xxxx): - asm.forget() + self._inverse._inverse = self + SR.cleanup_var(xp2) return self._inverse def set_inverse(self, *transformations, **kwds): @@ -3342,7 +3342,14 @@ def set_inverse(self, *transformations, **kwds): u == u *passed* v == v *passed* - TESTS:: + TESTS: + + Check that :trac:`31923` is fixed:: + + sage: X1_to_X2.inverse().inverse() is X1_to_X2 + True + + Check of keyword arguments:: sage: X1_to_X2.set_inverse((u+v)/2, (u-v)/2, bla=3) Traceback (most recent call last): @@ -3357,6 +3364,7 @@ def set_inverse(self, *transformations, **kwds): "argument".format(unknown_key)) self._inverse = type(self)(self._chart2, self._chart1, *transformations) + self._inverse._inverse = self if check: infos = ["Check of the inverse coordinate transformation:"] x1 = self._chart1._xx diff --git a/src/sage/manifolds/chart_func.py b/src/sage/manifolds/chart_func.py index e821af80367..d38eab686ae 100644 --- a/src/sage/manifolds/chart_func.py +++ b/src/sage/manifolds/chart_func.py @@ -816,8 +816,6 @@ def __bool__(self): val = self.expr(curr).is_zero return not val - __nonzero__ = __bool__ # For Python2 compatibility - def is_trivial_zero(self): r""" Check if ``self`` is trivially equal to zero without any diff --git a/src/sage/manifolds/continuous_map.py b/src/sage/manifolds/continuous_map.py index 7991191b900..d1cf5049f76 100644 --- a/src/sage/manifolds/continuous_map.py +++ b/src/sage/manifolds/continuous_map.py @@ -18,9 +18,12 @@ """ # **************************************************************************** -# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015-2019 Eric Gourgoulhon # Copyright (C) 2015 Michal Bejger # Copyright (C) 2016 Travis Scrimshaw +# Copyright (C) 2018 Florentin Jaffredo +# Copyright (C) 2020 Michael Jung +# Copyright (C) 2021 Matthias Koeppe # # 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 @@ -816,6 +819,53 @@ def _composition_(self, other, homset): pass return homset(resu_funct) + def image(self, subset=None, inverse=None): + r""" + Return the image of ``self`` or the image of ``subset`` under ``self``. + + INPUT: + + - ``inverse`` -- (default: ``None``) continuous map from + ``map.codomain()`` to ``map.domain()``, which once restricted to the image + of `\Phi` is the inverse of `\Phi` onto its image if the latter + exists (NB: no check of this is performed) + - ``subset`` -- (default: the domain of ``map``) a subset of the domain of + ``self`` + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure="topological") + sage: N = Manifold(1, 'N', ambient=M, structure="topological") + sage: CM. = M.chart() + sage: CN. = N.chart() + sage: CN.add_restrictions([u > -1, u < 1]) + sage: Phi = N.continuous_map(M, {(CN,CM): [u, u^2]}, name='Phi') + sage: Phi.image() + Image of the Continuous map Phi + from the 1-dimensional topological submanifold N + immersed in the 2-dimensional topological manifold M + to the 2-dimensional topological manifold M + + sage: S = N.subset('S') + sage: Phi_S = Phi.image(S); Phi_S + Image of the Subset S of the + 1-dimensional topological submanifold N + immersed in the 2-dimensional topological manifold M + under the Continuous map Phi + from the 1-dimensional topological submanifold N + immersed in the 2-dimensional topological manifold M + to the 2-dimensional topological manifold M + sage: Phi_S.is_subset(M) + True + """ + from .continuous_map_image import ImageManifoldSubset + if self._is_identity: + if subset is None: + return self.domain() + else: + return subset + return ImageManifoldSubset(self, inverse=inverse, domain_subset=subset) + # # Monoid methods # @@ -1963,7 +2013,7 @@ def __invert__(self): n2 = len(chart2._xx) # New symbolic variables (different from chart2._xx to allow for a # correct solution even when chart2 = chart1): - x2 = [SR.var('xxxx' + str(i)) for i in range(n2)] + x2 = SR.temp_var(n=n2) equations = [x2[i] == coord_map._functions[i].expr() for i in range(n2)] solutions = solve(equations, chart1._xx, solution_dict=True) @@ -1983,6 +2033,7 @@ def __invert__(self): except AttributeError: pass coord_functions[(chart2, chart1)] = inv_functions + SR.cleanup_var(x2) if self._name is None: name = None else: diff --git a/src/sage/manifolds/continuous_map_image.py b/src/sage/manifolds/continuous_map_image.py new file mode 100644 index 00000000000..467124551d1 --- /dev/null +++ b/src/sage/manifolds/continuous_map_image.py @@ -0,0 +1,157 @@ +r""" +Images of Manifold Subsets under Continuous Maps as Subsets of the Codomain + +:class:`ImageManifoldSubset` implements the image of a continuous map `\Phi` +from a manifold `M` to some manifold `N` as a subset `\Phi(M)` of `N`, +or more generally, the image `\Phi(S)` of a subset `S \subseteq M` as a +subset of `N`. + +""" + +# **************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# 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.manifolds.subset import ManifoldSubset + +class ImageManifoldSubset(ManifoldSubset): + r""" + Subset of a topological manifold that is a continuous image of a manifold subset. + + INPUT: + + - ``map`` -- continuous map `\Phi` + - ``inverse`` -- (default: ``None``) continuous map from + ``map.codomain()`` to ``map.domain()``, which once restricted to the image + of `\Phi` is the inverse of `\Phi` onto its image if the latter + exists (NB: no check of this is performed) + - ``name`` -- (default: computed from the names of the map and the subset) + string; name (symbol) given to the subset + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the subset; if none is provided, it is set to ``name`` + - ``domain_subset`` -- (default: the domain of ``map``) a subset of the domain of + ``map`` + """ + + def __init__(self, map, inverse=None, name=None, latex_name=None, domain_subset=None): + r""" + Construct a manifold subset that is the image of a continuous map. + + TESTS:: + + sage: M = Manifold(2, 'M', structure="topological") + sage: N = Manifold(1, 'N', ambient=M, structure="topological") + sage: CM. = M.chart() + sage: CN. = N.chart() + sage: CN.add_restrictions([u > -1, u < 1]) + sage: Phi = N.continuous_map(M, {(CN,CM): [u, 1 + u^2]}, name='Phi') + sage: Phi_inv = M.continuous_map(N, {(CM, CN): [x]}, name='Phi_inv') + sage: Phi_N = Phi.image(inverse=Phi_inv) + sage: TestSuite(Phi_N).run() + """ + self._map = map + self._inverse = inverse + if domain_subset is None: + domain_subset = map.domain() + self._domain_subset = domain_subset + base_manifold = map.codomain() + map_name = map._name or 'f' + map_latex_name = map._latex_name or map_name + if latex_name is None: + if name is None: + latex_name = map_latex_name + r'(' + domain_subset._latex_name + ')' + else: + latex_name = name + if name is None: + name = map_name + '_' + domain_subset._name + ManifoldSubset.__init__(self, base_manifold, name, latex_name=latex_name) + + def _repr_(self): + r""" + String representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M', structure="topological") + sage: N = Manifold(1, 'N', ambient=M, structure="topological") + sage: CM. = M.chart() + sage: CN. = N.chart() + sage: CN.add_restrictions([u > -1, u < 1]) + sage: Phi = N.continuous_map(M, {(CN,CM): [u, 1 + u^2]}, name='Phi') + sage: Phi.image() # indirect doctest + Image of the Continuous map Phi + from the 1-dimensional topological submanifold N immersed in the + 2-dimensional topological manifold M + to the 2-dimensional topological manifold M + sage: S = N.subset('S') + sage: Phi.image(S) # indirect doctest + Image of the + Subset S of the + 1-dimensional topological submanifold N immersed in the + 2-dimensional topological manifold M + under the Continuous map Phi + from the 1-dimensional topological submanifold N immersed in the + 2-dimensional topological manifold M + to the 2-dimensional topological manifold M + """ + if self._domain_subset is self._map.domain(): + return f"Image of the {self._map}" + else: + return f"Image of the {self._domain_subset} under the {self._map}" + + def _an_element_(self): + r""" + Construct some point in the subset. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure="topological") + sage: N = Manifold(1, 'N', ambient=M, structure="topological") + sage: CM. = M.chart() + sage: CN. = N.chart() + sage: CN.add_restrictions([u > -1, u < 1]) + sage: Phi = N.continuous_map(M, {(CN,CM): [u, 1 + u^2]}, name='Phi') + sage: Phi_N = Phi.image() + sage: p = Phi_N.an_element(); p # indirect doctest + Point on the 2-dimensional topological manifold M + sage: p.coordinates() + (0, 1) + """ + return self._map(self._domain_subset.an_element()) + + def __contains__(self, point): + r""" + Check whether ``point`` is contained in ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M', structure="topological") + sage: N = Manifold(1, 'N', ambient=M, structure="topological") + sage: CM. = M.chart() + sage: CN. = N.chart() + sage: CN.add_restrictions([u > -1, u < 1]) + sage: Phi = N.continuous_map(M, {(CN,CM): [u, 1 + u^2]}, name='Phi') + sage: Phi_inv = M.continuous_map(N, {(CM, CN): [x]}, name='Phi_inv') + sage: Phi_N = Phi.image(inverse=Phi_inv) + sage: M((0, 0)) in Phi_N + False + sage: M((0, 1)) in Phi_N + True + + """ + if super().__contains__(point): + return True + if point not in self._map.codomain(): + return False + if self._inverse is not None: + preimage = self._inverse(point) + if preimage not in self._domain_subset: + return False + return self._map(preimage) == point + raise NotImplementedError diff --git a/src/sage/manifolds/differentiable/characteristic_class.py b/src/sage/manifolds/differentiable/characteristic_class.py index f4155b2577c..76f430ccfb2 100644 --- a/src/sage/manifolds/differentiable/characteristic_class.py +++ b/src/sage/manifolds/differentiable/characteristic_class.py @@ -122,7 +122,7 @@ obtained via :meth:`get_form`. sage: ch_form = ch.get_form(nab); ch_form.display_expansion() - ch(E, nabla^E) = [1] + [0] + [1/2*d(A)/dt/pi dt/\dx] + ch(E, nabla^E) = 1 + 1/2*d(A)/dt/pi dt/\dx .. _multiplicative: @@ -197,7 +197,7 @@ base space 2-dimensional differentiable manifold CP^1 sage: c_form = c.get_form(nab) sage: c_form.display_expansion(c_comp.frame(), chart=c_comp) - c(gamma^1, nabla) = [1] + [0] + [1/2*I/(pi + pi*z^2*zbar^2 + 2*pi*z*zbar) dz/\dzbar] + c(gamma^1, nabla) = 1 + 1/2*I/(pi + pi*z^2*zbar^2 + 2*pi*z*zbar) dz/\dzbar Since `U` and `\CC\mathbf{P}^1` differ only by a point and therefore a null set, it is enough to integrate the top form over the domain `U`:: @@ -302,8 +302,7 @@ sage: cmatrices = {eU: cmatrix_U, eV: cmatrix_V} sage: e_class_form = e_class.get_form(nab, cmatrices) sage: e_class_form.display_expansion() - e(TS2, nabla_g) = [0] + [0] + [2/(pi + pi*x^4 + pi*y^4 + 2*pi*x^2 + - 2*(pi + pi*x^2)*y^2) dx/\dy] + e(TS2, nabla_g) = 2/(pi + pi*x^4 + pi*y^4 + 2*pi*x^2 + 2*(pi + pi*x^2)*y^2) dx/\dy Let us check whether this form represents the Euler class correctly:: @@ -498,28 +497,28 @@ def _get_coeff_list(self, distinct_real=True): pow_range = self._base_space._dim // 2 def_var = self._func.default_variable() # Use a complex variable without affecting the old one: - new_var = SR.symbol('x_char_class_', domain='complex') - if self._vbundle._field_type == 'real' and distinct_real: - if self._class_type == 'additive': - func = self._func.subs({def_var: new_var ** 2}) / 2 - elif self._class_type == 'multiplicative': - # This could case problems in the real domain, where sqrt(x^2) - # is simplified to |x|. However, the variable must be complex - # anyway. - func = self._func.subs({def_var : new_var**2}).sqrt() - elif self._class_type == 'Pfaffian': - # There are no canonical Pfaffian classes, however, consider the - # projection onto the odd part of the function to keep the - # matrices skew: - func = (self._func.subs({def_var: new_var}) - - self._func.subs({def_var: -new_var})) / 2 - else: - func = self._func.subs({def_var: new_var}) + with SR.temp_var(domain='complex') as new_var: + if self._vbundle._field_type == 'real' and distinct_real: + if self._class_type == 'additive': + func = self._func.subs({def_var: new_var ** 2}) / 2 + elif self._class_type == 'multiplicative': + # This could case problems in the real domain, where sqrt(x^2) + # is simplified to |x|. However, the variable must be complex + # anyway. + func = self._func.subs({def_var : new_var**2}).sqrt() + elif self._class_type == 'Pfaffian': + # There are no canonical Pfaffian classes, however, consider the + # projection onto the odd part of the function to keep the + # matrices skew: + func = (self._func.subs({def_var: new_var}) - + self._func.subs({def_var: -new_var})) / 2 + else: + func = self._func.subs({def_var: new_var}) - if self._vbundle._field_type == 'real' and not distinct_real: - pow_range = pow_range // 2 + if self._vbundle._field_type == 'real' and not distinct_real: + pow_range = pow_range // 2 - return func.taylor(new_var, 0, pow_range).coefficients(sparse=False) + return func.taylor(new_var, 0, pow_range).coefficients(sparse=False) def _init_derived(self): r""" @@ -811,7 +810,7 @@ def get_form(self, connection, cmatrices=None): sage: ch_form.display() ch(E, nabla^E) = ch_0(E, nabla^E) + zero + ch_1(E, nabla^E) sage: ch_form.display_expansion() - ch(E, nabla^E) = [1] + [0] + [1/2*d(A)/dt/pi dt/\dx] + ch(E, nabla^E) = 1 + 1/2*d(A)/dt/pi dt/\dx Due to long computation times, the form is saved:: diff --git a/src/sage/manifolds/differentiable/chart.py b/src/sage/manifolds/differentiable/chart.py index 396792d3792..0e0ec6205c8 100644 --- a/src/sage/manifolds/differentiable/chart.py +++ b/src/sage/manifolds/differentiable/chart.py @@ -357,12 +357,9 @@ def transition_map(self, other, transformations, intersection_name=None, The subset `W`, intersection of `U` and `V`, has been created by ``transition_map()``:: - sage: M.list_of_subsets() - [1-dimensional differentiable manifold S^1, - Open subset U of the 1-dimensional differentiable manifold S^1, - Open subset V of the 1-dimensional differentiable manifold S^1, - Open subset W of the 1-dimensional differentiable manifold S^1] - sage: W = M.list_of_subsets()[3] + sage: F = M.subset_family(); F + Set {S^1, U, V, W} of open subsets of the 1-dimensional differentiable manifold S^1 + sage: W = F['W'] sage: W is U.intersection(V) True sage: M.atlas() @@ -385,9 +382,8 @@ def transition_map(self, other, transformations, intersection_name=None, In this case, no new subset has been created since `U\cap M = U`:: - sage: M.list_of_subsets() - [2-dimensional differentiable manifold R^2, - Open subset U of the 2-dimensional differentiable manifold R^2] + sage: M.subset_family() + Set {R^2, U} of open subsets of the 2-dimensional differentiable manifold R^2 but a new chart has been created: `(U, (x, y))`:: @@ -439,17 +435,17 @@ def frame(self): sage: ey = c_xy.frame()[1] ; ey Vector field d/dy on the 2-dimensional differentiable manifold M sage: ex(M.scalar_field(x)).display() - M --> R - (x, y) |--> 1 + 1: M --> R + (x, y) |--> 1 sage: ex(M.scalar_field(y)).display() - M --> R - (x, y) |--> 0 + zero: M --> R + (x, y) |--> 0 sage: ey(M.scalar_field(x)).display() - M --> R - (x, y) |--> 0 + zero: M --> R + (x, y) |--> 0 sage: ey(M.scalar_field(y)).display() - M --> R - (x, y) |--> 1 + 1: M --> R + (x, y) |--> 1 """ return self._frame @@ -563,7 +559,7 @@ def restrict(self, subset, restrictions=None): sframe._restrictions[subset] = resu._frame # The subchart frame is not a "top frame" in the supersets # (including self._domain): - for dom in self._domain._supersets: + for dom in self._domain.open_supersets(): if resu._frame in dom._top_frames: # it was added by the Chart constructor invoked in # Chart.restrict above @@ -1046,7 +1042,7 @@ def restrict(self, subset, restrictions=None): sframe._restrictions[subset] = resu._frame # The subchart frame is not a "top frame" in the supersets # (including self._domain): - for dom in self._domain._supersets: + for dom in self._domain.open_supersets(): if resu._frame in dom._top_frames: # it was added by the Chart constructor invoked in # Chart.restrict above @@ -1138,7 +1134,7 @@ def __init__(self, chart1, chart2, *transformations): ch_basis.add_comp(frame1)[:, chart1] = self._jacobian ch_basis.add_comp(frame2)[:, chart1] = self._jacobian vf_module._basis_changes[(frame2, frame1)] = ch_basis - for sdom in domain._supersets: + for sdom in domain.open_supersets(): sdom._frame_changes[(frame2, frame1)] = ch_basis # The inverse is computed only if it does not exist already # (because if it exists it may have a simpler expression than that @@ -1146,7 +1142,7 @@ def __init__(self, chart1, chart2, *transformations): if (frame1, frame2) not in vf_module._basis_changes: ch_basis_inv = ch_basis.inverse() vf_module._basis_changes[(frame1, frame2)] = ch_basis_inv - for sdom in domain._supersets: + for sdom in domain.open_supersets(): sdom._frame_changes[(frame1, frame2)] = ch_basis_inv def jacobian(self): diff --git a/src/sage/manifolds/differentiable/de_rham_cohomology.py b/src/sage/manifolds/differentiable/de_rham_cohomology.py new file mode 100644 index 00000000000..9bcc82efe45 --- /dev/null +++ b/src/sage/manifolds/differentiable/de_rham_cohomology.py @@ -0,0 +1,505 @@ +r""" +De Rham Cohomology + +Let `M` and `N` be differentiable manifolds and `\varphi\colon M \to N` be +a differentiable map. Then the associated de Rham complex is given by + +.. MATH:: + + 0 \rightarrow \Omega^0(M,\varphi) \xrightarrow{\mathrm{d}_0} + \Omega^1(M,\varphi) \xrightarrow{\mathrm{d}_1} \dots + \xrightarrow{\mathrm{d}_{n-1}} \Omega^n(M,\varphi) + \xrightarrow{\mathrm{d}_{n}} 0, + +where `\Omega^k(M,\varphi)` is the module of differential forms of degree `k`, +and `d_k` is the associated exterior derivative. Then the `k`-*th de Rham +cohomology group* is given by + +.. MATH:: + + H^k_{\mathrm{dR}}(M, \varphi) = + \left. \mathrm{ker}(\mathrm{d}_k) \middle/ + \mathrm{im}(\mathrm{d}_{k-1}) \right. , + +and the corresponding ring is obtained by + +.. MATH:: + + H^*_{\mathrm{dR}}(M, \varphi) = \bigoplus^n_{k=0} H^k_{\mathrm{dR}}(M, \varphi). + +The de Rham cohomology ring is implemented via :class:`DeRhamCohomologyRing`. +Its elements, the cohomology classes, are represented by +:class:`DeRhamCohomologyClass`. + +AUTHORS: + +- Michael Jung (2021) : initial version + +""" + +#****************************************************************************** +# Copyright (C) 2021 Michael Jung +# +# 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. +# https://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.cachefunc import cached_method +from sage.structure.parent import Parent +from sage.structure.element import RingElement +from sage.categories.rings import Rings + +class DeRhamCohomologyClass(RingElement): + r""" + Define a cohomology class in the de Rham cohomology ring. + + INPUT: + + - ``parent`` -- de Rham cohomology ring represented by an instance of + :class:`DeRhamCohomologyRing` + - ``representative`` -- a closed (mixed) differential form representing the + cohomology class + + .. NOTE:: + + The current implementation only provides basic features. Comparison via + exact forms are not supported at the time being. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: u = H(omega); u + [omega] + + Cohomology classes can be lifted to the algebra of mixed differential + forms:: + + sage: u.lift() + Mixed differential form omega on the 2-dimensional differentiable + manifold M + + However, comparison of two cohomology classes is limited the time being:: + + sage: eta = M.diff_form(1, [1,1], name='eta') + sage: H(eta) == u + True + sage: H.one() == u + Traceback (most recent call last): + ... + NotImplementedError: comparison via exact forms is currently not supported + + """ + def __init__(self, parent, representative): + r""" + Construct an element of the de Rham cohomology ring. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega', latex_name=r'\omega') + sage: u = H(omega) + sage: TestSuite(u).run(skip=['_test_eq', '_test_nonzero_equal']) # equality not fully supported yet + + """ + super().__init__(parent=parent) + self._representative = representative + + def _repr_(self): + r""" + Return a string representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.an_element() # indirect doctest + [one] + sage: H.an_element()._repr_() + '[one]' + + """ + name = self._representative._name + if name is None: + name = 'unnamed form' + return f"[{name}]" + + def _latex_(self): + r""" + Return a LaTeX representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M', latex_name=r'\mathcal{M}') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega', latex_name=r'\omega') + sage: u = H(omega) + sage: latex(u) # indirect doctest + \left[\omega\right] + sage: u._latex_() + '\\left[\\omega\\right]' + + """ + latex_name = self._representative._latex_name + if latex_name is None: + latex_name = r'\mathrm{unnamed form}' + return rf"\left[{latex_name}\right]" + + def representative(self): + r""" + Return a representative of ``self`` in the associated de Rham + complex. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(2, name='omega') + sage: omega[0,1] = x + sage: omega.display() + omega = x dx/\dy + sage: u = H(omega); u + [omega] + sage: u.representative() + Mixed differential form omega on the 2-dimensional differentiable + manifold M + + """ + return self._representative + + lift = representative + + def _add_(self, other): + r""" + Addition of two cohomology classes. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega) + H(eta) + [omega+eta] + + """ + return self.parent()(self.representative() + other.representative()) + + def cup(self, other): + r""" + Cup product of two cohomology classes. + + INPUT: + + - ``other``-- another cohomology class in the de Rham cohomology + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega).cup(H(eta)) + [omega/\eta] + + """ + return self * other + + def _mul_(self, other): + r""" + Cup product of two cohomology classes. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega) * H(eta) + [omega/\eta] + + """ + return self.parent()(self.representative().wedge(other.representative())) + + def _sub_(self, other): + r""" + Subtraction of two cohomology classes. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,-1], name='eta') + sage: H(omega) - H(eta) + [omega-eta] + + """ + return self.parent()(self.representative() - other.representative()) + + def __eq__(self, other): + r""" + Comparison (equality) operator. + + .. WARNING:: + + At current stage, the equality operator only checks whether the + representatives are equal. No further checks are supported so far. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: omega = M.diff_form(1, [1,1], name='omega') + sage: eta = M.diff_form(1, [1,1], name='eta') + sage: H(omega) == H(eta) + True + sage: H(omega) == H.one() + Traceback (most recent call last): + ... + NotImplementedError: comparison via exact forms is currently not supported + + """ + if self is other: + return True + if isinstance(other, type(self)): + if self.representative() == other.representative(): + return True + raise NotImplementedError('comparison via exact forms is currently not supported') + +class DeRhamCohomologyRing(Parent, UniqueRepresentation): + r""" + The de Rham cohomology ring of a de Rham complex. + + This ring is naturally endowed with a multiplication induced by the wedge + product, called *cup product*, see :meth:`DeRhamCohomologyClass.cup`. + + .. NOTE:: + + The current implementation only provides basic features. Comparison via + exact forms are not supported at the time being. + + INPUT: + + - ``de_rham_complex`` -- a de Rham complex, typically an instance of + :class:`~sage.manifolds.differentiable.mixed_form_algebra.MixedFormAlgebra` + + EXAMPLES: + + We define the de Rham cohomology ring on a parallelizable manifold `M`:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology(); H + De Rham cohomology ring on the 2-dimensional differentiable manifold M + + Its elements are induced by closed differential forms on `M`:: + + sage: beta = M.diff_form(1, [1,0], name='beta') + sage: beta.display() + beta = dx + sage: d1 = C.differential(1) + sage: d1(beta).display() + dbeta = 0 + sage: b = H(beta); b + [beta] + + Cohomology classes can be lifted to the algebra of mixed differential + forms:: + + sage: b.representative() + Mixed differential form beta on the 2-dimensional differentiable + manifold M + + The ring admits a zero and unit element:: + + sage: H.zero() + [zero] + sage: H.one() + [one] + + """ + def __init__(self, de_rham_complex): + r""" + Construct the de Rham cohomology ring. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology(); H + De Rham cohomology ring on the 2-dimensional differentiable + manifold M + sage: TestSuite(H).run(skip=['_test_elements', + ....: '_test_elements_eq_symmetric', + ....: '_test_elements_eq_transitive', + ....: '_test_elements_neq']) # equality not fully supported yet + + """ + Parent.__init__(self, category=Rings()) + self._de_rham_complex = self._module = de_rham_complex + self._manifold = de_rham_complex._domain + + Element = DeRhamCohomologyClass + + def _element_constructor_(self, x): + r""" + Construct an element of ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H._element_constructor_(C.one()) + [one] + + Non-cycle element:: + + sage: omega = M.diff_form(1, name='omega') + sage: omega[0] = y + sage: omega.display() + omega = y dx + sage: H(omega) + Traceback (most recent call last): + ... + ValueError: Mixed differential form omega on the 2-dimensional + differentiable manifold M must be a closed form + + """ + if x not in self._module: + raise TypeError(f"{x} must be an element of {self._module}") + x = self._module(x) + if x.derivative() != 0: + raise ValueError(f"{x} must be a closed form") + return self.element_class(self, x) + + def _repr_(self): + r""" + Return a string representation of the object. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology(); H + De Rham cohomology ring on the 2-dimensional differentiable + manifold M + + """ + desc = "De Rham cohomology ring " + if self._module._dest_map is self._manifold.identity_map(): + desc += "on the {}".format(self._manifold) + else: + desc += "along the {} mapped ".format(self._manifold) + desc += "into the {} ".format(self._module._ambient_domain) + if self._module._dest_map._name is None: + dm_name = "unnamed map" + else: + dm_name = self._module._dest_map._name + desc += "via " + dm_name + return desc + + def _latex_(self): + r""" + Return a LaTeX representation of the object. + + TESTS:: + + sage: M = Manifold(3, 'M', latex_name=r'\mathcal{M}') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H._latex_() + 'H^*_{\\mathrm{dR}}\\left(\\mathcal{M}\\right)' + sage: latex(H) # indirect doctest + H^*_{\mathrm{dR}}\left(\mathcal{M}\right) + + """ + latex_name = r"H^*_{\mathrm{dR}}\left(" + self._manifold._latex_name + if self._module._dest_map is not self._manifold.identity_map(): + dm_latex_name = self._module._dest_map._latex_name + if dm_latex_name is None: + dm_latex_name = r"\mathrm{unnamed\; map}" + latex_name += "," + dm_latex_name + latex_name += r"\right)" + return latex_name + + def _an_element_(self): + r""" + Return an element of ``self``. + + TESTS:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.an_element() + [one] + + """ + return self.one() + + @cached_method + def zero(self): + r""" + Return the zero element of ``self``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.zero() + [zero] + sage: H.zero().representative() + Mixed differential form zero on the 2-dimensional differentiable + manifold M + + """ + return self.element_class(self, self._module.zero()) + + @cached_method + def one(self): + r""" + Return the one element of ``self``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: H.one() + [one] + sage: H.one().representative() + Mixed differential form one on the 2-dimensional differentiable + manifold M + + """ + return self.element_class(self, self._module.one()) diff --git a/src/sage/manifolds/differentiable/degenerate_submanifold.py b/src/sage/manifolds/differentiable/degenerate_submanifold.py index a3d37fbece6..2001dbfdb9a 100644 --- a/src/sage/manifolds/differentiable/degenerate_submanifold.py +++ b/src/sage/manifolds/differentiable/degenerate_submanifold.py @@ -12,7 +12,7 @@ lightlike submanifold) and in General Relativity. In geometry of lightlike submanifolds, according to the dimension `r` of the radical distribution (see below for definition of radical distribution), degenerate submanifolds -have been classify into 4 subgroups: `r`-lightlike submanifolds, Coisotropic +have been classified into 4 subgroups: `r`-lightlike submanifolds, Coisotropic submanifolds, Isotropic submanifolds and Totally lightlike submanifolds. (See the book of Krishan L. Duggal and Aurel Bejancu [DS2010]_.) diff --git a/src/sage/manifolds/differentiable/diff_form_module.py b/src/sage/manifolds/differentiable/diff_form_module.py index 31e6a3a450b..309dee3e733 100644 --- a/src/sage/manifolds/differentiable/diff_form_module.py +++ b/src/sage/manifolds/differentiable/diff_form_module.py @@ -384,16 +384,14 @@ def _an_element_(self): """ resu = self.element_class(self._vmodule, self._degree) - # Non-trivial open covers of the domain: - open_covers = self._domain.open_covers()[1:] # the open cover 0 - # is trivial - if open_covers != []: - oc = open_covers[0] # the first non-trivial open cover is selected + for oc in self._domain.open_covers(trivial=False): + # the first non-trivial open cover is selected for dom in oc: vmodule_dom = dom.vector_field_module( dest_map=self._dest_map.restrict(dom)) dmodule_dom = vmodule_dom.dual_exterior_power(self._degree) resu.set_restriction(dmodule_dom._an_element_()) + return resu return resu def _coerce_map_from_(self, other): diff --git a/src/sage/manifolds/differentiable/diff_map.py b/src/sage/manifolds/differentiable/diff_map.py index 8caab15c093..ad9a2a94f95 100644 --- a/src/sage/manifolds/differentiable/diff_map.py +++ b/src/sage/manifolds/differentiable/diff_map.py @@ -13,6 +13,7 @@ AUTHORS: - Eric Gourgoulhon, Michal Bejger (2013-2015): initial version +- Marco Mancini (2018): pullback parallelization REFERENCES: @@ -22,8 +23,9 @@ """ # **************************************************************************** -# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015-2021 Eric Gourgoulhon # Copyright (C) 2015 Michal Bejger +# Copyright (C) 2018 Marco Mancini # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -917,13 +919,26 @@ def pullback(self, tensor): sage: pa.display() # should be zero (as any 3-form on a 2-dimensional manifold) Phi^*(A) = 0 + TESTS: + + Check that :trac:`31904` is fixed:: + + sage: E. = EuclideanSpace() + sage: polar. = E.polar_coordinates() + sage: g = E.metric() + sage: M = Manifold(1, 'M') + sage: Ct. = M.chart() + sage: F = M.diff_map(E, coord_functions={(Ct, polar): (1 + cos(t), t)}) + sage: gM = F.pullback(g) + sage: gM.display() + (2*cos(t) + 2) dt*dt + """ from sage.manifolds.differentiable.tensorfield_paral import TensorFieldParal - from sage.manifolds.differentiable.vectorframe import CoordFrame from sage.tensor.modules.comp import (Components, CompWithSym, CompFullySym, CompFullyAntiSym) - def _pullback_chart(diff_map, tensor): + def _pullback_chart(diff_map, tensor, chart1, chart2): r""" Helper function performing the pullback on chart domains only. @@ -931,9 +946,13 @@ def _pullback_chart(diff_map, tensor): INPUT: - ``diff_map`` -- a restriction of ``self``, whose both - domain and codomain are chart domains + domain and codomain are chart domains, corresponding + respectively to ``chart1`` and ``chart2`` - ``tensor`` -- a covariant tensor field, whose domain is - the codomain of ``diff_map`` + the codomain of ``diff_map`` and whose components are known + in ``chart2.frame()`` + - ``chart1`` -- chart on the domain of ``diff_map`` + - ``chart2`` -- chart on the codomain of ``diff_map`` OUTPUT: @@ -963,82 +982,72 @@ def _pullback_chart(diff_map, tensor): nproc = Parallelism().get('tensor') ind_old_list = list(dom2.manifold().index_generator(ncov)) - for frame2 in tensor._components: - if isinstance(frame2, CoordFrame): - chart2 = frame2._chart - for chart1 in dom1._atlas: - if (chart1._domain is dom1 and (chart1, chart2) in - diff_map._coord_expression): - # Computation at the component level: - frame1 = chart1._frame - tcomp = tensor._components[frame2] - if isinstance(tcomp, CompFullySym): - ptcomp = CompFullySym(ring1, frame1, ncov, - start_index=si1, - output_formatter=of1) - elif isinstance(tcomp, CompFullyAntiSym): - ptcomp = CompFullyAntiSym(ring1, frame1, ncov, - start_index=si1, - output_formatter=of1) - elif isinstance(tcomp, CompWithSym): - ptcomp = CompWithSym(ring1, frame1, ncov, - start_index=si1, - output_formatter=of1, - sym=tcomp.sym, - antisym=tcomp.antisym) - else: - ptcomp = Components(ring1, frame1, ncov, - start_index=si1, - output_formatter=of1) - phi = diff_map._coord_expression[(chart1, chart2)] - jacob = phi.jacobian() - # X2 coordinates expressed in terms of - # X1 ones via the diff. map: - coord2_1 = phi(*(chart1._xx)) - - if nproc != 1: - # Parallel computation - lol = lambda lst, sz: [lst[i:i+sz] for i in range(0, len(lst), sz)] - ind_list = [ind for ind in ptcomp.non_redundant_index_generator()] - ind_step = max(1, int(len(ind_list)/nproc/2)) - local_list = lol(ind_list, ind_step) - # list of input parameters - listParalInput = [(tcomp,chart1,chart2,coord2_1,jacob, - ind_old_list,si1,si2,ncov,ind_part) for ind_part in local_list] - - @parallel(p_iter='multiprocessing', ncpus=nproc) - def paral_comp(tcomp,chart1,chart2,coord2_1,jacob, - ind_old_list,si1,si2,ncov,local_list_ind): - partial = [] - for ind_new in local_list_ind: - res = 0 - for ind_old in ind_old_list: - ff = tcomp[[ind_old]].coord_function(chart2) - t = chart1.function(ff(*coord2_1)) - for i in range(ncov): - t *= jacob[ind_old[i]-si2, ind_new[i]-si1] - res += t - partial.append([ind_new, res]) - return partial - - for ii, val in paral_comp(listParalInput): - for jj in val: - ptcomp[[jj[0]]] = jj[1] - - else: - # Sequential computation - for ind_new in ptcomp.non_redundant_index_generator(): - res = 0 - for ind_old in ind_old_list: - ff = tcomp[[ind_old]].coord_function(chart2) - t = chart1.function(ff(*coord2_1)) - for i in range(ncov): - t *= jacob[ind_old[i]-si2, ind_new[i]-si1] - res += t - ptcomp[ind_new] = res - - resu._components[frame1] = ptcomp - return resu + frame1 = chart1.frame() + frame2 = chart2.frame() + + tcomp = tensor._components[frame2] + if isinstance(tcomp, CompFullySym): + ptcomp = CompFullySym(ring1, frame1, ncov, start_index=si1, + output_formatter=of1) + elif isinstance(tcomp, CompFullyAntiSym): + ptcomp = CompFullyAntiSym(ring1, frame1, ncov, start_index=si1, + output_formatter=of1) + elif isinstance(tcomp, CompWithSym): + ptcomp = CompWithSym(ring1, frame1, ncov, start_index=si1, + output_formatter=of1, sym=tcomp.sym, + antisym=tcomp.antisym) + else: + ptcomp = Components(ring1, frame1, ncov, start_index=si1, + output_formatter=of1) + phi = diff_map._coord_expression[(chart1, chart2)] + jacob = phi.jacobian() + # X2 coordinates expressed in terms of X1 ones via the diff. map: + coord2_1 = phi(*(chart1._xx)) + + if nproc != 1: + # Parallel computation + lol = lambda lst, sz: [lst[i:i+sz] for i in range(0, len(lst), sz)] + ind_list = [ind for ind in ptcomp.non_redundant_index_generator()] + ind_step = max(1, int(len(ind_list)/nproc/2)) + local_list = lol(ind_list, ind_step) + # list of input parameters + listParalInput = [(tcomp, chart1, chart2, coord2_1, jacob, + ind_old_list, si1, si2, ncov, ind_part) + for ind_part in local_list] + + @parallel(p_iter='multiprocessing', ncpus=nproc) + def paral_comp(tcomp, chart1, chart2, coord2_1, jacob, + ind_old_list, si1, si2, ncov, local_list_ind): + partial = [] + for ind_new in local_list_ind: + res = 0 + for ind_old in ind_old_list: + ff = tcomp[[ind_old]].coord_function(chart2) + t = chart1.function(ff(*coord2_1)) + for i in range(ncov): + t *= jacob[ind_old[i]-si2, ind_new[i]-si1] + res += t + partial.append([ind_new, res]) + return partial + + for ii, val in paral_comp(listParalInput): + for jj in val: + ptcomp[[jj[0]]] = jj[1] + + else: + # Sequential computation + for ind_new in ptcomp.non_redundant_index_generator(): + res = 0 + for ind_old in ind_old_list: + ff = tcomp[[ind_old]].coord_function(chart2) + t = chart1.function(ff(*coord2_1)) + for i in range(ncov): + t *= jacob[ind_old[i]-si2, ind_new[i]-si1] + res += t + ptcomp[ind_new] = res + + resu._components[frame1] = ptcomp + return resu # End of function _pullback_chart # Special case of the identity map: @@ -1091,7 +1100,9 @@ def paral_comp(tcomp,chart1,chart2,coord2_1,jacob, if ch2dom.is_subset(tdom): self_r = self.restrict(chart1._domain, subcodomain=ch2dom) tensor_r = tensor.restrict(ch2dom) - resu_rst.append(_pullback_chart(self_r, tensor_r)) + if chart2.frame() in tensor_r._components: + resu_rst.append(_pullback_chart(self_r, tensor_r, + chart1, chart2)) dom_resu = resu_rst[0]._domain for rst in resu_rst[1:]: dom_resu = dom_resu.union(rst._domain) diff --git a/src/sage/manifolds/differentiable/differentiable_submanifold.py b/src/sage/manifolds/differentiable/differentiable_submanifold.py index cb21dc5dd39..6b0f4cac6ad 100644 --- a/src/sage/manifolds/differentiable/differentiable_submanifold.py +++ b/src/sage/manifolds/differentiable/differentiable_submanifold.py @@ -18,6 +18,8 @@ AUTHORS: - Florentin Jaffredo (2018): initial version +- Eric Gourgoulhon (2018-2019): add documentation +- Matthias Koeppe (2021): open subsets of submanifolds REFERENCES: @@ -26,7 +28,9 @@ """ # ***************************************************************************** -# Copyright (C) 2018 Florentin Jaffredo +# Copyright (C) 2018 Florentin Jaffredo +# Copyright (C) 2018-2019 Eric Gourgoulhon +# Copyright (C) 2021 Matthias Koeppe # # 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 @@ -208,6 +212,8 @@ def _repr_(self): 3-dimensional differentiable manifold M """ + if self is not self._manifold: + return "Open subset {} of the {}".format(self._name, self._manifold) if self._ambient is None: return super(DifferentiableManifold, self).__repr__() if self._embedded: @@ -215,3 +221,74 @@ def _repr_(self): self._dim, self._structure.name, self._name, self._ambient) return "{}-dimensional {} submanifold {} immersed in the {}".format( self._dim, self._structure.name, self._name, self._ambient) + + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): + r""" + Create an open subset of the manifold. + + An open subset is a set that is (i) included in the manifold and (ii) + open with respect to the manifold's topology. It is a differentiable + manifold by itself. + + As ``self`` is a submanifold of its ambient manifold, + the new open subset is also considered a submanifold of that. + Hence the returned object is an instance of + :class:`DifferentiableSubmanifold`. + + INPUT: + + - ``name`` -- name given to the open subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + subset; if none is provided, it is set to ``name`` + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts in the manifold's atlas and values the symbolic expressions + formed by the coordinates to define the subset. + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of + + OUTPUT: + + - the open subset, as an instance of :class:`DifferentiableSubmanifold` + + EXAMPLES:: + + sage: M = Manifold(3, 'M', structure="differentiable") + sage: N = Manifold(2, 'N', ambient=M, structure="differentiable"); N + 2-dimensional differentiable submanifold N immersed in the + 3-dimensional differentiable manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional differentiable submanifold N immersed in the + 3-dimensional differentiable manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional differentiable submanifold N immersed in the + 3-dimensional differentiable manifold M + + sage: phi = N.diff_map(M) + sage: N.set_embedding(phi) + sage: N + 2-dimensional differentiable submanifold N embedded in the + 3-dimensional differentiable manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional differentiable submanifold N embedded in the + 3-dimensional differentiable manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional differentiable submanifold N embedded in the + 3-dimensional differentiable manifold M + + """ + resu = DifferentiableSubmanifold(self._dim, name, self._field, + self._structure, ambient=self._ambient, + base_manifold=self._manifold, + diff_degree=self._diff_degree, + latex_name=latex_name, + start_index=self._sindex) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) + return resu diff --git a/src/sage/manifolds/differentiable/examples/real_line.py b/src/sage/manifolds/differentiable/examples/real_line.py index 6582fa96f41..cd9efabee5f 100644 --- a/src/sage/manifolds/differentiable/examples/real_line.py +++ b/src/sage/manifolds/differentiable/examples/real_line.py @@ -229,11 +229,11 @@ class OpenInterval(DifferentiableManifold): We have:: - sage: I.list_of_subsets() + sage: list(I.subset_family()) [Real interval (0, 1), Real interval (0, pi), Real interval (1/2, 1)] - sage: J.list_of_subsets() + sage: list(J.subset_family()) [Real interval (0, 1), Real interval (1/2, 1)] - sage: K.list_of_subsets() + sage: list(K.subset_family()) [Real interval (1/2, 1)] As any open subset of a manifold, open subintervals are created in a @@ -376,9 +376,7 @@ def __init__(self, lower, upper, ambient_interval=None, if upper > ambient_interval.upper_bound(): raise ValueError("the upper bound is larger than that of " + "the containing interval") - self._supersets.update(ambient_interval._supersets) - for sd in ambient_interval._supersets: - sd._subsets.add(self) + self.declare_subset(ambient_interval) ambient_interval._top_subsets.add(self) t = ambient_interval.canonical_coordinate() if lower != minus_infinity: @@ -684,7 +682,7 @@ def open_interval(self, lower, upper, name=None, latex_name=None): Real interval (0, pi) sage: J.is_subset(I) True - sage: I.list_of_subsets() + sage: list(I.subset_family()) [Real interval (-4, 4), Real interval (0, pi)] ``J`` is considered as an open submanifold of ``I``:: @@ -863,7 +861,7 @@ class RealLine(OpenInterval): Real interval (0, 1) sage: I.manifold() Real number line R - sage: R.list_of_subsets() + sage: list(R.subset_family()) [Real interval (0, 1), Real number line R] """ diff --git a/src/sage/manifolds/differentiable/examples/sphere.py b/src/sage/manifolds/differentiable/examples/sphere.py index 67b40872c7b..7315285605a 100644 --- a/src/sage/manifolds/differentiable/examples/sphere.py +++ b/src/sage/manifolds/differentiable/examples/sphere.py @@ -275,12 +275,9 @@ class Sphere(PseudoRiemannianSubmanifold): sage: stereoN, stereoS = S2.coordinate_charts('stereographic') sage: stereoN, stereoS (Chart (S^2-{NP}, (y1, y2)), Chart (S^2-{SP}, (yp1, yp2))) - sage: S2.open_covers() - [[2-sphere S^2 of radius 1 smoothly embedded in the Euclidean space E^3], - [Open subset S^2-{NP} of the 2-sphere S^2 of radius 1 smoothly - embedded in the Euclidean space E^3, - Open subset S^2-{SP} of the 2-sphere S^2 of radius 1 smoothly - embedded in the Euclidean space E^3]] + sage: list(S2.open_covers()) + [Set {S^2} of open subsets of the 2-sphere S^2 of radius 1 smoothly embedded in the Euclidean space E^3, + Set {S^2-{NP}, S^2-{SP}} of open subsets of the 2-sphere S^2 of radius 1 smoothly embedded in the Euclidean space E^3] .. NOTE:: @@ -501,11 +498,10 @@ def _init_chart_domains(self): TESTS:: sage: S2 = manifolds.Sphere(2) - sage: S2.open_covers() - [[2-sphere S^2 of radius 1 smoothly embedded in the Euclidean space E^3], - [Open subset S^2-{NP} of the 2-sphere S^2 of radius 1 smoothly embedded in the Euclidean space E^3, - Open subset S^2-{SP} of the 2-sphere S^2 of radius 1 smoothly embedded in the Euclidean space E^3]] - sage: S2.subsets() # random + sage: list(S2.open_covers()) + [Set {S^2} of open subsets of the 2-sphere S^2 of radius 1 smoothly embedded in the Euclidean space E^3, + Set {S^2-{NP}, S^2-{SP}} of open subsets of the 2-sphere S^2 of radius 1 smoothly embedded in the Euclidean space E^3] + sage: frozenset(S2.subsets()) # random frozenset({Euclidean 2-sphere S^2 of radius 1, Open subset A of the Euclidean 2-sphere S^2 of radius 1, Open subset S^2-{NP,SP} of the Euclidean 2-sphere S^2 of radius 1, @@ -1145,7 +1141,7 @@ def minimal_triangulation(self): 2 """ - from sage.homology.examples import Sphere as SymplicialSphere + from sage.topology.simplicial_complex_examples import Sphere as SymplicialSphere return SymplicialSphere(self._dim) def center(self): diff --git a/src/sage/manifolds/differentiable/manifold.py b/src/sage/manifolds/differentiable/manifold.py index 61d94a414f8..a6243f0ba65 100644 --- a/src/sage/manifolds/differentiable/manifold.py +++ b/src/sage/manifolds/differentiable/manifold.py @@ -125,11 +125,8 @@ At this stage, we have four open subsets on `S^2`:: - sage: M.list_of_subsets() - [2-dimensional differentiable manifold S^2, - Open subset U of the 2-dimensional differentiable manifold S^2, - Open subset V of the 2-dimensional differentiable manifold S^2, - Open subset W of the 2-dimensional differentiable manifold S^2] + sage: M.subset_family() + Set {S^2, U, V, W} of open subsets of the 2-dimensional differentiable manifold S^2 `W` is the open subset that is the complement of the two poles:: @@ -196,7 +193,7 @@ Algebra of differentiable scalar fields on the 2-dimensional differentiable manifold S^2 sage: f.parent().category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces A differentiable manifold has a default vector frame, which, unless otherwise specified, is the coordinate frame associated with the first defined chart:: @@ -350,11 +347,8 @@ The following subsets and charts have been defined:: - sage: M.list_of_subsets() - [Open subset A of the 1-dimensional complex manifold C*, - 1-dimensional complex manifold C*, - Open subset U of the 1-dimensional complex manifold C*, - Open subset V of the 1-dimensional complex manifold C*] + sage: M.subset_family() + Set {A, C*, U, V} of open subsets of the 1-dimensional complex manifold C* sage: M.atlas() [Chart (U, (z,)), Chart (V, (w,)), Chart (A, (z,)), Chart (A, (w,))] @@ -376,7 +370,7 @@ Algebra of differentiable scalar fields on the 1-dimensional complex manifold C* sage: f.parent().category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces A vector field on the Riemann sphere:: @@ -393,12 +387,12 @@ The vector field `v` acting on the scalar field `f`:: sage: v(f) - Scalar field v(f) on the 1-dimensional complex manifold C* + Scalar field zero on the 1-dimensional complex manifold C* Since `f` is constant, `v(f)` is vanishing:: sage: v(f).display() - v(f): C* --> C + zero: C* --> C on U: z |--> 0 on V: w |--> 0 @@ -417,6 +411,7 @@ - Eric Gourgoulhon (2015): initial version - Travis Scrimshaw (2016): review tweaks - Michael Jung (2020): tensor bundles and orientability +- Matthias Koeppe (2021): refactoring of subsets code REFERENCES: @@ -430,9 +425,13 @@ """ # **************************************************************************** -# Copyright (C) 2015 Eric Gourgoulhon -# Copyright (C) 2015 Michal Bejger -# Copyright (C) 2016 Travis Scrimshaw +# Copyright (C) 2015-2019 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger +# Copyright (C) 2015-2016 Travis Scrimshaw +# Copyright (C) 2017 Karim Van Aelst +# Copyright (C) 2019 Hans Fotsing Tetsing +# Copyright (C) 2019-2020 Michael Jung +# Copyright (C) 2021 Matthias Koeppe # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -723,7 +722,7 @@ def diff_degree(self): """ return self._diff_degree - def open_subset(self, name, latex_name=None, coord_def={}): + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): r""" Create an open subset of the manifold. @@ -741,6 +740,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): terms of coordinates; ``coord_def`` must a be dictionary with keys charts in the manifold's atlas and values the symbolic expressions formed by the coordinates to define the subset. + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of OUTPUT: @@ -773,9 +774,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): We have then:: - sage: A.list_of_subsets() - [Open subset A of the 2-dimensional differentiable manifold M, - Open subset B of the 2-dimensional differentiable manifold M] + sage: A.subset_family() + Set {A, B} of open subsets of the 2-dimensional differentiable manifold M sage: B.is_subset(A) True sage: B.is_subset(M) @@ -826,25 +826,39 @@ def open_subset(self, name, latex_name=None, coord_def={}): diff_degree=self._diff_degree, latex_name=latex_name, start_index=self._sindex) - resu._calculus_method = self._calculus_method - resu._supersets.update(self._supersets) - for sd in self._supersets: - sd._subsets.add(resu) - self._top_subsets.add(resu) - # Charts on the result from the coordinate definition: - for chart, restrictions in coord_def.items(): - if chart not in self._atlas: - raise ValueError("the {} does not belong to ".format(chart) + - "the atlas of {}".format(self)) - chart.restrict(resu, restrictions) - # Transition maps on the result inferred from those of self: - for chart1 in coord_def: - for chart2 in coord_def: - if chart2 != chart1 and (chart1, chart2) in self._coord_changes: - self._coord_changes[(chart1, chart2)].restrict(resu) - #!# update vector frames and change of frames + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) return resu + def _init_open_subset(self, resu, coord_def): + r""" + Initialize ``resu`` as an open subset of ``self``. + + INPUT: + + - ``resu`` -- an instance of ``:class:`TopologicalManifold` or + a subclass. + + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='differentiable') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: from sage.manifolds.differentiable.manifold import DifferentiableManifold + sage: U = DifferentiableManifold(2, 'U', field=M._field, structure=M._structure, base_manifold=M) + sage: M._init_open_subset(U, coord_def={c_cart: x^2+y^2<1}) + sage: U + Open subset U of the 2-dimensional differentiable manifold R^2 + """ + super()._init_open_subset(resu, coord_def=coord_def) + #!# update vector frames and change of frames + def diff_map(self, codomain, coord_functions=None, chart1=None, chart2=None, name=None, latex_name=None): r""" @@ -1521,7 +1535,7 @@ def mixed_form_algebra(self, dest_map=None): Graded algebra Omega^*(M) of mixed differential forms on the 2-dimensional differentiable manifold M sage: M.mixed_form_algebra().category() - Category of graded algebras over Symbolic Ring + Join of Category of graded algebras over Symbolic Ring and Category of chain complexes over Symbolic Ring sage: M.mixed_form_algebra().base_ring() Symbolic Ring @@ -1534,6 +1548,8 @@ def mixed_form_algebra(self, dest_map=None): vmodule = self.vector_field_module(dest_map=dest_map) return MixedFormAlgebra(vmodule) + de_rham_complex = mixed_form_algebra + def multivector_module(self, degree, dest_map=None): r""" Return the set of multivector fields of a given degree defined @@ -2743,7 +2759,7 @@ def orientation(self): """ if not self._orientation: # try to get an orientation from super domains: - for sdom in self._supersets: + for sdom in self.open_supersets(): sorient = sdom._orientation if sorient: rst_orient = [f.restrict(self) for f in sorient] @@ -2951,11 +2967,11 @@ def set_change_of_frame(self, frame1, frame2, change_of_frame, "instance of AutomorphismFieldParal") fmodule.set_change_of_basis(frame1, frame2, change_of_frame, compute_inverse=compute_inverse) - for sdom in self._supersets: + for sdom in self.open_supersets(): sdom._frame_changes[(frame1, frame2)] = change_of_frame if compute_inverse: if (frame2, frame1) not in self._frame_changes: - for sdom in self._supersets: + for sdom in self.open_supersets(): sdom._frame_changes[(frame2, frame1)] = change_of_frame.inverse() def vector_frame(self, *args, **kwargs): @@ -3141,7 +3157,7 @@ def vector_frame(self, *args, **kwargs): # dictionary _frame_changes of self and its supersets: for frame_pair, chge in resu._fmodule._basis_changes.items(): if resu in frame_pair: - for sdom in self._supersets: + for sdom in self.open_supersets(): sdom._frame_changes[frame_pair] = chge return resu @@ -3168,7 +3184,7 @@ def _set_covering_frame(self, frame): self._covering_frames.append(frame) self._parallelizable_parts = set([self]) # if self contained smaller parallelizable parts, they are forgotten - for sd in self._supersets: + for sd in self.open_supersets(): if not sd.is_manifestly_parallelizable(): sd._parallelizable_parts.add(self) @@ -4108,3 +4124,118 @@ class :class:`~sage.manifolds.differentiable.diff_map.DiffMap` else: signat = 2 - dim return vmodule.metric(name, signature=signat, latex_name=latex_name) + + def tangent_vector(self, *args, **kwargs): + r""" + Define a tangent vector at a given point of ``self``. + + INPUT: + + - ``point`` -- :class:`~sage.manifolds.point.ManifoldPoint`; + point `p` on ``self`` + - ``comp`` -- components of the vector with respect to the basis + specified by the argument ``basis``, either as an iterable or as a + sequence of `n` components, `n` being the dimension of ``self`` (see + examples below) + - ``basis`` -- (default: ``None``) + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis`; + basis of the tangent space at `p` with respect to which the + components are defined; if ``None``, the default basis of the tangent + space is used + - ``name`` -- (default: ``None``) string; symbol given to the vector + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote + the vector; if ``None``, ``name`` will be used + + OUTPUT: + + - :class:`~sage.manifolds.differentiable.tangent_vector.TangentVector` + representing the tangent vector at point `p` + + + EXAMPLES: + + Vector at a point `p` of the Euclidean plane:: + + sage: E.= EuclideanSpace() + sage: p = E((1, 2), name='p') + sage: v = E.tangent_vector(p, -1, 3, name='v'); v + Vector v at Point p on the Euclidean plane E^2 + sage: v.display() + v = -e_x + 3 e_y + sage: v.parent() + Tangent space at Point p on the Euclidean plane E^2 + sage: v in E.tangent_space(p) + True + + An alias of ``tangent_vector`` is ``vector``:: + + sage: v = E.vector(p, -1, 3, name='v'); v + Vector v at Point p on the Euclidean plane E^2 + + The components can be passed as a tuple or a list:: + + sage: v1 = E.vector(p, (-1, 3)); v1 + Vector at Point p on the Euclidean plane E^2 + sage: v1 == v + True + + or as an object created by the ``vector`` function:: + + sage: v2 = E.vector(p, vector([-1, 3])); v2 + Vector at Point p on the Euclidean plane E^2 + sage: v2 == v + True + + Example of use with the options ``basis`` and ``latex_name``:: + + sage: polar_basis = E.polar_frame().at(p) + sage: polar_basis + Basis (e_r,e_ph) on the Tangent space at Point p on the Euclidean plane E^2 + sage: v = E.vector(p, 2, -1, basis=polar_basis, name='v', + ....: latex_name=r'\vec{v}') + sage: v + Vector v at Point p on the Euclidean plane E^2 + sage: v.display(polar_basis) + v = 2 e_r - e_ph + sage: v.display() + v = 4/5*sqrt(5) e_x + 3/5*sqrt(5) e_y + sage: latex(v) + \vec{v} + + TESTS:: + + sage: E.vector(-1, 3) + Traceback (most recent call last): + ... + TypeError: -1 is not a manifold point + sage: E.vector([-1, 3]) + Traceback (most recent call last): + ... + TypeError: a point and a set of components must be provided + sage: E.vector(p, 4, 2, 1) + Traceback (most recent call last): + ... + ValueError: 2 components must be provided + + """ + basis = kwargs.pop('basis', None) + name = kwargs.pop('name', None) + latex_name = kwargs.pop('latex_name', None) + if len(args) < 2: + raise TypeError("a point and a set of components must be provided") + point = args[0] + tspace = self.tangent_space(point) # checks on point are performed here + comp0 = args[1] + if hasattr(comp0, '__len__') and hasattr(comp0, '__getitem__'): + # comp0 is a list/vector of components + comp = comp0 + else: + # the components are provided as args[1], args[2], ..., args[dim] + dim = self._dim + if len(args) != dim + 1: + raise ValueError(f"{dim} components must be provided") + comp = args[1:dim + 1] + return tspace._element_constructor_(comp=comp, basis=basis, name=name, + latex_name=latex_name) + + vector = tangent_vector diff --git a/src/sage/manifolds/differentiable/mixed_form.py b/src/sage/manifolds/differentiable/mixed_form.py index e9fdcdb9916..87b20e51177 100644 --- a/src/sage/manifolds/differentiable/mixed_form.py +++ b/src/sage/manifolds/differentiable/mixed_form.py @@ -97,7 +97,7 @@ class MixedForm(AlgebraElement): sage: A[:] = [f, omega, eta]; A.display() # display names A = f + omega + eta sage: A.display_expansion() # display in coordinates - A = [x] + [x*y dx] + [x*y^2 dx/\dy] + A = x + x*y dx + x*y^2 dx/\dy sage: A[0] Scalar field f on the 2-dimensional differentiable manifold M sage: A[0] is f @@ -124,26 +124,26 @@ class MixedForm(AlgebraElement): Mixed differential form x/\A on the 2-dimensional differentiable manifold M sage: C.display_expansion() - x/\A = [x^2] + [x^2*y dx] + [x^2*y^2 dx/\dy] + x/\A = x^2 + x^2*y dx + x^2*y^2 dx/\dy sage: D = A+C; D Mixed differential form A+x/\A on the 2-dimensional differentiable manifold M sage: D.display_expansion() - A+x/\A = [x^2 + x] + [(x^2 + x)*y dx] + [(x^2 + x)*y^2 dx/\dy] + A+x/\A = x^2 + x + (x^2 + x)*y dx + (x^2 + x)*y^2 dx/\dy sage: E = A*C; E Mixed differential form A/\(x/\A) on the 2-dimensional differentiable manifold M sage: E.display_expansion() - A/\(x/\A) = [x^3] + [2*x^3*y dx] + [2*x^3*y^2 dx/\dy] + A/\(x/\A) = x^3 + 2*x^3*y dx + 2*x^3*y^2 dx/\dy Coercions are fully implemented:: sage: F = omega*A sage: F.display_expansion() - omega/\A = [0] + [x^2*y dx] + [0] + omega/\A = x^2*y dx sage: G = omega+A sage: G.display_expansion() - omega+A = [x] + [2*x*y dx] + [x*y^2 dx/\dy] + omega+A = x + 2*x*y dx + x*y^2 dx/\dy Moreover, it is possible to compute the exterior derivative of a mixed form:: @@ -151,7 +151,7 @@ class MixedForm(AlgebraElement): sage: dA = A.exterior_derivative(); dA.display() dA = zero + df + domega sage: dA.display_expansion() - dA = [0] + [dx] + [-x dx/\dy] + dA = dx - x dx/\dy Initialize a mixed form on a 2-dimensional non-parallelizable differentiable manifold:: @@ -181,11 +181,10 @@ class MixedForm(AlgebraElement): eta = u*v^2 du/\dv sage: A.add_comp_by_continuation(e_uv, W, c_uv) sage: A.display_expansion(e_uv) - A = [1/2*u + 1/2*v] + [(1/8*u^2 - 1/8*v^2) du + (1/8*u^2 - 1/8*v^2) dv] - + [u*v^2 du/\dv] + A = 1/2*u + 1/2*v + (1/8*u^2 - 1/8*v^2) du + (1/8*u^2 - 1/8*v^2) dv + u*v^2 du/\dv sage: A.add_comp_by_continuation(e_xy, W, c_xy) sage: A.display_expansion(e_xy) - A = [x] + [x*y dx] + [(-2*x^3 + 2*x^2*y + 2*x*y^2 - 2*y^3) dx/\dy] + A = x + x*y dx + (-2*x^3 + 2*x^2*y + 2*x*y^2 - 2*y^3) dx/\dy Since zero and one are special elements, their components cannot be changed:: @@ -267,15 +266,25 @@ def _repr_(self): 'Mixed differential form F on the 3-dimensional differentiable manifold M' + Check whether :trac:`31784` is fixed:: + + sage: E3 = EuclideanSpace(3) + sage: S2 = E3.sphere() + sage: iota = S2.embedding() + sage: Omega = S2.mixed_form_algebra(dest_map=iota) + sage: Omega(1) + Mixed differential form one along the 2-sphere S^2 of radius 1 + smoothly embedded in the Euclidean space E^3 with values on the + Euclidean space E^3 via the map iota + """ desc = "Mixed differential form " if self._name is not None: desc += self._name + " " if self._dest_map is self._domain.identity_map(): - desc += "on the {}".format(self._domain) + desc += f"on the {self._domain}" else: - desc += "along the {} with values on the {} " - desc += desc.format(self._domain, self._ambient_domain) + desc += f"along the {self._domain} with values on the {self._ambient_domain} " if self._dest_map._name is None: dm_name = "unnamed map" else: @@ -366,22 +375,20 @@ def display_expansion(self, frame=None, chart=None, from_chart=None): sage: F.display() # display names of homogeneous components F = zero + omega + eta sage: F.display_expansion(e_uv) - F = [0] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] + [u*v du/\dv] + F = (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv + u*v du/\dv sage: F.display_expansion(e_xy) - F = [0] + [x dx] + [(2*x^2 - 2*y^2) dx/\dy] + F = x dx + (2*x^2 - 2*y^2) dx/\dy """ from sage.misc.latex import latex - from sage.tensor.modules.format_utilities import FormattedExpansion - ### + from sage.tensor.modules.format_utilities import (is_atomic, + FormattedExpansion) # In case, no frame is given: if frame is None: frame = self._domain._def_frame - ### # In case, no chart is given: if chart is None: chart = frame._chart - ### # Check names: if self._name is not None: resu_txt = self._name + " = " @@ -391,18 +398,74 @@ def display_expansion(self, frame=None, chart=None, from_chart=None): resu_latex = self._latex_name + r" = " else: resu_latex = "" - ### - # Scalar field: - resu_txt += "[" + repr(self[0].expr(chart, from_chart)) + "]" - resu_latex += r"\left[" + latex(self[0].expr(chart, from_chart)) + \ - r"\right]_0" - ### - # Differential forms: + # Get terms + terms_txt = [] + terms_latex = [] + # Scalar field term: + if not self[0].is_trivial_zero(): + terms_txt.append(repr(self[0].expr(chart, from_chart))) + terms_latex.append(latex(self[0].expr(chart, from_chart))) + # Differential form terms: for j in self.irange(1): rst = self[j].restrict(frame._domain, dest_map=frame._dest_map) - rst_exp = rst._display_expansion(basis=frame, format_spec=chart) - resu_txt += " + [" + repr(rst_exp) + "]" - resu_latex += r"+ \left[" + latex(rst_exp) + r"\right]_{}".format(j) + basis, format_spec = rst._preparse_display(basis=frame, + format_spec=chart) + cobasis = basis.dual_basis() + comp = rst.comp(basis) + for ind in comp.non_redundant_index_generator(): + ind_arg = ind + (format_spec,) + coef = comp[ind_arg] + # Check whether the coefficient is zero, preferably via + # the fast method is_trivial_zero(): + if hasattr(coef, 'is_trivial_zero'): + zero_coef = coef.is_trivial_zero() + else: + zero_coef = coef == 0 + if not zero_coef: + bases_txt = [] + bases_latex = [] + for k in range(rst._tensor_rank): + bases_txt.append(cobasis[ind[k]]._name) + bases_latex.append(latex(cobasis[ind[k]])) + basis_term_txt = "/\\".join(bases_txt) + basis_term_latex = r"\wedge ".join(bases_latex) + coef_txt = repr(coef) + if coef_txt == "1": + terms_txt.append(basis_term_txt) + terms_latex.append(basis_term_latex) + elif coef_txt == "-1": + terms_txt.append("-" + basis_term_txt) + terms_latex.append("-" + basis_term_latex) + else: + coef_latex = latex(coef) + if is_atomic(coef_txt): + terms_txt.append(coef_txt + " " + basis_term_txt) + else: + terms_txt.append("(" + coef_txt + ") " + + basis_term_txt) + if is_atomic(coef_latex): + terms_latex.append(coef_latex + basis_term_latex) + else: + terms_latex.append(r"\left(" + coef_latex + \ + r"\right)" + basis_term_latex) + if not terms_txt: + resu_txt += "0" + else: + resu_txt += terms_txt[0] + for term in terms_txt[1:]: + if term[0] == "-": + resu_txt += " - " + term[1:] + else: + resu_txt += " + " + term + if not terms_latex: + resu_latex += "0" + else: + resu_latex += terms_latex[0] + for term in terms_latex[1:]: + if term[0] == "-": + resu_latex += term + else: + resu_latex += "+" + term return FormattedExpansion(resu_txt, resu_latex) disp_exp = display_expansion @@ -430,7 +493,6 @@ def display(self): """ from sage.misc.latex import latex from sage.tensor.modules.format_utilities import FormattedExpansion - ### # Mixed form name: if self._name is not None: resu_txt = self._name + " = " @@ -440,7 +502,6 @@ def display(self): resu_latex = self._latex_name + r" = " else: resu_latex = "" - ### # Scalar field: if self[0]._name is None: resu_txt += "(unnamed scalar field) " @@ -450,7 +511,6 @@ def display(self): resu_latex += r"\mbox{(unnamed scalar field)}" else: resu_latex += latex(self[0]) - ### # Differential forms: for j in self.irange(1): if self[j]._name is None: @@ -540,8 +600,6 @@ def __bool__(self): self._is_zero = True return False - __nonzero__ = __bool__ # For Python2 compatibility - def _richcmp_(self, other, op): r""" Comparison method for sage objects. @@ -638,15 +696,13 @@ def _add_(self, other): Mixed differential form A+B on the 2-dimensional differentiable manifold M sage: A.display_expansion(e_uv) - A = [1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] - + [0] + A = 1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv sage: B.display_expansion(e_xy) - B = [x + y] + [(x - y) dx + (-x + y) dy] + [0] + B = x + y + (x - y) dx + (-x + y) dy sage: C.display_expansion(e_xy) - A+B = [2*x + y] + [(2*x - y) dx + (-x + y) dy] + [0] + A+B = 2*x + y + (2*x - y) dx + (-x + y) dy sage: C.display_expansion(e_uv) - A+B = [3/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 5/4*v) dv] - + [0] + A+B = 3/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 5/4*v) dv sage: C == A + B # indirect doctest True sage: Z = A.parent().zero(); Z @@ -714,13 +770,13 @@ def _sub_(self, other): Mixed differential form A-B on the 2-dimensional differentiable manifold M sage: A.display_expansion(e_uv) - A = [1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] + [0] + A = 1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv sage: B.display_expansion(e_xy) - B = [x + y] + [(x - y) dx + (-x + y) dy] + [0] + B = x + y + (x - y) dx + (-x + y) dy sage: C.display_expansion(e_xy) - A-B = [-y] + [y dx + (x - y) dy] + [0] + A-B = -y + y dx + (x - y) dy sage: C.display_expansion(e_uv) - A-B = [-1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u - 3/4*v) dv] + [0] + A-B = -1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u - 3/4*v) dv sage: C == A - B # indirect doctest True sage: Z = A.parent().zero(); Z @@ -814,10 +870,10 @@ def wedge(self, other): mu = z dx/\dz sage: A = M.mixed_form([f, omega, mu, 0], name='A') sage: A.display_expansion() - A = [x] + [x dx] + [z dx/\dz] + [0] + A = x + x dx + z dx/\dz sage: B = M.mixed_form([g, eta, mu, 0], name='B') sage: B.display_expansion() - B = [y] + [y dy] + [z dx/\dz] + [0] + B = y + y dy + z dx/\dz The wedge product of ``A`` and ``B`` yields:: @@ -825,14 +881,12 @@ def wedge(self, other): Mixed differential form A/\B on the 3-dimensional differentiable manifold M sage: C.display_expansion() - A/\B = [x*y] + [x*y dx + x*y dy] + [x*y dx/\dy + (x + y)*z dx/\dz] + - [-y*z dx/\dy/\dz] + A/\B = x*y + x*y dx + x*y dy + x*y dx/\dy + (x + y)*z dx/\dz - y*z dx/\dy/\dz sage: D = B.wedge(A); D # Don't even try, it's not commutative! Mixed differential form B/\A on the 3-dimensional differentiable manifold M sage: D.display_expansion() # I told you so! - B/\A = [x*y] + [x*y dx + x*y dy] + [-x*y dx/\dy + (x + y)*z dx/\dz] - + [-y*z dx/\dy/\dz] + B/\A = x*y + x*y dx + x*y dy - x*y dx/\dy + (x + y)*z dx/\dz - y*z dx/\dy/\dz Alternatively, the multiplication symbol can be used:: @@ -845,9 +899,9 @@ def wedge(self, other): Yet, the multiplication includes coercions:: sage: E = x*A; E.display_expansion() - x/\A = [x^2] + [x^2 dx] + [x*z dx/\dz] + [0] + x/\A = x^2 + x^2 dx + x*z dx/\dz sage: F = A*eta; F.display_expansion() - A/\eta = [0] + [x*y dy] + [x*y dx/\dy] + [-y*z dx/\dy/\dz] + A/\eta = x*y dy + x*y dx/\dy - y*z dx/\dy/\dz """ # Case zero: @@ -898,7 +952,7 @@ def _lmul_(self, other): Mixed differential form y/\(x/\F) on the 2-dimensional differentiable manifold M sage: A.display_expansion() - y/\(x/\F) = [0] + [x^2*y^2 dx] + [0] + y/\(x/\F) = x^2*y^2 dx """ try: @@ -971,7 +1025,7 @@ def exterior_derivative(self): dF = zero + df + dzero + da sage: dF = F.exterior_derivative() sage: dF.display_expansion() - dF = [0] + [2*z dz] + [0] + [(2*x + 1) dx/\dy/\dz] + dF = 2*z dz + (2*x + 1) dx/\dy/\dz Due to long calculation times, the result is cached:: @@ -990,6 +1044,8 @@ def exterior_derivative(self): resu._latex_name = format_unop_latex(r'\mathrm{d}', self._latex_name) return resu + derivative = exterior_derivative + def copy(self, name=None, latex_name=None): r""" Return an exact copy of ``self``. @@ -1032,7 +1088,7 @@ def copy(self, name=None, latex_name=None): sage: A = M.mixed_form([f, omega, 0], name='A'); A.display() A = f + omega + zero sage: A.display_expansion(e_uv) - A = [1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] + [0] + A = 1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv An exact copy is made. The copy is an entirely new instance and has a different name, but has the very same values:: @@ -1040,7 +1096,7 @@ def copy(self, name=None, latex_name=None): sage: B = A.copy(); B.display() (unnamed scalar field) + (unnamed 1-form) + (unnamed 2-form) sage: B.display_expansion(e_uv) - [1/2*u + 1/2*v] + [(1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv] + [0] + 1/2*u + 1/2*v + (1/4*u + 1/4*v) du + (1/4*u + 1/4*v) dv sage: A == B True sage: A is B @@ -1053,9 +1109,9 @@ def copy(self, name=None, latex_name=None): sage: omega[e_xy,0] = y; omega.display() omega = y dx sage: A.display_expansion(e_xy) - A = [x] + [y dx] + [0] + A = x + y dx sage: B.display_expansion(e_xy) - [x] + [x dx] + [0] + x + x dx """ resu = self._new_instance() @@ -1087,7 +1143,7 @@ def __setitem__(self, index, values): sage: A[1:3] = [a, b]; A.display() A = f + a + b sage: A.display_expansion() - A = [x] + [y dx] + [x*y dx/\dy] + A = x + y dx + x*y dx/\dy """ if self is self.parent().one() or self is self.parent().zero(): @@ -1178,7 +1234,7 @@ def set_restriction(self, rst): Mixed differential form A on the Open subset U of the 2-dimensional differentiable manifold M sage: AU.display_expansion(e_xy) - A = [x] + [y dx] + [0] + A = x + y dx A mixed form on ``M`` can be specified by some mixed form on a subset:: @@ -1187,11 +1243,10 @@ def set_restriction(self, rst): manifold M sage: A.set_restriction(AU) sage: A.display_expansion(e_xy) - A = [x] + [y dx] + [0] + A = x + y dx sage: A.add_comp_by_continuation(e_uv, W, c_uv) sage: A.display_expansion(e_uv) - A = [u/(u^2 + v^2)] + [-(u^2*v - v^3)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + - v^6) du - 2*u*v^2/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) dv] + [0] + A = u/(u^2 + v^2) - (u^2*v - v^3)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) du - 2*u*v^2/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) dv sage: A.restrict(U) == AU True @@ -1268,11 +1323,7 @@ def restrict(self, subdomain, dest_map=None): 2-form eta on the Open subset V of the 2-dimensional differentiable manifold M] sage: FV.display_expansion(e_uv) - F = [u^2/(u^4 + 2*u^2*v^2 + v^4)] + [-(u^2*v^2 - v^4)/(u^8 + - 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) du - 2*u*v^3/(u^8 + - 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) dv] + [-u^2*v^2/(u^12 + - 6*u^10*v^2 + 15*u^8*v^4 + 20*u^6*v^6 + 15*u^4*v^8 + 6*u^2*v^10 + - v^12) du/\dv] + F = u^2/(u^4 + 2*u^2*v^2 + v^4) - (u^2*v^2 - v^4)/(u^8 + 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) du - 2*u*v^3/(u^8 + 4*u^6*v^2 + 6*u^4*v^4 + 4*u^2*v^6 + v^8) dv - u^2*v^2/(u^12 + 6*u^10*v^2 + 15*u^8*v^4 + 20*u^6*v^6 + 15*u^4*v^8 + 6*u^2*v^10 + v^12) du/\dv """ resu = type(self)(subdomain.mixed_form_algebra(dest_map=dest_map), @@ -1326,12 +1377,9 @@ def add_comp_by_continuation(self, frame, subdomain, chart=None): sage: F.add_comp_by_continuation(e_uv, W, c_uv) sage: F.add_comp_by_continuation(e_xy, W, c_xy) # Now, F is fully defined sage: F.display_expansion(e_xy) - F = [x] + [x dx] + [-x*y/(x^8 + 4*x^6*y^2 + 6*x^4*y^4 + 4*x^2*y^6 + - y^8) dx/\dy] + F = x + x dx - x*y/(x^8 + 4*x^6*y^2 + 6*x^4*y^4 + 4*x^2*y^6 + y^8) dx/\dy sage: F.display_expansion(e_uv) - F = [u/(u^2 + v^2)] + [-(u^3 - u*v^2)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + - v^6) du - 2*u^2*v/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) dv] + - [u*v du/\dv] + F = u/(u^2 + v^2) - (u^3 - u*v^2)/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) du - 2*u^2*v/(u^6 + 3*u^4*v^2 + 3*u^2*v^4 + v^6) dv + u*v du/\dv """ if chart is None: diff --git a/src/sage/manifolds/differentiable/mixed_form_algebra.py b/src/sage/manifolds/differentiable/mixed_form_algebra.py index 5edb7b22955..ec41c7125ff 100644 --- a/src/sage/manifolds/differentiable/mixed_form_algebra.py +++ b/src/sage/manifolds/differentiable/mixed_form_algebra.py @@ -6,7 +6,11 @@ denoted by `\Omega^*(M,\varphi)`, is given by the direct sum `\bigoplus^n_{j=0} \Omega^j(M,\varphi)` of differential form modules, where `n=\dim(N)`. With the wedge product, `\Omega^*(M,\varphi)` inherits the -structure of a graded algebra. +structure of a graded algebra. See :class:`MixedFormAlgebra` for details. + +This algebra is endowed with a natural chain complex structure induced by the +exterior derivative. The corresponding homology is called *de Rham cohomology*. +See :class:`DeRhamCohomologyRing` for details. AUTHORS: @@ -15,7 +19,7 @@ """ #****************************************************************************** -# Copyright (C) 2019 Michael Jung +# Copyright (C) 2019-2021 Michael Jung # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -26,14 +30,16 @@ from sage.misc.cachefunc import cached_method from sage.structure.parent import Parent from sage.categories.graded_algebras import GradedAlgebras +from sage.categories.chain_complexes import ChainComplexes +from sage.categories.morphism import SetMorphism from sage.structure.unique_representation import UniqueRepresentation from sage.symbolic.ring import SR from sage.manifolds.differentiable.mixed_form import MixedForm class MixedFormAlgebra(Parent, UniqueRepresentation): r""" - An instance of this class represents the graded algebra of mixed form. That - is, if `\varphi: M \to N` is a differentiable map between two + An instance of this class represents the graded algebra of mixed forms. + That is, if `\varphi: M \to N` is a differentiable map between two differentiable manifolds `M` and `N`, the *graded algebra of mixed forms* `\Omega^*(M,\varphi)` *along* `\varphi` is defined via the direct sum `\bigoplus^{n}_{j=0} \Omega^j(M,\varphi)` consisting of differential form @@ -57,6 +63,20 @@ class MixedFormAlgebra(Parent, UniqueRepresentation): \Omega^k(M,\varphi) \wedge \Omega^l(M,\varphi) \subset \Omega^{k+l}(M,\varphi). + Moreover, `\Omega^*(M,\varphi)` inherits the structure of a chain complex, + called *de Rham complex*, with the exterior derivative as boundary map, + that is + + .. MATH:: + + 0 \rightarrow \Omega^0(M,\varphi) \xrightarrow{\mathrm{d}_0} + \Omega^1(M,\varphi) \xrightarrow{\mathrm{d}_1} \dots + \xrightarrow{\mathrm{d}_{n-1}} \Omega^n(M,\varphi) + \xrightarrow{\mathrm{d}_{n}} 0. + + The induced cohomology is called *de Rham cohomology*, see + :meth:`cohomology` or :class:`DeRhamCohomologyRing` respectively. + INPUT: - ``vector_field_module`` -- module `\mathfrak{X}(M,\varphi)` of vector @@ -72,7 +92,8 @@ class MixedFormAlgebra(Parent, UniqueRepresentation): Graded algebra Omega^*(M) of mixed differential forms on the 3-dimensional differentiable manifold M sage: Omega.category() - Category of graded algebras over Symbolic Ring + Join of Category of graded algebras over Symbolic Ring and Category of + chain complexes over Symbolic Ring sage: Omega.base_ring() Symbolic Ring sage: Omega.vector_field_module() @@ -165,8 +186,8 @@ def __init__(self, vector_field_module): base_field = domain.base_field() if domain.base_field_type() in ['real', 'complex']: base_field = SR - Parent.__init__(self, base=base_field, - category=GradedAlgebras(base_field)) + category = GradedAlgebras(base_field) & ChainComplexes(base_field) + Parent.__init__(self, base=base_field, category=category) # Define attributes: self._domain = domain self._ambient_domain = vector_field_module._ambient_domain @@ -407,6 +428,85 @@ def _latex_(self): """ return self._latex_name + def differential(self, degree=None): + r""" + Return the differential of the de Rham complex ``self`` given by the + exterior derivative. + + INPUT: + + - ``degree`` -- (default: ``None``) degree of the differential + operator; if none is provided, the differential operator on + ``self`` is returned. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: d = C.differential(); d + Generic endomorphism of Graded algebra Omega^*(M) of mixed + differential forms on the 2-dimensional differentiable manifold M + sage: d0 = C.differential(0); d0 + Generic morphism: + From: Algebra of differentiable scalar fields on the + 2-dimensional differentiable manifold M + To: Free module Omega^1(M) of 1-forms on the 2-dimensional + differentiable manifold M + sage: f = M.scalar_field(x, name='f'); f.display() + f: M --> R + (x, y) |--> x + sage: d0(f).display() + df = dx + + """ + if degree is None: + domain = codomain = self + else: + domain = self._domain.diff_form_module(degree) + codomain = self._domain.diff_form_module(degree + 1) + return SetMorphism(domain.Hom(codomain), lambda x: x.derivative()) + + def cohomology(self, *args, **kwargs): + r""" + Return the de Rham cohomology of the de Rham complex ``self``. + + The `k`-th de Rham cohomology is given by + + .. MATH:: + + H^k_{\mathrm{dR}}(M, \varphi) = + \left. \mathrm{ker}(\mathrm{d}_k) \middle/ + \mathrm{im}(\mathrm{d}_{k-1}) \right. . + + The corresponding ring is given by + + .. MATH:: + + H^*_{\mathrm{dR}}(M, \varphi) = \bigoplus^n_{k=0} H^k_{\mathrm{dR}}(M, \varphi), + + endowed with the cup product as multiplication induced by the wedge + product. + + .. SEEALSO:: + + See :class:`~sage.manifolds.differentiable.de_rham_cohomology.DeRhamCohomologyRing` + for details. + + EXAMPLES:: + + sage: M = Manifold(3, 'M', latex_name=r'\mathcal{M}') + sage: A = M.mixed_form_algebra() + sage: A.cohomology() + De Rham cohomology ring on the 3-dimensional differentiable + manifold M + + """ + from .de_rham_cohomology import DeRhamCohomologyRing + return DeRhamCohomologyRing(self) + + homology = cohomology + def irange(self, start=None): r""" Single index generator. @@ -442,3 +542,25 @@ def irange(self, start=None): while i < imax: yield i i += 1 + + def lift_from_homology(self, x): + r""" + Lift a cohomology class to the algebra of mixed differential forms. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: X. = M.chart() + sage: C = M.de_rham_complex() + sage: H = C.cohomology() + sage: alpha = M.diff_form(1, [1,1], name='alpha') + sage: alpha.display() + alpha = dx + dy + sage: a = H(alpha); a + [alpha] + sage: C.lift_from_homology(a) + Mixed differential form alpha on the 2-dimensional differentiable + manifold M + + """ + return x.lift() diff --git a/src/sage/manifolds/differentiable/multivector_module.py b/src/sage/manifolds/differentiable/multivector_module.py index c489db19e2b..6ab1972a844 100644 --- a/src/sage/manifolds/differentiable/multivector_module.py +++ b/src/sage/manifolds/differentiable/multivector_module.py @@ -344,17 +344,14 @@ def _an_element_(self): """ resu = self.element_class(self._vmodule, self._degree) - # Non-trivial open covers of the domain: - open_covers = self._domain.open_covers()[1:] # the open cover 0 - # is trivial - if open_covers != []: - oc = open_covers[0] # the first non-trivial open cover is - # selected + for oc in self._domain.open_covers(trivial=False): + # the first non-trivial open cover is selected for dom in oc: vmodule_dom = dom.vector_field_module( dest_map=self._dest_map.restrict(dom)) dmodule_dom = vmodule_dom.exterior_power(self._degree) resu.set_restriction(dmodule_dom._an_element_()) + return resu return resu def _coerce_map_from_(self, other): diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian.py b/src/sage/manifolds/differentiable/pseudo_riemannian.py index 1192bcee2ee..713f0807c89 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian.py @@ -672,7 +672,7 @@ def volume_form(self, contra=0): """ return self.metric().volume_form(contra=contra) - def open_subset(self, name, latex_name=None, coord_def={}): + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): r""" Create an open subset of ``self``. @@ -692,6 +692,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): terms of coordinates; ``coord_def`` must a be dictionary with keys charts in the manifold's atlas and values the symbolic expressions formed by the coordinates to define the subset. + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of OUTPUT: @@ -745,22 +747,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): latex_name=latex_name, metric_latex_name=self._metric_latex_name, start_index=self._sindex) - resu._calculus_method = self._calculus_method - resu._supersets.update(self._supersets) - for sd in self._supersets: - sd._subsets.add(resu) - self._top_subsets.add(resu) - # Charts on the result from the coordinate definition: - for chart, restrictions in coord_def.items(): - if chart not in self._atlas: - raise ValueError("the {} does not belong to ".format(chart) + - "the atlas of {}".format(self)) - chart.restrict(resu, restrictions) - # Transition maps on the result inferred from those of self: - for chart1 in coord_def: - for chart2 in coord_def: - if chart2 != chart1 and (chart1, chart2) in self._coord_changes: - self._coord_changes[(chart1, chart2)].restrict(resu) - #!# update non-coordinate vector frames and change of frames - # + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) return resu diff --git a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py index df1746788ab..82426ad8890 100644 --- a/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py +++ b/src/sage/manifolds/differentiable/pseudo_riemannian_submanifold.py @@ -168,6 +168,8 @@ AUTHORS: - Florentin Jaffredo (2018): initial version +- Eric Gourgoulhon (2018-2019): add documentation +- Matthias Koeppe (2021): open subsets of submanifolds REFERENCES: @@ -177,7 +179,9 @@ """ # ***************************************************************************** -# Copyright (C) 2018 Florentin Jaffredo +# Copyright (C) 2018 Florentin Jaffredo +# Copyright (C) 2018-2019 Eric Gourgoulhon +# Copyright (C) 2021 Matthias Koeppe # # 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 @@ -382,6 +386,8 @@ def _repr_(self): 3-dimensional Lorentzian manifold M """ + if self is not self._manifold: + return "Open subset {} of the {}".format(self._name, self._manifold) if self._ambient is None: return super(PseudoRiemannianManifold, self).__repr__() if self._embedded: @@ -390,6 +396,82 @@ def _repr_(self): return "{}-dimensional {} submanifold {} immersed in the {}".format( self._dim, self._structure.name, self._name, self._ambient) + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): + r""" + Create an open subset of ``self``. + + An open subset is a set that is (i) included in the manifold and (ii) + open with respect to the manifold's topology. It is a differentiable + manifold by itself. Moreover, equipped with the restriction of the + manifold metric to itself, it is a pseudo-Riemannian manifold. + + As ``self`` is a submanifold of its ambient manifold, + the new open subset is also considered a submanifold of that. + Hence the returned object is an instance of + :class:`PseudoRiemannianSubmanifold`. + + INPUT: + + - ``name`` -- name given to the open subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + subset; if none is provided, it is set to ``name`` + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts in the manifold's atlas and values the symbolic expressions + formed by the coordinates to define the subset. + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of + + OUTPUT: + + - instance of :class:`PseudoRiemannianSubmanifold` representing the + created open subset + + EXAMPLES:: + + sage: M = Manifold(3, 'M', structure="Riemannian") + sage: N = Manifold(2, 'N', ambient=M, structure="Riemannian"); N + 2-dimensional Riemannian submanifold N immersed in the + 3-dimensional Riemannian manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional Riemannian submanifold N immersed in the + 3-dimensional Riemannian manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional Riemannian submanifold N immersed in the + 3-dimensional Riemannian manifold M + + sage: phi = N.diff_map(M) + sage: N.set_embedding(phi) + sage: N + 2-dimensional Riemannian submanifold N embedded in the + 3-dimensional Riemannian manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional Riemannian submanifold N embedded in the + 3-dimensional Riemannian manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional Riemannian submanifold N embedded in the + 3-dimensional Riemannian manifold M + + """ + resu = PseudoRiemannianSubmanifold(self._dim, name, + ambient=self._ambient, + metric_name=self._metric_name, + signature=self._metric_signature, + base_manifold=self._manifold, + diff_degree=self._diff_degree, + latex_name=latex_name, + metric_latex_name=self._metric_latex_name, + start_index=self._sindex) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) + return resu + def ambient_metric(self): r""" Return the metric of the ambient manifold. diff --git a/src/sage/manifolds/differentiable/scalarfield_algebra.py b/src/sage/manifolds/differentiable/scalarfield_algebra.py index c0a0a3b59fa..4ea29402c18 100644 --- a/src/sage/manifolds/differentiable/scalarfield_algebra.py +++ b/src/sage/manifolds/differentiable/scalarfield_algebra.py @@ -93,11 +93,11 @@ class DiffScalarFieldAlgebra(ScalarFieldAlgebra): algebras over `\RR` (represented here by Sage's Symbolic Ring):: sage: CM.category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces sage: CM.base_ring() Symbolic Ring sage: CW.category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces sage: CW.base_ring() Symbolic Ring diff --git a/src/sage/manifolds/differentiable/tangent_vector.py b/src/sage/manifolds/differentiable/tangent_vector.py index a58c832eb4b..75febeca837 100644 --- a/src/sage/manifolds/differentiable/tangent_vector.py +++ b/src/sage/manifolds/differentiable/tangent_vector.py @@ -62,6 +62,26 @@ class TangentVector(FiniteRankFreeModuleElement): sage: v in Tp True + Tangent vectors can also be constructed via the manifold method + :meth:`~sage.manifolds.differentiable.manifold.DifferentiableManifold.tangent_vector`:: + + sage: v = M.tangent_vector(p, (-2, 1), name='v'); v + Tangent vector v at Point p on the 2-dimensional differentiable + manifold M + sage: v.display() + v = -2 d/dx + d/dy + + or via the method + :meth:`~sage.manifolds.differentiable.tensorfield_paral.TensorFieldParal.at` + of vector fields:: + + sage: vf = M.vector_field(x - 4*y/3, (x-y)^2, name='v') + sage: v = vf.at(p); v + Tangent vector v at Point p on the 2-dimensional differentiable + manifold M + sage: v.display() + v = -2 d/dx + d/dy + By definition, a tangent vector at `p\in M` is a *derivation at* `p` on the space `C^\infty(M)` of smooth scalar fields on `M`. Indeed let us consider a generic scalar field `f`:: diff --git a/src/sage/manifolds/differentiable/tensorfield.py b/src/sage/manifolds/differentiable/tensorfield.py index 52dff82b202..b032a4bd0c6 100644 --- a/src/sage/manifolds/differentiable/tensorfield.py +++ b/src/sage/manifolds/differentiable/tensorfield.py @@ -544,8 +544,6 @@ def __bool__(self): self._is_zero = True return False - __nonzero__ = __bool__ # For Python2 compatibility - ##### End of required methods for ModuleElement (beside arithmetic) ##### def _repr_(self): @@ -1021,9 +1019,8 @@ def set_restriction(self, rst): if self._domain is rst._domain: self.copy_from(rst) else: - self._restrictions[rst._domain] = rst.copy() - self._restrictions[rst._domain].set_name(name=self._name, - latex_name=self._latex_name) + self._restrictions[rst._domain] = rst.copy(name=self._name, + latex_name=self._latex_name) self._is_zero = False # a priori def restrict(self, subdomain, dest_map=None): @@ -2056,12 +2053,11 @@ def __setitem__(self, args, value): def copy_from(self, other): r""" - Make ``self`` to a copy from ``other``. + Make ``self`` a copy of ``other``. INPUT: - - ``other`` -- other tensor field in the very same module from which - ``self`` should be a copy of + - ``other`` -- other tensor field, in the same module as ``self`` .. NOTE:: @@ -2092,7 +2088,7 @@ def copy_from(self, other): sage: s == t True - If the original tensor field is modified, the copy is not:: + While the original tensor field is modified, the copy is not:: sage: t[e_xy,0,0] = -1 sage: t.display(e_xy) @@ -2107,14 +2103,13 @@ def copy_from(self, other): raise ValueError("the components of an immutable element " "cannot be changed") if other not in self.parent(): - raise TypeError("the original must be an element " - + "of {}".format(self.parent())) + raise TypeError("the original must be an element of " + f"{self.parent()}") self._del_derived() self._del_restrictions() # delete restrictions - name, latex_name = self._name, self._latex_name # keep names for dom, rst in other._restrictions.items(): - self._restrictions[dom] = rst.copy() - self.set_name(name=name, latex_name=latex_name) + self._restrictions[dom] = rst.copy(name=self._name, + latex_name=self._latex_name) self._is_zero = other._is_zero def copy(self, name=None, latex_name=None): @@ -2167,9 +2162,17 @@ def copy(self, name=None, latex_name=None): """ resu = self._new_instance() + # set resu name + if name is not None: + resu._name = name + if latex_name is None: + resu._latex_name = name + if latex_name is not None: + resu._latex_name = latex_name + # set restrictions for dom, rst in self._restrictions.items(): - resu._restrictions[dom] = rst.copy() - resu.set_name(name=name, latex_name=latex_name) + resu._restrictions[dom] = rst.copy(name=name, + latex_name=latex_name) resu._is_zero = self._is_zero return resu @@ -2276,9 +2279,7 @@ def __eq__(self, other): if other._tensor_type != self._tensor_type: return False # Non-trivial open covers of the domain: - open_covers = self._domain.open_covers()[1:] # the open cover 0 - # is trivial - for oc in open_covers: + for oc in self._domain.open_covers(trivial=False): resu = True for dom in oc: try: diff --git a/src/sage/manifolds/differentiable/tensorfield_module.py b/src/sage/manifolds/differentiable/tensorfield_module.py index ca22433e208..92c76de177e 100644 --- a/src/sage/manifolds/differentiable/tensorfield_module.py +++ b/src/sage/manifolds/differentiable/tensorfield_module.py @@ -403,14 +403,13 @@ def _an_element_(self): """ resu = self.element_class(self._vmodule, self._tensor_type) - # Non-trivial open covers of the domain: - open_covers = self._domain.open_covers()[1:] # the open cover 0 is trivial - if open_covers != []: - oc = open_covers[0] # the first non-trivial open cover is selected + for oc in self._domain.open_covers(trivial=False): + # the first non-trivial open cover is selected for dom in oc: vmodule_dom = dom.vector_field_module(dest_map=self._dest_map.restrict(dom)) tmodule_dom = vmodule_dom.tensor_module(*(self._tensor_type)) resu.set_restriction(tmodule_dom._an_element_()) + return resu return resu def _coerce_map_from_(self, other): diff --git a/src/sage/manifolds/differentiable/vector_bundle.py b/src/sage/manifolds/differentiable/vector_bundle.py index 21b62ccaa87..ad50447ec89 100644 --- a/src/sage/manifolds/differentiable/vector_bundle.py +++ b/src/sage/manifolds/differentiable/vector_bundle.py @@ -240,7 +240,7 @@ def characteristic_class(self, func, **kwargs): sage: p.function() x + 1 sage: p_form = p.get_form(nab); p_form.display_expansion() - p(TM, nabla_g) = [1] + [0] + [0] + [0] + [0] + p(TM, nabla_g) = 1 .. SEEALSO:: diff --git a/src/sage/manifolds/differentiable/vectorfield.py b/src/sage/manifolds/differentiable/vectorfield.py index a69dae09087..b9ac1ade6de 100644 --- a/src/sage/manifolds/differentiable/vectorfield.py +++ b/src/sage/manifolds/differentiable/vectorfield.py @@ -346,33 +346,20 @@ def __call__(self, scalar): return scalar(self) if scalar._tensor_type != (0,0): raise TypeError("the argument must be a scalar field") - #!# Could it be simply - # return scalar.differential()(self) - # ? - dom_resu = self._domain.intersection(scalar._domain) - self_r = self.restrict(dom_resu) - scalar_r = scalar.restrict(dom_resu) - if scalar_r._is_zero: - return dom_resu._zero_scalar_field - if isinstance(self_r, VectorFieldParal): - return self_r(scalar_r) - # Creation of the result: - if self._name is not None and scalar._name is not None: - resu_name = "{}({})".format(self._name, scalar._name) - else: - resu_name = None - if self._latex_name is not None and scalar._latex_name is not None: - resu_latex = r"{}\left({}\right)".format(self._latex_name , - scalar._latex_name) - else: - resu_latex = None - resu = dom_resu.scalar_field(name=resu_name, latex_name=resu_latex) - for dom, rst in self_r._restrictions.items(): - resu_rst = rst(scalar_r.restrict(dom)) - for chart, funct in resu_rst._express.items(): - resu._express[chart] = funct + resu = scalar.differential()(self) + if not resu.is_immutable(): + if self._name is not None and scalar._name is not None: + name = f"{self._name}({scalar._name})" + else: + name = None + if self._latex_name is not None and scalar._latex_name is not None: + latex_name = fr"{self._latex_name}\left({scalar._latex_name}\right)" + else: + latex_name = None + resu.set_name(name=name, latex_name=latex_name) return resu + @options(max_range=8, scale=1, color='blue') def plot(self, chart=None, ambient_coords=None, mapping=None, chart_domain=None, fixed_coords=None, ranges=None, @@ -1746,57 +1733,7 @@ def __call__(self, scalar): (x, y) |--> 2*x^2*y - y^3 """ - from sage.manifolds.differentiable.vectorframe import CoordFrame - if scalar._tensor_type == (0,1): - # This is actually the action of the vector field on a 1-form, - # as a tensor field of type (1,0): - return scalar(self) - if scalar._tensor_type != (0,0): - raise TypeError("the argument must be a scalar field") - #!# Could it be simply - # return scalar.differential()(self) - # ? - dom_resu = self._domain.intersection(scalar._domain) - self_r = self.restrict(dom_resu) - scalar_r = scalar.restrict(dom_resu) - if scalar_r._is_zero: - return dom_resu._zero_scalar_field - # Creation of the result: - if self._name is not None and scalar._name is not None: - resu_name = self._name + "(" + scalar._name + ")" - else: - resu_name = None - if self._latex_name is not None and scalar._latex_name is not None: - resu_latex = (self._latex_name + r"\left(" + scalar._latex_name - + r"\right)") - else: - resu_latex = None - resu = dom_resu.scalar_field(name=resu_name, latex_name=resu_latex) - # Search for common charts for the computation: - common_charts = set() - for chart in scalar_r._express: - try: - self_r.comp(chart._frame) - common_charts.add(chart) - except ValueError: - pass - for frame in self_r._components: - if isinstance(frame, CoordFrame): - chart = frame._chart - try: - scalar_r.coord_function(chart) - common_charts.add(chart) - except ValueError: - pass - if not common_charts: - raise ValueError("no common chart found") - # The computation: - manif = scalar._manifold - for chart in common_charts: - v = self_r.comp(chart._frame) - f = scalar_r.coord_function(chart) - res = 0 - for i in manif.irange(): - res += v[i, chart] * f.diff(i) - resu._express[chart] = res - return resu + # This method enforces VectorField.__call__ + # instead of FiniteRankFreeModuleElement.__call__, which would have + # been inheritated otherwise + return VectorField.__call__(self, scalar) diff --git a/src/sage/manifolds/differentiable/vectorfield_module.py b/src/sage/manifolds/differentiable/vectorfield_module.py index 3fe82857b8e..16c8e6c091e 100644 --- a/src/sage/manifolds/differentiable/vectorfield_module.py +++ b/src/sage/manifolds/differentiable/vectorfield_module.py @@ -306,15 +306,13 @@ def _an_element_(self): """ resu = self.element_class(self) - # Non-trivial open covers of the domain: - open_covers = self._domain.open_covers()[1:] # the open cover 0 - # is trivial - if open_covers != []: - oc = open_covers[0] # the first non-trivial open cover is selected + for oc in self._domain.open_covers(trivial=False): + # the first non-trivial open cover is selected for dom in oc: vmodule_dom = dom.vector_field_module( dest_map=self._dest_map.restrict(dom)) resu.set_restriction(vmodule_dom._an_element_()) + return resu return resu def _coerce_map_from_(self, other): @@ -1436,7 +1434,7 @@ def __init__(self, domain, dest_map=None): # basis is added to the restrictions of bases on a larger # domain - for dom in domain._supersets: + for dom in domain.open_supersets(): if dom is not domain: for supbase in dom._frames: if (supbase.domain() is dom and diff --git a/src/sage/manifolds/differentiable/vectorframe.py b/src/sage/manifolds/differentiable/vectorframe.py index 355ee322021..a726c96f397 100644 --- a/src/sage/manifolds/differentiable/vectorframe.py +++ b/src/sage/manifolds/differentiable/vectorframe.py @@ -321,7 +321,7 @@ def __init__(self, frame, symbol, latex_symbol=None, indices=None, latex_indices=latex_indices) # The coframe is added to the domain's set of coframes, as well as to # all the superdomains' sets of coframes - for sd in self._domain._supersets: + for sd in self._domain.open_supersets(): sd._coframes.append(self) def _repr_(self): @@ -728,7 +728,7 @@ def __init__(self, vector_field_module, symbol, latex_symbol=None, # the superdomains' sets of frames; moreover the first defined frame # is considered as the default one dest_map = self._dest_map - for sd in self._domain._supersets: + for sd in self._domain.open_supersets(): sd._frames.append(self) sd._top_frames.append(self) if sd._def_frame is None: @@ -1069,7 +1069,7 @@ def new_frame(self, change_of_frame, symbol, latex_symbol=None, latex_indices=latex_indices, symbol_dual=symbol_dual, latex_symbol_dual=latex_symbol_dual) - for sdom in self._domain._supersets: + for sdom in self._domain.open_supersets(): sdom._frame_changes[(self, the_new_frame)] = \ self._fmodule._basis_changes[(self, the_new_frame)] sdom._frame_changes[(the_new_frame, self)] = \ @@ -1163,7 +1163,7 @@ def restrict(self, subdomain): res._from_frame = self._from_frame - for dom in subdomain._supersets: + for dom in subdomain.open_supersets(): if dom is not subdomain: dom._top_frames.remove(res) # since it was added by # VectorFrame constructor diff --git a/src/sage/manifolds/family.py b/src/sage/manifolds/family.py new file mode 100644 index 00000000000..ca3f1148128 --- /dev/null +++ b/src/sage/manifolds/family.py @@ -0,0 +1,265 @@ +r""" +Families of Manifold Objects + +The class :class:`ManifoldObjectFiniteFamily` is a subclass of :class:`FiniteFamily` +that provides an associative container of manifold objects, indexed by their +``_name`` attributes. + +:class:`ManifoldObjectFiniteFamily` instances are totally ordered according +to their lexicographically ordered element names. + +The subclass :class:`ManifoldSubsetFiniteFamily` customizes the print +representation further. + +AUTHORS: + +- Matthias Koeppe (2021): initial version + +""" +#***************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from functools import total_ordering +from sage.sets.family import FiniteFamily + +@total_ordering +class ManifoldObjectFiniteFamily(FiniteFamily): + + r""" + Finite family of manifold objects, indexed by their names. + + The class :class:`ManifoldObjectFiniteFamily` inherits from + :class:`FiniteFamily`. Therefore it is an associative container. + + It provides specialized ``__repr__`` and ``_latex_`` methods. + + :class:`ManifoldObjectFiniteFamily` instances are totally ordered + according to their lexicographically ordered element names. + + EXAMPLES:: + + sage: from sage.manifolds.family import ManifoldObjectFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: C = B.subset('C') + sage: F = ManifoldObjectFiniteFamily([A, B, C]); F + Set {A, B, C} of objects of the 2-dimensional topological manifold M + sage: latex(F) + \{A, B, C\} + sage: F['B'] + Subset B of the 2-dimensional topological manifold M + + All objects must have the same base manifold:: + + sage: N = Manifold(2, 'N', structure='topological') + sage: ManifoldObjectFiniteFamily([M, N]) + Traceback (most recent call last): + ... + TypeError: all objects must have the same manifold + + """ + def __init__(self, objects=(), keys=None): + r""" + Initialize a new instance of :class:`ManifoldObjectFiniteFamily`. + + TESTS: + + sage: from sage.manifolds.family import ManifoldObjectFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: C = B.subset('C') + sage: F = ManifoldObjectFiniteFamily([A, B, C]); F + Set {A, B, C} of objects of the 2-dimensional topological manifold M + sage: TestSuite(F).run(skip='_test_elements') + + Like ``frozenset``, it can be created from any iterable:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: I = M.subset('I') + sage: gen = (subset for subset in (M, I, M, I, M, I)); gen + + sage: ManifoldSubsetFiniteFamily(gen) + Set {I, M} of subsets of the 2-dimensional topological manifold M + + """ + if isinstance(objects, dict): + dictionary = objects + else: + dictionary = {object._name: object for object in objects} + if keys is None: + keys = sorted(dictionary.keys()) + FiniteFamily.__init__(self, dictionary, keys) + names_and_latex_names = sorted((object._name, object._latex_name) + for object in self) + self._name = '{' + ', '.join(keys) + '}' + latex_names = (latex_name for name, latex_name in names_and_latex_names) + self._latex_name = r'\{' + ', '.join(latex_names) + r'\}' + try: + object_iter = iter(self) + self._manifold = next(object_iter)._manifold + except StopIteration: + self._manifold = None + else: + if not all(object._manifold == self._manifold for object in object_iter): + raise TypeError(f'all {self._repr_object_type()} must have the same manifold') + + def _repr_object_type(self): + r""" + String that describes the type of the elements (plural). + + TESTS:: + + sage: from sage.manifolds.family import ManifoldObjectFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: ManifoldObjectFiniteFamily([A, B]).__repr__() # indirect doctest + 'Set {A, B} of objects of the 2-dimensional topological manifold M' + + """ + return "objects" + + def __lt__(self, other): + r""" + Implement the total order on instances of :class:`ManifoldObjectFiniteFamily`. + + TESTS:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: sorted([ManifoldSubsetFiniteFamily([A, B]), ManifoldSubsetFiniteFamily([]), + ....: ManifoldSubsetFiniteFamily([B]), ManifoldSubsetFiniteFamily([A])]) + [{}, + Set {A} of subsets of the 2-dimensional topological manifold M, + Set {A, B} of subsets of the 2-dimensional topological manifold M, + Set {B} of subsets of the 2-dimensional topological manifold M] + """ + if not isinstance(other, ManifoldSubsetFiniteFamily): + return NotImplemented + return self.keys() < other.keys() + + def __repr__(self): + r""" + String representation of the object. + + TESTS:: + + sage: from sage.manifolds.family import ManifoldObjectFiniteFamily + sage: ManifoldObjectFiniteFamily().__repr__() + '{}' + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: ManifoldObjectFiniteFamily([A, B]).__repr__() + 'Set {A, B} of objects of the 2-dimensional topological manifold M' + + """ + if self: + return "Set {} of {} of the {}".format(self._name, self._repr_object_type(), self._manifold) + else: + return "{}" + + def _latex_(self): + r""" + LaTeX representation of ``self``. + + TESTS:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: ManifoldSubsetFiniteFamily([B, A])._latex_() + '\\{A, B\\}' + """ + return self._latex_name + +class ManifoldSubsetFiniteFamily(ManifoldObjectFiniteFamily): + + r""" + Finite family of subsets of a topological manifold, indexed by their names. + + The class :class:`ManifoldSubsetFiniteFamily` inherits from + :class:`ManifoldObjectFiniteFamily`. It provides an associative + container with specialized ``__repr__`` and ``_latex_`` methods. + + :class:`ManifoldSubsetFiniteFamily` instances are totally ordered according + to their lexicographically ordered element (subset) names. + + EXAMPLES:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: C = B.subset('C') + sage: ManifoldSubsetFiniteFamily([A, B, C]) + Set {A, B, C} of subsets of the 2-dimensional topological manifold M + sage: latex(_) + \{A, B, C\} + + All subsets must have the same base manifold:: + + sage: N = Manifold(2, 'N', structure='topological') + sage: ManifoldSubsetFiniteFamily([M, N]) + Traceback (most recent call last): + ... + TypeError: all open subsets must have the same manifold + + """ + + @classmethod + def from_subsets_or_families(cls, *subsets_or_families): + r""" + Construct a ManifoldSubsetFiniteFamily from given subsets or iterables of subsets. + + EXAMPLES:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: Bs = (M.subset(f'B{i}') for i in range(5)) + sage: Cs = ManifoldSubsetFiniteFamily([M.subset('C0'), M.subset('C1')]) + sage: ManifoldSubsetFiniteFamily.from_subsets_or_families(A, Bs, Cs) + Set {A, B0, B1, B2, B3, B4, C0, C1} of subsets of the 2-dimensional topological manifold M + """ + def generate_subsets(): + from sage.manifolds.subset import ManifoldSubset + for arg in subsets_or_families: + if isinstance(arg, ManifoldSubset): + yield arg + else: + # arg must be an iterable of ManifoldSubset instances + yield from arg + return cls(generate_subsets()) + + def _repr_object_type(self): + r""" + String that describes the type of the elements (plural). + + TESTS:: + + sage: from sage.manifolds.family import ManifoldSubsetFiniteFamily + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B = M.subset('B') + sage: ManifoldSubsetFiniteFamily([A, B]).__repr__() # indirect doctest + 'Set {A, B} of subsets of the 2-dimensional topological manifold M' + + """ + if all(subset.is_open() for subset in self): + return "open subsets" + else: + return "subsets" diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py index 41fb5a18234..96bbe13d6b6 100644 --- a/src/sage/manifolds/manifold.py +++ b/src/sage/manifolds/manifold.py @@ -117,11 +117,8 @@ At this stage, we have four open subsets on `S^2`:: - sage: M.list_of_subsets() - [2-dimensional topological manifold S^2, - Open subset U of the 2-dimensional topological manifold S^2, - Open subset V of the 2-dimensional topological manifold S^2, - Open subset W of the 2-dimensional topological manifold S^2] + sage: M.subset_family() + Set {S^2, U, V, W} of open subsets of the 2-dimensional topological manifold S^2 `W` is the open subset that is the complement of the two poles:: @@ -188,7 +185,7 @@ sage: f.parent() Algebra of scalar fields on the 2-dimensional topological manifold S^2 sage: f.parent().category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces .. RUBRIC:: Example 2: the Riemann sphere as a topological manifold of @@ -269,11 +266,8 @@ The following subsets and charts have been defined:: - sage: M.list_of_subsets() - [Open subset A of the Complex 1-dimensional topological manifold C*, - Complex 1-dimensional topological manifold C*, - Open subset U of the Complex 1-dimensional topological manifold C*, - Open subset V of the Complex 1-dimensional topological manifold C*] + sage: M.subset_family() + Set {A, C*, U, V} of open subsets of the Complex 1-dimensional topological manifold C* sage: M.atlas() [Chart (U, (z,)), Chart (V, (w,)), Chart (A, (z,)), Chart (A, (w,))] @@ -295,7 +289,7 @@ Algebra of scalar fields on the Complex 1-dimensional topological manifold C* sage: f.parent().category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces AUTHORS: @@ -316,8 +310,13 @@ """ #***************************************************************************** -# Copyright (C) 2015 Eric Gourgoulhon -# Copyright (C) 2015 Travis Scrimshaw +# Copyright (C) 2015-2020 Eric Gourgoulhon +# Copyright (C) 2015 Travis Scrimshaw +# Copyright (C) 2016 Andrew Mathas +# Copyright (C) 2018 Florentin Jaffredo +# Copyright (C) 2019 Hans Fotsing Tetsing +# Copyright (C) 2019-2020 Michael Jung +# Copyright (C) 2021 Matthias Koeppe # # 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 @@ -782,7 +781,7 @@ def __contains__(self, point): return True return False - def open_subset(self, name, latex_name=None, coord_def={}): + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): r""" Create an open subset of the manifold. @@ -800,6 +799,8 @@ def open_subset(self, name, latex_name=None, coord_def={}): terms of coordinates; ``coord_def`` must a be dictionary with keys charts on the manifold and values the symbolic expressions formed by the coordinates to define the subset + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of OUTPUT: @@ -833,7 +834,7 @@ def open_subset(self, name, latex_name=None, coord_def={}): We have then:: - sage: A.subsets() # random (set output) + sage: frozenset(A.subsets()) # random (set output) {Open subset B of the 2-dimensional topological manifold M, Open subset A of the 2-dimensional topological manifold M} sage: B.is_subset(A) @@ -872,10 +873,41 @@ def open_subset(self, name, latex_name=None, coord_def={}): base_manifold=self._manifold, latex_name=latex_name, start_index=self._sindex) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) + return resu + + def _init_open_subset(self, resu, coord_def): + r""" + Initialize ``resu`` as an open subset of ``self``. + + INPUT: + + - ``resu`` -- an instance of ``:class:`TopologicalManifold` or + a subclass. + + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: from sage.manifolds.manifold import TopologicalManifold + sage: U = TopologicalManifold(2, 'U', field=M._field, structure=M._structure, base_manifold=M) + sage: M._init_open_subset(U, coord_def={c_cart: x^2+y^2<1}) + sage: U + Open subset U of the 2-dimensional topological manifold R^2 + """ resu._calculus_method = self._calculus_method - resu._supersets.update(self._supersets) - for sd in self._supersets: - sd._subsets.add(resu) + if self.is_empty(): + self.declare_equal(resu) + else: + self.declare_superset(resu) self._top_subsets.add(resu) # Charts on the result from the coordinate definition: for chart, restrictions in coord_def.items(): @@ -888,7 +920,6 @@ def open_subset(self, name, latex_name=None, coord_def={}): for chart2 in coord_def: if chart2 != chart1 and (chart1, chart2) in self._coord_changes: self._coord_changes[(chart1, chart2)].restrict(resu) - return resu def get_chart(self, coordinates, domain=None): r""" @@ -1833,7 +1864,7 @@ def scalar_field_algebra(self): sage: CU = U.scalar_field_algebra() ; CU Algebra of scalar fields on the Open subset U of the 3-dimensional topological manifold M sage: CU.category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces sage: CU.zero() Scalar field zero on the Open subset U of the 3-dimensional topological manifold M diff --git a/src/sage/manifolds/point.py b/src/sage/manifolds/point.py index e01fbbb99fa..152703075c5 100644 --- a/src/sage/manifolds/point.py +++ b/src/sage/manifolds/point.py @@ -182,7 +182,10 @@ def __init__(self, parent, coords=None, chart=None, name=None, sage: TestSuite(q).run() """ + if parent.is_empty(): + raise TypeError(f'cannot define a point on the {parent} because it has been declared empty') Element.__init__(self, parent) + parent._has_defined_points = True self._manifold = parent.manifold() # a useful shortcut self._coordinates = {} # dictionary of the point coordinates in various # charts, with the charts as keys diff --git a/src/sage/manifolds/scalarfield.py b/src/sage/manifolds/scalarfield.py index 98e931e212e..7fe11508580 100644 --- a/src/sage/manifolds/scalarfield.py +++ b/src/sage/manifolds/scalarfield.py @@ -1192,8 +1192,6 @@ def __bool__(self): self._is_zero = True return False - __nonzero__ = __bool__ # For Python2 compatibility - def is_trivial_zero(self): r""" Check if ``self`` is trivially equal to zero without any @@ -1580,6 +1578,21 @@ def domain(self): """ return self._domain + def codomain(self): + r""" + Return the codomain of the scalar field. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: f = M.scalar_field(x+2*y) + sage: f.codomain() + Real Field with 53 bits of precision + + """ + return self._domain.base_field() + def copy(self, name=None, latex_name=None): r""" Return an exact copy of the scalar field. @@ -1614,6 +1627,62 @@ def copy(self, name=None, latex_name=None): result._is_zero = self._is_zero return result + def copy_from(self, other): + r""" + Make ``self`` a copy of ``other``. + + INPUT: + + - ``other`` -- other scalar field, in the same module as ``self`` + + .. NOTE:: + + While the derived quantities are not copied, the name is kept. + + .. WARNING:: + + All previous defined expressions and restrictions will be deleted! + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: c_xy. = M.chart() + sage: f = M.scalar_field(x*y^2, name='f') + sage: f.display() + f: M --> R + (x, y) |--> x*y^2 + sage: g = M.scalar_field(name='g') + sage: g.copy_from(f) + sage: g.display() + g: M --> R + (x, y) |--> x*y^2 + sage: f == g + True + + While the original scalar field is modified, the copy is not:: + + sage: f.set_expr(x-y) + sage: f.display() + f: M --> R + (x, y) |--> x - y + sage: g.display() + g: M --> R + (x, y) |--> x*y^2 + sage: f == g + False + + """ + if self.is_immutable(): + raise ValueError("the expressions of an immutable element " + "cannot be changed") + if other not in self.parent(): + raise TypeError("the original must be an element of " + f"{self.parent()}") + self._del_derived() + for chart, funct in other._express.items(): + self._express[chart] = funct.copy() + self._is_zero = other._is_zero + def coord_function(self, chart=None, from_chart=None): r""" Return the function of the coordinates representing the scalar field @@ -2016,9 +2085,8 @@ def set_restriction(self, rst): if not rst._domain.is_subset(self._domain): raise ValueError("the domain of the declared restriction is not " + "a subset of the field's domain") - self._restrictions[rst._domain] = rst.copy() - self._restrictions[rst._domain].set_name(name=self._name, - latex_name=self._latex_name) + self._restrictions[rst._domain] = rst.copy(name=self._name, + latex_name=self._latex_name) for chart, expr in rst._express.items(): intersection = chart._domain.intersection(rst._domain) self._express[chart.restrict(intersection)] = expr diff --git a/src/sage/manifolds/scalarfield_algebra.py b/src/sage/manifolds/scalarfield_algebra.py index 3ee4b383f9d..3e05fb0371f 100644 --- a/src/sage/manifolds/scalarfield_algebra.py +++ b/src/sage/manifolds/scalarfield_algebra.py @@ -35,6 +35,7 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.misc.cachefunc import cached_method from sage.categories.commutative_algebras import CommutativeAlgebras +from sage.categories.topological_spaces import TopologicalSpaces from sage.symbolic.ring import SR from sage.manifolds.scalarfield import ScalarField @@ -86,11 +87,11 @@ class ScalarFieldAlgebra(UniqueRepresentation, Parent): :class:`~sage.symbolic.ring.SymbolicRing`):: sage: CM.category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces sage: CM.base_ring() Symbolic Ring sage: CW.category() - Category of commutative algebras over Symbolic Ring + Join of Category of commutative algebras over Symbolic Ring and Category of homsets of topological spaces sage: CW.base_ring() Symbolic Ring @@ -383,7 +384,7 @@ def __init__(self, domain): if domain.base_field_type() in ['real', 'complex']: base_field = SR Parent.__init__(self, base=base_field, - category=CommutativeAlgebras(base_field)) + category=CommutativeAlgebras(base_field) & TopologicalSpaces().Homsets()) self._domain = domain self._populate_coercion_lists_() diff --git a/src/sage/manifolds/section.py b/src/sage/manifolds/section.py index 29ed3a88040..6215ec6fe5f 100644 --- a/src/sage/manifolds/section.py +++ b/src/sage/manifolds/section.py @@ -298,8 +298,6 @@ def __bool__(self): self._is_zero = True return False - __nonzero__ = __bool__ # For Python2 compatibility - ##### End of required methods for ModuleElement (beside arithmetic) ##### def _repr_(self): @@ -627,9 +625,8 @@ def set_restriction(self, rst): if self.is_immutable(): raise ValueError("the restrictions of an immutable element " "cannot be changed") - self._restrictions[rst._domain] = rst.copy() - self._restrictions[rst._domain].set_name(name=self._name, - latex_name=self._latex_name) + self._restrictions[rst._domain] = rst.copy(name=self._name, + latex_name=self._latex_name) self._is_zero = False # a priori def restrict(self, subdomain): @@ -1646,12 +1643,11 @@ def __setitem__(self, args, value): def copy_from(self, other): r""" - Make ``self`` to a copy from ``other``. + Make ``self`` a copy of ``other``. INPUT: - - ``other`` -- other section in the very same module from which - ``self`` should be a copy of + - ``other`` -- other section, in the same module as ``self`` .. NOTE:: @@ -1702,14 +1698,13 @@ def copy_from(self, other): raise ValueError("the components of an immutable element " "cannot be changed") if other not in self.parent(): - raise TypeError("the original must be an element " - + "of {}".format(self.parent())) + raise TypeError("the original must be an element of " + f"{self.parent()}") self._del_derived() self._del_restrictions() # delete restrictions - name, latex_name = self._name, self._latex_name # keep names for dom, rst in other._restrictions.items(): - self._restrictions[dom] = rst.copy() - self.set_name(name=name, latex_name=latex_name) + self._restrictions[dom] = rst.copy(name=self._name, + latex_name=self._latex_name) self._is_zero = other._is_zero def copy(self, name=None, latex_name=None): @@ -1768,10 +1763,17 @@ def copy(self, name=None, latex_name=None): """ resu = self._new_instance() + # set resu name + if name is not None: + resu._name = name + if latex_name is None: + resu._latex_name = name + if latex_name is not None: + resu._latex_name = latex_name + # set restrictions for dom, rst in self._restrictions.items(): - resu._restrictions[dom] = rst.copy() - # Propagate names to all restrictions - resu.set_name(name=name, latex_name=latex_name) + resu._restrictions[dom] = rst.copy(name=name, + latex_name=latex_name) resu._is_zero = self._is_zero return resu @@ -1882,9 +1884,7 @@ def __eq__(self, other): if other._smodule != self._smodule: return False # Non-trivial open covers of the domain: - open_covers = self._domain.open_covers()[1:] # the open cover 0 - # is trivial - for oc in open_covers: + for oc in self._domain.open_covers(trivial=False): resu = True for dom in oc: try: diff --git a/src/sage/manifolds/section_module.py b/src/sage/manifolds/section_module.py index cb30e5ce406..975113ca2c9 100644 --- a/src/sage/manifolds/section_module.py +++ b/src/sage/manifolds/section_module.py @@ -260,14 +260,12 @@ def _an_element_(self): """ resu = self.element_class(self) - # Non-trivial open covers of the domain: - open_covers = self._domain.open_covers()[1:] # the open cover 0 - # is trivial - if open_covers != []: - oc = open_covers[0] # the first non-trivial open cover is selected + for oc in self._domain.open_covers(trivial=False): + # the first non-trivial open cover is selected for dom in oc: smodule_dom = self._vbundle.section_module(domain=dom) resu.set_restriction(smodule_dom._an_element_()) + return resu return resu def _coerce_map_from_(self, other): diff --git a/src/sage/manifolds/subset.py b/src/sage/manifolds/subset.py index 6ee2826bca1..b68659ca615 100644 --- a/src/sage/manifolds/subset.py +++ b/src/sage/manifolds/subset.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Subsets of Topological Manifolds @@ -5,12 +6,15 @@ topological manifold. Open subsets are implemented by the class :class:`~sage.manifolds.manifold.TopologicalManifold` (since an open subset of a manifold is a manifold by itself), which inherits -from :class:`ManifoldSubset`. +from :class:`ManifoldSubset`. Besides, subsets that are images of +a manifold subset under a continuous map are implemented by the +subclass :class:`~sage.manifolds.continuous_map_image.ImageManifoldSubset`. AUTHORS: - Eric Gourgoulhon, Michal Bejger (2013-2015): initial version - Travis Scrimshaw (2015): review tweaks; removal of facade parents +- Matthias Koeppe (2021): Families and posets of subsets REFERENCES: @@ -26,10 +30,8 @@ Subset A of the 2-dimensional topological manifold M sage: b = M.subset('B'); b Subset B of the 2-dimensional topological manifold M - sage: M.list_of_subsets() - [Subset A of the 2-dimensional topological manifold M, - Subset B of the 2-dimensional topological manifold M, - 2-dimensional topological manifold M] + sage: M.subset_family() + Set {A, B, M} of subsets of the 2-dimensional topological manifold M The intersection of the two subsets:: @@ -41,30 +43,23 @@ sage: d = a.union(b); d Subset A_union_B of the 2-dimensional topological manifold M -Lists of subsets after the above operations:: - - sage: M.list_of_subsets() - [Subset A of the 2-dimensional topological manifold M, - Subset A_inter_B of the 2-dimensional topological manifold M, - Subset A_union_B of the 2-dimensional topological manifold M, - Subset B of the 2-dimensional topological manifold M, - 2-dimensional topological manifold M] - sage: a.list_of_subsets() - [Subset A of the 2-dimensional topological manifold M, - Subset A_inter_B of the 2-dimensional topological manifold M] - sage: c.list_of_subsets() - [Subset A_inter_B of the 2-dimensional topological manifold M] - sage: d.list_of_subsets() - [Subset A of the 2-dimensional topological manifold M, - Subset A_inter_B of the 2-dimensional topological manifold M, - Subset A_union_B of the 2-dimensional topological manifold M, - Subset B of the 2-dimensional topological manifold M] +Families of subsets after the above operations:: + + sage: M.subset_family() + Set {A, A_inter_B, A_union_B, B, M} of subsets of the 2-dimensional topological manifold M + sage: a.subset_family() + Set {A, A_inter_B} of subsets of the 2-dimensional topological manifold M + sage: c.subset_family() + Set {A_inter_B} of subsets of the 2-dimensional topological manifold M + sage: d.subset_family() + Set {A, A_inter_B, A_union_B, B} of subsets of the 2-dimensional topological manifold M """ #***************************************************************************** -# Copyright (C) 2015 Eric Gourgoulhon -# Copyright (C) 2015 Michal Bejger -# Copyright (C) 2015 Travis Scrimshaw +# Copyright (C) 2015-2020 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger +# Copyright (C) 2015-2016 Travis Scrimshaw +# Copyright (C) 2021 Matthias Koeppe # # 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 @@ -73,9 +68,13 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +from collections import defaultdict +import itertools from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.superseded import deprecation from sage.categories.sets_cat import Sets +from sage.manifolds.family import ManifoldObjectFiniteFamily, ManifoldSubsetFiniteFamily from sage.manifolds.point import ManifoldPoint class ManifoldSubset(UniqueRepresentation, Parent): @@ -122,10 +121,8 @@ class :class:`~sage.structure.parent.Parent`. sage: B = M.subset('B', latex_name=r'\mathcal{B}'); B Subset B of the 2-dimensional topological manifold M - sage: M.list_of_subsets() - [Subset A of the 2-dimensional topological manifold M, - Subset B of the 2-dimensional topological manifold M, - 2-dimensional topological manifold M] + sage: M.subset_family() + Set {A, B, M} of subsets of the 2-dimensional topological manifold M The manifold is itself a subset:: @@ -207,6 +204,7 @@ def __init__(self, manifold, name, latex_name=None, category=None): self._open_covers = [] # list of open covers of self self._is_open = False # a priori (may be redefined by subclasses) self._manifold = manifold # the ambient manifold + self._has_defined_points = False def _repr_(self): r""" @@ -525,67 +523,243 @@ def is_open(self): """ return False - def open_covers(self): + def is_closed(self): + """ + Return if ``self`` is a closed set. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: M.is_closed() + True + sage: also_M = M.subset('also_M') + sage: M.declare_subset(also_M) + sage: also_M.is_closed() + True + + sage: A = M.subset('A') + sage: A.is_closed() + False + sage: A.declare_empty() + sage: A.is_closed() + True + + sage: N = M.open_subset('N') + sage: N.is_closed() + False + sage: complement_N = M.subset('complement_N') + sage: M.declare_union(N, complement_N, disjoint=True) + sage: complement_N.is_closed() + True + + """ + if self.manifold().is_subset(self): + return True + if self.is_empty(): + return True + for other_name, intersection in self._intersections.items(): + if intersection.is_empty(): + other = self.manifold().subset_family()[other_name] + if other.is_open(): + try: + union = self._unions[other_name] + except KeyError: + pass + else: + if union.is_open(): + # self is complement of open other in open union + return True + return False + + def open_covers(self, trivial=True, supersets=False): r""" - Return the list of open covers of the current subset. + Generate the open covers of the current subset. If the current subset, `A` say, is a subset of the manifold `M`, an - *open cover* of `A` is list (indexed set) `(U_i)_{i\in I}` of - open subsets of `M` such that + *open cover* of `A` is a :class:`ManifoldSubsetFiniteFamily` `F` + of open subsets `U \in F` of `M` such that .. MATH:: - A \subset \bigcup_{i \in I} U_i. + A \subset \bigcup_{U \in F} U. If `A` is open, we ask that the above inclusion is actually an identity: .. MATH:: - A = \bigcup_{i \in I} U_i. + A = \bigcup_{U \in F} U. + + .. NOTE:: + + To get the open covers as a family, sorted lexicographically by the + names of the subsets forming the open covers, use the method + :meth:`open_cover_family` instead. + + INPUT: + + - ``trivial`` -- (default: ``True``) if ``self`` is open, include the trivial + open cover of ``self`` by itself + - ``supersets`` -- (default: ``False``) if ``True``, include open covers of + all the supersets; it can also be an iterable of supersets to include EXAMPLES:: sage: M = Manifold(2, 'M', structure='topological') sage: M.open_covers() - [[2-dimensional topological manifold M]] + + sage: list(M.open_covers()) + [Set {M} of open subsets of the 2-dimensional topological manifold M] sage: U = M.open_subset('U') - sage: U.open_covers() - [[Open subset U of the 2-dimensional topological manifold M]] + sage: list(U.open_covers()) + [Set {U} of open subsets of the 2-dimensional topological manifold M] sage: A = U.open_subset('A') sage: B = U.open_subset('B') sage: U.declare_union(A,B) - sage: U.open_covers() - [[Open subset U of the 2-dimensional topological manifold M], - [Open subset A of the 2-dimensional topological manifold M, - Open subset B of the 2-dimensional topological manifold M]] + sage: list(U.open_covers()) + [Set {U} of open subsets of the 2-dimensional topological manifold M, + Set {A, B} of open subsets of the 2-dimensional topological manifold M] + sage: list(U.open_covers(trivial=False)) + [Set {A, B} of open subsets of the 2-dimensional topological manifold M] sage: V = M.open_subset('V') sage: M.declare_union(U,V) - sage: M.open_covers() - [[2-dimensional topological manifold M], - [Open subset U of the 2-dimensional topological manifold M, - Open subset V of the 2-dimensional topological manifold M], - [Open subset A of the 2-dimensional topological manifold M, - Open subset B of the 2-dimensional topological manifold M, - Open subset V of the 2-dimensional topological manifold M]] + sage: list(M.open_covers()) + [Set {M} of open subsets of the 2-dimensional topological manifold M, + Set {U, V} of open subsets of the 2-dimensional topological manifold M, + Set {A, B, V} of open subsets of the 2-dimensional topological manifold M] """ - return list(self._open_covers) + if supersets is False: + supersets = [self] + elif supersets is True: + supersets = self._supersets + for superset in supersets: + for oc in superset._open_covers: + if not trivial: + if any(x in supersets for x in oc): + continue + yield ManifoldSubsetFiniteFamily(oc) + + def open_cover_family(self, trivial=True, supersets=False): + r""" + Return the family of open covers of the current subset. - def subsets(self): + If the current subset, `A` say, is a subset of the manifold `M`, an + *open cover* of `A` is a :class:`ManifoldSubsetFiniteFamily` `F` + of open subsets `U \in F` of `M` such that + + .. MATH:: + + A \subset \bigcup_{U \in F} U. + + If `A` is open, we ask that the above inclusion is actually an + identity: + + .. MATH:: + + A = \bigcup_{U \in F} U. + + The family is sorted lexicographically by the names of the subsets + forming the open covers. + + .. NOTE:: + + If you only need to iterate over the open covers in arbitrary + order, you can use the generator method :meth:`open_covers` + instead. + + INPUT: + + - ``trivial`` -- (default: ``True``) if ``self`` is open, include the trivial + open cover of ``self`` by itself + - ``supersets`` -- (default: ``False``) if ``True``, include open covers of + all the supersets; it can also be an iterable of supersets to include + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: M.open_cover_family() + Set {{M}} of objects of the 2-dimensional topological manifold M + sage: U = M.open_subset('U') + sage: U.open_cover_family() + Set {{U}} of objects of the 2-dimensional topological manifold M + sage: A = U.open_subset('A') + sage: B = U.open_subset('B') + sage: U.declare_union(A,B) + sage: U.open_cover_family() + Set {{A, B}, {U}} of objects of the 2-dimensional topological manifold M + sage: U.open_cover_family(trivial=False) + Set {{A, B}} of objects of the 2-dimensional topological manifold M + sage: V = M.open_subset('V') + sage: M.declare_union(U,V) + sage: M.open_cover_family() + Set {{A, B, V}, {M}, {U, V}} of objects of the 2-dimensional topological manifold M + + """ + return ManifoldObjectFiniteFamily(self.open_covers( + trivial=trivial, supersets=supersets)) + + def open_supersets(self): + r""" + Generate the open supersets of ``self``. + + .. NOTE:: + + To get the open supersets as a family, sorted by name, use the method + :meth:`open_superset_family` instead. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = U.subset('V') + sage: W = V.subset('W') + sage: sorted(W.open_supersets(), key=lambda S: S._name) + [2-dimensional topological manifold M, + Open subset U of the 2-dimensional topological manifold M] + + """ + for superset in self._supersets: + if superset.is_open(): + yield superset + + def open_superset_family(self): r""" - Return the set of subsets that have been defined on the - current subset. + Return the family of open supersets of ``self``. + + The family is sorted by the alphabetical names of the subsets. OUTPUT: - - a Python set containing all the subsets that have been defined on - the current subset + - a :class:`ManifoldSubsetFiniteFamily` instance containing all the + open supersets that have been defined on the current subset + + .. NOTE:: + + If you only need to iterate over the open supersets in arbitrary + order, you can use the generator method :meth:`open_supersets` + instead. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = U.subset('V') + sage: W = V.subset('W') + sage: W.open_superset_family() + Set {M, U} of open subsets of the 2-dimensional topological manifold M + + """ + return ManifoldSubsetFiniteFamily(self.open_supersets()) + + def subsets(self): + r""" + Generate the subsets that have been defined on the current subset. .. NOTE:: - To get the subsets as a list, used the method - :meth:`list_of_subsets` instead. + To get the subsets as a family, sorted by name, use the method + :meth:`subset_family` instead. EXAMPLES: @@ -594,25 +768,21 @@ def subsets(self): sage: M = Manifold(2, 'M', structure='topological') sage: U = M.open_subset('U') sage: V = M.subset('V') - sage: M.subsets() # random (set output) + sage: frozenset(M.subsets()) # random (set output) {Subset V of the 2-dimensional topological manifold M, 2-dimensional topological manifold M, Open subset U of the 2-dimensional topological manifold M} - sage: type(M.subsets()) - <... 'frozenset'> sage: U in M.subsets() True - The method :meth:`list_of_subsets` returns a list (sorted - alphabetically by the subset names) instead of a set:: + The method :meth:`subset_family` returns a family (sorted + alphabetically by the subset names):: - sage: M.list_of_subsets() - [2-dimensional topological manifold M, - Open subset U of the 2-dimensional topological manifold M, - Subset V of the 2-dimensional topological manifold M] + sage: M.subset_family() + Set {M, U, V} of subsets of the 2-dimensional topological manifold M """ - return frozenset(self._subsets) + yield from self._subsets def list_of_subsets(self): r""" @@ -628,31 +798,481 @@ def list_of_subsets(self): .. NOTE:: - To get the subsets as a Python set, used the method - :meth:`subsets` instead. + This method is deprecated. + + To get the subsets as a :class:`ManifoldSubsetFiniteFamily` + instance (which sorts its elements alphabetically by name), + use :meth:`subset_family` instead. + + To loop over the subsets in an arbitrary order, use the + generator method :meth:`subsets` instead. EXAMPLES: - Subsets of a 2-dimensional manifold:: + List of subsets of a 2-dimensional manifold (deprecated):: sage: M = Manifold(2, 'M', structure='topological') sage: U = M.open_subset('U') sage: V = M.subset('V') sage: M.list_of_subsets() + doctest:...: DeprecationWarning: the method list_of_subsets of ManifoldSubset + is deprecated; use subset_family or subsets instead... [2-dimensional topological manifold M, Open subset U of the 2-dimensional topological manifold M, Subset V of the 2-dimensional topological manifold M] - The method :meth:`subsets` returns a set instead of a list:: + Using :meth:`subset_family` instead (recommended when order matters):: + + sage: M.subset_family() + Set {M, U, V} of subsets of the 2-dimensional topological manifold M - sage: M.subsets() # random (set output) + The method :meth:`subsets` generates the subsets in an unspecified order. + To create a set:: + + sage: frozenset(M.subsets()) # random (set output) {Subset V of the 2-dimensional topological manifold M, 2-dimensional topological manifold M, Open subset U of the 2-dimensional topological manifold M} """ + deprecation(31727, "the method list_of_subsets of ManifoldSubset is deprecated; use subset_family or subsets instead") return sorted(self._subsets, key=lambda x: x._name) + def subset_family(self): + r""" + Return the family of subsets that have been defined on the current subset. + + The family is sorted by the alphabetical names of the subsets. + + OUTPUT: + + - a :class:`ManifoldSubsetFiniteFamily` instance containing all the + subsets that have been defined on the current subset + + .. NOTE:: + + If you only need to iterate over the subsets in arbitrary order, + you can use the generator method :meth:`subsets` instead. + + EXAMPLES: + + Subsets of a 2-dimensional manifold:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = M.subset('V') + sage: M.subset_family() + Set {M, U, V} of subsets of the 2-dimensional topological manifold M + + """ + return ManifoldSubsetFiniteFamily(self.subsets()) + + def subset_digraph(self, loops=False, quotient=False, open_covers=False, points=False, lower_bound=None): + r""" + Return the digraph whose arcs represent subset relations among the subsets of ``self``. + + INPUT: + + - ``loops`` -- (default: ``False``) whether to include the trivial containment + of each subset in itself as loops of the digraph + - ``quotient`` -- (default: ``False``) whether to contract directed cycles in the graph, + replacing equivalence classes of equal subsets by a single vertex. + In this case, each vertex of the digraph is a set of :class:`ManifoldSubset` + instances. + - ``open_covers`` -- (default: ``False``) whether to include vertices for open covers + - ``points`` -- (default: ``False``) whether to include vertices for declared points; + this can also be an iterable for the points to include + - ``lower_bound`` -- (default: ``None``) only include supersets of this + + OUTPUT: + + A digraph. Each vertex of the digraph is either: + + - a :class:`ManifoldSubsetFiniteFamily` containing one instance of :class:`ManifoldSubset`. + - (if ``open_covers`` is ``True``) a tuple of :class:`ManifoldSubsetFiniteFamily` instances, + representing an open cover. + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + sage: D = M.subset_digraph(); D + Digraph on 4 vertices + sage: D.edges(key=lambda e: (e[0]._name, e[1]._name)) + [(Set {U} of open subsets of the 3-dimensional differentiable manifold M, + Set {M} of open subsets of the 3-dimensional differentiable manifold M, + None), + (Set {V} of open subsets of the 3-dimensional differentiable manifold M, + Set {M} of open subsets of the 3-dimensional differentiable manifold M, + None), + (Set {W} of open subsets of the 3-dimensional differentiable manifold M, + Set {M} of open subsets of the 3-dimensional differentiable manifold M, + None)] + sage: D.plot(layout='acyclic') + Graphics object consisting of 8 graphics primitives + sage: def label(element): + ....: try: + ....: return element._name + ....: except AttributeError: + ....: return '[' + ', '.join(sorted(x._name for x in element)) + ']' + sage: D.relabel(label, inplace=False).plot(layout='acyclic') + Graphics object consisting of 8 graphics primitives + + sage: VW = V.union(W) + sage: D = M.subset_digraph(); D + Digraph on 5 vertices + sage: D.relabel(label, inplace=False).plot(layout='acyclic') + Graphics object consisting of 12 graphics primitives + + If ``open_covers`` is ``True``, the digraph includes a special vertex for + each nontrivial open cover of a subset:: + + sage: D = M.subset_digraph(open_covers=True) + sage: D.relabel(label, inplace=False).plot(layout='acyclic') + Graphics object consisting of 14 graphics primitives + + .. PLOT:: + + def label(element): + try: + return element._name + except AttributeError: + return '[' + ', '.join(sorted(x._name for x in element)) + ']' + M = Manifold(3, 'M') + U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + D = M.subset_digraph() + g1 = D.relabel(label, inplace=False).plot(layout='acyclic') + VW = V.union(W) + D = M.subset_digraph() + g2 = D.relabel(label, inplace=False).plot(layout='acyclic') + D = M.subset_digraph(open_covers=True) + g3 = D.relabel(label, inplace=False).plot(layout='acyclic') + sphinx_plot(graphics_array([g1, g2, g3]), figsize=(8, 3)) + + """ + from sage.graphs.digraph import DiGraph + D = DiGraph(multiedges=False, loops=loops) + + if loops: + add_edges = D.add_edges + else: + def add_edges(edges): + for u, v in edges: + if u != v: + D.add_edge((u, v)) + + if quotient: + def vertex_family(subset): + return ManifoldSubsetFiniteFamily(subset.equal_subsets()) + else: + def vertex_family(subset): + return ManifoldSubsetFiniteFamily([subset]) + subset_to_vertex = {} + def vertex(subset): + try: + return subset_to_vertex[subset] + except KeyError: + family = vertex_family(subset) + for S in family: + subset_to_vertex[S] = family + return family + + if lower_bound is not None: + if not lower_bound.is_subset(self): + return D + visited = set() + to_visit = [self] + while to_visit: + S = to_visit.pop() + if S in visited: + continue + visited.add(S) + + if lower_bound is None: + subsets = S._subsets + else: + subsets = [subset for subset in S._subsets + if lower_bound.is_subset(subset)] + + add_edges((vertex(subset), vertex(S)) for subset in subsets) + + subsets_without_S = [subset for subset in subsets + if subset is not S] + to_visit.extend(subsets_without_S) + + # Make sure to include isolated vertices in the graph + D.add_vertices(subset_to_vertex.values()) + + if open_covers: + + def open_cover_vertex(open_cover): + return tuple(sorted(ManifoldSubsetFiniteFamily([subset]) for subset in open_cover)) + + for S in visited: + add_edges((vertex(S), open_cover_vertex(open_cover)) + for open_cover in S.open_covers(trivial=False)) + + if points is not False: + subset_to_points = defaultdict(list) + if points is not True: + # Manifolds do not keep track of the points defined on them. + # Use the provided iterator. + def point_vertex(point): + return point + + for point in points: + S = point.parent() + subset_to_points[S].append(point) + D.add_edge((point_vertex(point), vertex(S))) + + # Add a placeholder vertex under each subset that has a defined + # point that we do not know about. + def anonymous_point_vertex(S): + return f"p{S._name}" + + add_edges((anonymous_point_vertex(S), vertex(S)) + for S in visited + if S.has_defined_points(subsets=False) + and S not in subset_to_points) + + return D + + def subset_poset(self, open_covers=False, points=False, lower_bound=None): + r""" + Return the poset of equivalence classes of the subsets of ``self``. + + Each element of the poset is a set of :class:`ManifoldSubset` instances, + which are known to be equal. + + INPUT: + + - ``open_covers`` -- (default: ``False``) whether to include vertices for open covers + - ``points`` -- (default: ``False``) whether to include vertices for declared points; + this can also be an iterable for the points to include + - ``lower_bound`` -- (default: ``None``) only include supersets of this + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + sage: P = M.subset_poset(); P + Finite poset containing 4 elements + sage: P.plot(element_labels={element: element._name for element in P}) + Graphics object consisting of 8 graphics primitives + sage: VW = V.union(W) + sage: P = M.subset_poset(); P + Finite poset containing 5 elements + sage: P.maximal_elements() + [Set {M} of open subsets of the 3-dimensional differentiable manifold M] + sage: sorted(P.minimal_elements(), key=lambda v: v._name) + [Set {U} of open subsets of the 3-dimensional differentiable manifold M, + Set {V} of open subsets of the 3-dimensional differentiable manifold M, + Set {W} of open subsets of the 3-dimensional differentiable manifold M] + sage: from sage.manifolds.subset import ManifoldSubsetFiniteFamily + sage: sorted(P.lower_covers(ManifoldSubsetFiniteFamily([M])), key=str) + [Set {U} of open subsets of the 3-dimensional differentiable manifold M, + Set {V_union_W} of open subsets of the 3-dimensional differentiable manifold M] + sage: P.plot(element_labels={element: element._name for element in P}) + Graphics object consisting of 10 graphics primitives + + If ``open_covers`` is ``True``, the poset includes a special vertex for + each nontrivial open cover of a subset:: + + sage: P = M.subset_poset(open_covers=True); P + Finite poset containing 6 elements + sage: from sage.manifolds.subset import ManifoldSubsetFiniteFamily + sage: sorted(P.upper_covers(ManifoldSubsetFiniteFamily([VW])), key=str) + [(Set {V} of open subsets of the 3-dimensional differentiable manifold M, + Set {W} of open subsets of the 3-dimensional differentiable manifold M), + Set {M} of open subsets of the 3-dimensional differentiable manifold M] + sage: def label(element): + ....: try: + ....: return element._name + ....: except AttributeError: + ....: return '[' + ', '.join(sorted(x._name for x in element)) + ']' + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 12 graphics primitives + + .. PLOT:: + + def label(element): + try: + return element._name + except AttributeError: + return '[' + ', '.join(sorted(x._name for x in element)) + ']' + M = Manifold(3, 'M') + U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + P = M.subset_poset() + g1 = P.plot(element_labels={element: label(element) for element in P}) + VW = V.union(W) + P = M.subset_poset() + g2 = P.plot(element_labels={element: label(element) for element in P}) + P = M.subset_poset(open_covers=True) + g3 = P.plot(element_labels={element: label(element) for element in P}) + sphinx_plot(graphics_array([g1, g2, g3]), figsize=(8, 3)) + + """ + from sage.combinat.posets.posets import Poset + return Poset(self.subset_digraph(open_covers=open_covers, points=points, + quotient=True, lower_bound=lower_bound)) + + def equal_subsets(self): + r""" + Generate the declared manifold subsets that are equal to ``self``. + + .. NOTE:: + + To get the equal subsets as a family, sorted by name, use the method + :meth:`equal_subset_family` instead. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = U.subset('V') + sage: V.declare_equal(M) + sage: sorted(V.equal_subsets(), key=lambda v: v._name) + [2-dimensional topological manifold M, + Open subset U of the 2-dimensional topological manifold M, + Subset V of the 2-dimensional topological manifold M] + + """ + for S in self.supersets(): + if S in self._subsets: + yield S + + def equal_subset_family(self): + r""" + Generate the declared manifold subsets that are equal to ``self``. + + .. NOTE:: + + To get the equal subsets as a family, sorted by name, use the method + :meth:`equal_subset_family` instead. + + .. NOTE:: + + If you only need to iterate over the equal sets in arbitrary order, + you can use the generator method :meth:`equal_subsets` instead. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = U.subset('V') + sage: V.declare_equal(M) + sage: V.equal_subset_family() + Set {M, U, V} of subsets of the 2-dimensional topological manifold M + + """ + return ManifoldSubsetFiniteFamily(self.equal_subsets()) + + def supersets(self): + r""" + Generate the declared supersets of the current subset. + + .. NOTE:: + + To get the supersets as a family, sorted by name, use the method + :meth:`superset_family` instead. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = M.subset('V') + sage: sorted(V.supersets(), key=lambda v: v._name) + [2-dimensional topological manifold M, + Subset V of the 2-dimensional topological manifold M] + + """ + yield from self._supersets + + def superset_family(self): + r""" + Return the family of declared supersets of the current subset. + + The family is sorted by the alphabetical names of the supersets. + + OUTPUT: + + - a :class:`ManifoldSubsetFiniteFamily` instance containing all the + supersets + + .. NOTE:: + + If you only need to iterate over the supersets in arbitrary order, + you can use the generator method :meth:`supersets` instead. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: U = M.open_subset('U') + sage: V = M.subset('V') + sage: V.superset_family() + Set {M, V} of subsets of the 2-dimensional topological manifold M + + """ + return ManifoldSubsetFiniteFamily(self.supersets()) + + def superset_digraph(self, loops=False, quotient=False, open_covers=False, points=False, upper_bound=None): + """ + Return the digraph whose arcs represent subset relations among the supersets of ``self``. + + INPUT: + + - ``loops`` -- (default: ``False``) whether to include the trivial containment + of each subset in itself as loops of the digraph + - ``quotient`` -- (default: ``False``) whether to contract directed cycles in the graph, + replacing equivalence classes of equal subsets by a single vertex. + In this case, each vertex of the digraph is a set of :class:`ManifoldSubset` + instances. + - ``open_covers`` -- (default: ``False``) whether to include vertices for open covers + - ``points`` -- (default: ``False``) whether to include vertices for declared points; + this can also be an iterable for the points to include + - ``upper_bound`` -- (default: ``None``) only include subsets of this + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + sage: VW = V.union(W) + sage: P = V.superset_digraph(loops=False, upper_bound=VW); P + Digraph on 2 vertices + + """ + if upper_bound is None: + upper_bound = self._manifold + return upper_bound.subset_digraph(loops=loops, open_covers=open_covers, points=points, + quotient=quotient, lower_bound=self) + + def superset_poset(self, open_covers=False, points=False, upper_bound=None): + r""" + Return the poset of the supersets of ``self``. + + INPUT: + + - ``open_covers`` -- (default: ``False``) whether to include vertices for open covers + - ``points`` -- (default: ``False``) whether to include vertices for declared points; + this can also be an iterable for the points to include + - ``upper_bound`` -- (default: ``None``) only include subsets of this + + EXAMPLES:: + + sage: M = Manifold(3, 'M') + sage: U = M.open_subset('U'); V = M.open_subset('V'); W = M.open_subset('W') + sage: VW = V.union(W) + sage: P = V.superset_poset(); P + Finite poset containing 3 elements + sage: P.plot(element_labels={element: element._name for element in P}) + Graphics object consisting of 6 graphics primitives + + """ + if upper_bound is None: + upper_bound = self._manifold + return upper_bound.subset_poset(open_covers=open_covers, points=points, + lower_bound=self) + def get_subset(self, name): r""" Get a subset by its name. @@ -678,11 +1298,8 @@ def get_subset(self, name): sage: A = M.subset('A') sage: B = A.subset('B') sage: U = M.open_subset('U') - sage: M.list_of_subsets() - [Subset A of the 4-dimensional topological manifold M, - Subset B of the 4-dimensional topological manifold M, - 4-dimensional topological manifold M, - Open subset U of the 4-dimensional topological manifold M] + sage: M.subset_family() + Set {A, B, M, U} of subsets of the 4-dimensional topological manifold M sage: M.get_subset('A') Subset A of the 4-dimensional topological manifold M sage: M.get_subset('A') is A @@ -729,12 +1346,96 @@ def is_subset(self, other): """ return self in other._subsets - def declare_union(self, dom1, dom2): + def declare_union(self, *subsets_or_families, disjoint=False): r""" Declare that the current subset is the union of two subsets. Suppose `U` is the current subset, then this method declares - that `U` + that `U = \bigcup_{S\in F} S`. + + INPUT: + + - ``subsets_or_families`` -- finitely many subsets or iterables of subsets + - ``disjoint`` -- (default: ``False``) whether to declare the subsets + pairwise disjoint + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: AB = M.subset('AB') + sage: A = AB.subset('A') + sage: B = AB.subset('B') + sage: def label(element): + ....: try: + ....: return element._name + ....: except AttributeError: + ....: return '[' + ', '.join(sorted(x._name for x in element)) + ']' + sage: P = M.subset_poset(open_covers=True); P + Finite poset containing 4 elements + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 8 graphics primitives + + sage: AB.declare_union(A, B) + sage: A.union(B) + Subset AB of the 2-dimensional topological manifold M + sage: P = M.subset_poset(open_covers=True); P + Finite poset containing 4 elements + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 8 graphics primitives + + sage: B1 = B.subset('B1', is_open=True) + sage: B2 = B.subset('B2', is_open=True) + sage: B.declare_union(B1, B2, disjoint=True) + sage: P = M.subset_poset(open_covers=True); P + Finite poset containing 9 elements + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 19 graphics primitives + + .. PLOT:: + + def label(element): + try: + return element._name + except AttributeError: + return '[' + ', '.join(sorted(x._name for x in element)) + ']' + M = Manifold(2, 'M', structure='topological') + AB = M.subset('AB') + A = AB.subset('A') + B = AB.subset('B') + P = M.subset_poset(open_covers=True); P + g1 = P.plot(element_labels={element: label(element) for element in P}) + AB.declare_union(A, B) + A.union(B) + P = M.subset_poset(open_covers=True); P + g2 = P.plot(element_labels={element: label(element) for element in P}) + B1 = B.subset('B1', is_open=True) + B2 = B.subset('B2', is_open=True) + B.declare_union(B1, B2, disjoint=True) + P = M.subset_poset(open_covers=True); P + g3 = P.plot(element_labels={element: label(element) for element in P}) + sphinx_plot(graphics_array([g1, g2, g3]), figsize=(8, 3)) + + """ + subsets = ManifoldSubsetFiniteFamily.from_subsets_or_families(*subsets_or_families) + if disjoint: + for U, V in itertools.combinations(subsets, 2): + U.intersection(V).declare_empty() + subsets = self._reduce_union_members(subsets) + if not subsets: + self.declare_empty() + elif len(subsets) == 1: + self.declare_equal(*subsets) + else: + subset_iter = iter(subsets) + first = next(subset_iter) + second = next(subset_iter) + self._declare_union_2_subsets(first, second.union(subset_iter)) + + def _declare_union_2_subsets(self, dom1, dom2): + r""" + Declare that the current subset is the union of two of its subsets. + + Suppose `U` is the current subset, then this method declares that .. MATH:: @@ -776,13 +1477,392 @@ def declare_union(self, dom1, dom2): for s in oc2: if s not in oc: oc.append(s) - self._open_covers.append(oc) + self._open_covers.append(oc) - def point(self, coords=None, chart=None, name=None, latex_name=None): + def declare_equal(self, *others): r""" - Define a point in ``self``. + Declare that ``self`` and ``others`` are the same sets. - See :class:`~sage.manifolds.point.ManifoldPoint` for a + INPUT: + + - ``others`` -- finitely many subsets or iterables of subsets of the same + manifold as ``self``. + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: U = M.open_subset('U') + sage: V = M.open_subset('V') + sage: Vs = [M.open_subset(f'V{i}') for i in range(2)] + sage: UV = U.intersection(V) + sage: W = UV.open_subset('W') + sage: P = M.subset_poset() + sage: def label(element): + ....: return element._name + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 15 graphics primitives + sage: V.declare_equal(Vs) + sage: P = M.subset_poset() + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 11 graphics primitives + sage: W.declare_equal(U) + sage: P = M.subset_poset() + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 6 graphics primitives + + .. PLOT:: + + def label(element): + return element._name + M = Manifold(2, 'M') + U = M.open_subset('U') + V = M.open_subset('V') + Vs = [M.open_subset(f'V{i}') for i in range(2)] + UV = U.intersection(V) + W = UV.open_subset('W') + P = M.subset_poset() + g1 = P.plot(element_labels={element: label(element) for element in P}) + V.declare_equal(Vs) + P = M.subset_poset() + g2 = P.plot(element_labels={element: label(element) for element in P}) + W.declare_equal(U) + P = M.subset_poset() + g3 = P.plot(element_labels={element: label(element) for element in P}) + sphinx_plot(graphics_array([g1, g2, g3]), figsize=(8, 3)) + + """ + F = ManifoldSubsetFiniteFamily.from_subsets_or_families + equal_sets = F(self, *others) + all_supersets = F(*[S.supersets() for S in equal_sets]) + all_subsets = F(*[S.subsets() for S in equal_sets]) + for superset in all_supersets: + superset._subsets.update(all_subsets) + for subset in all_subsets: + subset._supersets.update(all_supersets) + + def declare_subset(self, *supersets): + r""" + Declare ``self`` to be a subset of each of the given supersets. + + INPUT: + + - ``supersets`` -- other subsets of the same manifold + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: U1 = M.open_subset('U1') + sage: U2 = M.open_subset('U2') + sage: V = M.open_subset('V') + sage: V.superset_family() + Set {M, V} of open subsets of the 2-dimensional differentiable manifold M + sage: U1.subset_family() + Set {U1} of open subsets of the 2-dimensional differentiable manifold M + sage: P = M.subset_poset() + sage: def label(element): + ....: return element._name + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 8 graphics primitives + sage: V.declare_subset(U1, U2) + sage: V.superset_family() + Set {M, U1, U2, V} of open subsets of the 2-dimensional differentiable manifold M + sage: P = M.subset_poset() + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 9 graphics primitives + + Subsets in a directed cycle of inclusions are equal:: + + sage: M.declare_subset(V) + sage: M.superset_family() + Set {M, U1, U2, V} of open subsets of the 2-dimensional differentiable manifold M + sage: M.equal_subset_family() + Set {M, U1, U2, V} of open subsets of the 2-dimensional differentiable manifold M + sage: P = M.subset_poset() + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 2 graphics primitives + + .. PLOT:: + + def label(element): + return element._name + M = Manifold(2, 'M') + U1 = M.open_subset('U1') + U2 = M.open_subset('U2') + V = M.open_subset('V') + P = M.subset_poset() + g1 = P.plot(element_labels={element: label(element) for element in P}) + V.declare_subset(U1, U2) + P = M.subset_poset() + g2 = P.plot(element_labels={element: label(element) for element in P}) + M.declare_subset(V) + P = M.subset_poset() + g3 = P.plot(element_labels={element: label(element) for element in P}) + sphinx_plot(graphics_array([g1, g2, g3]), figsize=(8, 3)) + """ + F = ManifoldSubsetFiniteFamily.from_subsets_or_families + supersets = F(*supersets) + all_supersets = F(*[S.supersets() for S in supersets]) + for superset in all_supersets: + superset._subsets.update(self._subsets) + for subset in self._subsets: + subset._supersets.update(all_supersets) + + def declare_superset(self, *subsets): + r""" + Declare ``self`` to be a superset of each of the given subsets. + + INPUT: + + - ``subsets`` -- other subsets of the same manifold + + EXAMPLES:: + + sage: M = Manifold(2, 'M') + sage: U = M.open_subset('U') + sage: V1 = M.open_subset('V1') + sage: V2 = M.open_subset('V2') + sage: W = V1.intersection(V2) + sage: U.subset_family() + Set {U} of open subsets of the 2-dimensional differentiable manifold M + sage: P = M.subset_poset() + sage: def label(element): + ....: return element._name + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 11 graphics primitives + sage: U.declare_superset(V1, V2) + sage: U.subset_family() + Set {U, V1, V1_inter_V2, V2} of open subsets of the 2-dimensional differentiable manifold M + sage: P = M.subset_poset() + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 11 graphics primitives + + Subsets in a directed cycle of inclusions are equal:: + + sage: W.declare_superset(U) + sage: W.subset_family() + Set {U, V1, V1_inter_V2, V2} of open subsets of the 2-dimensional differentiable manifold M + sage: W.equal_subset_family() + Set {U, V1, V1_inter_V2, V2} of open subsets of the 2-dimensional differentiable manifold M + sage: P = M.subset_poset() + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 4 graphics primitives + + .. PLOT:: + + def label(element): + return element._name + M = Manifold(2, 'M') + U = M.open_subset('U') + V1 = M.open_subset('V1') + V2 = M.open_subset('V2') + W = V1.intersection(V2) + P = M.subset_poset() + def label(element): + return element._name + g1 = P.plot(element_labels={element: label(element) for element in P}) + U.declare_superset(V1, V2) + P = M.subset_poset() + g2 = P.plot(element_labels={element: label(element) for element in P}) + W.declare_superset(U) + P = M.subset_poset() + g3 = P.plot(element_labels={element: label(element) for element in P}) + sphinx_plot(graphics_array([g1, g2, g3]), figsize=(8, 3)) + """ + F = ManifoldSubsetFiniteFamily.from_subsets_or_families + subsets = F(*subsets) + all_subsets = F(*[S.subsets() for S in subsets]) + for subset in all_subsets: + subset._supersets.update(self._supersets) + for superset in self._supersets: + superset._subsets.update(all_subsets) + + def declare_empty(self): + r""" + Declare that ``self`` is the empty set. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A', is_open=True) + sage: AA = A.subset('AA') + sage: A + Open subset A of the 2-dimensional topological manifold M + sage: A.declare_empty() + sage: A.is_empty() + True + + Empty sets do not allow to define points on them:: + + sage: A.point() + Traceback (most recent call last): + ... + TypeError: cannot define a point on the + Open subset A of the 2-dimensional topological manifold M + because it has been declared empty + + Emptiness transfers to subsets:: + + sage: AA.is_empty() + True + sage: AA.point() + Traceback (most recent call last): + ... + TypeError: cannot define a point on the + Subset AA of the 2-dimensional topological manifold M + because it has been declared empty + sage: AD = A.subset('AD') + sage: AD.is_empty() + True + + If points have already been defined on ``self`` (or its subsets), + it is an error to declare it to be empty:: + + sage: B = M.subset('B') + sage: b = B.point(name='b'); b + Point b on the 2-dimensional topological manifold M + sage: B.declare_empty() + Traceback (most recent call last): + ... + TypeError: cannot be empty because it has defined points + + Emptiness is recorded as empty open covers:: + + sage: P = M.subset_poset(open_covers=True, points=[b]) + sage: def label(element): + ....: if isinstance(element, str): + ....: return element + ....: try: + ....: return element._name + ....: except AttributeError: + ....: return '[' + ', '.join(sorted(x._name for x in element)) + ']' + sage: P.plot(element_labels={element: label(element) for element in P}) + Graphics object consisting of 10 graphics primitives + + .. PLOT:: + + def label(element): + if isinstance(element, str): + return element + try: + return element._name + except AttributeError: + return '[' + ', '.join(sorted(x._name for x in element)) + ']' + M = Manifold(2, 'M', structure='topological') + A = M.subset('A', is_open=True) + AA = A.subset('AA') + A.declare_empty() + AD = A.subset('AD') + B = M.subset('B') + b = B.point(name='b') + + D = M.subset_digraph(open_covers=True, points=[b]) + g1 = D.relabel(label, inplace=False).plot(layout='spring') + P = M.subset_poset(open_covers=True, points=[b]) + g2 = P.plot(element_labels={element: label(element) for element in P}) + sphinx_plot(graphics_array([g1, g2]), figsize=(8, 5)) + + """ + if self.has_defined_points(): + raise TypeError('cannot be empty because it has defined points') + if not self.is_empty(): + self._open_covers.append([]) + self.declare_equal(self.subsets()) + + def is_empty(self): + r""" + Return whether the current subset is empty. + + By default, manifold subsets are considered nonempty: The method :meth:`point` can be + used to define points on it, either with or without coordinates some chart. + + However, using :meth:`declare_empty`, a subset can be declared empty, and emptiness + transfers to all of its subsets. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A', is_open=True) + sage: AA = A.subset('AA') + sage: A.is_empty() + False + sage: A.declare_empty() + sage: A.is_empty() + True + sage: AA.is_empty() + True + + """ + if self.has_defined_points(subsets=False): + # Fast path, do not check subsets + return False + return any(not cover + for cover in self.open_covers(trivial=False, supersets=True)) + + def declare_nonempty(self): + r""" + Declare that ``self`` is nonempty. + + Once declared nonempty, ``self`` (or any of its supersets) cannot be declared empty. + + This is equivalent to defining a point on ``self`` using :meth:`point` + but is cheaper than actually creating a :class:`~sage.manifolds.point.ManifoldPoint` + instance. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A', is_open=True) + sage: AA = A.subset('AA') + sage: AA.declare_nonempty() + sage: A.has_defined_points() + True + sage: A.declare_empty() + Traceback (most recent call last): + ... + TypeError: cannot be empty because it has defined points + + """ + if self.has_defined_points(subsets=False): + # Fast path, do not check subsets + return + if self.is_empty(): + raise TypeError('cannot be nonempty because it has already been declared empty') + self._has_defined_points = True + + def has_defined_points(self, subsets=True): + r""" + Return whether any points have been defined on ``self`` or any of its subsets. + + INPUT: + + - ``subsets`` -- (default: ``True``) if ``False``, only consider points that have + been defined directly on ``self``; if ``True``, also consider points on all subsets. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A', is_open=True) + sage: AA = A.subset('AA') + sage: AA.point() + Point on the 2-dimensional topological manifold M + sage: AA.has_defined_points() + True + sage: A.has_defined_points(subsets=False) + False + sage: A.has_defined_points() + True + + """ + if subsets: + return any(subset._has_defined_points for subset in self.subsets()) + else: + return self._has_defined_points + + def point(self, coords=None, chart=None, name=None, latex_name=None): + r""" + Define a point in ``self``. + + See :class:`~sage.manifolds.point.ManifoldPoint` for a complete documentation. INPUT: @@ -825,6 +1905,37 @@ def point(self, coords=None, chart=None, name=None, latex_name=None): return self.element_class(self, coords=coords, chart=chart, name=name, latex_name=latex_name) + def declare_closed(self): + r""" + Declare ``self`` to be a closed subset of the manifold. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B1 = A.subset('B1') + sage: B1.is_closed() + False + sage: B1.declare_closed() + sage: B1.is_closed() + True + + sage: B2 = A.subset('B2') + sage: cl_B2 = B2.closure() + sage: A.declare_closed() + sage: cl_B2.is_subset(A) + True + + """ + if self.is_closed(): + return + self.complement(is_open=True) + from .subsets.closure import ManifoldSubsetClosure + for closure in self.manifold().subsets(): + if isinstance(closure, ManifoldSubsetClosure): + if closure._subset.is_subset(self): + closure.declare_subset(self) + #### Construction of new sets from self: def subset(self, name, latex_name=None, is_open=False): @@ -871,12 +1982,120 @@ def subset(self, name, latex_name=None, is_open=False): if is_open: return self.open_subset(name, latex_name=latex_name) res = ManifoldSubset(self._manifold, name, latex_name=latex_name) - res._supersets.update(self._supersets) - for sd in self._supersets: - sd._subsets.add(res) + if self.is_empty(): + self.declare_equal(res) + else: + self.declare_superset(res) self._top_subsets.add(res) return res + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): + r""" + Create an open subset of the manifold that is a subset of ``self``. + + An open subset is a set that is (i) included in the manifold and (ii) + open with respect to the manifold's topology. It is a topological + manifold by itself. Hence the returned object is an instance of + :class:`TopologicalManifold`. + + INPUT: + + - ``name`` -- name given to the open subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote + the subset; if none are provided, it is set to ``name`` + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of + + OUTPUT: + + - the open subset, as an instance of :class:`TopologicalManifold` + or one of its subclasses + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: cl_D = M.subset('cl_D'); cl_D + Subset cl_D of the 2-dimensional topological manifold R^2 + sage: D = cl_D.open_subset('D', coord_def={c_cart: x^2+y^2<1}); D + Open subset D of the 2-dimensional topological manifold R^2 + sage: D.is_subset(cl_D) + True + sage: D.is_subset(M) + True + + sage: M = Manifold(2, 'R^2', structure='differentiable') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: cl_D = M.subset('cl_D'); cl_D + Subset cl_D of the 2-dimensional differentiable manifold R^2 + sage: D = cl_D.open_subset('D', coord_def={c_cart: x^2+y^2<1}); D + Open subset D of the 2-dimensional differentiable manifold R^2 + sage: D.is_subset(cl_D) + True + sage: D.is_subset(M) + True + + sage: M = Manifold(2, 'R^2', structure='Riemannian') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: cl_D = M.subset('cl_D'); cl_D + Subset cl_D of the 2-dimensional Riemannian manifold R^2 + sage: D = cl_D.open_subset('D', coord_def={c_cart: x^2+y^2<1}); D + Open subset D of the 2-dimensional Riemannian manifold R^2 + sage: D.is_subset(cl_D) + True + sage: D.is_subset(M) + True + + """ + if supersets is None: + supersets = set() + else: + supersets = set(supersets) + supersets.update([self]) + # Delegate to the manifold's method. + return self._manifold.open_subset(name, latex_name=latex_name, + coord_def=coord_def, + supersets=supersets) + + def _init_open_subset(self, resu, coord_def): + r""" + Initialize ``resu`` as an open subset of ``self``. + + INPUT: + + - ``resu`` -- an instance of ``:class:`TopologicalManifold` or + a subclass. + + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: cl_D = M.subset('cl_D') + sage: coord_def = {c_cart: x^2+y^2<1} + sage: D = M.open_subset('D', coord_def=coord_def) + sage: D.is_subset(cl_D) + False + sage: cl_D._init_open_subset(D, coord_def) + sage: D.is_subset(cl_D) + True + + """ + resu._supersets.update(self._supersets) + self._subsets.add(resu) + # Recursively delegate to the supersets. + for superset in self._supersets: + if superset is not self: + superset._init_open_subset(resu, coord_def=coord_def) + def superset(self, name, latex_name=None, is_open=False): r""" Create a superset of the current subset. @@ -907,13 +2126,10 @@ def superset(self, name, latex_name=None, is_open=False): sage: a = M.subset('A') sage: b = a.superset('B'); b Subset B of the 2-dimensional topological manifold M - sage: b.list_of_subsets() - [Subset A of the 2-dimensional topological manifold M, - Subset B of the 2-dimensional topological manifold M] - sage: a._supersets # random (set output) - {Subset B of the 2-dimensional topological manifold M, - Subset A of the 2-dimensional topological manifold M, - 2-dimensional topological manifold M} + sage: b.subset_family() + Set {A, B} of subsets of the 2-dimensional topological manifold M + sage: a.superset_family() + Set {A, B, M} of subsets of the 2-dimensional topological manifold M The superset of the whole manifold is itself:: @@ -933,9 +2149,7 @@ def superset(self, name, latex_name=None, is_open=False): res = self._manifold.open_subset(name, latex_name=latex_name) else: res = ManifoldSubset(self._manifold, name, latex_name=latex_name) - res._subsets.update(self._subsets) - for sd in self._subsets: - sd._supersets.add(res) + res.declare_superset(self) if is_open and self._is_open: res._atlas = list(self._atlas) res._top_charts = list(self._top_charts) @@ -943,13 +2157,17 @@ def superset(self, name, latex_name=None, is_open=False): res._def_chart = self._def_chart return res - def intersection(self, other, name=None, latex_name=None): + def intersection(self, *others, name=None, latex_name=None): r""" - Return the intersection of the current subset with another subset. + Return the intersection of the current subset with other subsets. + + This method may return a previously constructed intersection instead + of creating a new subset. In this case, ``name`` and ``latex_name`` + are not used. INPUT: - - ``other`` -- another subset of the same manifold + - ``others`` -- other subsets of the same manifold - ``name`` -- (default: ``None``) name given to the intersection in the case the latter has to be created; the default is ``self._name`` inter ``other._name`` @@ -960,7 +2178,7 @@ def intersection(self, other, name=None, latex_name=None): OUTPUT: - instance of :class:`ManifoldSubset` representing the - subset that is the intersection of the current subset with ``other`` + subset that is the intersection of the current subset with ``others`` EXAMPLES: @@ -971,19 +2189,56 @@ def intersection(self, other, name=None, latex_name=None): sage: b = M.subset('B') sage: c = a.intersection(b); c Subset A_inter_B of the 2-dimensional topological manifold M - sage: a.list_of_subsets() - [Subset A of the 2-dimensional topological manifold M, - Subset A_inter_B of the 2-dimensional topological manifold M] - sage: b.list_of_subsets() - [Subset A_inter_B of the 2-dimensional topological manifold M, - Subset B of the 2-dimensional topological manifold M] - sage: c._supersets # random (set output) - {Subset B of the 2-dimensional topological manifold M, - Subset A_inter_B of the 2-dimensional topological manifold M, - Subset A of the 2-dimensional topological manifold M, - 2-dimensional topological manifold M} - - Some checks:: + sage: a.subset_family() + Set {A, A_inter_B} of subsets of the 2-dimensional topological manifold M + sage: b.subset_family() + Set {A_inter_B, B} of subsets of the 2-dimensional topological manifold M + sage: c.superset_family() + Set {A, A_inter_B, B, M} of subsets of the 2-dimensional topological manifold M + + Intersection of six subsets:: + + sage: T = Manifold(2, 'T', structure='topological') + sage: S = [T.subset(f'S{i}') for i in range(6)] + sage: [S[i].intersection(S[i+3]) for i in range(3)] + [Subset S0_inter_S3 of the 2-dimensional topological manifold T, + Subset S1_inter_S4 of the 2-dimensional topological manifold T, + Subset S2_inter_S5 of the 2-dimensional topological manifold T] + sage: inter_S_i = T.intersection(*S, name='inter_S_i'); inter_S_i + Subset inter_S_i of the 2-dimensional topological manifold T + sage: inter_S_i.superset_family() + Set {S0, S0_inter_S3, S0_inter_S3_inter_S1_inter_S4, S1, S1_inter_S4, + S2, S2_inter_S5, S3, S4, S5, T, inter_S_i} of + subsets of the 2-dimensional topological manifold T + + .. PLOT:: + + def label(element): + if isinstance(element, str): + return element + try: + return element._name.replace('_inter_', '∩') + except AttributeError: + return '[' + ', '.join(sorted(label(x) for x in element)) + ']' + + M = Manifold(2, 'M', structure='topological') + a = M.subset('A') + b = M.subset('B') + c = a.intersection(b); c + P = M.subset_poset(open_covers=True) + g1 = P.plot(element_labels={element: label(element) for element in P}) + + T = Manifold(2, 'T', structure='topological') + from sage.typeset.unicode_art import unicode_subscript + S = [T.subset(f'S{unicode_subscript(i)}') for i in range(6)] + [S[i].intersection(S[i+3]) for i in range(3)] + T.intersection(*S, name='⋂ᵢSᵢ') + P = T.subset_poset(open_covers=True) + g2 = P.plot(element_labels={element: label(element) for element in P}) + + sphinx_plot(graphics_array([g1, g2]), figsize=(8, 3)) + + TESTS:: sage: (a.intersection(b)).is_subset(a) True @@ -1001,49 +2256,142 @@ def intersection(self, other, name=None, latex_name=None): True """ - if other._manifold != self._manifold: - raise ValueError("the two subsets do not belong to the same manifold") - # Particular cases: - if self is self._manifold: - return other - if other is self._manifold: - return self - if self in other._subsets: - return self - if other in self._subsets: - return other - # Generic case: - if other._name in self._intersections: - # the intersection has already been created: - return self._intersections[other._name] - else: - # the intersection must be created: - if latex_name is None: - if name is None: - latex_name = self._latex_name + r'\cap ' + other._latex_name + subsets = ManifoldSubsetFiniteFamily.from_subsets_or_families(self, *others) + subset_iter = iter(self._reduce_intersection_members(subsets)) + # _intersection_subset is able to build the intersection of several + # subsets directly; but because we cache only pairwise intersections, + # we build the intersection by a sequence of pairwise intersections. + res = next(subset_iter) + others = list(subset_iter) + if not others: + return res + for other in others[:-1]: + res = res._intersection_subset(other) + # The last one gets the name + return res._intersection_subset(others[-1], name=name, latex_name=latex_name) + + @staticmethod + def _reduce_intersection_members(subsets): + r""" + Return a reduced set of subsets with the same intersection as the given subsets. + + It is reduced with respect to two operations: + + - replacing an inclusion chain by its minimal element + + - replacing a pair of subsets with a declared intersection by the intersection + + INPUT: + + - ``subsets`` -- a non-empty iterable of :class:`ManifoldSubset` instances + of the same manifold. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B1 = A.subset('B1') + sage: B2 = A.subset('B2') + sage: C = B1.intersection(B2) + sage: M._reduce_intersection_members([A, M, A]) + Set {A} of subsets of the 2-dimensional topological manifold M + sage: M._reduce_intersection_members([A, B1]) + Set {B1} of subsets of the 2-dimensional topological manifold M + sage: M._reduce_intersection_members([B1, B2]) + Set {B1_inter_B2} of subsets of the 2-dimensional topological manifold M + sage: M._reduce_intersection_members([]) + Traceback (most recent call last): + ... + TypeError: input set must be nonempty + + """ + subsets = set(subsets) + if not subsets: + raise TypeError('input set must be nonempty') + def reduce(): + # Greedily replace inclusion chains by their minimal element + # and pairs with declared intersections by their intersection + for U, V in itertools.combinations(subsets, 2): + if U.is_subset(V): + subsets.remove(V) + return True + if V.is_subset(U): + subsets.remove(U) + return True + try: + UV = U._intersections[V._name] + except KeyError: + pass else: - latex_name = name + subsets.difference_update([U, V]) + subsets.add(UV) + return True + return False + while reduce(): + pass + assert subsets # there must be a survivor + return ManifoldSubsetFiniteFamily(subsets) + + def _intersection_subset(self, *others, name=None, latex_name=None): + r""" + Return a subset that is the intersection of ``self`` and ``others``. + + The result is always a new subset of the manifold. If the intersection + involves two subsets only, the result is stored in the dictionaries + of known intersections for later reuse by other methods. + + INPUT: + + - ``others`` -- an iterable of :class:`ManifoldSubset` instances + of the same manifold. + - ``name`` -- (default: ``None``) name given to the intersection; the + default is ``self._name`` inter [...] inter ``last_other._name`` + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + intersection; the default is built upon the symbol `\cap` + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: B1 = M.subset('B1') + sage: B2 = M.subset('B2') + sage: B3 = M.subset('B3') + sage: B1._intersection_subset(B2) + Subset B1_inter_B2 of the 2-dimensional topological manifold M + sage: B1._intersection_subset(B2, B3) + Subset B1_inter_B2_inter_B3 of the 2-dimensional topological manifold M + + """ + subsets = ManifoldSubsetFiniteFamily.from_subsets_or_families(self, *others) + if latex_name is None: if name is None: - name = self._name + "_inter_" + other._name - if self._is_open and other._is_open: - res = self.open_subset(name, latex_name=latex_name) + latex_name = r'\cap '.join(S._latex_name for S in subsets) else: - res = self.subset(name, latex_name=latex_name) - res._supersets.update(other._supersets) - for sd in other._supersets: - sd._subsets.add(res) - other._top_subsets.add(res) - self._intersections[other._name] = res - other._intersections[self._name] = res - return res + latex_name = name + if name is None: + name = "_inter_".join(S._name for S in subsets) + if all(S.is_open() for S in subsets): + res = self.open_subset(name, latex_name=latex_name, supersets=subsets) + else: + res = self.subset(name, latex_name=latex_name) + res.declare_subset(subsets) + for S in subsets: + S._top_subsets.add(res) + if len(subsets) == 2: + S1, S2 = subsets + S1._intersections[S2._name] = S2._intersections[S1._name] = res + return res - def union(self, other, name=None, latex_name=None): + def union(self, *others, name=None, latex_name=None): r""" - Return the union of the current subset with another subset. + Return the union of the current subset with other subsets. + + This method may return a previously constructed union instead + of creating a new subset. In this case, ``name`` and ``latex_name`` + are not used. INPUT: - - ``other`` -- another subset of the same manifold + - ``others`` -- other subsets of the same manifold - ``name`` -- (default: ``None``) name given to the union in the case the latter has to be created; the default is ``self._name`` union ``other._name`` @@ -1054,7 +2402,7 @@ def union(self, other, name=None, latex_name=None): OUTPUT: - instance of :class:`ManifoldSubset` representing the - subset that is the union of the current subset with ``other`` + subset that is the union of the current subset with ``others`` EXAMPLES: @@ -1065,20 +2413,56 @@ def union(self, other, name=None, latex_name=None): sage: b = M.subset('B') sage: c = a.union(b); c Subset A_union_B of the 2-dimensional topological manifold M - sage: a._supersets # random (set output) - set([subset 'A_union_B' of the 2-dimensional manifold 'M', - 2-dimensional manifold 'M', - subset 'A' of the 2-dimensional manifold 'M']) - sage: b._supersets # random (set output) - set([subset 'B' of the 2-dimensional manifold 'M', - 2-dimensional manifold 'M', - subset 'A_union_B' of the 2-dimensional manifold 'M']) - sage: c._subsets # random (set output) - set([subset 'A_union_B' of the 2-dimensional manifold 'M', - subset 'A' of the 2-dimensional manifold 'M', - subset 'B' of the 2-dimensional manifold 'M']) - - Some checks:: + sage: a.superset_family() + Set {A, A_union_B, M} of subsets of the 2-dimensional topological manifold M + sage: b.superset_family() + Set {A_union_B, B, M} of subsets of the 2-dimensional topological manifold M + sage: c.superset_family() + Set {A_union_B, M} of subsets of the 2-dimensional topological manifold M + + Union of six subsets:: + + sage: T = Manifold(2, 'T', structure='topological') + sage: S = [T.subset(f'S{i}') for i in range(6)] + sage: [S[i].union(S[i+3]) for i in range(3)] + [Subset S0_union_S3 of the 2-dimensional topological manifold T, + Subset S1_union_S4 of the 2-dimensional topological manifold T, + Subset S2_union_S5 of the 2-dimensional topological manifold T] + sage: union_S_i = S[0].union(S[1:], name='union_S_i'); union_S_i + Subset union_S_i of the 2-dimensional topological manifold T + sage: T.subset_family() + Set {S0, S0_union_S3, S0_union_S3_union_S1_union_S4, S1, + S1_union_S4, S2, S2_union_S5, S3, S4, S5, T, union_S_i} + of subsets of the 2-dimensional topological manifold T + + .. PLOT:: + + def label(element): + if isinstance(element, str): + return element + try: + return element._name.replace('_union_', '∪') + except AttributeError: + return '[' + ', '.join(sorted(label(x) for x in element)) + ']' + + M = Manifold(2, 'M', structure='topological') + a = M.subset('A') + b = M.subset('B') + c = a.union(b); c + P = M.subset_poset(open_covers=True) + g1 = P.plot(element_labels={element: label(element) for element in P}) + + T = Manifold(2, 'T', structure='topological') + from sage.typeset.unicode_art import unicode_subscript + S = [T.subset(f'S{unicode_subscript(i)}') for i in range(6)] + [S[i].union(S[i+3]) for i in range(3)] + union_S_i = S[0].union(S[1:], name='⋃ᵢSᵢ'); union_S_i + P = T.subset_poset(open_covers=True) + g2 = P.plot(element_labels={element: label(element) for element in P}) + + sphinx_plot(graphics_array([g1, g2]), figsize=(8, 3)) + + TESTS:: sage: a.is_subset(a.union(b)) True @@ -1095,8 +2479,6 @@ def union(self, other, name=None, latex_name=None): sage: M.union(a) is M True - TESTS: - Check that :trac:`30401` is fixed:: sage: d = a.subset('D') @@ -1105,58 +2487,303 @@ def union(self, other, name=None, latex_name=None): True """ - if other._manifold != self._manifold: - raise ValueError("the two subsets do not belong to the same manifold") - # Particular cases: - if (self is self._manifold) or (other is self._manifold): - return self._manifold - if self in other._subsets: - return other - if other in self._subsets: - return self - # Generic case: - if other._name in self._unions: - # the union has already been created: - return self._unions[other._name] - else: - # the union must be created: - if latex_name is None: - if name is None: - latex_name = self._latex_name + r'\cup ' + other._latex_name + subsets = ManifoldSubsetFiniteFamily.from_subsets_or_families(self, *others) + subsets = self._reduce_union_members(subsets) + assert subsets + subset_iter = iter(subsets) + res = next(subset_iter) + others = list(subset_iter) + if not others: + return res + for other in others[:-1]: + res = res._union_subset(other) + # The last one gets the name + return res._union_subset(others[-1], name=name, latex_name=latex_name) + + @staticmethod + def _reduce_union_members(subsets): + r""" + Return a reduced set of subsets with the same union as the given subsets. + + It is reduced with respect to two operations: + + - replacing an inclusion chain by its maximal element + + - replacing a pair of subsets with a declared union by the union + + INPUT: + + - ``subsets`` -- an iterable of :class:`ManifoldSubset` instances + of the same manifold. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B1 = A.subset('B1') + sage: B2 = A.subset('B2') + sage: B = B1.union(B2) + sage: M._reduce_union_members([]) + {} + sage: M._reduce_union_members([B1, B]) + Set {B1_union_B2} of subsets of the 2-dimensional topological manifold M + sage: M._reduce_union_members([A, B1, B2]) + Set {A} of subsets of the 2-dimensional topological manifold M + + """ + subsets = set(subsets) + def reduce(): + # Greedily replace inclusion chains by their maximal element + # and pairs with declared unions by their union + for U, V in itertools.combinations(subsets, 2): + if U.is_subset(V): + subsets.remove(U) + return True + if V.is_subset(U): + subsets.remove(V) + return True + try: + UV = U._unions[V._name] + except KeyError: + pass else: - latex_name = name + subsets.difference_update([U, V]) + subsets.add(UV) + return True + return False + while reduce(): + pass + return ManifoldSubsetFiniteFamily(subsets) + + def _union_subset(self, other, name=None, latex_name=None): + r""" + Return a subset of the manifold that is the union of ``self`` and ``other``. + + The result is always a new subset of the manifold and is also + stored in ``self`` and ``other``'s dictionaries of known unions. + + INPUT: + + - ``other`` -- an instance of :class:`ManifoldSubset` + - ``name`` -- (default: ``None``) name given to the union; the default is + ``self._name`` union ``other._name`` + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + union; the default is built upon the symbol `\cup` + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: B1 = M.subset('B1') + sage: B2 = M.subset('B2') + sage: B1._union_subset(B2) + Subset B1_union_B2 of the 2-dimensional topological manifold M + + """ + if latex_name is None: if name is None: - name = self._name + "_union_" + other._name - res_open = self._is_open and other._is_open - res = self.superset(name, latex_name, is_open=res_open) - res._subsets.update(other._subsets) - res._top_subsets.add(self) - res._top_subsets.add(other) - for sd in other._subsets: - sd._supersets.add(res) - for sp in self._supersets: - if sp in other._supersets: - sp._subsets.add(res) - res._supersets.add(sp) - if res._is_open: - for chart in other._atlas: - if chart not in res._atlas: - res._atlas.append(chart) - for chart in other._top_charts: - if chart not in res._top_charts: - res._top_charts.append(chart) - res._coord_changes.update(other._coord_changes) - self._unions[other._name] = res - other._unions[self._name] = res - # Open covers of the union: - for oc1 in self._open_covers: - for oc2 in other._open_covers: - oc = oc1[:] - for s in oc2: - if s not in oc: - oc.append(s) - res._open_covers.append(oc) - return res + latex_name = r'\cup '.join(S._latex_name for S in (self, other)) + else: + latex_name = name + if name is None: + name = "_union_".join(S._name for S in (self, other)) + res_open = all(S.is_open() for S in (self, other)) + res = self.superset(name, latex_name, is_open=res_open) + res.declare_superset(other) + res._top_subsets.add(self) + res._top_subsets.add(other) + self._unions[other._name] = other._unions[self._name] = res + for sp in self._supersets: + if sp in other._supersets: + sp._subsets.add(res) + res._supersets.add(sp) + if res._is_open: + for chart in other._atlas: + if chart not in res._atlas: + res._atlas.append(chart) + for chart in other._top_charts: + if chart not in res._top_charts: + res._top_charts.append(chart) + res._coord_changes.update(other._coord_changes) + # Open covers of the union: + for oc1 in self._open_covers: + for oc2 in other._open_covers: + oc = oc1[:] + for s in oc2: + if s not in oc: + oc.append(s) + res._open_covers.append(oc) + return res - #### End of construction of new sets from self + def complement(self, superset=None, name=None, latex_name=None, is_open=False): + r""" + Return the complement of ``self`` in the manifold or in ``superset``. + + INPUT: + + - ``superset`` -- (default: ``self.manifold()``) a superset of ``self`` + - ``name`` -- (default: ``None``) name given to the complement in the + case the latter has to be created; the default is + ``superset._name`` minus ``self._name`` + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + complement in the case the latter has to be created; the default + is built upon the symbol `\setminus` + + OUTPUT: + + - instance of :class:`ManifoldSubset` representing the + subset that is difference of ``superset`` minus ``self`` + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: B1 = A.subset('B1') + sage: B2 = A.subset('B2') + sage: B1.complement() + Subset M_minus_B1 of the 2-dimensional topological manifold M + sage: B1.complement(A) + Subset A_minus_B1 of the 2-dimensional topological manifold M + sage: B1.complement(B2) + Traceback (most recent call last): + ... + TypeError: superset must be a superset of self + + """ + if superset is None: + superset = self.manifold() + elif not self.is_subset(superset): + raise TypeError("superset must be a superset of self") + return superset.difference(self, + name=name, latex_name=latex_name, + is_open=is_open) + + def difference(self, other, name=None, latex_name=None, is_open=False): + r""" + Return the set difference of ``self`` minus ``other``. + + INPUT: + + - ``other`` -- another subset of the same manifold + - ``name`` -- (default: ``None``) name given to the difference in the + case the latter has to be created; the default is + ``self._name`` minus ``other._name`` + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + difference in the case the latter has to be created; the default + is built upon the symbol `\setminus` + + OUTPUT: + + - instance of :class:`ManifoldSubset` representing the + subset that is difference of ``self`` minus ``other`` + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure='topological') + sage: A = M.subset('A') + sage: CA = M.difference(A); CA + Subset M_minus_A of the 2-dimensional topological manifold M + sage: latex(CA) + M\setminus A + sage: A.intersection(CA).is_empty() + True + sage: A.union(CA) + 2-dimensional topological manifold M + + sage: O = M.open_subset('O') + sage: CO = M.difference(O); CO + Subset M_minus_O of the 2-dimensional topological manifold M + sage: M.difference(O) is CO + True + + sage: CO2 = M.difference(O, is_open=True, name='CO2'); CO2 + Open subset CO2 of the 2-dimensional topological manifold M + sage: CO is CO2 + False + sage: CO.is_subset(CO2) and CO2.is_subset(CO) + True + sage: M.difference(O, is_open=True) + Open subset CO2 of the 2-dimensional topological manifold M + + """ + # See if it has been created already + diffs = [] + for diff_name, intersection in other._intersections.items(): + if intersection.is_empty(): + try: + union = other._unions[diff_name] + except KeyError: + pass + else: + if union == self: + diff = self.subset_family()[diff_name] + if not is_open: + return diff + if diff.is_open(): + return diff + # is_open=True but we found a subset that + # is not known to be open - and we cannot + # declare it open. + diffs.append(diff) + + if latex_name is None: + if name is None: + latex_name = r'\setminus '.join(S._latex_name for S in (self, other)) + else: + latex_name = name + if name is None: + name = "_minus_".join(S._name for S in (self, other)) + + is_open = is_open or (self.is_open() and other.is_closed()) + + diff = self.subset(name=name, latex_name=latex_name, is_open=is_open) + diff.declare_equal(diffs) + self.declare_union(other, diff, disjoint=True) + return diff + + def closure(self, name=None, latex_name=None): + r""" + Return the topological closure of ``self`` as a subset of the manifold. + + INPUT: + + - ``name`` -- (default: ``None``) name given to the difference in the + case the latter has to be created; the default prepends ``cl_`` + to ``self._name`` + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + difference in the case the latter has to be created; the default + is built upon the operator `\mathrm{cl}` + + OUTPUT: + + - if ``self`` is already known to be closed (see :meth:`is_closed`), + ``self``; otherwise, an instance of + :class:`~sage.manifolds.subsets.closure.ManifoldSubsetClosure` + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: M.closure() is M + True + sage: D2 = M.open_subset('D2', coord_def={c_cart: x^2+y^2<2}); D2 + Open subset D2 of the 2-dimensional topological manifold R^2 + sage: cl_D2 = D2.closure(); cl_D2 + Topological closure cl_D2 of the + Open subset D2 of the 2-dimensional topological manifold R^2 + sage: cl_D2.is_closed() + True + sage: cl_D2 is cl_D2.closure() + True + + sage: D1 = D2.open_subset('D1'); D1 + Open subset D1 of the 2-dimensional topological manifold R^2 + sage: D1.closure().is_subset(D2.closure()) + True + + """ + if self.is_closed(): + return self + from .subsets.closure import ManifoldSubsetClosure + return ManifoldSubsetClosure(self, name=name, latex_name=latex_name) + + #### End of construction of new sets from self diff --git a/src/sage/manifolds/subsets/__init__.py b/src/sage/manifolds/subsets/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/sage/manifolds/subsets/closure.py b/src/sage/manifolds/subsets/closure.py new file mode 100644 index 00000000000..ec60272cb8e --- /dev/null +++ b/src/sage/manifolds/subsets/closure.py @@ -0,0 +1,133 @@ +r""" +Topological Closures of Manifold Subsets + +:class:`ManifoldSubsetClosure` implements the topological closure +of a manifold subset in the topology of the manifold. + +""" + +# **************************************************************************** +# Copyright (C) 2021 Matthias Koeppe +# +# 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.manifolds.subset import ManifoldSubset + +class ManifoldSubsetClosure(ManifoldSubset): + + r""" + Topological closure of a manifold subset in the topology of the manifold. + + INPUT: + + - ``subset`` -- a :class:`ManifoldSubset` + - ``name`` -- (default: computed from the name of the subset) + string; name (symbol) given to the closure + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the subset; if none is provided, it is set to ``name`` + + EXAMPLES:: + + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: D = M.open_subset('D', coord_def={c_cart: x^2+y^2<1}); D + Open subset D of the 2-dimensional topological manifold R^2 + sage: cl_D = D.closure() + sage: cl_D + Topological closure cl_D of the Open subset D of the 2-dimensional + topological manifold R^2 + sage: latex(cl_D) + \mathop{\mathrm{cl}}(D) + sage: type(cl_D) + + sage: cl_D.category() + Category of subobjects of sets + + The closure of the subset `D` is a subset of every closed superset + of `D`:: + + sage: S = D.superset('S') + sage: S.declare_closed() + sage: cl_D.is_subset(S) + True + + """ + + def __init__(self, subset, name=None, latex_name=None): + r""" + Initialize a :class:`ManifoldSubsetClosure` instance. + + TESTS:: + + sage: from sage.manifolds.subsets.closure import ManifoldSubsetClosure + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: D = M.open_subset('D', coord_def={c_cart: x^2+y^2<1}); D + Open subset D of the 2-dimensional topological manifold R^2 + sage: cl_D = D.closure(); cl_D # indirect doctest + Topological closure cl_D of the + Open subset D of the 2-dimensional topological manifold R^2 + sage: also_cl_D = ManifoldSubsetClosure(D, name='also_cl_D'); also_cl_D + Topological closure also_cl_D of the + Open subset D of the 2-dimensional topological manifold R^2 + sage: cl_D is also_cl_D + False + sage: cl_D == also_cl_D + False + + """ + self._subset = subset + base_manifold = subset.manifold() + if latex_name is None: + if name is None: + latex_name = r'\mathop{\mathrm{cl}}(' + subset._latex_name + ')' + else: + latex_name = name + if name is None: + name = 'cl_' + subset._name + ManifoldSubset.__init__(self, base_manifold, name, latex_name=latex_name) + self.declare_superset(subset) + self.declare_subset(superset + for superset in subset.supersets() + if superset.is_closed()) + + def _repr_(self): + r""" + String representation of the object. + + TESTS:: + + sage: from sage.manifolds.subsets.closure import ManifoldSubsetClosure + sage: M = Manifold(2, 'R^2', structure='topological') + sage: D = M.open_subset('D') + sage: cl_D = D.closure(); cl_D # indirect doctest + Topological closure cl_D of the + Open subset D of the 2-dimensional topological manifold R^2 + """ + return "Topological closure {} of the {}".format(self._name, self._subset) + + def is_closed(self): + r""" + Return if ``self`` is a closed set. + + This implementation of the method always returns ``True``. + + EXAMPLES:: + + sage: from sage.manifolds.subsets.closure import ManifoldSubsetClosure + sage: M = Manifold(2, 'R^2', structure='topological') + sage: c_cart. = M.chart() # Cartesian coordinates on R^2 + sage: D = M.open_subset('D', coord_def={c_cart: x^2+y^2<1}); D + Open subset D of the 2-dimensional topological manifold R^2 + sage: cl_D = D.closure(); cl_D # indirect doctest + Topological closure cl_D of the Open subset D of the 2-dimensional topological manifold R^2 + sage: cl_D.is_closed() + True + + """ + return True diff --git a/src/sage/manifolds/topological_submanifold.py b/src/sage/manifolds/topological_submanifold.py index a5613578821..d6942bca7db 100644 --- a/src/sage/manifolds/topological_submanifold.py +++ b/src/sage/manifolds/topological_submanifold.py @@ -24,6 +24,8 @@ AUTHORS: - Florentin Jaffredo (2018): initial version +- Eric Gourgoulhon (2018-2019): add documentation +- Matthias Koeppe (2021): open subsets of submanifolds REFERENCES: @@ -33,7 +35,9 @@ # ***************************************************************************** -# Copyright (C) 2018 Florentin Jaffredo +# Copyright (C) 2018 Florentin Jaffredo +# Copyright (C) 2018-2019 Eric Gourgoulhon +# Copyright (C) 2021 Matthias Koeppe # # 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 @@ -246,6 +250,8 @@ def _repr_(self): 3-dimensional topological manifold M """ + if self is not self._manifold: + return "Open subset {} of the {}".format(self._name, self._manifold) if self._ambient is self: return super(TopologicalManifold, self).__repr__() if self._embedded: @@ -254,6 +260,122 @@ def _repr_(self): return "{}-dimensional {} submanifold {} immersed in the {}".format( self._dim, self._structure.name, self._name, self._ambient) + def open_subset(self, name, latex_name=None, coord_def={}, supersets=None): + r""" + Create an open subset of the manifold. + + An open subset is a set that is (i) included in the manifold and (ii) + open with respect to the manifold's topology. It is a topological + manifold by itself. + + As ``self`` is a submanifold of its ambient manifold, + the new open subset is also considered a submanifold of that. + Hence the returned object is an instance of + :class:`TopologicalSubmanifold`. + + INPUT: + + - ``name`` -- name given to the open subset + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote + the subset; if none are provided, it is set to ``name`` + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + - ``supersets`` -- (default: only ``self``) list of sets that the + new open subset is a subset of + + OUTPUT: + + - the open subset, as an instance of + :class:`~sage.manifolds.manifold.topological_submanifold.TopologicalSubmanifold` + + EXAMPLES:: + + sage: M = Manifold(3, 'M', structure="topological") + sage: N = Manifold(2, 'N', ambient=M, structure="topological"); N + 2-dimensional topological submanifold N immersed in the + 3-dimensional topological manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional topological submanifold N immersed in the + 3-dimensional topological manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional topological submanifold N immersed in the + 3-dimensional topological manifold M + + sage: phi = N.continuous_map(M) + sage: N.set_embedding(phi) + sage: N + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M + sage: S = N.subset('S'); S + Subset S of the + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M + sage: O = N.subset('O', is_open=True); O # indirect doctest + Open subset O of the + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M + + """ + resu = TopologicalSubmanifold(self._dim, name, self._field, + self._structure, self._ambient, + base_manifold=self._manifold, + latex_name=latex_name, + start_index=self._sindex) + if supersets is None: + supersets = [self] + for superset in supersets: + superset._init_open_subset(resu, coord_def=coord_def) + return resu + + def _init_open_subset(self, resu, coord_def): + r""" + Initialize ``resu`` as an open subset of ``self``. + + INPUT: + + - ``resu`` -- an instance of ``:class:`TopologicalManifold` or + a subclass. + + - ``coord_def`` -- (default: {}) definition of the subset in + terms of coordinates; ``coord_def`` must a be dictionary with keys + charts on the manifold and values the symbolic expressions formed + by the coordinates to define the subset + + EXAMPLES: + + sage: M = Manifold(3, 'M', structure="topological") + sage: N = Manifold(2, 'N', ambient=M, structure="topological") + sage: phi = N.continuous_map(M) + sage: N.set_embedding(phi) + sage: N + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M + sage: from sage.manifolds.topological_submanifold import TopologicalSubmanifold + sage: O = TopologicalSubmanifold(3, 'O', field=M._field, structure=M._structure, + ....: ambient=M, base_manifold=N) + sage: N._init_open_subset(O, {}) + sage: O + Open subset O of the + 2-dimensional topological submanifold N embedded in the + 3-dimensional topological manifold M + sage: O.embedding() + Continuous map + from the Open subset O of the 2-dimensional topological submanifold N + embedded in the 3-dimensional topological manifold M + to the 3-dimensional topological manifold M + """ + super()._init_open_subset(resu, coord_def=coord_def) + ## Extras for Submanifold + if self._immersed: + resu.set_immersion(self._immersion.restrict(resu), + var=self._var, t_inverse=self._t_inverse) + if self._embedded: + resu.declare_embedding() + def set_immersion(self, phi, inverse=None, var=None, t_inverse=None): r""" @@ -747,3 +869,30 @@ def embedding(self): if not self._embedded: raise ValueError("the submanifold is not embedded") return self._immersion + + def as_subset(self): + r""" + Return ``self`` as a subset of the ambient manifold. + + ``self`` must be an embedded submanifold. + + EXAMPLES:: + + sage: M = Manifold(2, 'M', structure="topological") + sage: N = Manifold(1, 'N', ambient=M, structure="topological") + sage: CM. = M.chart() + sage: CN. = N.chart() + sage: CN.add_restrictions([u > -1, u < 1]) + sage: phi = N.continuous_map(M, {(CN,CM): [u, u^2]}) + sage: N.set_embedding(phi) + sage: N + 1-dimensional topological submanifold N + embedded in the 2-dimensional topological manifold M + sage: N.as_subset() + Image of the Continuous map + from the 1-dimensional topological submanifold N + embedded in the 2-dimensional topological manifold M + to the 2-dimensional topological manifold M + + """ + return self.embedding().image() diff --git a/src/sage/matrix/constructor.pyx b/src/sage/matrix/constructor.pyx index c6712f37e3f..2525155b642 100644 --- a/src/sage/matrix/constructor.pyx +++ b/src/sage/matrix/constructor.pyx @@ -669,6 +669,30 @@ class options(GlobalOptions): sage: matrix(ZZ, 4, 6) 4 x 6 dense matrix over Integer Ring... sage: matrix.options._reset() + + The precision can also be set via the IPython magic:: + + sage: from sage.repl.interpreter import get_test_shell + sage: shell = get_test_shell() + sage: shell.run_cell('%precision 5') + '%.5f' + sage: matrix.options.precision + 5 + sage: A = matrix(RR, [[200/3]]); A + [66.667] + + The number format can be specified as well:: + + sage: matrix.options.format_numeric = '{:.{prec}e}' + sage: A + [6.66667e+1] + sage: matrix.options.format_numeric = '{:.{prec}f}' + sage: A + [66.66667] + sage: matrix.options.format_numeric = '{:+.{prec}g}' + sage: A + [+66.667] + sage: matrix.options._reset() """ NAME = 'Matrix' max_cols = dict(default=49, @@ -677,3 +701,17 @@ class options(GlobalOptions): max_rows = dict(default=19, description='maximum number of rows to display', checker=lambda val: val >= 0) + precision = \ + dict(default=None, + description='number of digits to display for floating point ' + 'entries; if ``None``, the exact representation is ' + 'used instead. This option is also set by the ' + '`IPython magic `_ ' + '``%precision``.', + checker=lambda val: val is None or val >= 0) + format_numeric = \ + dict(default='{:.{prec}}', + description='string used for formatting floating point numbers of' + ' an (optional) precision ``prec``; only supported ' + 'for entry types implementing ``__format__``', + checker=lambda val: isinstance(val.format(3.1415, prec=3), str)) diff --git a/src/sage/matrix/matrix0.pyx b/src/sage/matrix/matrix0.pyx index e58ee774efb..88d9c2af4d0 100644 --- a/src/sage/matrix/matrix0.pyx +++ b/src/sage/matrix/matrix0.pyx @@ -1875,6 +1875,25 @@ cdef class Matrix(sage.structure.element.Matrix): -0.35104242112828943 0.5084492941557279⎟ -0.9541798283979341 -0.8948790563276592⎠ + The number of floating point digits to display is controlled by + :obj:`matrix.options.precision <.constructor.options>` and can also be + set by the `IPython magic + `_ + ``%precision``. This does not affect the internal precision of the + represented data, but only the textual display of matrices:: + + sage: matrix.options.precision = 4 + sage: A = matrix(RR, [[1/3, 200/3], [-3, 1e6]]); A + [ 0.3333 66.67] + [ -3.000 1.000E+6] + sage: unicode_art(A) + ⎛ 0.3333 66.67⎞ + ⎝ -3.000 1.000E+6⎠ + sage: matrix.options.precision = None + sage: A + [ 0.333333333333333 66.6666666666667] + [ -3.00000000000000 1.00000000000000e6] + TESTS: Prior to :trac:`11544` this could take a full minute to run (2011). :: @@ -1898,6 +1917,19 @@ cdef class Matrix(sage.structure.element.Matrix): ⎛─⎞ ⎜1⎟ ⎝─⎠ + + Check that exact number types are not affected by the precision + option:: + + sage: matrix.options.precision = 4 + sage: matrix(ZZ, [[10^10]]) + [10000000000] + sage: matrix(QQ, [[2/3, 10^6]]) + [ 2/3 1000000] + sage: R. = QQ[[]] + sage: matrix(R, [[2/3 - 10^6 * x^3 + 3 * y + O(x, y)^4]]) + [2/3 + 3*y - 1000000*x^3 + O(x, y)^4] + sage: matrix.options._reset() """ cdef Py_ssize_t nr, nc, r, c nr = self._nrows @@ -1988,15 +2020,30 @@ cdef class Matrix(sage.structure.element.Matrix): if minus_one is not None: rep_mapping[-self.base_ring().one()] = minus_one + entries = self.list() + + # only use floating point formatting for inexact types that have + # custom implementation of __format__ + from .constructor import options + prec = options.precision() + if prec is None or callable(rep_mapping) or not entries \ + or type(entries[0]).__format__ is Element.__format__ \ + or self._base_ring.is_exact(): + fmt_numeric = None + else: + fmt_numeric = options.format_numeric() + # compute column widths S = [] - for x in self.list(): + for x in entries: # Override the usual representations with those specified if callable(rep_mapping): rep = rep_mapping(x) # avoid hashing entries, especially algebraic numbers elif rep_mapping and x in rep_mapping: rep = rep_mapping.get(x) + elif fmt_numeric is not None: + rep = fmt_numeric.format(x, prec=prec) else: rep = repr(x) S.append(rep) diff --git a/src/sage/matrix/matrix1.pyx b/src/sage/matrix/matrix1.pyx index f3007973a9d..80b6d583d19 100644 --- a/src/sage/matrix/matrix1.pyx +++ b/src/sage/matrix/matrix1.pyx @@ -222,12 +222,12 @@ cdef class Matrix(Matrix0): sage: P. = ZZ[] sage: M = matrix(P, 2, [-9*x^2-2*x+2, x-1, x^2+8*x, -3*x^2+5]) sage: giac(M) - [[-9*x^2-2*x+2,x-1],[x^2+8*x,-3*x^2+5]] + [[-9*sageVARx^2-2*sageVARx+2,sageVARx-1],[sageVARx^2+8*sageVARx,-3*sageVARx^2+5]] sage: y = var('y') sage: M = matrix(SR, 2, [y+sin(y), y - 4, 1/y, dilog(y)]) - sage: giac(M).det() - (y^2*dilog(y)+y*sin(y)*dilog(y)-y+4)/y + sage: giac(M).det().sage() + (y^2*dilog(y) + y*dilog(y)*sin(y) - y + 4)/y """ s = ','.join('[' + ','.join(cf._giac_init_() for cf in row) + ']' for row in self.rows()) @@ -524,6 +524,97 @@ cdef class Matrix(Matrix0): """ return scilab(self._scilab_init_()) + def _sympy_(self): + r""" + Return a SymPy matrix corresponding to ``self``. + + OUTPUT: + + - An instance of either an ``ImmutableMatrix`` or ``ImmutableSparseMatrix``, + regardless of whether ``self`` is mutable or not. + + EXAMPLES:: + + sage: A = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); A + [1 2 3] + [4 5 6] + [7 8 9] + sage: sA = A._sympy_(); sA + Matrix([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9]]) + sage: type(sA) + + + sage: I = MatrixSpace(QQ, 5, 5, sparse=True).identity_matrix() + sage: sI = I._sympy_(); sI + Matrix([ + [1, 0, 0, 0, 0], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 1]]) + sage: type(sI) + + + If ``self`` was immutable, then converting the result to Sage gives + back ``self``:: + + sage: immA = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]], immutable=True) + sage: immA._sympy_()._sage_() is immA + True + + If ``self`` was mutable, then converting back to Sage creates a new matrix:: + + sage: sA._sage_() is A + False + sage: sA._sage_() == A + True + + Symbolic matrices are supported:: + + sage: M = matrix([[sin(x), cos(x)], [-cos(x), sin(x)]]); M + [ sin(x) cos(x)] + [-cos(x) sin(x)] + sage: sM = M._sympy_(); sM + Matrix([ + [ sin(x), cos(x)], + [-cos(x), sin(x)]]) + sage: sM.subs(x, pi/4) + Matrix([ + [ sqrt(2)/2, sqrt(2)/2], + [-sqrt(2)/2, sqrt(2)/2]]) + + TESTS: + + Dense 0-column/0-row matrices:: + + sage: ZeroCol = matrix(QQ, 3, 0, sparse=False); ZeroCol + [] + sage: sZeroCol = ZeroCol._sympy_(); sZeroCol + Matrix(3, 0, []) + + sage: ZeroRow = matrix(QQ, 0, 2, sparse=False); ZeroRow + [] + sage: sZeroRow = ZeroRow._sympy_(); sZeroRow + Matrix(0, 2, []) + + """ + from sage.interfaces.sympy import sympy_init + sympy_init() + from sympy.matrices import ImmutableMatrix, ImmutableSparseMatrix + if self.is_sparse(): + matrix = ImmutableSparseMatrix(self.nrows(), self.ncols(), + self.dict(copy=False)) + else: + if not self.nrows() or not self.ncols(): + matrix = ImmutableMatrix(self.nrows(), self.ncols(), ()) + else: + matrix = ImmutableMatrix(self.rows()) + if self.is_immutable(): + matrix._sage_object = self + return matrix def _sage_input_(self, sib, coerce): r""" @@ -534,9 +625,10 @@ cdef class Matrix(Matrix0): sage: sage_input(matrix(QQ, 3, 3, [5..13])/7, verify=True) # Verified matrix(QQ, [[5/7, 6/7, 1], [8/7, 9/7, 10/7], [11/7, 12/7, 13/7]]) - sage: sage_input(MatrixSpace(GF(5), 50, 50, sparse=True).random_element(density=0.002), verify=True) - # Verified - matrix(GF(5), 50, 50, {(4,44):2, (5,25):1, (26,9):3, (43,24):3, (44,38):4}) + sage: M = MatrixSpace(GF(5), 50, 50, sparse=True).random_element(density=0.002) + sage: input = sage_input(M, verify=True) + sage: sage_eval(input) == M + True sage: from sage.misc.sage_input import SageInputBuilder sage: matrix(RDF, [[3, 1], [4, 1]])._sage_input_(SageInputBuilder(), False) {call: {atomic:matrix}({atomic:RDF}, {list: ({list: ({atomic:3}, {atomic:1})}, {list: ({atomic:4}, {atomic:1})})})} diff --git a/src/sage/matrix/matrix2.pxd b/src/sage/matrix/matrix2.pxd index a6f5b4b0251..6f99ff58d02 100644 --- a/src/sage/matrix/matrix2.pxd +++ b/src/sage/matrix/matrix2.pxd @@ -17,7 +17,8 @@ from .matrix1 cimport Matrix as Matrix1 cdef class Matrix(Matrix1): cdef _det_by_minors(self, Py_ssize_t level) cdef _pf_bfl(self) + cdef bint _is_positive_definite_or_semidefinite(self, bint semi) except -1 + cdef tuple _block_ldlt(self, bint classical) cpdef _echelon(self, str algorithm) cpdef _echelon_in_place(self, str algorithm) cpdef matrix_window(self, Py_ssize_t row=*, Py_ssize_t col=*, Py_ssize_t nrows=*, Py_ssize_t ncols=*, bint check=*) - diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 1925fd80a0e..780583b9312 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -2386,14 +2386,14 @@ cdef class Matrix(Matrix1): r""" Computes the Pfaffian of ``self`` using the Baer-Faddeev-LeVerrier algorithm. - + .. WARNING:: - + This method assumes that the base ring is an `\QQ`-algebra. OUTPUT: - - an element (possibly coerced) originated from the base ring of + - an element (possibly coerced) originated from the base ring of ``self`` representing the Pfaffian EXAMPLES: @@ -2409,7 +2409,7 @@ cdef class Matrix(Matrix1): ....: (1/2, -3/2, -1, -5/2, -1/2, 0)]) sage: A.pfaffian(algorithm='bfl') -1/2 - + TESTS:: sage: A = random_matrix(ZZ[x], 6) @@ -3224,10 +3224,8 @@ cdef class Matrix(Matrix1): sage: M = random_matrix(ZZ, 10, 20) sage: N = random_matrix(ZZ, 20, 10) - sage: M.trace_of_product(N) - -1629 - sage: (M*N).trace() - -1629 + sage: M.trace_of_product(N) == (M*N).trace() + True """ if self._nrows != other._ncols or other._nrows != self._ncols: raise ArithmeticError("incompatible dimensions") @@ -7164,10 +7162,9 @@ cdef class Matrix(Matrix1): [ 0 1 2] [ 0 0 0] - sage: B=random_matrix(QQ,3,num_bound=10); B - [ -4 -3 6] - [ 5 -5 9/2] - [3/2 -4 -7] + sage: B = random_matrix(QQ, 3, num_bound=10) + sage: while B.rank() != 3: + ....: B = random_matrix(QQ, 3, num_bound=10) sage: B.rref() [1 0 0] [0 1 0] @@ -9055,18 +9052,14 @@ cdef class Matrix(Matrix1): We then randomize roughly half the entries:: sage: a.randomize(0.5) - sage: a - [ 0 0 0] - [ 0 0 1/2*x^2 - x - 12] - [1/2*x^2 - 1/95*x - 1/2 0 0] + sage: a.density() <= 0.5 + True Now we randomize all the entries of the resulting matrix:: - sage: a.randomize() - sage: a - [ 0 -5/2*x^2 + 2/3*x - 1/4 -x^2 + 2/3*x] - [ 1 x^2 + 1/3*x - 1 -1] - [ -1 -x^2 - 1/4*x + 1 -1/14] + sage: while a.density() < 0.9: + ....: a = matrix(QQ['x'], 3) + ....: a.randomize() We create the zero matrix over the integers:: @@ -9081,9 +9074,10 @@ cdef class Matrix(Matrix1): :: sage: a.randomize(x=-2^64, y=2^64) - sage: a - [-3789934696463997112 -3775200185786627805] - [-8014573080042851913 7914454492632997238] + sage: while all(abs(b) < 2^63 for b in a.list()): + ....: a.randomize(x=-2^64, y=2^64) + sage: all(abs(b) < 2^64 for b in a.list()) + True """ randint = current_randstate().python_random().randint @@ -9609,8 +9603,8 @@ cdef class Matrix(Matrix1): :: sage: A = random_matrix(GF(127),200,200,density=0.3) - sage: A.density() - 5211/20000 + sage: A.density() <= 0.3 + True :: @@ -10671,19 +10665,19 @@ cdef class Matrix(Matrix1): correctness. Set this to ``False`` for a speedup if the eigenvalues are known to be correct. - NOTES: + .. NOTE:: - Currently, the Jordan normal form is not computed over inexact rings - in any but the trivial cases when the matrix is either `0 \times 0` - or `1 \times 1`. + Currently, the Jordan normal form is not computed over + inexact rings in any but the trivial cases when the matrix + is either `0 \times 0` or `1 \times 1`. - In the case of exact rings, this method does not compute any - generalized form of the Jordan normal form, but is only able to - compute the result if the characteristic polynomial of the matrix - splits over the specific base ring. + In the case of exact rings, this method does not compute any + generalized form of the Jordan normal form, but is only able to + compute the result if the characteristic polynomial of the matrix + splits over the specific base ring. - Note that the base ring must be a field or a ring with an implemented - fraction field. + Note that the base ring must be a field or a ring with an + implemented fraction field. EXAMPLES:: @@ -12205,45 +12199,19 @@ cdef class Matrix(Matrix1): else: return subspace - def _cholesky_decomposition_(self): - r""" - Return the Cholesky decomposition of ``self``; see ``cholesky_decomposition``. - - This generic implementation uses a standard recursion. - """ - L = self.fetch('cholesky_broken') - if L is None: - A = self.__copy__() - L = A.parent()(0) - n = self.nrows() - for k in range(0, n-1 + 1): - try: - L[k, k] = A[k, k].sqrt() - except TypeError: - raise ValueError("The input matrix was not symmetric and positive definite") - - for s in range(k+1, n): - L[s, k] = A[s, k] / L[k, k] - for j in range(k+1, n): - for i in range(j, n): - A[i, j] -= L[i, k]*L[j, k].conjugate() - L.set_immutable() - self.cache('cholesky_broken', L) - return L def cholesky(self): r""" - Returns the Cholesky decomposition of a symmetric or Hermitian matrix. + Returns the Cholesky decomposition of a Hermitian matrix. INPUT: - A square matrix that is real, symmetric and positive definite. - Or a square matrix that is complex, Hermitian and positive - definite. Generally, the base ring for the entries of the - matrix needs to be a subfield of the algebraic numbers - (``QQbar``). Examples include the rational numbers (``QQ``), - some number fields, and real algebraic numbers and the - algebraic numbers themselves. + A positive-definite matrix. Generally, the base ring for the + entries of the matrix needs to be a subfield of the algebraic + numbers (``QQbar``). Examples include the rational numbers + (``QQ``), some number fields, and real algebraic numbers and + the algebraic numbers themselves. Symbolic matrices can also + occasionally be factored. OUTPUT: @@ -12254,30 +12222,37 @@ cdef class Matrix(Matrix1): A = LL^\ast - where `L^\ast` is the conjugate-transpose in the complex case, - and just the transpose in the real case. If the matrix - fails to be positive definite (perhaps because it is not - symmetric or Hermitian), then a ``ValueError`` results. + where `L^\ast` is the conjugate-transpose. If the matrix is + not positive-definite (for example, if it is not Hermitian) + then a ``ValueError`` results. + + If possible, the output matrix will be over the fraction field + of the base ring of the input matrix. If that fraction field + is missing the requisite square roots but if no imaginaries + are encountered, then the algebraic-reals will be used. + Otherwise, the algebraic closure of the fraction field + (typically ``QQbar``) will be used. ALGORITHM: - Whether or not the matrix is positive definite is checked - first in every case. This is accomplished with an - indefinite factorization (see :meth:`indefinite_factorization`) - which caches its result. This algorithm is of an order `n^3/3`. - If the matrix is positive definite, this computation always - succeeds, using just field operations. The transition to a - Cholesky decomposition "only" requires computing square roots - of the positive (real) entries of the diagonal matrix produced in - the indefinite factorization. Hence, there is no real penalty - in the positive definite check (here, or prior to calling this - routine), but a field extension with square roots may not be - implemented in all reasonable cases. + First we ensure that the matrix `A` + :meth:`~.Matrix.is_hermitian`. Afterwards, we attempt to + compute a classical :meth:`block_ldlt` factorization, `A = + LDL^{*}`, of the matrix. If that fails, then the matrix was + not positive-definite and an error is raised. Otherwise we + take the entrywise square-root `\sqrt{D}` of the diagonal + matrix `D` (whose entries are the positive eigenvalues of the + original matrix) to obtain the Cholesky factorization `A = + \left(L\sqrt{D}\right)\left(L\sqrt{D}\right)^{*}`. If the + necessary square roots cannot be taken in the fraction field + of original base ring, then we move to either its algebraic + closure or the algebraic reals, depending on whether or not + imaginary numbers are required. EXAMPLES: This simple example has a result with entries that remain - in the field of rational numbers. :: + in the field of rational numbers:: sage: A = matrix(QQ, [[ 4, -2, 4, 2], ....: [-2, 10, -2, -7], @@ -12299,7 +12274,7 @@ cdef class Matrix(Matrix1): This seemingly simple example requires first moving to the rational numbers for field operations, and then square roots necessitate that the result has entries in the field - of algebraic numbers. :: + of algebraic numbers:: sage: A = matrix(ZZ, [[ 78, -30, -37, -2], ....: [-30, 102, 179, -18], @@ -12320,7 +12295,7 @@ cdef class Matrix(Matrix1): Some subfields of the complex numbers, such as this number field of complex numbers with rational real and imaginary parts, - allow for this computation. :: + allow for this computation:: sage: C. = QuadraticField(-1) sage: A = matrix(C, [[ 23, 17*I + 3, 24*I + 25, 21*I], @@ -12341,7 +12316,7 @@ cdef class Matrix(Matrix1): True The field of algebraic numbers is an ideal setting for this - computation. :: + computation:: sage: A = matrix(QQbar, [[ 2, 4 + 2*I, 6 - 4*I], ....: [ -2*I + 4, 11, 10 - 12*I], @@ -12355,12 +12330,11 @@ cdef class Matrix(Matrix1): [4.242640687119285? + 2.828427124746190?*I -2*I + 2 1.732050807568878?] sage: L.parent() Full MatrixSpace of 3 by 3 dense matrices over Algebraic Field - sage: (L*L.conjugate_transpose() - A.change_ring(QQbar)).norm() < 10^-10 + sage: L*L.conjugate_transpose() == A True - Results are cached, hence immutable. Use the ``copy`` function - if you need to make a change. :: + if you need to make a change:: sage: A = matrix(QQ, [[ 4, -2, 4, 2], ....: [-2, 10, -2, -7], @@ -12369,7 +12343,6 @@ cdef class Matrix(Matrix1): sage: L = A.cholesky() sage: L.is_immutable() True - sage: from copy import copy sage: LC = copy(L) sage: LC[0,0] = 1000 @@ -12379,50 +12352,48 @@ cdef class Matrix(Matrix1): [ 2 0 2 0] [ 1 -2 1 1] - There are a variety of situations which will prevent the computation of a Cholesky decomposition. - - The base ring must be exact. For numerical work, create a - matrix with a base ring of ``RDF`` or ``CDF`` and use the - :meth:`~sage.matrix.matrix_double_dense.Matrix_double_dense.cholesky` - method for matrices of that type. :: + The base ring need not be exact, although you should expect + the result to be inexact (correct only in the norm) as well + in that case:: sage: F = RealField(100) - sage: A = matrix(F, [[1.0, 3.0], [3.0, -6.0]]) + sage: A = A = matrix(F, [[1.0, 2.0], [2.0, 6.0]]) + sage: L = A.cholesky(); L + [ 1.000... 0.000...] + [ 2.000... 1.414...] + sage: (L*L.transpose() - A).norm() < 1e-10 + True + + Even symbolic matrices can sometimes be factored:: + + sage: A = matrix(SR, [[pi,0],[0,pi]]) sage: A.cholesky() - Traceback (most recent call last): - ... - TypeError: base ring of the matrix must be exact, not Real Field with 100 bits of precision + [sqrt(pi) 0] + [ 0 sqrt(pi)] + + There are a variety of situations which will prevent the + computation of a Cholesky decomposition. - The base ring may not have a fraction field. :: + The base ring may not be able to be viewed as a subset of the + complex numbers, implying that "Hermitian" is meaningless:: sage: A = matrix(Integers(6), [[2, 0], [0, 4]]) sage: A.cholesky() Traceback (most recent call last): ... - ValueError: Could not see Ring of integers modulo 6 as a subring of - the real or complex numbers + AttributeError: 'sage.rings.finite_rings.integer_mod.IntegerMod_int' + object has no attribute 'conjugate' - The base field may not have elements that are comparable to zero. :: + The matrix may not be Hermitian:: sage: F. = FiniteField(5^4) sage: A = matrix(F, [[2+a^3, 3], [3, 3]]) sage: A.cholesky() Traceback (most recent call last): ... - ValueError: Could not see Finite Field in a of size 5^4 as a subring - of the real or complex numbers - - The algebraic closure of the fraction field of the base ring may not be implemented. :: - - sage: F = Integers(7) - sage: A = matrix(F, [[4, 0], [0, 3]]) - sage: A.cholesky() - Traceback (most recent call last): - ... - ValueError: Could not see Ring of integers modulo 7 as a subring of - the real or complex numbers + ValueError: matrix is not Hermitian - The matrix may not be positive definite. :: + The matrix may not be positive-definite:: sage: C. = QuadraticField(-1) sage: B = matrix(C, [[ 2, 4 - 2*I, 2 + 2*I], @@ -12433,11 +12404,9 @@ cdef class Matrix(Matrix1): sage: B.cholesky() Traceback (most recent call last): ... - ValueError: matrix is not positive definite, - so cannot compute Cholesky decomposition + ValueError: matrix is not positive definite - The matrix could be positive semi-definite, and thus - lack a Cholesky decomposition. :: + :: sage: A = matrix(QQ, [[21, 15, 12, -3], ....: [15, 12, 9, 12], @@ -12445,17 +12414,14 @@ cdef class Matrix(Matrix1): ....: [-3, 12, 3, 8]]) sage: A.is_positive_definite() False - sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] - [21, 27, 0, 0] sage: A.cholesky() Traceback (most recent call last): ... - ValueError: matrix is not positive definite, - so cannot compute Cholesky decomposition + ValueError: matrix is not positive definite TESTS: - This verifies that :trac:`11274` is resolved. :: + This verifies that :trac:`11274` is resolved:: sage: E = matrix(QQ, [[2, 1], [1, 1]]) sage: E.is_symmetric() @@ -12478,53 +12444,88 @@ cdef class Matrix(Matrix1): sage: E = matrix(QQ, [[2, 1], [1, 1]]) sage: E.cholesky().base_ring() Algebraic Real Field + + Check that sparse floating-point matrices can be factored + using a toy example reported as part of :trac:`13674`:: + + sage: A = matrix(RDF, [[1, 1], [1, 2]], sparse=True) + sage: A.cholesky() + [1.0 0.0] + [1.0 1.0] + sage: A = matrix(CDF, [[1, I], [-I, 2]], sparse=True) + sage: A.cholesky() + [ 1.0 0.0] + [-1.0*I 1.0] """ - from copy import copy + cdef Matrix C # output matrix C = self.fetch('cholesky') - if C is None: - if not self.is_square(): - msg = "matrix must be square, not {0} x {1}" - raise ValueError(msg.format(self.nrows(), self.ncols())) - if not self.base_ring().is_exact(): - msg = 'base ring of the matrix must be exact, not {0}' - raise TypeError(msg.format(self.base_ring())) - if not self.is_positive_definite(): - msg = 'matrix is not positive definite, so cannot compute Cholesky decomposition' - raise ValueError(msg) - # the successful positive definite check will cache a Hermitian - # or symmetric indefinite factorization, as appropriate - factors = self.fetch('indefinite_factorization_hermitian') - if factors is None: - factors = self.fetch('indefinite_factorization_symmetric') - from sage.rings.qqbar import AA as F_ac + if C is not None: + return C + + cdef Py_ssize_t n = self.nrows() + + if not self.is_hermitian(): + raise ValueError("matrix is not Hermitian") + + # Use classical=True to ensure that we don't get a permuted L. + cdef Matrix L # block_ldlt() results + cdef list d # block_ldlt() results + try: + _,L,d = self._block_ldlt(True) + except ValueError: + # If the matrix was positive-definite, that would + # have worked. + raise ValueError("matrix is not positive definite") + + F = L.base_ring() # field really + zero = F.zero() + + cdef list splits = [] # square roots of diagonal entries + cdef bint extend = False + for X in d: + # The X are guaranteed to be one-by-one blocks. + x = X[0,0] + + if x <= zero: + raise ValueError("matrix is not positive definite") + + if not extend and x.is_square(): + sqrt = x.sqrt() else: - from sage.rings.qqbar import QQbar as F_ac - - L = factors[0] - d = factors[1] - F = L.base_ring() # field really - splits = [] # square roots of diagonal entries - extend = False - for x in d: - if not extend and x.is_square(): - sqrt = x.sqrt() - else: - extend = True - sqrt = F_ac(x).sqrt() - splits.append(sqrt) - # move square root of the diagonal matrix - # into the lower triangular matrix - # We need a copy, to break immutability - # and the field may have changed as well - if extend: + extend = True + if hasattr(F, 'algebraic_closure'): + # This can fail if, say, F is the symbolic ring which does + # contain the requisite roots in its own special way but + # does not have an algebraic_closure() method. + F_ac = F.algebraic_closure() + sqrt = F_ac(x).sqrt() + splits.append(sqrt) + # move square root of the diagonal matrix + # into the lower triangular matrix + # We need a copy, to break immutability + # and the field may have changed as well + if extend: + # Try to use the algebraic reals but fall back to what is + # probably QQbar if we find an entry in "L" that won't fit + # in AA. This was Trac ticket #18381. + from sage.rings.qqbar import AA + try: + C = L.change_ring(AA) + except ValueError: # cannot coerce... C = L.change_ring(F_ac) - else: - C = L.__copy__() + else: + C = L.__copy__() - for c in range(C.ncols()): - C.rescale_col(c, splits[c]) - C.set_immutable() - self.cache('cholesky', C) + # Overwrite the (strict) upper-triangular part of "C", since a + # priori it contains junk after _block_ldlt(). + zero = C.base_ring().zero() + cdef Py_ssize_t i, j # loop indices + for i in range(n): + C.rescale_col_c(i, splits[i], 0) + for j in range(i+1,n): + C.set_unsafe(i,j,zero) + C.set_immutable() + self.cache('cholesky', C) return C def inverse_positive_definite(self): @@ -12641,17 +12642,14 @@ cdef class Matrix(Matrix1): sage: actual == expected True """ - # Does it hurt if we conjugate a real number? - L, diags = self.indefinite_factorization(algorithm='hermitian', - check=False) + P,L,D = self.block_ldlt() # The default "echelonize" inverse() method works just fine for # triangular matrices. L_inv = L.inverse() - from sage.matrix.constructor import diagonal_matrix - D_inv = diagonal_matrix( ~d for d in diags ) - return L_inv.conjugate_transpose()*D_inv*L_inv + # Take A = PLDL^{*}P^{T} and simply invert. + return P*L_inv.conjugate_transpose()*D.inverse()*L_inv*P.transpose() def LU(self, pivot=None, format='plu'): @@ -13554,7 +13552,7 @@ cdef class Matrix(Matrix1): raise ValueError(msg.format(d)) return L, vector(L.base_ring(), d) - def _block_ldlt(self): + cdef tuple _block_ldlt(self, bint classical): r""" Perform a user-unfriendly block-`LDL^{T}` factorization of the Hermitian matrix `A` @@ -13581,21 +13579,14 @@ cdef class Matrix(Matrix1): method can be found in the user-facing :meth:`block_ldlt` method. - EXAMPLES: - - Test that this method can be called directly; the returned ``l`` - is not inspected because its upper-triangular part is undefined:: - - sage: A = matrix(QQ, [[0, 1, 0], - ....: [1, 1, 2], - ....: [0, 2, 0]]) - sage: p,l,d = A._block_ldlt() - sage: p - array('I', [1, 2, 0]) - sage: d - [[1], [-4], [0]] - """ + cdef str cache_string = "_block_ldlt" + if classical: + cache_string += "_classical" + cdef tuple result = self.fetch(cache_string) + if result is not None: + return result + cdef Py_ssize_t i, j, k # loop indices cdef Py_ssize_t r # another row/column index @@ -13686,6 +13677,19 @@ cdef class Matrix(Matrix1): k += 1 continue + if classical: + try: + # This is a "step zero" that doesn't appear in the published algorithms. + # It's a back door that lets us escape with only the standard non-block + # non-pivoting LDL^T factorization. This allows us to implement e.g. + # indefinite_factorization() in terms of this method. + d.append( one_by_one_space(A_kk) ) + _block_ldlt_pivot1x1(A,k) + k += 1 + continue + except ZeroDivisionError: + raise ValueError("matrix has no classical LDL^T factorization") + # Find the largest subdiagonal entry (in magnitude) in the # kth column. This occurs prior to Step (1) in Higham, # but is part of Step (1) in Bunch and Kaufman. We adopt @@ -13821,9 +13825,11 @@ cdef class Matrix(Matrix1): # correctness. A.set_unsafe(i, i, one) - return (p,A,d) + result = (p,A,d) + self.cache(cache_string, result) + return result - def block_ldlt(self): + def block_ldlt(self, classical=False): r""" Compute a block-`LDL^{T}` factorization of a Hermitian matrix. @@ -13849,9 +13855,23 @@ cdef class Matrix(Matrix1): Hermitian systems, and that is how we choose our row and column swaps. + INPUT: + + * ``classical`` -- (default: ``False``) whether or not to + attempt a classical non-block `LDL^{T}` factorization + with no row/column swaps. + + .. WARNING:: + + Not all matrices have a classical `LDL^{T}` factorization. + Set ``classical=True`` at your own risk, preferably after + verifying that your matrix is positive-definite and (over + inexact rings) not ill-conditioned. + OUTPUT: - If the input matrix is Hermitian, we return a triple `(P,L,D)` + If the input matrix is not Hermitian, the output from this + function is undefined. Otherwise, we return a triple `(P,L,D)` such that `A = PLDL^{*}P^{T}` and * `P` is a permutation matrix, @@ -13859,8 +13879,10 @@ cdef class Matrix(Matrix1): * `D` is a block-diagonal matrix whose blocks are of size one or two. - If the input matrix is not Hermitian, the output from this - function is undefined. + With ``classical=True``, the permutation matrix `P` is always + an identity matrix and the diagonal blocks are always + one-by-one. A ``ValueError`` is raised if the matrix has no + classical `LDL^{T}` factorization. ALGORITHM: @@ -13904,11 +13926,15 @@ cdef class Matrix(Matrix1): sage: P.transpose()*A*P == L*D*L.transpose() True - This two-by-two matrix has no standard factorization, but it + This two-by-two matrix has no classical factorization, but it constitutes its own block-factorization:: sage: A = matrix(QQ, [ [0,1], ....: [1,0] ]) + sage: A.block_ldlt(classical=True) + Traceback (most recent call last): + ... + ValueError: matrix has no classical LDL^T factorization sage: A.block_ldlt() ( [1 0] [1 0] [0 1] @@ -13919,6 +13945,10 @@ cdef class Matrix(Matrix1): sage: A = matrix(QQbar, [ [ 0,I], ....: [-I,0] ]) + sage: A.block_ldlt(classical=True) + Traceback (most recent call last): + ... + ValueError: matrix has no classical LDL^T factorization sage: A.block_ldlt() ( [1 0] [1 0] [ 0 I] @@ -13932,6 +13962,10 @@ cdef class Matrix(Matrix1): sage: A = matrix(RDF, 2, 2, [ [1e-10, 1 ], ....: [1 , 2e-10] ]) + sage: _,L,D = A.block_ldlt(classical=True) + sage: L*D*L.T + [1e-10 1.0] + [ 1.0 0.0] sage: A.block_ldlt() ( [1.0 0.0] [1.0 0.0] [1e-10 1.0] @@ -13950,6 +13984,72 @@ cdef class Matrix(Matrix1): sage: (P.T*A*P - L*D*L.H).norm() < 1e-10 True + This matrix has a singular three-by-three leading principal + submatrix, and therefore has no classical factorization:: + + sage: A = matrix(QQ, [[21, 15, 12, -2], + ....: [15, 12, 9, 6], + ....: [12, 9, 7, 3], + ....: [-2, 6, 3, 8]]) + sage: A[0:3,0:3].det() == 0 + True + sage: A.block_ldlt(classical=True) + Traceback (most recent call last): + ... + ValueError: matrix has no classical LDL^T factorization + sage: A.block_ldlt() + ( + [1 0 0 0] [ 1 0 0 0] + [0 0 1 0] [ -2/21 1 0 0] + [0 0 0 1] [ 5/7 39/41 1 0] + [0 1 0 0], [ 4/7 87/164 48/79 1], + + [ 21| 0| 0| 0] + [-------+-------+-------+-------] + [ 0| 164/21| 0| 0] + [-------+-------+-------+-------] + [ 0| 0|-237/41| 0] + [-------+-------+-------+-------] + [ 0| 0| 0| 25/316] + ) + + An indefinite symmetric matrix that happens to have a + classical factorization:: + + sage: A = matrix(QQ, [[ 3, -6, 9, 6, -9], + ....: [-6, 11, -16, -11, 17], + ....: [ 9, -16, 28, 16, -40], + ....: [ 6, -11, 16, 9, -19], + ....: [-9, 17, -40, -19, 68]]) + sage: A.block_ldlt(classical=True)[1:] + ( + [ 3| 0| 0| 0| 0] + [--+--+--+--+--] + [ 0|-1| 0| 0| 0] + [--+--+--+--+--] + [ 1 0 0 0 0] [ 0| 0| 5| 0| 0] + [-2 1 0 0 0] [--+--+--+--+--] + [ 3 -2 1 0 0] [ 0| 0| 0|-2| 0] + [ 2 -1 0 1 0] [--+--+--+--+--] + [-3 1 -3 1 1], [ 0| 0| 0| 0|-1] + ) + + An indefinite Hermitian matrix that happens to have a + classical factorization:: + + sage: F. = QuadraticField(-1) + sage: A = matrix(F, [[ 2, 4 - 2*I, 2 + 2*I], + ....: [4 + 2*I, 8, 10*I], + ....: [2 - 2*I, -10*I, -3]]) + sage: A.block_ldlt(classical=True)[1:] + ( + [ 2| 0| 0] + [--+--+--] + [ 1 0 0] [ 0|-2| 0] + [ I + 2 1 0] [--+--+--] + [ -I + 1 2*I + 1 1], [ 0| 0| 3] + ) + TESTS: All three factors should be the identity when the input matrix is:: @@ -13976,7 +14076,7 @@ cdef class Matrix(Matrix1): sage: set_random_seed() sage: n = ZZ.random_element(6) - sage: F = NumberField(x^2 +1, 'I') + sage: F = QuadraticField(-1, 'I') sage: A = matrix.random(F, n) sage: A = A + A.conjugate_transpose() sage: P,L,D = A.block_ldlt() @@ -13989,7 +14089,7 @@ cdef class Matrix(Matrix1): sage: set_random_seed() sage: n = ZZ.random_element(6) - sage: F = NumberField(x^2 +1, 'I') + sage: F = QuadraticField(-1, 'I') sage: A = matrix.random(F, n) sage: A = A*A.conjugate_transpose() sage: P,L,D = A.block_ldlt() @@ -14018,12 +14118,25 @@ cdef class Matrix(Matrix1): sage: L.base_ring() == D.base_ring() True + Ensure that a "random" real positive-definite symmetric matrix + has a classical factorization that agrees with + :meth:`indefinite_factorization`:: + + sage: set_random_seed() + sage: n = ZZ.random_element(6) + sage: A = matrix.random(QQ, n) + sage: A = A*A.transpose() + matrix.identity(QQ, n) + sage: _,L,D = A.block_ldlt(classical=True) + sage: l,d = A.indefinite_factorization() + sage: L == l and D == matrix.diagonal(d) + True + """ cdef Py_ssize_t n # size of the matrices cdef Py_ssize_t i, j # loop indices cdef Matrix P,L,D # output matrices - p,L,d = self._block_ldlt() + p,L,d = self._block_ldlt(classical) MS = L.matrix_space() P = MS.matrix(lambda i,j: p[j] == i) @@ -14044,6 +14157,51 @@ cdef class Matrix(Matrix1): return (P,L,D) + cdef bint _is_positive_definite_or_semidefinite(self, bint semi) except -1: + """ + This is an internal wrapper that allows us to implement both + :meth:`is_positive_definite` and + :meth:`is_positive_semidefinite` with essentially the same + code. The boolean ``semi`` argument exists only to change + "greater than zero" into "greater than or equal to zero." + """ + from sage.symbolic.ring import SR + from sage.rings.real_lazy import RLF,CLF + + R = self.base_ring() + + if not (RLF.has_coerce_map_from(R) or + R.has_coerce_map_from(RLF) or + CLF.has_coerce_map_from(R) or + R.has_coerce_map_from(CLF) or + R is SR): + # This is necessary to avoid "going through the motions" + # with e.g. a one-by-one identity matrix over the finite + # field of order 5^2, which might otherwise look positive- + # definite. + raise ValueError("Could not see {} as a subring of the " + "real or complex numbers".format(R)) + + if not self.is_hermitian(): + return False + + if self._nrows == 0: + return True # vacuously + + cdef list d + _,_,d = self._block_ldlt(False) + + # Check each 1x1 block for a nonpositive (negative) entry. If + # we don't find any, the matrix is positive-(semi)definite. The + # presence of any 2x2 blocks also indicates indefiniteness. + import operator + op = operator.gt + if semi: + op = operator.ge + + return all(d_i.nrows() == 1 and op(d_i[0,0], 0) for d_i in d) + + def is_positive_semidefinite(self): r""" Returns whether or not this matrix is positive-semidefinite. @@ -14144,7 +14302,7 @@ cdef class Matrix(Matrix1): return ``False``):: sage: set_random_seed() - sage: F = NumberField(x^2 + 1, 'I') + sage: F = QuadraticField(-1, 'I') sage: from sage.misc.prandom import choice sage: ring = choice([ZZ, QQ, F, RDF, CDF]) sage: A = matrix.random(ring, 10); A = A + A.conjugate_transpose() @@ -14157,76 +14315,78 @@ cdef class Matrix(Matrix1): sage: actual = A.is_positive_semidefinite() sage: actual == expected True - """ - if not self.is_hermitian(): - return False - if self._nrows == 0: - return True # vacuously + We reject matrices whose base fields cannot be coerced to + either real numbers, complex numbers, or symbolics; otherwise + we risk returning nonsensical results:: - cdef list d - _,_,d = self._block_ldlt() - - # Check each 1x1 block for a negative entry. If we don't - # find any, it's positive-semidefinite. The presence of - # any 2x2 blocks also indicates indefiniteness. - for d_i in d: - if d_i.nrows() == 1: - if d_i < 0: - return False - else: - # There's a 2x2 block - return False - return True + sage: F = FiniteField(5^2) + sage: A = matrix.identity(F, 1) + sage: A.is_positive_semidefinite() + Traceback (most recent call last): + ... + ValueError: Could not see Finite Field in z2 of size 5^2 + as a subring of the real or complex numbers + + """ + return self._is_positive_definite_or_semidefinite(True) def is_positive_definite(self, certificate=False): r""" - Determines if a real or symmetric matrix is positive definite. + Determine if a matrix is positive-definite. - A square matrix `A` is positive definite if it is - symmetric with real entries or Hermitian with complex entries, - and for every non-zero vector `\vec{x}` + A matrix `A` is positive definite if it + :meth:`~.Matrix.is_hermitian` and if, for every non-zero + vector `x`, .. MATH:: - \vec{x}^\ast A\vec{x} > 0. - - Here `\vec{x}^\ast` is the conjugate-transpose, which can be - simplified to just the transpose in the real case. + \left\langle Ax, x \right\rangle > 0. ALGORITHM: - A matrix is positive definite if and only if the - diagonal entries from the indefinite factorization - are all positive (see :meth:`indefinite_factorization`). - So this algorithm is of order ``n^3/3`` and may be applied - to matrices with elements of any ring that has a fraction - field contained within the reals or complexes. + A Hermitian matrix is positive-definite if and only if the + diagonal blocks in its :meth:`block_ldlt` factorization are + all 1-by-1 and have positive entries. We first check that the + matrix :meth:`~.Matrix.is_hermitian`, and then compute this + factorization. INPUT: - Any square matrix. - + - ``self`` -- a matrix - ``certificate`` -- (default: ``False``) return the - decomposition from the indefinite factorization if possible + lower-triangular and diagonal parts of the :meth:`block_ldlt` + factorization when the matrix is positive-definite. Deprecated. OUTPUT: - This routine will return ``True`` if the matrix is square, - symmetric or Hermitian, and meets the condition above - for the quadratic form. + This routine will return ``True`` if the matrix is Hermitian + and meets the condition above for the quadratic form. + + The base ring for the elements of the matrix must - The base ring for the elements of the matrix needs to - have a fraction field implemented and the computations - that result from the indefinite factorization must be - convertible to real numbers that are comparable to zero. + 1. Have a fraction field implemented; and + 2. Be a subring of the real numbers, complex numbers, + or symbolic ring. + + If ``certificate`` is ``True``, a triplet ``(b, L, d)`` will + be returned instead, with ``b`` containing the result (true or + false). If the matrix is positive-definite, then ``L`` and + ``d`` will contain the lower-triangular and diagonal parts of + the :meth:`block_ldlt` factorization, respectively. Or if the + matrix is not positive-definite (that is, if ``b`` is + ``False``), then both ``L`` and ``d`` will be ``None``. + + .. SEEALSO:: + + :meth:`block_ldlt`, :meth:`~.Matrix.is_hermitian`, + :meth:`is_positive_semidefinite` EXAMPLES: - A real symmetric matrix that is positive definite, - as evidenced by the positive entries for the diagonal - matrix of the indefinite factorization and the positive - determinants of the leading principal submatrices. :: + A real symmetric matrix that is positive-definite, as + evidenced by the positive determinants of its leading + principal submatrices:: sage: A = matrix(QQ, [[ 4, -2, 4, 2], ....: [-2, 10, -2, -7], @@ -14234,14 +14394,12 @@ cdef class Matrix(Matrix1): ....: [ 2, -7, 4, 7]]) sage: A.is_positive_definite() True - sage: _, d = A.indefinite_factorization(algorithm='symmetric') - sage: d - (4, 9, 4, 1) sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] [4, 36, 144, 144] - A real symmetric matrix which is not positive definite, along - with a vector that makes the quadratic form negative. :: + A real symmetric matrix that is not positive-definite and a + vector ``u`` that makes the corresponding quadratic form + negative:: sage: A = matrix(QQ, [[ 3, -6, 9, 6, -9], ....: [-6, 11, -16, -11, 17], @@ -14250,18 +14408,13 @@ cdef class Matrix(Matrix1): ....: [-9, 17, -40, -19, 68]]) sage: A.is_positive_definite() False - sage: _, d = A.indefinite_factorization(algorithm='symmetric') - sage: d - (3, -1, 5, -2, -1) - sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] - [3, -3, -15, 30, -30] sage: u = vector(QQ, [2, 2, 0, 1, 0]) - sage: u.row()*A*u - (-3) + sage: (A*u).inner_product(u) + -3 - A real symmetric matrix with a singular leading - principal submatrix, that is therefore not positive definite. - The vector ``u`` makes the quadratic form zero. :: + Another real symmetric matrix that is not positive-definite + and a vector ``u`` that makes the corresponding quadratic form + zero:: sage: A = matrix(QQ, [[21, 15, 12, -2], ....: [15, 12, 9, 6], @@ -14269,13 +14422,13 @@ cdef class Matrix(Matrix1): ....: [-2, 6, 3, 8]]) sage: A.is_positive_definite() False - sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] - [21, 27, 0, -75] sage: u = vector(QQ, [1,1,-3,0]) - sage: u.row()*A*u - (0) + sage: (A*u).inner_product(u) + 0 - An Hermitian matrix that is positive definite. :: + A complex Hermitian matrix that is positive-definite, + confirmed by the positive determinants of its leading + principal submatrices:: sage: C. = NumberField(x^2 + 1, embedding=CC(0,1)) sage: A = matrix(C, [[ 23, 17*I + 3, 24*I + 25, 21*I], @@ -14284,14 +14437,11 @@ cdef class Matrix(Matrix1): ....: [ -21*I, -7*I + 15, -24*I + 6, 28]]) sage: A.is_positive_definite() True - sage: _, d = A.indefinite_factorization(algorithm='hermitian') - sage: d - (23, 576/23, 89885/144, 142130/17977) sage: [A[:i,:i].determinant() for i in range(1,A.nrows()+1)] [23, 576, 359540, 2842600] - An Hermitian matrix that is not positive definite. - The vector ``u`` makes the quadratic form negative. :: + An Hermitian matrix that is not positive-definite and a vector + ``u`` that makes the corresponding quadratic form negative:: sage: C. = QuadraticField(-1) sage: B = matrix(C, [[ 2, 4 - 2*I, 2 + 2*I], @@ -14299,16 +14449,13 @@ cdef class Matrix(Matrix1): ....: [2 - 2*I, -10*I, -3]]) sage: B.is_positive_definite() False - sage: _, d = B.indefinite_factorization(algorithm='hermitian') - sage: d - (2, -2, 3) - sage: [B[:i,:i].determinant() for i in range(1,B.nrows()+1)] - [2, -4, -12] sage: u = vector(C, [-5 + 10*I, 4 - 3*I, 0]) - sage: u.row().conjugate()*B*u - (-50) + sage: (B*u).hermitian_inner_product(u) + -50 - A positive definite matrix over an algebraically closed field. :: + A positive-definite matrix over an algebraically-closed field, + confirmed by the positive determinants of its leading + principal submatrices:: sage: A = matrix(QQbar, [[ 2, 4 + 2*I, 6 - 4*I], ....: [ -2*I + 4, 11, 10 - 12*I], @@ -14320,11 +14467,9 @@ cdef class Matrix(Matrix1): TESTS: - If the base ring lacks a ``conjugate`` method, it - will be assumed to not be Hermitian and thus symmetric. - If the base ring does not make sense as a subfield of - the reals, then this routine will fail since comparison - to zero is meaningless. :: + If the base ring does not make sense as a subfield of the real + numbers, complex numbers, or symbolic ring, then this routine + will fail since comparison to zero is meaningless:: sage: F. = FiniteField(5^3) sage: a.conjugate() @@ -14342,47 +14487,38 @@ cdef class Matrix(Matrix1): ValueError: Could not see Finite Field in a of size 5^3 as a subring of the real or complex numbers - The 0x0 matrix is trivially positive definite:: + The 0x0 matrix is trivially positive-definite:: sage: Matrix(0).is_positive_definite() True - """ - from sage.rings.real_lazy import RLF,CLF - R = self.base_ring() - if RLF.has_coerce_map_from(R): - if not self.is_symmetric(): - return False - L, d = self._indefinite_factorization('symmetric', check=False) - real = True - elif CLF.has_coerce_map_from(R): - if not self.is_hermitian(): - return False - L, d = self._indefinite_factorization('hermitian', check=False) - real = False - else: - raise ValueError("Could not see {} as a subring of the " - "real or complex numbers".format(R)) - - if L is False: - return False - - # Now have diagonal entries (hopefully real) and so can - # test with a generator (which will short-circuit) - # positive definite iff all entries of d are positive - zero = R.fraction_field().zero() - if real: - is_pos = all(x > zero for x in d) - else: - is_pos = all(x.real() > zero for x in d) + We can check positive-definiteness of matrices over + approximate real/complex and symbolic rings:: + sage: matrix.identity(RR,4).is_positive_definite() + True + sage: matrix.identity(CC,4).is_positive_definite() + True + sage: matrix.identity(SR,4).is_positive_definite() + True + """ + result = self._is_positive_definite_or_semidefinite(False) if certificate: - if is_pos: - return is_pos, L, d - else: - return is_pos, None, None + from sage.misc.superseded import deprecation + msg = "the 'certificate' argument is deprecated; if you " + msg += "need the corresponding factorization, you can " + msg += "simply compute it yourself (the results are cached)" + deprecation(31619, msg) + L = None + d = None + if result: + from sage.modules.free_module_element import vector + _,L,D = self.block_ldlt() + d = vector(D.base_ring(), D.diagonal()) + return (result, L, d) else: - return is_pos + return result + def principal_square_root(self, check_positivity=True): r""" @@ -15313,7 +15449,7 @@ cdef class Matrix(Matrix1): cdef Py_ssize_t i = 0 cdef Py_ssize_t j = 0 - cdef Py_ssize_t k, l + cdef Py_ssize_t k, l, c if transformation: from sage.matrix.constructor import identity_matrix @@ -15349,9 +15485,9 @@ cdef class Matrix(Matrix1): U.set_unsafe(k, c, p * Ukc + q * Ulc) U.set_unsafe(l, c, (-f) * Ukc + e * Ulc) if i != k: - A.swap_rows(i,k) + A.swap_rows_c(i,k) if transformation: - U.swap_rows(i,k) + U.swap_rows_c(i,k) pivot_cols.append(j) i += 1 j += 1 @@ -15366,9 +15502,9 @@ cdef class Matrix(Matrix1): coeff = normalization(pivot) for c in range(j,n): A.set_unsafe(i, c, A.get_unsafe(i,c) * coeff) - if transformation: - for c in range(m): - U.set_unsafe(i, c, U.get_unsafe(i,c) * coeff) + if transformation: + for c in range(m): + U.set_unsafe(i, c, U.get_unsafe(i,c) * coeff) pivot = A.get_unsafe(i,j) for k in range(i): @@ -17595,16 +17731,17 @@ def _binomial(Py_ssize_t n, Py_ssize_t k): i, n, k = i + 1, n - 1, k - 1 return result + def _jordan_form_vector_in_difference(V, W): r""" Given two lists of vectors ``V`` and ``W`` over the same base field, returns a vector in the difference ``V - W``. If the difference is empty, returns ``None``. - NOTES: + .. NOTE:: - This is meant to be a private helper method for the ``jordan_form`` method - in the above class. + This is meant to be a private helper method for the + ``jordan_form`` method in the above class. TESTS:: @@ -17791,7 +17928,7 @@ class NotFullRankError(ValueError): pass -cdef inline void _block_ldlt_pivot1x1(Matrix A, Py_ssize_t k): +cdef inline bint _block_ldlt_pivot1x1(Matrix A, Py_ssize_t k) except 1: r""" Update the `n`-by-`n` matrix `A` as part of a 1x1 pivot in the ``k,k`` position (whose value is ``pivot``). Relies on the fact @@ -17799,7 +17936,9 @@ cdef inline void _block_ldlt_pivot1x1(Matrix A, Py_ssize_t k): this routine should overwrite its argument. There is no return value from this function, as its intended - effect is to update the matrix `A` in-place. + effect is to update the matrix `A` in-place, but we allow it + to return zero/one so that ``1`` can be used to indicate that + a python exception occurred. """ cdef Py_ssize_t i,j # dumy loop indices cdef Py_ssize_t n = A._nrows @@ -17825,4 +17964,4 @@ cdef inline void _block_ldlt_pivot1x1(Matrix A, Py_ssize_t k): k, A.get_unsafe(k+i+1,k)/ pivot) - return + return 0 diff --git a/src/sage/matrix/matrix_cdv.pyx b/src/sage/matrix/matrix_cdv.pyx index 2206b48f5d6..a964b242c0f 100644 --- a/src/sage/matrix/matrix_cdv.pyx +++ b/src/sage/matrix/matrix_cdv.pyx @@ -52,10 +52,16 @@ cpdef hessenbergize_cdvf(Matrix_generic_dense H): sage: M = random_matrix(K, 6, 6) sage: M.charpoly()[0] == M.determinant() True + + We check that :trac:`31753` is resolved:: + + sage: R. = GF(5)[[]] + sage: M = matrix(3, 3, [ 1, t + O(t^3), t^2, 1 + t + O(t^3), 2 + t^2, 3 + 2*t + O(t^3), t - t^2, 2*t, 1 + t ]) + sage: M.charpoly() + x^3 + (1 + 4*t + 4*t^2 + O(t^3))*x^2 + (t + 2*t^2 + O(t^3))*x + 3 + 2*t^2 + O(t^3) """ - cdef Py_ssize_t n, m, i, j, k - cdef Matrix_generic_dense c - cdef RingElement pivot, inv, scalar + cdef Py_ssize_t n, i, j, k + cdef RingElement entry, pivot, inv, scalar n = H.nrows() for j in range(n-1): diff --git a/src/sage/matrix/matrix_cyclo_dense.pyx b/src/sage/matrix/matrix_cyclo_dense.pyx index bcd549b7057..eef7a331ee7 100644 --- a/src/sage/matrix/matrix_cyclo_dense.pyx +++ b/src/sage/matrix/matrix_cyclo_dense.pyx @@ -72,7 +72,6 @@ from sage.rings.real_mpfr import create_RealNumber as RealNumber from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing -from sage.rings.number_field.number_field import NumberField_quadratic from sage.rings.number_field.number_field_element cimport NumberFieldElement from sage.rings.number_field.number_field_element_quadratic cimport NumberFieldElement_quadratic @@ -201,74 +200,76 @@ cdef class Matrix_cyclo_dense(Matrix_dense): # cyclotomic field is implemented. cdef Py_ssize_t k, c cdef NumberFieldElement v + cdef NumberFieldElement_quadratic qv cdef mpz_t numer, denom cdef fmpz_t ftmp # The i,j entry is the (i * self._ncols + j)'th column. c = i * self._ncols + j - if type(value) is NumberFieldElement_quadratic: + if self._degree == 2: # Must be coded differently, since elements of # quadratic number fields are stored differently. + qv = value if self._n == 4: fmpz_set_mpz(fmpq_mat_entry_num(self._matrix._matrix, 0, c), - (value).a) + qv.a) fmpz_set_mpz(fmpq_mat_entry_den(self._matrix._matrix, 0, c), - (value).denom) + qv.denom) fmpq_canonicalise(fmpq_mat_entry(self._matrix._matrix, 0, c)) fmpz_set_mpz(fmpq_mat_entry_num(self._matrix._matrix, 1, c), - (value).b) + qv.b) fmpz_set_mpz(fmpq_mat_entry_den(self._matrix._matrix, 1, c), - (value).denom) + qv.denom) fmpq_canonicalise(fmpq_mat_entry(self._matrix._matrix, 1, c)) elif self._n == 3: # mat[0,c] = (x.a + x.b) / x.denom fmpz_set_mpz(fmpq_mat_entry_num(self._matrix._matrix, 0, c), - (value).a) + qv.a) # NOTE: it would be convenient here to have fmpz_add_mpz fmpz_init(ftmp) - fmpz_set_mpz(ftmp, (value).b) + fmpz_set_mpz(ftmp, qv.b) fmpz_add(fmpq_mat_entry_num(self._matrix._matrix, 0, c), fmpq_mat_entry_num(self._matrix._matrix, 0, c), ftmp) fmpz_clear(ftmp) fmpz_set_mpz(fmpq_mat_entry_den(self._matrix._matrix, 0, c), - (value).denom) + qv.denom) fmpq_canonicalise(fmpq_mat_entry(self._matrix._matrix, 0, c)) # mat[1,c] = (2 x.b) / x.denom fmpz_set_mpz(fmpq_mat_entry_num(self._matrix._matrix, 1, c), - (value).b) + qv.b) fmpz_mul_si(fmpq_mat_entry_num(self._matrix._matrix, 1, c), fmpq_mat_entry_num(self._matrix._matrix, 1, c), 2) fmpz_set_mpz(fmpq_mat_entry_den(self._matrix._matrix, 1, c), - (value).denom) + qv.denom) fmpq_canonicalise(fmpq_mat_entry(self._matrix._matrix, 1, c)) else: # self._n is 6 fmpz_set_mpz(fmpq_mat_entry_num(self._matrix._matrix, 0, c), - (value).a) + qv.a) # NOTE: it would be convenient here to have fmpz_add_mpz fmpz_init(ftmp) - fmpz_set_mpz(ftmp, (value).b) + fmpz_set_mpz(ftmp, qv.b) fmpz_sub(fmpq_mat_entry_num(self._matrix._matrix, 0, c), fmpq_mat_entry_num(self._matrix._matrix, 0, c), ftmp) fmpz_clear(ftmp) fmpz_set_mpz(fmpq_mat_entry_den(self._matrix._matrix, 0, c), - (value).denom) + qv.denom) fmpq_canonicalise(fmpq_mat_entry(self._matrix._matrix, 0, c)) fmpz_set_mpz(fmpq_mat_entry_num(self._matrix._matrix, 1, c), - (value).b) + qv.b) fmpz_mul_si(fmpq_mat_entry_num(self._matrix._matrix, 1, c), fmpq_mat_entry_num(self._matrix._matrix, 1, c), 2) fmpz_set_mpz(fmpq_mat_entry_den(self._matrix._matrix, 1, c), - (value).denom) + qv.denom) fmpq_canonicalise(fmpq_mat_entry(self._matrix._matrix, 1, c)) return @@ -1010,23 +1011,30 @@ cdef class Matrix_cyclo_dense(Matrix_dense): The following doctests are all indirect:: sage: MS = MatrixSpace(CyclotomicField(10), 4, 4) - sage: A = MS.random_element(); A + sage: A = MS.random_element(); A # random [ -2*zeta10^3 + 2*zeta10^2 - zeta10 zeta10^3 + 2*zeta10^2 - zeta10 + 1 0 -2*zeta10^3 + zeta10^2 - 2*zeta10 + 2] [ 0 -zeta10^3 + 2*zeta10^2 - zeta10 -zeta10^3 + 1 zeta10^3 + zeta10] [ 1/2*zeta10^2 -2*zeta10^2 + 2 -1/2*zeta10^3 + 1/2*zeta10^2 + 2 2*zeta10^3 - zeta10^2 - 2] [ 1 zeta10^2 + 2 2*zeta10^2 2*zeta10 - 2] + sage: A.parent() is MS + True + + :: + sage: B = MS.random_element(density=0.5) - sage: B._rational_matrix() - [ 0 0 0 0 1 0 0 2 0 2 0 0 0 0 0 0] - [ 0 0 0 0 0 0 0 0 -1 -2 0 0 0 0 0 2] - [ 0 -1 0 0 -1 0 0 0 0 0 0 0 0 0 -2 -1] - [ 0 0 0 0 0 0 0 2 -1/2 1/2 0 0 0 0 -1 0] + sage: all(a in (-2, -1, -1/2, 0, 1/2, 1, 2) for a in B._rational_matrix().list()) + True + sage: while set(B._rational_matrix().list()) != set((-2, -1, -1/2, 0, 1/2, 1, 2)): + ....: B = MS.random_element(density=0.5) + + :: + sage: C = MS.random_element(density=0.5, num_bound=20, den_bound=20) - sage: C._rational_matrix() - [ 0 0 8/11 -10/3 -11/7 8 1 -3 0 0 1 0 0 0 0 0] - [ 0 0 -11/17 -3/13 -5/6 17/3 -19/17 -4/5 0 0 9 0 0 0 0 0] - [ 0 0 -11 -3/2 -5/12 8/11 0 -3/19 0 0 -5/6 0 0 0 0 0] - [ 0 0 0 5/8 -5/11 -5/4 6/11 2/3 0 0 -16/11 0 0 0 0 0] + sage: all(abs(a.denominator()) <= 20 and abs(a.numerator()) <= 20 for a in C._rational_matrix().list()) + True + sage: while not (any(abs(a.denominator()) == 20 for a in C._rational_matrix().list()) + ....: and any(abs(a.numerator()) == 20 for a in C._rational_matrix().list())): + ....: C = MS.random_element(density=0.5, num_bound=20, den_bound=20) """ cdef Py_ssize_t i cdef Matrix_rational_dense mat = self._matrix @@ -1671,7 +1679,7 @@ cdef class Matrix_cyclo_dense(Matrix_dense): cdef int i cdef Matrix_cyclo_dense res cdef bint is_square - + verbose("entering _echelon_form_multimodular", level=echelon_verbose_level) diff --git a/src/sage/matrix/matrix_generic_dense.pyx b/src/sage/matrix/matrix_generic_dense.pyx index ad75514bc58..3f01179dd14 100644 --- a/src/sage/matrix/matrix_generic_dense.pyx +++ b/src/sage/matrix/matrix_generic_dense.pyx @@ -25,21 +25,19 @@ cdef class Matrix_generic_dense(matrix_dense.Matrix_dense): EXAMPLES:: - sage: A = random_matrix(Integers(25)['x'],2); A - [ 0 8*x + 1] - [17*x + 4 0] + sage: A = random_matrix(Integers(25)['x'], 2) sage: type(A) sage: TestSuite(A).run() Test comparisons:: - sage: A = random_matrix(Integers(25)['x'],2) + sage: A = random_matrix(Integers(25)['x'], 2) sage: A == A True - sage: A < A + 1 + sage: A < A + 1 or A[0, 0].coefficients()[0] == 24 True - sage: A+1 < A + sage: A+1 < A and A[0, 0].coefficients()[0] != 24 False Test hashing:: diff --git a/src/sage/matrix/matrix_gf2e_dense.pyx b/src/sage/matrix/matrix_gf2e_dense.pyx index 63dc9e3ce67..0b7f68fbe35 100644 --- a/src/sage/matrix/matrix_gf2e_dense.pyx +++ b/src/sage/matrix/matrix_gf2e_dense.pyx @@ -39,15 +39,11 @@ EXAMPLES:: sage: K. = GF(2^8) sage: A = random_matrix(K, 3,4) - sage: A - [ a^6 + a^5 + a^4 + a^2 a^6 + a^3 + a + 1 a^5 + a^3 + a^2 + a + 1 a^7 + a^6 + a + 1] - [ a^7 + a^6 + a^3 a^7 + a^6 + a^5 + 1 a^5 + a^4 + a^3 + a + 1 a^6 + a^5 + a^4 + a^3 + a^2 + 1] - [ a^6 + a^5 + a + 1 a^7 + a^3 + 1 a^7 + a^3 + a + 1 a^7 + a^6 + a^3 + a^2 + a + 1] - - sage: A.echelon_form() - [ 1 0 0 a^6 + a^5 + a^4 + a^2] - [ 0 1 0 a^7 + a^5 + a^3 + a + 1] - [ 0 0 1 a^6 + a^4 + a^3 + a^2 + 1] + sage: E = A.echelon_form() + sage: A.row_space() == E.row_space() + True + sage: all(r[r.nonzero_positions()[0]] == 1 for r in E.rows() if r) + True AUTHOR: @@ -151,10 +147,8 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): [0 0 0 0] [0 0 0 0] - sage: A.randomize(); A - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 a + 1] - [ a^3 1 a^3 + a + 1 a^3 + a^2 + 1] - [ a + 1 a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] + sage: A.randomize() + sage: TestSuite(A).run() sage: K. = GF(2^3) sage: A = Matrix(K,3,4); A @@ -162,10 +156,8 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): [0 0 0 0] [0 0 0 0] - sage: A.randomize(); A - [ a^2 + a a^2 + 1 a^2 + a a^2 + a] - [ a^2 + 1 a^2 + a + 1 a^2 + 1 a^2] - [ a^2 + a a^2 + 1 a^2 + a + 1 a + 1] + sage: A.randomize() + sage: TestSuite(A).run() """ cdef M4RIE_finite_field FF @@ -221,19 +213,14 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^4) - sage: l = [K.random_element() for _ in range(3*4)]; l - [a^2 + 1, a^3 + 1, 0, 0, a, a^3 + a + 1, a + 1, a + 1, a^2, a^3 + a + 1, a^3 + a, a^3 + a] - - sage: A = Matrix(K, 3, 4, l); A - [ a^2 + 1 a^3 + 1 0 0] - [ a a^3 + a + 1 a + 1 a + 1] - [ a^2 a^3 + a + 1 a^3 + a a^3 + a] + sage: l = [K.random_element() for _ in range(3*4)] - sage: A.list() - [a^2 + 1, a^3 + 1, 0, 0, a, a^3 + a + 1, a + 1, a + 1, a^2, a^3 + a + 1, a^3 + a, a^3 + a] + sage: A = Matrix(K, 3, 4, l) + sage: l == A.list() + True - sage: l[0], A[0,0] - (a^2 + 1, a^2 + 1) + sage: l[0] == A[0,0] + True sage: A = Matrix(K, 3, 3, a); A [a 0 0] @@ -257,16 +244,18 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^4) - sage: A = Matrix(K,3,4,[K.random_element() for _ in range(3*4)]); A - [ a^2 + 1 a^3 + 1 0 0] - [ a a^3 + a + 1 a + 1 a + 1] - [ a^2 a^3 + a + 1 a^3 + a a^3 + a] + sage: l = [K.random_element() for _ in range(3*4)] + sage: A = Matrix(K, 3, 4, l) - sage: A[0,0] = a # indirect doctest - sage: A - [ a a^3 + 1 0 0] - [ a a^3 + a + 1 a + 1 a + 1] - [ a^2 a^3 + a + 1 a^3 + a a^3 + a] + sage: i = randrange(3) + sage: j = randrange(4) + sage: A[i,j] = a # indirect doctest + sage: A[i,j] == a == A.list()[j + 4*i] + True + sage: A.list()[:j + 4*i] == l[:j + 4*i] + True + sage: A.list()[j + 4*i + 1:] == l[j + 4*i + 1:] + True """ mzed_write_elem(self._entries, i, j, poly_to_word(value)) @@ -281,15 +270,15 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^4) - sage: A = random_matrix(K,3,4) - sage: A[2,3] # indirect doctest - a^3 + a^2 + a + 1 + sage: l = [K.random_element() for _ in range(3*4)] + sage: A = Matrix(K, 3, 4, l) + sage: A[2,3] == l[3 + 4*2] # indirect doctest + True sage: K. = GF(2^3) - sage: m,n = 3, 4 - sage: A = random_matrix(K,3,4); A - [ a^2 + a a^2 + 1 a^2 + a a^2 + a] - [ a^2 + 1 a^2 + a + 1 a^2 + 1 a^2] - [ a^2 + a a^2 + 1 a^2 + a + 1 a + 1] + sage: l = [K.random_element() for _ in range(3*4)] + sage: A = Matrix(K, 3, 4, l) + sage: A.list() == l + True """ cdef int r = mzed_read_elem(self._entries, i, j) cdef Cache_base cache = self._base_ring._cache @@ -320,20 +309,12 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^4) - sage: A = random_matrix(K,3,4); A - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 a + 1] - [ a^3 1 a^3 + a + 1 a^3 + a^2 + 1] - [ a + 1 a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] - - sage: B = random_matrix(K,3,4); B - [ a^2 + a a^2 + 1 a^2 + a a^3 + a^2 + a] - [ a^2 + 1 a^3 + a^2 + a + 1 a^2 + 1 a^2] - [ a^3 + a^2 + a a^2 + 1 a^2 + a + 1 a^3 + a + 1] + sage: A = random_matrix(K,3,4) + sage: B = random_matrix(K,3,4) - sage: C = A + B; C # indirect doctest - [ a a^3 + a^2 + a a^3 + 1 a^3 + a^2 + 1] - [a^3 + a^2 + 1 a^3 + a^2 + a a^3 + a^2 + a a^3 + 1] - [a^3 + a^2 + 1 a^3 + a^2 a^3 + a^2 a^2] + sage: C = A + B # indirect doctest + sage: all(C.list()[i] == A.list()[i] + B.list()[i] for i in range(12)) + True """ cdef Matrix_gf2e_dense A A = Matrix_gf2e_dense.__new__(Matrix_gf2e_dense, self._parent, 0, 0, 0, alloc=False) @@ -349,22 +330,11 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: from sage.matrix.matrix_gf2e_dense import Matrix_gf2e_dense sage: K. = GF(2^4) - sage: m,n = 3, 4 - sage: MS = MatrixSpace(K,m,n) - sage: A = random_matrix(K,3,4); A - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 a + 1] - [ a^3 1 a^3 + a + 1 a^3 + a^2 + 1] - [ a + 1 a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] - - sage: B = random_matrix(K,3,4); B - [ a^2 + a a^2 + 1 a^2 + a a^3 + a^2 + a] - [ a^2 + 1 a^3 + a^2 + a + 1 a^2 + 1 a^2] - [ a^3 + a^2 + a a^2 + 1 a^2 + a + 1 a^3 + a + 1] - - sage: C = A - B; C # indirect doctest - [ a a^3 + a^2 + a a^3 + 1 a^3 + a^2 + 1] - [a^3 + a^2 + 1 a^3 + a^2 + a a^3 + a^2 + a a^3 + 1] - [a^3 + a^2 + 1 a^3 + a^2 a^3 + a^2 a^2] + sage: A = random_matrix(K,3,4) + sage: B = random_matrix(K,3,4) + sage: C = A - B # indirect doctest + sage: all(C.list()[i] == A.list()[i] - B.list()[i] for i in range(12)) + True """ return self._add_(right) @@ -634,29 +604,9 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(4) sage: A = random_matrix(K,10,10) - sage: A - [ 0 a + 1 a + 1 a + 1 0 1 a + 1 1 a + 1 1] - [a + 1 a + 1 a 1 a a 1 a + 1 1 0] - [ a 1 a + 1 a + 1 0 a + 1 a 1 a a] - [a + 1 a 0 0 1 a + 1 a + 1 0 a + 1 1] - [ a 0 a + 1 a a 0 a + 1 a 1 a + 1] - [ a 0 a a + 1 a 1 a + 1 a a a] - [a + 1 a 0 1 0 a + 1 a + 1 a 0 a + 1] - [a + 1 a + 1 0 a + 1 a 1 a + 1 a + 1 a + 1 0] - [ 0 0 0 a + 1 1 a + 1 0 a + 1 1 0] - [ 1 a + 1 0 1 a 0 0 a a + 1 a] - - sage: a*A # indirect doctest - [ 0 1 1 1 0 a 1 a 1 a] - [ 1 1 a + 1 a a + 1 a + 1 a 1 a 0] - [a + 1 a 1 1 0 1 a + 1 a a + 1 a + 1] - [ 1 a + 1 0 0 a 1 1 0 1 a] - [a + 1 0 1 a + 1 a + 1 0 1 a + 1 a 1] - [a + 1 0 a + 1 1 a + 1 a 1 a + 1 a + 1 a + 1] - [ 1 a + 1 0 a 0 1 1 a + 1 0 1] - [ 1 1 0 1 a + 1 a 1 1 1 0] - [ 0 0 0 1 a 1 0 1 a 0] - [ a 1 0 a a + 1 0 0 a + 1 1 a + 1] + sage: B = a*A # indirect doctest + sage: all(B.list()[i] == a*A.list()[i] for i in range(100)) + True """ cdef m4ri_word a = poly_to_word(right) cdef Matrix_gf2e_dense C = Matrix_gf2e_dense.__new__(Matrix_gf2e_dense, self._parent, 0, 0, 0) @@ -668,15 +618,10 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^4) - sage: A = random_matrix(K, 3, 4); A - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 a + 1] - [ a^3 1 a^3 + a + 1 a^3 + a^2 + 1] - [ a + 1 a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] - - sage: -A - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 a + 1] - [ a^3 1 a^3 + a + 1 a^3 + a^2 + 1] - [ a + 1 a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] + sage: A = random_matrix(K, 3, 4) + sage: B = -A + sage: all(B.list()[i] == -A.list()[i] for i in range(12)) + True """ return self.__copy__() @@ -686,6 +631,7 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^4) sage: A = random_matrix(K,3,4) + sage: A[0,0] = 0 sage: B = copy(A) sage: A == B True @@ -704,19 +650,14 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^4) sage: m,n = 3, 4 - sage: A = random_matrix(K,3,4); A - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 a + 1] - [ a^3 1 a^3 + a + 1 a^3 + a^2 + 1] - [ a + 1 a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] - - sage: A2 = copy(A); A2 - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 a + 1] - [ a^3 1 a^3 + a + 1 a^3 + a^2 + 1] - [ a + 1 a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] + sage: A = random_matrix(K,3,4) + sage: A2 = copy(A) + sage: A2.list() == A.list() + True - sage: A[0,0] = 1 - sage: A2[0,0] - a^2 + sage: A[0,0] = 1 if A[0,0] != 1 else 0 + sage: A2[0,0] == A[0,0] + False """ cdef Matrix_gf2e_dense A A = Matrix_gf2e_dense.__new__(Matrix_gf2e_dense, self._parent, 0, 0, 0) @@ -749,14 +690,10 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^4) - sage: m,n = 3, 4 - sage: A = random_matrix(K,3,4); A - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 a + 1] - [ a^3 1 a^3 + a + 1 a^3 + a^2 + 1] - [ a + 1 a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] - - sage: A.list() # indirect doctest - [a^2, a^3 + a + 1, a^3 + a^2 + a + 1, a + 1, a^3, 1, a^3 + a + 1, a^3 + a^2 + 1, a + 1, a^3 + 1, a^3 + a + 1, a^3 + a^2 + a + 1] + sage: l = [K.random_element() for _ in range(3*4)] + sage: A = Matrix(K, 3, 4, l) + sage: A.list() == l # indirect doctest + True """ cdef int i,j l = [] @@ -784,37 +721,78 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^4) - sage: A = Matrix(K,3,3) - - sage: A.randomize(); A - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1] - [ a + 1 a^3 1] - [ a^3 + a + 1 a^3 + a^2 + 1 a + 1] - - sage: K. = GF(2^4) - sage: A = random_matrix(K,1000,1000,density=0.1) - sage: float(A.density()) - 0.0999... - - sage: A = random_matrix(K,1000,1000,density=1.0) - sage: float(A.density()) - 1.0 - - sage: A = random_matrix(K,1000,1000,density=0.5) - sage: float(A.density()) - 0.4996... + sage: total_count = 0 + sage: from collections import defaultdict + sage: dic = defaultdict(Integer) + sage: def add_samples(): + ....: global dic, total_count + ....: for _ in range(100): + ....: A = Matrix(K,3,3) + ....: A.randomize() + ....: for a in A.list(): + ....: dic[a] += 1 + ....: total_count += 1.0 + sage: add_samples() + sage: while not all(abs(dic[a]/total_count - 1/16) < 0.01 for a in dic): + ....: add_samples() + + sage: def add_sample(density): + ....: global density_sum, total_count + ....: total_count += 1.0 + ....: density_sum += random_matrix(K, 1000, 1000, density=density).density() + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(0.1) + sage: while abs(density_sum/total_count - 0.1) > 0.001: + ....: add_sample(0.1) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(1.0) + sage: while abs(density_sum/total_count - 1.0) > 0.001: + ....: add_sample(1.0) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(0.5) + sage: while abs(density_sum/total_count - 0.5) > 0.001: + ....: add_sample(0.5) Note, that the matrix is updated and not zero-ed out before being randomized:: - sage: A = matrix(K,1000,1000) - sage: A.randomize(nonzero=False, density=0.1) - sage: float(A.density()) - 0.0936... - - sage: A.randomize(nonzero=False, density=0.05) - sage: float(A.density()) - 0.135854 + sage: def add_sample(density, nonzero): + ....: global density_sum, total_count + ....: total_count += 1.0 + ....: A = matrix(K, 1000, 1000) + ....: A.randomize(nonzero=nonzero, density=density) + ....: A.randomize(nonzero=nonzero, density=density) + ....: density_sum += A.density() + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(0.1, True) + sage: while abs(density_sum/total_count - (1 - 0.9^2)) > 0.001: + ....: add_sample(0.1, True) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(0.1, False) + sage: while abs(density_sum/total_count - (1 - 0.9^2)*15/16) > 0.001: + ....: add_sample(0.1, False) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(0.05, True) + sage: while abs(density_sum/total_count - (1 - 0.95^2)) > 0.001: + ....: add_sample(0.05, True) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(0.5, True) + sage: while abs(density_sum/total_count - (1 - 0.5^2)) > 0.001: + ....: add_sample(0.5, True) """ if self._ncols == 0 or self._nrows == 0: return @@ -894,35 +872,25 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^4) sage: m,n = 3, 5 - sage: A = random_matrix(K, 3, 5); A - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 a + 1 a^3] - [ 1 a^3 + a + 1 a^3 + a^2 + 1 a + 1 a^3 + 1] - [ a^3 + a + 1 a^3 + a^2 + a + 1 a^2 + a a^2 + 1 a^2 + a] - - sage: A.echelonize(); A - [ 1 0 0 a + 1 a^2 + 1] - [ 0 1 0 a^2 a + 1] - [ 0 0 1 a^3 + a^2 + a a^3] + sage: A = random_matrix(K, 3, 5) + sage: R = A.row_space() + sage: A.echelonize() + sage: all(r[r.nonzero_positions()[0]] == 1 for r in A.rows() if r) + True + sage: A.row_space() == R + True sage: K. = GF(2^3) sage: m,n = 3, 5 sage: MS = MatrixSpace(K,m,n) sage: A = random_matrix(K, 3, 5) - - sage: copy(A).echelon_form('newton_john') - [ 1 0 a + 1 0 a^2 + 1] - [ 0 1 a^2 + a + 1 0 a] - [ 0 0 0 1 a^2 + a + 1] - - sage: copy(A).echelon_form('naive') - [ 1 0 a + 1 0 a^2 + 1] - [ 0 1 a^2 + a + 1 0 a] - [ 0 0 0 1 a^2 + a + 1] - - sage: copy(A).echelon_form('builtin') - [ 1 0 a + 1 0 a^2 + 1] - [ 0 1 a^2 + a + 1 0 a] - [ 0 0 0 1 a^2 + a + 1] + sage: B = copy(A).echelon_form('newton_john') + sage: C = copy(A).echelon_form('naive') + sage: D = copy(A).echelon_form('builtin') + sage: B == C == D + True + sage: all(r[r.nonzero_positions()[0]] == 1 for r in B.rows() if r) + True """ if self._nrows == 0 or self._ncols == 0: self.cache('in_echelon_form',True) @@ -976,7 +944,9 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^8) sage: A = random_matrix(K, 15, 15) - sage: A.pivots() # indirect doctest + sage: while A.rank() != 15: + ....: A = random_matrix(K, 15, 15) + sage: A.pivots() # indirect doctest (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) """ if not self.fetch('in_echelon_form'): @@ -1000,20 +970,15 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^3) - sage: A = random_matrix(K,3,3); A - [ a^2 a + 1 a^2 + a + 1] - [ a + 1 0 1] - [ a + 1 a^2 + 1 a + 1] - - sage: B = ~A; B - [a^2 + a + 1 a^2 a^2] - [ a + 1 a^2 + a + 1 a + 1] - [ a a^2 + a a^2 + a + 1] - + sage: A = random_matrix(K, 3, 3) + sage: while A.rank() != 3: + ....: A = random_matrix(K, 3, 3) + sage: B = ~A sage: A*B [1 0 0] [0 1 0] [0 0 1] + """ cdef Matrix_gf2e_dense A A = Matrix_gf2e_dense.__new__(Matrix_gf2e_dense, self._parent, 0, 0, 0) @@ -1041,20 +1006,29 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^3) - sage: A = random_matrix(K,3,3); A - [ a^2 a + 1 a^2 + a + 1] - [ a + 1 0 1] - [ a + 1 a^2 + 1 a + 1] + sage: A = random_matrix(K,3,3) + sage: B = copy(A) + sage: B.rescale_row(0, a, 0) + sage: B[0] == a*A[0] + True + sage: B[1:] == A[1:] + True - sage: A.rescale_row(0, a , 0); A - [ a + 1 a^2 + a a^2 + 1] - [ a + 1 0 1] - [ a + 1 a^2 + 1 a + 1] + sage: B = copy(A) + sage: B.rescale_row(1, 0, 0) + sage: B[0] == A[0] + True + sage: B[2] == A[2] + True + sage: B[1].is_zero() + True - sage: A.rescale_row(0,0,0); A - [ 0 0 0] - [ a + 1 0 1] - [ a + 1 a^2 + 1 a + 1] + sage: B = copy(A) + sage: B.rescale_row(2, a^2, 2) + sage: B.list()[:-1] == A.list()[:-1] + True + sage: B[2,2] == a^2*A[2,2] + True """ cdef m4ri_word x = poly_to_word(multiple) mzed_rescale_row(self._entries, row, start_col, x) @@ -1074,15 +1048,20 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^3) - sage: A = random_matrix(K,3,3); A - [ a^2 a + 1 a^2 + a + 1] - [ a + 1 0 1] - [ a + 1 a^2 + 1 a + 1] + sage: A = random_matrix(K,3,3) + sage: B = copy(A) + sage: B.add_multiple_of_row(0,1,a,0) + sage: B[1:] == A[1:] + True + sage: B[0] == A[0] + a*A[1] + True - sage: A.add_multiple_of_row(0,1,a,0); A - [ a a + 1 a^2 + 1] - [ a + 1 0 1] - [ a + 1 a^2 + 1 a + 1] + sage: B = copy(A) + sage: B.add_multiple_of_row(2,1,a,2) + sage: B.list()[:-1] == A.list()[:-1] + True + sage: B[2,2] == A[2,2] + a*A[1,2] + True """ cdef m4ri_word x = poly_to_word(multiple) @@ -1102,16 +1081,14 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^3) sage: A = random_matrix(K,3,3) - sage: A - [ a^2 a + 1 a^2 + a + 1] - [ a + 1 0 1] - [ a + 1 a^2 + 1 a + 1] - - sage: A.swap_rows(0,1); A - [ a + 1 0 1] - [ a^2 a + 1 a^2 + a + 1] - [ a + 1 a^2 + 1 a + 1] - + sage: B = copy(A) + sage: B.swap_rows(0,1) + sage: B[0] == A[1] + True + sage: B[1] == A[0] + True + sage: B[2] == A[2] + True """ mzed_row_swap(self._entries, row1, row2) @@ -1128,18 +1105,16 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^3) sage: A = random_matrix(K,3,3) - sage: A - [ a^2 a + 1 a^2 + a + 1] - [ a + 1 0 1] - [ a + 1 a^2 + 1 a + 1] - - sage: A.swap_columns(0,1); A - [ a + 1 a^2 a^2 + a + 1] - [ 0 a + 1 1] - [ a^2 + 1 a + 1 a + 1] + sage: B = copy(A) + sage: B.swap_columns(0,1) + sage: B.column(0) == A.column(1) + True + sage: B.column(1) == A.column(0) + True + sage: B.column(2) == A.column(2) + True sage: A = random_matrix(K,4,16) - sage: B = copy(A) sage: B.swap_columns(0,1) sage: B.swap_columns(0,1) @@ -1168,25 +1143,12 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^4) sage: MS = MatrixSpace(K,3,3) sage: A = random_matrix(K,3,3) - sage: B = A.augment(MS(1)); B - [ a^2 a^3 + a + 1 a^3 + a^2 + a + 1 1 0 0] - [ a + 1 a^3 1 0 1 0] - [ a^3 + a + 1 a^3 + a^2 + 1 a + 1 0 0 1] - - sage: B.echelonize(); B - [ 1 0 0 a^2 + a a^3 + 1 a^3 + a] - [ 0 1 0 a^3 + a^2 + a a^3 + a^2 + a + 1 a^2 + a] - [ 0 0 1 a + 1 a^3 a^3] - - sage: C = B.matrix_from_columns([3,4,5]); C - [ a^2 + a a^3 + 1 a^3 + a] - [ a^3 + a^2 + a a^3 + a^2 + a + 1 a^2 + a] - [ a + 1 a^3 a^3] - - sage: C == ~A + sage: B = A.augment(MS(1)) + sage: B.echelonize() + sage: C = B.matrix_from_columns([3,4,5]) + sage: A.rank() < 3 or C == ~A True - - sage: C*A == MS(1) + sage: A.rank() < 3 or C*A == MS(1) True TESTS:: @@ -1194,13 +1156,11 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^4) sage: A = random_matrix(K,2,3) sage: B = random_matrix(K,2,0) - sage: A.augment(B) - [ a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] - [ a^2 + a a^2 + 1 a^2 + a] + sage: A.augment(B) == A + True - sage: B.augment(A) - [ a^3 + 1 a^3 + a + 1 a^3 + a^2 + a + 1] - [ a^2 + a a^2 + 1 a^2 + a] + sage: B.augment(A) == A + True sage: M = Matrix(K, 0, 0, 0) sage: N = Matrix(K, 0, 19, 0) @@ -1229,50 +1189,38 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): A._entries = mzed_concat(A._entries, self._entries, right._entries) return A - def stack(self, Matrix_gf2e_dense other): - """ - Stack ``self`` on top of ``other``. + cdef _stack_impl(self, bottom): + r""" + Stack ``self`` on top of ``bottom``. INPUT: - - ``other`` - a matrix + - ``bottom`` -- a matrix with the same number of columns as ``self`` EXAMPLES:: sage: K. = GF(2^4) - sage: A = random_matrix(K,2,2); A - [ a^2 a^3 + a + 1] - [a^3 + a^2 + a + 1 a + 1] - - sage: B = random_matrix(K,2,2); B - [ a^3 1] - [ a^3 + a + 1 a^3 + a^2 + 1] - - sage: A.stack(B) - [ a^2 a^3 + a + 1] - [a^3 + a^2 + a + 1 a + 1] - [ a^3 1] - [ a^3 + a + 1 a^3 + a^2 + 1] - - sage: B.stack(A) - [ a^3 1] - [ a^3 + a + 1 a^3 + a^2 + 1] - [ a^2 a^3 + a + 1] - [a^3 + a^2 + a + 1 a + 1] + sage: A = random_matrix(K,2,2) + sage: B = random_matrix(K,2,2) + sage: C = A.stack(B) + sage: C[:2] == A + True + sage: C[2:] == B + True + sage: D = B.stack(A) + sage: D[:2] == B + True + sage: D[2:] == A + True TESTS:: sage: A = random_matrix(K,0,3) sage: B = random_matrix(K,3,3) - sage: A.stack(B) - [ a + 1 a^3 + 1 a^3 + a + 1] - [a^3 + a^2 + a + 1 a^2 + a a^2 + 1] - [ a^2 + a a^3 + a^2 + a a^2 + 1] - - sage: B.stack(A) - [ a + 1 a^3 + 1 a^3 + a + 1] - [a^3 + a^2 + a + 1 a^2 + a a^2 + 1] - [ a^2 + a a^3 + a^2 + a a^2 + 1] + sage: A.stack(B) == B + True + sage: B.stack(A) == B + True sage: M = Matrix(K, 0, 0, 0) sage: N = Matrix(K, 19, 0, 0) @@ -1283,9 +1231,17 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: N = Matrix(K, 1, 0, 0) sage: M.stack(N) [] + + Check that we can stack a vector (:trac:`31708`):: + + sage: R. = GF(2^3) + sage: M = matrix(R, [[1,1],[0,a+1]]) + sage: M.stack(vector(R, [a,0])) + [ 1 1] + [ 0 a + 1] + [ a 0] """ - if self._ncols != other._ncols: - raise TypeError("Both numbers of columns must match.") + cdef Matrix_gf2e_dense other = bottom if self._nrows == 0: return other.__copy__() @@ -1293,7 +1249,7 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): return self.__copy__() cdef Matrix_gf2e_dense A - A = self.new_matrix(nrows = self._nrows + other._nrows) + A = self.new_matrix(nrows=self._nrows + other._nrows) if self._ncols == 0: return A A._entries = mzed_stack(A._entries, self._entries, other._entries) @@ -1437,51 +1393,21 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): EXAMPLES:: sage: K. = GF(2^2) - sage: A = random_matrix(K, 5, 5); A - [ 0 a + 1 a + 1 a + 1 0] - [ 1 a + 1 1 a + 1 1] - [a + 1 a + 1 a 1 a] - [ a 1 a + 1 1 0] - [ a 1 a + 1 a + 1 0] - - sage: A1,A0 = A.slice() - sage: A0 - [0 1 1 1 0] - [0 1 0 1 0] - [1 1 1 0 1] - [1 0 1 0 0] - [1 0 1 1 0] - - sage: A1 - [0 1 1 1 0] - [1 1 1 1 1] - [1 1 0 1 0] - [0 1 1 1 0] - [0 1 1 1 0] - - sage: A0[2,4]*a + A1[2,4], A[2,4] - (a, a) + sage: A = random_matrix(K, 5, 5) + sage: A0, A1 = A.slice() + sage: all(A.list()[i] == A0.list()[i] + a*A1.list()[i] for i in range(25)) + True sage: K. = GF(2^3) - sage: A = random_matrix(K, 5, 5); A - [ a + 1 a^2 + a 1 a a^2 + a] - [ a + 1 a^2 + a a^2 a^2 a^2 + 1] - [a^2 + a + 1 a^2 + a + 1 0 a^2 + a + 1 a^2 + 1] - [ a^2 + a 0 a^2 + a + 1 a a] - [ a^2 a + 1 a a^2 + 1 a^2 + a + 1] - - sage: A0,A1,A2 = A.slice() - sage: A0 - [1 0 1 0 0] - [1 0 0 0 1] - [1 1 0 1 1] - [0 0 1 0 0] - [0 1 0 1 1] + sage: A = random_matrix(K, 5, 5) + sage: A0, A1, A2 = A.slice() + sage: all(A.list()[i] == A0.list()[i] + a*A1.list()[i] + a^2*A2.list()[i] for i in range(25)) + True Slicing and clinging are inverse operations:: sage: B = matrix(K, 5, 5) - sage: B.cling(A0,A1,A2) + sage: B.cling(A0, A1, A2) sage: B == A True """ @@ -1530,33 +1456,15 @@ cdef class Matrix_gf2e_dense(matrix_dense.Matrix_dense): sage: K. = GF(2^2) sage: A = matrix(K, 5, 5) - sage: A0 = random_matrix(GF(2), 5, 5); A0 - [0 1 0 1 1] - [0 1 1 1 0] - [0 0 0 1 0] - [0 1 1 0 0] - [0 0 0 1 1] - - sage: A1 = random_matrix(GF(2), 5, 5); A1 - [0 0 1 1 1] - [1 1 1 1 0] - [0 0 0 1 1] - [1 0 0 0 1] - [1 0 0 1 1] - - sage: A.cling(A1, A0); A - [ 0 a 1 a + 1 a + 1] - [ 1 a + 1 a + 1 a + 1 0] - [ 0 0 0 a + 1 1] - [ 1 a a 0 1] - [ 1 0 0 a + 1 a + 1] - - sage: A0[0,3]*a + A1[0,3], A[0,3] - (a + 1, a + 1) + sage: A0 = random_matrix(GF(2), 5, 5) + sage: A1 = random_matrix(GF(2), 5, 5) + sage: A.cling(A0, A1) + sage: all(A.list()[i] == A0.list()[i] + a*A1.list()[i] for i in range(25)) + True Slicing and clinging are inverse operations:: - sage: B1, B0 = A.slice() + sage: B0, B1 = A.slice() sage: B0 == A0 and B1 == A1 True diff --git a/src/sage/matrix/matrix_gfpn_dense.pyx b/src/sage/matrix/matrix_gfpn_dense.pyx index 1a2b5d5d235..62fd4af2ee8 100644 --- a/src/sage/matrix/matrix_gfpn_dense.pyx +++ b/src/sage/matrix/matrix_gfpn_dense.pyx @@ -1168,9 +1168,13 @@ cdef class Matrix_gfpn_dense(Matrix_dense): raise ValueError("self must be a square matrix") return self._converter.fel_to_field(MatTrace(self.Data)) - def stack(self, Matrix_gfpn_dense other): - """ - Stack two matrices of the same number of columns. + cdef _stack_impl(self, bottom): + r""" + Stack ``self`` on top of ``bottom``. + + INPUT: + + - ``bottom`` -- a matrix with the same number of columns as ``self`` EXAMPLES:: @@ -1181,9 +1185,17 @@ cdef class Matrix_gfpn_dense(Matrix_dense): [ 0 1 2 x x + 1 x + 2 2*x 2*x + 1 2*x + 2] [ 0 1 2 x x + 1 x + 2 2*x 2*x + 1 2*x + 2] + Check that we can stack a vector (:trac:`31708`):: + + sage: R. = GF(3^3) + sage: M = matrix(R, [[1,1],[0,a+1]]) + sage: M.stack(vector(R, [a,0])) + [ 1 1] + [ 0 a + 1] + [ a 0] """ - if self._ncols != other._ncols: - raise TypeError("Both numbers of columns must match.") + cdef Matrix_gfpn_dense other = bottom + if self._nrows == 0 or self.Data == NULL: return other.__copy__() if other._nrows == 0 or other.Data == NULL: diff --git a/src/sage/matrix/matrix_integer_dense.pyx b/src/sage/matrix/matrix_integer_dense.pyx index 5c2d94bab22..abbd4a57fe9 100644 --- a/src/sage/matrix/matrix_integer_dense.pyx +++ b/src/sage/matrix/matrix_integer_dense.pyx @@ -1998,8 +1998,8 @@ cdef class Matrix_integer_dense(Matrix_dense): sage: m = random_matrix(ZZ, 100, 100, x=-1000, y=1000, density=.1) sage: m.parent() Full MatrixSpace of 100 by 100 dense matrices over Integer Ring - sage: H, U = m.hermite_form(algorithm="flint", transformation=True) - sage: H == U*m + sage: H, U = m.hermite_form(algorithm="flint", transformation=True) # long time + sage: H == U*m # long time True """ key = 'hnf-%s-%s'%(include_zero_rows,transformation) @@ -2400,13 +2400,13 @@ cdef class Matrix_integer_dense(Matrix_dense): [0 3 0] [0 0 0] sage: U - [ 0 1 0] + [ 0 2 -1] [ 0 -1 1] - [-1 2 -1] + [ 1 -2 1] sage: V - [-1 4 1] - [ 1 -3 -2] [ 0 0 1] + [-1 2 -2] + [ 1 -1 1] sage: U*A*V [1 0 0] [0 3 0] @@ -2421,12 +2421,12 @@ cdef class Matrix_integer_dense(Matrix_dense): [0 2] [0 0] sage: U - [ 0 1 0] + [ 0 2 -1] [ 0 -1 1] - [-1 2 -1] + [ 1 -2 1] sage: V - [-1 3] - [ 1 -2] + [-1 1] + [ 1 0] sage: U * A * V [1 0] [0 2] @@ -2445,7 +2445,8 @@ cdef class Matrix_integer_dense(Matrix_dense): :meth:`elementary_divisors` """ - v = self.__pari__().matsnf(1).sage() + X = self.matrix_space()([self[i,j] for i in xrange(self._nrows-1,-1,-1) for j in xrange(self._ncols-1,-1,-1)]) + v = X.__pari__().matsnf(1).sage() # need to reverse order of rows of U, columns of V, and both of D. D = self.matrix_space()([v[2][i,j] for i in xrange(self._nrows-1,-1,-1) for j in xrange(self._ncols-1,-1,-1)]) @@ -2461,13 +2462,13 @@ cdef class Matrix_integer_dense(Matrix_dense): # silly special cases for matrices with 0 columns (PARI has a unique empty matrix) U = self.matrix_space(ncols = self._nrows)(1) else: - U = self.matrix_space(ncols = self._nrows)([v[0][i,j] for i in xrange(self._nrows-1,-1,-1) for j in xrange(self._nrows)]) + U = self.matrix_space(ncols = self._nrows)([v[0][i,j] for i in xrange(self._nrows-1,-1,-1) for j in xrange(self._nrows-1,-1,-1)]) if self._nrows == 0: # silly special cases for matrices with 0 rows (PARI has a unique empty matrix) V = self.matrix_space(nrows = self._ncols)(1) else: - V = self.matrix_space(nrows = self._ncols)([v[1][i,j] for i in xrange(self._ncols) for j in xrange(self._ncols-1,-1,-1)]) + V = self.matrix_space(nrows = self._ncols)([v[1][i,j] for i in xrange(self._ncols-1,-1,-1) for j in xrange(self._ncols-1,-1,-1)]) return D, U, V @@ -3433,17 +3434,18 @@ cdef class Matrix_integer_dense(Matrix_dense): EXAMPLES:: - sage: A = matrix(ZZ, 2,3, [1..6]); A - [1 2 3] - [4 5 6] - sage: A.randomize() - sage: A - [-8 2 0] - [ 0 1 -1] - sage: A.randomize(x=-30,y=30) - sage: A - [ 5 -19 24] - [ 24 23 -9] + sage: A = matrix(ZZ, 2,3, [1..6]) + sage: ranks = [True, True, True] + sage: while any(ranks): + ....: A.randomize() + ....: ranks[A.rank()] = False + + sage: mini = 0 + sage: maxi = 0 + sage: while mini != -30 and maxi != 30: + ....: A.randomize(x=-30, y=30) + ....: mini = min(min(A.list()), mini) + ....: maxi = min(min(A.list()), maxi) """ density = float(density) if density <= 0: @@ -5530,7 +5532,7 @@ cdef class Matrix_integer_dense(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(ZZ,3,3) + sage: A = matrix(ZZ, 3, 3, [-8, 2, 0, 0, 1, -1, 2, 1, -95]) sage: As = singular(A); As -8 2 0 0 1 -1 diff --git a/src/sage/matrix/matrix_integer_dense_saturation.py b/src/sage/matrix/matrix_integer_dense_saturation.py index e256849c1c9..87b55b6d7e6 100644 --- a/src/sage/matrix/matrix_integer_dense_saturation.py +++ b/src/sage/matrix/matrix_integer_dense_saturation.py @@ -81,10 +81,18 @@ def random_sublist_of_size(k, n): EXAMPLES:: sage: import sage.matrix.matrix_integer_dense_saturation as s - sage: s.random_sublist_of_size(10,3) - [0, 1, 5] - sage: s.random_sublist_of_size(10,7) - [0, 1, 3, 4, 5, 7, 8] + sage: l = s.random_sublist_of_size(10, 3) + sage: len(l) + 3 + sage: l_check = [-1] + l + [10] + sage: all(l_check[i] < l_check[i+1] for i in range(4)) + True + sage: l = s.random_sublist_of_size(10, 7) + sage: len(l) + 7 + sage: l_check = [-1] + l + [10] + sage: all(l_check[i] < l_check[i+1] for i in range(8)) + True """ if n > k: raise ValueError("n must be <= len(v)") diff --git a/src/sage/matrix/matrix_integer_sparse.pyx b/src/sage/matrix/matrix_integer_sparse.pyx index c53c92602ed..8223fa8ab08 100644 --- a/src/sage/matrix/matrix_integer_sparse.pyx +++ b/src/sage/matrix/matrix_integer_sparse.pyx @@ -611,13 +611,13 @@ cdef class Matrix_integer_sparse(Matrix_sparse): [0 3 0] [0 0 0] sage: U - [ 0 1 0] + [ 0 2 -1] [ 0 -1 1] - [-1 2 -1] + [ 1 -2 1] sage: V - [-1 4 1] - [ 1 -3 -2] [ 0 0 1] + [-1 2 -2] + [ 1 -1 1] sage: U*A*V [1 0 0] [0 3 0] @@ -632,12 +632,12 @@ cdef class Matrix_integer_sparse(Matrix_sparse): [0 2] [0 0] sage: U - [ 0 1 0] + [ 0 2 -1] [ 0 -1 1] - [-1 2 -1] + [ 1 -2 1] sage: V - [-1 3] - [ 1 -2] + [-1 1] + [ 1 0] sage: U * A * V [1 0] [0 2] diff --git a/src/sage/matrix/matrix_mod2_dense.pyx b/src/sage/matrix/matrix_mod2_dense.pyx index 540cd66e28f..8f1374eb39f 100644 --- a/src/sage/matrix/matrix_mod2_dense.pyx +++ b/src/sage/matrix/matrix_mod2_dense.pyx @@ -277,10 +277,7 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse sage: B = random_matrix(GF(2),3,3) sage: B.set_immutable() - sage: {B:0} # indirect doctest - {[0 1 0] - [0 1 1] - [0 0 0]: 0} + sage: _ = {B:0} # indirect doctest sage: M = random_matrix(GF(2), 123, 321) sage: M.set_immutable() sage: MZ = M.change_ring(ZZ) @@ -396,8 +393,8 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse EXAMPLES:: - sage: B = random_matrix(GF(2),3,3) - sage: B # indirect doctest + sage: B = matrix(GF(2), 3, 3, [0, 1, 0, 0, 1, 1, 0, 0, 0]) + sage: B # indirect doctest [0 1 0] [0 1 1] [0 0 0] @@ -488,26 +485,15 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse EXAMPLES:: - sage: A = random_matrix(GF(2),10,10); A - [0 1 0 1 1 0 0 0 1 1] - [0 1 1 1 0 1 1 0 0 1] - [0 0 0 1 0 1 0 0 1 0] - [0 1 1 0 0 1 0 1 1 0] - [0 0 0 1 1 1 1 0 1 1] - [0 0 1 1 1 1 0 0 0 0] - [1 1 1 1 0 1 0 1 1 0] - [0 0 0 1 1 0 0 0 1 1] - [1 0 0 0 1 1 1 0 1 1] - [1 0 0 1 1 0 1 0 0 0] - - sage: A.row(0) - (0, 1, 0, 1, 1, 0, 0, 0, 1, 1) - - sage: A.row(-1) - (1, 0, 0, 1, 1, 0, 1, 0, 0, 0) + sage: l = [GF(2).random_element() for _ in range(100)] + sage: A = matrix(GF(2), 10, 10 , l) + sage: list(A.row(0)) == l[:10] + True + sage: list(A.row(-1)) == l[-10:] + True - sage: A.row(2,from_list=True) - (0, 0, 0, 1, 0, 1, 0, 0, 1, 0) + sage: list(A.row(2, from_list=True)) == l[20:30] + True sage: A = Matrix(GF(2),1,0) sage: A.row(0) @@ -1175,18 +1161,19 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse EXAMPLES:: sage: A = matrix(GF(2), 5, 5, 0) - sage: A.randomize(0.5); A - [0 0 0 1 1] - [0 1 0 0 1] - [1 0 0 0 0] - [0 1 0 0 0] - [0 0 0 1 0] - sage: A.randomize(); A - [0 0 1 1 0] - [1 1 0 0 1] - [1 1 1 1 0] - [1 1 1 1 1] - [0 0 1 1 0] + sage: A.randomize(0.5) + sage: A.density() < 0.5 + True + sage: expected = 0.5 + sage: A = matrix(GF(2), 5, 5, 0) + sage: A.randomize() + sage: density_sum = float(A.density()) + sage: total = 1 + sage: while abs(density_sum/total - expected) > 0.001: + ....: A = matrix(GF(2), 5, 5, 0) + ....: A.randomize() + ....: density_sum += float(A.density()) + ....: total += 1 TESTS: @@ -1196,8 +1183,10 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse problem is gone, with Mersenne Twister:: sage: MS2 = MatrixSpace(GF(2), 1000) - sage: [MS2.random_element().rank() for i in range(5)] - [999, 998, 1000, 999, 999] + sage: from collections import defaultdict + sage: found = defaultdict(bool) + sage: while not all(found[i] for i in range(997, 1001)): + ....: found[MS2.random_element().rank()] = True Testing corner case:: @@ -1259,15 +1248,10 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse """ EXAMPLES:: - sage: A = random_matrix(GF(2),3,3); A - [0 1 0] - [0 1 1] - [0 0 0] - - sage: A.rescale_row(0,0,0); A - [0 0 0] - [0 1 1] - [0 0 0] + sage: A = random_matrix(GF(2),3,3) + sage: A.rescale_row(0,0,0) + sage: A.row(0) + (0, 0, 0) """ if (int(multiple)%2) == 0: mzd_row_clear_offset(self._entries, row, start_col); @@ -1277,14 +1261,11 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse """ EXAMPLES:: - sage: A = random_matrix(GF(2),3,3); A - [0 1 0] - [0 1 1] - [0 0 0] - sage: A.add_multiple_of_row(0,1,1,0); A - [0 0 1] - [0 1 1] - [0 0 0] + sage: A = random_matrix(GF(2),3,3) + sage: B = copy(A) + sage: A.add_multiple_of_row(0,1,1,0) + sage: A.row(0) == B.row(0) + B.row(1) + True """ if (int(multiple)%2) != 0: mzd_row_add_offset(self._entries, row_to, row_from, start_col) @@ -1293,14 +1274,15 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse """ EXAMPLES:: - sage: A = random_matrix(GF(2),3,3); A - [0 1 0] - [0 1 1] - [0 0 0] - sage: A.swap_rows(0,1); A - [0 1 1] - [0 1 0] - [0 0 0] + sage: A = random_matrix(GF(2),3,3) + sage: B = copy(A) + sage: A.swap_rows(0,1) + sage: A.row(0) == B.row(1) + True + sage: A.row(1) == B.row(0) + True + sage: A.row(2) == B.row(2) + True """ mzd_row_swap(self._entries, row1, row2) @@ -1308,15 +1290,15 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse """ EXAMPLES:: - sage: A = random_matrix(GF(2),3,3); A - [0 1 0] - [0 1 1] - [0 0 0] - - sage: A.swap_columns(0,1); A - [1 0 0] - [1 0 1] - [0 0 0] + sage: A = random_matrix(GF(2),3,3) + sage: B = copy(A) + sage: A.swap_columns(0,1) + sage: A.column(0) == B.column(1) + True + sage: A.column(1) == B.column(0) + True + sage: A.column(2) == B.column(2) + True sage: A = random_matrix(GF(2),3,65) @@ -1519,13 +1501,11 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse sage: A = random_matrix(GF(2),2,3) sage: B = random_matrix(GF(2),2,0) - sage: A.augment(B) - [0 1 0] - [0 1 1] + sage: A.augment(B) == A + True - sage: B.augment(A) - [0 1 0] - [0 1 1] + sage: B.augment(A) == A + True sage: M = Matrix(GF(2), 0, 0, 0) sage: N = Matrix(GF(2), 0, 19, 0) @@ -1630,15 +1610,11 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse sage: A = random_matrix(GF(2),0,3) sage: B = random_matrix(GF(2),3,3) - sage: A.stack(B) - [0 1 0] - [0 1 1] - [0 0 0] + sage: A.stack(B) == B + True - sage: B.stack(A) - [0 1 0] - [0 1 1] - [0 0 0] + sage: B.stack(A) == B + True sage: M = Matrix(GF(2), 0, 0, 0) sage: N = Matrix(GF(2), 19, 0, 0) @@ -1823,18 +1799,19 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse EXAMPLES:: - sage: A = random_matrix(GF(2),1000,1000) - sage: d = A.density(); d - 62483/125000 - - sage: float(d) - 0.499864 - - sage: A.density(approx=True) - 0.499864000... + sage: A = random_matrix(GF(2), 1000, 1000) + sage: d = A.density() + sage: float(d) == A.density(approx=True) + True + sage: len(A.nonzero_positions())/1000^2 == d + True - sage: float(len(A.nonzero_positions())/1000^2) - 0.499864 + sage: total = 1.0 + sage: density_sum = A.density() + sage: while abs(density_sum/total - 0.5) > 0.001: + ....: A = random_matrix(GF(2), 1000, 1000) + ....: total += 1 + ....: density_sum += A.density() """ if approx: from sage.rings.real_mpfr import create_RealNumber @@ -1857,9 +1834,8 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse EXAMPLES:: - sage: A = random_matrix(GF(2), 1000, 1000) - sage: A.rank() - 999 + sage: while random_matrix(GF(2), 1000, 1000).rank() != 999: + ....: pass sage: A = matrix(GF(2),10, 0) sage: A.rank() @@ -2201,12 +2177,12 @@ def pluq(Matrix_mod2_dense A, algorithm="standard", int param=0): EXAMPLES:: sage: from sage.matrix.matrix_mod2_dense import pluq - sage: A = random_matrix(GF(2),4,4); A + sage: A = matrix(GF(2), 4, 4, [0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0]) + sage: A [0 1 0 1] [0 1 1 1] [0 0 0 1] [0 1 1 0] - sage: LU, P, Q = pluq(A) sage: LU [1 0 1 0] @@ -2264,7 +2240,9 @@ def ple(Matrix_mod2_dense A, algorithm="standard", int param=0): EXAMPLES:: sage: from sage.matrix.matrix_mod2_dense import ple - sage: A = random_matrix(GF(2),4,4); A + + sage: A = matrix(GF(2), 4, 4, [0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0]) + sage: A [0 1 0 1] [0 1 1 1] [0 0 0 1] diff --git a/src/sage/matrix/matrix_modn_dense_double.pyx b/src/sage/matrix/matrix_modn_dense_double.pyx index b10d2e9a71d..cb8f2a09e24 100644 --- a/src/sage/matrix/matrix_modn_dense_double.pyx +++ b/src/sage/matrix/matrix_modn_dense_double.pyx @@ -80,31 +80,21 @@ cdef class Matrix_modn_dense_double(Matrix_modn_dense_template): EXAMPLES:: - sage: A = random_matrix(GF(3016963), 4, 4); A - [ 220081 2824836 765701 2282256] - [1795330 767112 2967421 1373921] - [2757699 1142917 2720973 2877160] - [1674049 1341486 2641133 2173280] - sage: A[0,0] = 220082r; A - [ 220082 2824836 765701 2282256] - [1795330 767112 2967421 1373921] - [2757699 1142917 2720973 2877160] - [1674049 1341486 2641133 2173280] + sage: A = random_matrix(GF(3016963), 4, 4) + sage: l = A.list() + sage: A[0,0] = 220082r + sage: A.list()[1:] == l[1:] + True sage: a = A[0,0]; a 220082 sage: ~a 2859358 - sage: A = random_matrix(Integers(5099106), 4, 4); A - [2629491 1237101 2033003 3788106] - [4649912 1157595 4928315 4382585] - [4252686 978867 2601478 1759921] - [1303120 1860486 3405811 2203284] - sage: A[0,0] = 220082r; A - [ 220082 1237101 2033003 3788106] - [4649912 1157595 4928315 4382585] - [4252686 978867 2601478 1759921] - [1303120 1860486 3405811 2203284] + sage: A = random_matrix(Integers(5099106), 4, 4) + sage: l = A.list() + sage: A[0,0] = 220082r + sage: A.list()[1:] == l[1:] + True sage: a = A[0,0]; a 220082 sage: a*a @@ -120,29 +110,23 @@ cdef class Matrix_modn_dense_double(Matrix_modn_dense_template): EXAMPLES:: - sage: A = random_matrix(GF(3016963), 4, 4); A - [ 220081 2824836 765701 2282256] - [1795330 767112 2967421 1373921] - [2757699 1142917 2720973 2877160] - [1674049 1341486 2641133 2173280] + sage: A = random_matrix(GF(3016963), 4, 4) sage: K = A.base_ring() - sage: A[0,0] = K(220082); A - [ 220082 2824836 765701 2282256] - [1795330 767112 2967421 1373921] - [2757699 1142917 2720973 2877160] - [1674049 1341486 2641133 2173280] + sage: l = A.list() + sage: A[0,0] = K(220082) + sage: A.list()[1:] == l[1:] + True sage: a = A[0,0]; a 220082 sage: ~a 2859358 - sage: A = random_matrix(Integers(5099106), 4, 4); A - [2629491 1237101 2033003 3788106] - [4649912 1157595 4928315 4382585] - [4252686 978867 2601478 1759921] - [1303120 1860486 3405811 2203284] + sage: A = random_matrix(Integers(5099106), 4, 4) sage: K = A.base_ring() + sage: l = A.list() sage: A[0,0] = K(220081) + sage: A.list()[1:] == l[1:] + True sage: a = A[0,0]; a 220081 sage: a*a @@ -166,31 +150,25 @@ cdef class Matrix_modn_dense_double(Matrix_modn_dense_template): EXAMPLES:: - sage: A = random_matrix(GF(3016963), 4, 4); A - [ 220081 2824836 765701 2282256] - [1795330 767112 2967421 1373921] - [2757699 1142917 2720973 2877160] - [1674049 1341486 2641133 2173280] - sage: a = A[0,0]; a - 220081 - sage: ~a - 697224 - sage: K = A.base_ring() - sage: ~K(220081) - 697224 - - sage: A = random_matrix(Integers(5099106), 4, 4); A - [2629491 1237101 2033003 3788106] - [4649912 1157595 4928315 4382585] - [4252686 978867 2601478 1759921] - [1303120 1860486 3405811 2203284] - sage: a = A[0,1]; a - 1237101 - sage: a*a - 3803997 - sage: K = A.base_ring() - sage: K(1237101)^2 - 3803997 + sage: K = GF(3016963) + sage: l = [K.random_element() for _ in range(4*4)] + sage: A = matrix(K, 4, 4, l) + sage: a = A[0,0] + sage: a == l[0] + True + sage: a == 0 or ~a*a == 1 + True + sage: a.parent() is K + True + + sage: K = Integers(5099106) + sage: l = [K.random_element() for _ in range(4*4)] + sage: A = matrix(Integers(5099106), 4, 4, l) + sage: a = A[0,1] + sage: a == l[1] + True + sage: a*a == K(Integer(l[1]))^2 + True """ cdef Matrix_modn_dense_double _self = self cdef double result = (self)._matrix[i][j] diff --git a/src/sage/matrix/matrix_modn_dense_float.pyx b/src/sage/matrix/matrix_modn_dense_float.pyx index b23389a83b9..8a215de6f66 100644 --- a/src/sage/matrix/matrix_modn_dense_float.pyx +++ b/src/sage/matrix/matrix_modn_dense_float.pyx @@ -73,27 +73,21 @@ cdef class Matrix_modn_dense_float(Matrix_modn_dense_template): EXAMPLES:: - sage: A = random_matrix(GF(7), 4, 4); A - [3 1 6 6] - [4 4 2 2] - [3 5 4 5] - [6 2 2 1] - sage: A[0,0] = 12; A - [5 1 6 6] - [4 4 2 2] - [3 5 4 5] - [6 2 2 1] - - sage: B = random_matrix(Integers(100), 4, 4); B - [13 95 1 16] - [18 33 7 31] - [92 19 18 93] - [82 42 15 38] - sage: B[0,0] = 422; B - [22 95 1 16] - [18 33 7 31] - [92 19 18 93] - [82 42 15 38] + sage: A = random_matrix(GF(7), 4, 4) + sage: l = A.list() + sage: A[0,0] = 12 + sage: A.list()[0] == 12 + True + sage: l[1:] == A.list()[1:] + True + + sage: B = random_matrix(Integers(100), 4, 4) + sage: l = B.list() + sage: B[0,0] = 422 + sage: B.list()[0] == 22 + True + sage: l[1:] == B.list()[1:] + True """ self._matrix[i][j] = value @@ -105,31 +99,25 @@ cdef class Matrix_modn_dense_float(Matrix_modn_dense_template): EXAMPLES:: - sage: A = random_matrix(GF(13), 4, 4); A - [ 0 0 2 9] - [10 6 11 8] - [10 12 8 8] - [ 3 6 8 0] + sage: A = random_matrix(GF(13), 4, 4) + sage: l = A.list() sage: K = A.base_ring() sage: x = K(27) - sage: A[0,0] = x; A - [ 1 0 2 9] - [10 6 11 8] - [10 12 8 8] - [ 3 6 8 0] - - sage: B = random_matrix(Integers(200), 4, 4); B - [ 13 95 101 116] - [118 133 7 131] - [192 19 118 193] - [ 82 142 115 38] + sage: A[0,0] = x + sage: A[0,0] == x + True + sage: l[1:] == A.list()[1:] + True + + sage: B = random_matrix(Integers(200), 4, 4) + sage: l = B.list() sage: R = B.base_ring() sage: x = R(311) - sage: B[0,0] = x; B - [111 95 101 116] - [118 133 7 131] - [192 19 118 193] - [ 82 142 115 38] + sage: B[0,0] = x + sage: B.list()[0] == x + True + sage: l[1:] == B.list()[1:] + True """ self._matrix[i][j] = (x).ivalue @@ -144,24 +132,21 @@ cdef class Matrix_modn_dense_float(Matrix_modn_dense_template): EXAMPLES:: - sage: A = random_matrix(Integers(100), 4, 4); A - [ 4 95 83 47] - [44 57 91 53] - [75 53 15 39] - [26 25 10 74] - sage: a = A[0,0]; a - 4 - sage: a in A.base_ring() + sage: R = Integers(100) + sage: l = [R.random_element() for _ in range(4*4)] + sage: A = matrix(Integers(100), 4, 4, l) + sage: a = A[0,0] + sage: a == l[0] + True + sage: a in R True - sage: B = random_matrix(Integers(100), 4, 4); B - [13 95 1 16] - [18 33 7 31] - [92 19 18 93] - [82 42 15 38] - sage: b = B[0,0]; b - 13 - sage: b in B.base_ring() + sage: l = [R.random_element() for _ in range(4*4)] + sage: B = matrix(Integers(100), 4, 4, l) + sage: b = B[0,0] + sage: b == l[0] + True + sage: b in R True """ cdef float result = (self)._matrix[i][j] diff --git a/src/sage/matrix/matrix_modn_dense_template.pxi b/src/sage/matrix/matrix_modn_dense_template.pxi index c0e6d2e7047..534cb7f0fc8 100644 --- a/src/sage/matrix/matrix_modn_dense_template.pxi +++ b/src/sage/matrix/matrix_modn_dense_template.pxi @@ -389,34 +389,10 @@ cpdef __matrix_from_rows_of_matrices(X): EXAMPLES:: - sage: X = [random_matrix(GF(17), 4, 4) for _ in range(10)]; X - [ - [ 2 14 0 15] [12 14 3 13] [ 9 15 8 1] [ 2 12 6 10] - [11 10 16 2] [10 1 14 6] [ 5 8 10 11] [12 0 6 9] - [ 9 4 10 14] [ 2 14 13 7] [ 5 12 4 9] [ 7 7 3 8] - [ 1 14 3 14], [ 6 14 10 3], [15 2 6 11], [ 2 9 1 5], - - [12 13 7 16] [ 5 3 16 2] [14 15 16 4] [ 1 15 11 0] - [ 7 11 11 1] [11 10 12 14] [14 1 12 13] [16 13 8 14] - [ 0 2 0 4] [ 0 7 16 4] [ 5 5 16 13] [13 14 16 4] - [ 7 9 8 15], [ 6 5 2 3], [10 12 1 7], [15 6 6 6], - - [ 4 10 11 15] [13 12 5 1] - [11 2 9 14] [16 13 16 7] - [12 5 4 4] [12 2 0 11] - [ 2 0 12 8], [13 11 6 15] - ] - sage: X[0]._matrix_from_rows_of_matrices(X) # indirect doctest - [ 2 14 0 15 11 10 16 2 9 4 10 14 1 14 3 14] - [12 14 3 13 10 1 14 6 2 14 13 7 6 14 10 3] - [ 9 15 8 1 5 8 10 11 5 12 4 9 15 2 6 11] - [ 2 12 6 10 12 0 6 9 7 7 3 8 2 9 1 5] - [12 13 7 16 7 11 11 1 0 2 0 4 7 9 8 15] - [ 5 3 16 2 11 10 12 14 0 7 16 4 6 5 2 3] - [14 15 16 4 14 1 12 13 5 5 16 13 10 12 1 7] - [ 1 15 11 0 16 13 8 14 13 14 16 4 15 6 6 6] - [ 4 10 11 15 11 2 9 14 12 5 4 4 2 0 12 8] - [13 12 5 1 16 13 16 7 12 2 0 11 13 11 6 15] + sage: X = [random_matrix(GF(17), 4, 4) for _ in range(10)] + sage: Y = X[0]._matrix_from_rows_of_matrices(X) # indirect doctest + sage: all(list(Y[i]) == X[i].list() for i in range(10)) + True OUTPUT: A single matrix mod ``p`` whose ``i``-th row is ``X[i].list()``. @@ -551,16 +527,11 @@ cdef class Matrix_modn_dense_template(Matrix_dense): sage: B = random_matrix(GF(127),3,3) sage: B.set_immutable() - sage: {B:0} # indirect doctest - {[ 9 75 94] - [ 4 57 112] - [ 59 85 45]: 0} + sage: _ = {B:0} # indirect doctest sage: M = random_matrix(GF(7), 10, 10) sage: M.set_immutable() - sage: hash(M) - -5724333594806680561 # 64-bit - -1581874161 # 32-bit + sage: _ = hash(M) sage: MZ = M.change_ring(ZZ) sage: MZ.set_immutable() sage: hash(MZ) == hash(M) @@ -1003,26 +974,15 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(7),2,2); A - [3 1] - [6 6] - - sage: B = random_matrix(GF(7),2,2); B - [4 4] - [2 2] - - sage: A*B - [0 0] - [1 1] - - sage: 3*A - [2 3] - [4 4] + sage: A = random_matrix(GF(7),2,2) + sage: B = random_matrix(GF(7),2,2) + sage: C = A*B + sage: all(C[i, j] == sum(A[i, k]*B[k, j] for k in range(2)) for i in range(2) for j in range(2)) + True sage: MS = parent(A) - sage: MS(3) * A - [2 3] - [4 4] + sage: MS(3) * A == 3*A + True :: @@ -1048,26 +1008,15 @@ cdef class Matrix_modn_dense_template(Matrix_dense): :: - sage: A = random_matrix(Integers(8),2,2); A - [0 5] - [6 4] - - sage: B = random_matrix(Integers(8),2,2); B - [4 4] - [5 6] - - sage: A*B - [1 6] - [4 0] - - sage: 3*A - [0 7] - [2 4] + sage: A = random_matrix(Integers(8),2,2) + sage: B = random_matrix(Integers(8),2,2) + sage: C = A*B + sage: all(C[i, j] == sum(A[i, k]*B[k, j] for k in range(2)) for i in range(2) for j in range(2)) + True sage: MS = parent(A) - sage: MS(3) * A - [0 7] - [2 4] + sage: MS(3) * A == 3*A + True :: @@ -1093,26 +1042,15 @@ cdef class Matrix_modn_dense_template(Matrix_dense): :: - sage: A = random_matrix(GF(16007),2,2); A - [ 6194 13327] - [ 5985 5926] - - sage: B = random_matrix(GF(16007),2,2); B - [ 6901 1242] - [13032 859] - - sage: A*B - [ 7618 12476] - [14289 6330] - - sage: 3*A - [2575 7967] - [1948 1771] + sage: A = random_matrix(GF(16007),2,2) + sage: B = random_matrix(GF(16007),2,2) + sage: C = A*B + sage: all(C[i, j] == sum(A[i, k]*B[k, j] for k in range(2)) for i in range(2) for j in range(2)) + True sage: MS = parent(A) - sage: MS(3) * A - [2575 7967] - [1948 1771] + sage: MS(3) * A == 3*A + True :: @@ -1140,26 +1078,15 @@ cdef class Matrix_modn_dense_template(Matrix_dense): :: - sage: A = random_matrix(Integers(1008),2,2); A - [ 41 973] - [851 876] - - sage: B = random_matrix(Integers(1008),2,2); B - [180 234] - [680 640] - - sage: A*B - [716 298] - [924 750] - - sage: 3*A - [123 903] - [537 612] + sage: A = random_matrix(Integers(1008),2,2) + sage: B = random_matrix(Integers(1008),2,2) + sage: C = A*B + sage: all(C[i, j] == sum(A[i, k]*B[k, j] for k in range(2)) for i in range(2) for j in range(2)) + True sage: MS = parent(A) - sage: MS(3) * A - [123 903] - [537 612] + sage: MS(3) * A == 3*A + True :: @@ -1329,52 +1256,29 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(19), 10, 10); A - [ 3 1 8 10 5 16 18 9 6 1] - [ 5 14 4 4 14 15 5 11 3 0] - [ 4 1 0 7 11 6 17 8 5 6] - [ 4 6 9 4 8 1 18 17 8 18] - [11 2 0 6 13 7 4 11 16 10] - [12 6 12 3 15 10 5 11 3 8] - [15 1 16 2 18 15 14 7 2 11] - [16 16 17 7 14 12 7 7 0 5] - [13 15 9 2 12 16 1 15 18 7] - [10 8 16 18 9 18 2 13 5 10] - + sage: A = random_matrix(GF(19), 10, 10) sage: B = copy(A) - sage: char_p = A.characteristic_polynomial(); char_p - x^10 + 2*x^9 + 18*x^8 + 4*x^7 + 13*x^6 + 11*x^5 + 2*x^4 + 5*x^3 + 7*x^2 + 16*x + 6 + sage: char_p = A.characteristic_polynomial() sage: char_p(A) == 0 True sage: B == A # A is not modified True - sage: min_p = A.minimal_polynomial(proof=True); min_p - x^10 + 2*x^9 + 18*x^8 + 4*x^7 + 13*x^6 + 11*x^5 + 2*x^4 + 5*x^3 + 7*x^2 + 16*x + 6 + sage: min_p = A.minimal_polynomial(proof=True) sage: min_p.divides(char_p) True :: - sage: A = random_matrix(GF(2916337), 7, 7); A - [ 514193 1196222 1242955 1040744 99523 2447069 40527] - [ 930282 2685786 2892660 1347146 1126775 2131459 869381] - [1853546 2266414 2897342 1342067 1054026 373002 84731] - [1270068 2421818 569466 537440 572533 297105 1415002] - [2079710 355705 2546914 2299052 2883413 1558788 1494309] - [1027319 1572148 250822 522367 2516720 585897 2296292] - [1797050 2128203 1161160 562535 2875615 1165768 286972] - + sage: A = random_matrix(GF(2916337), 7, 7) sage: B = copy(A) - sage: char_p = A.characteristic_polynomial(); char_p - x^7 + 1274305*x^6 + 1497602*x^5 + 12362*x^4 + 875330*x^3 + 31311*x^2 + 1858466*x + 700510 + sage: char_p = A.characteristic_polynomial() sage: char_p(A) == 0 True sage: B == A # A is not modified True - sage: min_p = A.minimal_polynomial(proof=True); min_p - x^7 + 1274305*x^6 + 1497602*x^5 + 12362*x^4 + 875330*x^3 + 31311*x^2 + 1858466*x + 700510 + sage: min_p = A.minimal_polynomial(proof=True) sage: min_p.divides(char_p) True @@ -1506,57 +1410,29 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(17), 10, 10); A - [ 2 14 0 15 11 10 16 2 9 4] - [10 14 1 14 3 14 12 14 3 13] - [10 1 14 6 2 14 13 7 6 14] - [10 3 9 15 8 1 5 8 10 11] - [ 5 12 4 9 15 2 6 11 2 12] - [ 6 10 12 0 6 9 7 7 3 8] - [ 2 9 1 5 12 13 7 16 7 11] - [11 1 0 2 0 4 7 9 8 15] - [ 5 3 16 2 11 10 12 14 0 7] - [16 4 6 5 2 3 14 15 16 4] - + sage: A = random_matrix(GF(17), 10, 10) sage: B = copy(A) - sage: min_p = A.minimal_polynomial(proof=True); min_p - x^10 + 13*x^9 + 10*x^8 + 9*x^7 + 10*x^6 + 4*x^5 + 10*x^4 + 10*x^3 + 12*x^2 + 14*x + 7 + sage: min_p = A.minimal_polynomial(proof=True) sage: min_p(A) == 0 True sage: B == A True - sage: char_p = A.characteristic_polynomial(); char_p - x^10 + 13*x^9 + 10*x^8 + 9*x^7 + 10*x^6 + 4*x^5 + 10*x^4 + 10*x^3 + 12*x^2 + 14*x + 7 + sage: char_p = A.characteristic_polynomial() sage: min_p.divides(char_p) True :: - sage: A = random_matrix(GF(1214471), 10, 10); A - [ 160562 831940 65852 173001 515930 714380 778254 844537 584888 392730] - [ 502193 959391 614352 775603 240043 1156372 104118 1175992 612032 1049083] - [ 660489 1066446 809624 15010 1002045 470722 314480 1155149 1173111 14213] - [1190467 1079166 786442 429883 563611 625490 1015074 888047 1090092 892387] - [ 4724 244901 696350 384684 254561 898612 44844 83752 1091581 349242] - [ 130212 580087 253296 472569 913613 919150 38603 710029 438461 736442] - [ 943501 792110 110470 850040 713428 668799 1122064 325250 1084368 520553] - [1179743 791517 34060 1183757 1118938 642169 47513 73428 1076788 216479] - [ 626571 105273 400489 1041378 1186801 158611 888598 1138220 1089631 56266] - [1092400 890773 1060810 211135 719636 1011640 631366 427711 547497 1084281] - + sage: A = random_matrix(GF(1214471), 10, 10) sage: B = copy(A) - sage: min_p = A.minimal_polynomial(proof=True); min_p - x^10 + 384251*x^9 + 702437*x^8 + 960299*x^7 + 202699*x^6 + 409368*x^5 + 1109249*x^4 + 1163061*x^3 + 333802*x^2 + 273775*x + 55190 - + sage: min_p = A.minimal_polynomial(proof=True) sage: min_p(A) == 0 True sage: B == A True - sage: char_p = A.characteristic_polynomial(); char_p - x^10 + 384251*x^9 + 702437*x^8 + 960299*x^7 + 202699*x^6 + 409368*x^5 + 1109249*x^4 + 1163061*x^3 + 333802*x^2 + 273775*x + 55190 - + sage: char_p = A.characteristic_polynomial() sage: min_p.divides(char_p) True @@ -1656,28 +1532,15 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(19), 10, 10); A - [ 3 1 8 10 5 16 18 9 6 1] - [ 5 14 4 4 14 15 5 11 3 0] - [ 4 1 0 7 11 6 17 8 5 6] - [ 4 6 9 4 8 1 18 17 8 18] - [11 2 0 6 13 7 4 11 16 10] - [12 6 12 3 15 10 5 11 3 8] - [15 1 16 2 18 15 14 7 2 11] - [16 16 17 7 14 12 7 7 0 5] - [13 15 9 2 12 16 1 15 18 7] - [10 8 16 18 9 18 2 13 5 10] - + sage: A = random_matrix(GF(19), 10, 10) sage: B = copy(A) - sage: char_p = A._charpoly_linbox(); char_p - x^10 + 2*x^9 + 18*x^8 + 4*x^7 + 13*x^6 + 11*x^5 + 2*x^4 + 5*x^3 + 7*x^2 + 16*x + 6 + sage: char_p = A._charpoly_linbox() sage: char_p(A) == 0 True sage: B == A # A is not modified True - sage: min_p = A.minimal_polynomial(proof=True); min_p - x^10 + 2*x^9 + 18*x^8 + 4*x^7 + 13*x^6 + 11*x^5 + 2*x^4 + 5*x^3 + 7*x^2 + 16*x + 6 + sage: min_p = A.minimal_polynomial(proof=True) sage: min_p.divides(char_p) True """ @@ -1726,61 +1589,24 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(7), 10, 20); A - [3 1 6 6 4 4 2 2 3 5 4 5 6 2 2 1 2 5 0 5] - [3 2 0 5 0 1 5 4 2 3 6 4 5 0 2 4 2 0 6 3] - [2 2 4 2 4 5 3 4 4 4 2 5 2 5 4 5 1 1 1 1] - [0 6 3 4 2 2 3 5 1 1 4 2 6 5 6 3 4 5 5 3] - [5 2 4 3 6 2 3 6 2 1 3 3 5 3 4 2 2 1 6 2] - [0 5 6 3 2 5 6 6 3 2 1 4 5 0 2 6 5 2 5 1] - [4 0 4 2 6 3 3 5 3 0 0 1 2 5 5 1 6 0 0 3] - [2 0 1 0 0 3 0 2 4 2 2 4 4 4 5 4 1 2 3 4] - [2 4 1 4 3 0 6 2 2 5 2 5 3 6 4 2 2 6 4 4] - [0 0 2 2 1 6 2 0 5 0 4 3 1 6 0 6 0 4 6 5] - - sage: A.echelon_form() - [1 0 0 0 0 0 0 0 0 0 6 2 6 0 1 1 2 5 6 2] - [0 1 0 0 0 0 0 0 0 0 0 4 5 4 3 4 2 5 1 2] - [0 0 1 0 0 0 0 0 0 0 6 3 4 6 1 0 3 6 5 6] - [0 0 0 1 0 0 0 0 0 0 0 3 5 2 3 4 0 6 5 3] - [0 0 0 0 1 0 0 0 0 0 0 6 3 4 5 3 0 4 3 2] - [0 0 0 0 0 1 0 0 0 0 1 1 0 2 4 2 5 5 5 0] - [0 0 0 0 0 0 1 0 0 0 1 0 1 3 2 0 0 0 5 3] - [0 0 0 0 0 0 0 1 0 0 4 4 2 6 5 4 3 4 1 0] - [0 0 0 0 0 0 0 0 1 0 1 0 4 2 3 5 4 6 4 0] - [0 0 0 0 0 0 0 0 0 1 2 0 5 0 5 5 3 1 1 4] + sage: A = random_matrix(GF(7), 10, 20) + sage: E = A.echelon_form() + sage: A.row_space() == E.row_space() + True + sage: all(r[r.nonzero_positions()[0]] == 1 for r in E.rows() if r) + True :: - sage: A = random_matrix(GF(13), 10, 10); A - [ 8 3 11 11 9 4 8 7 9 9] - [ 2 9 6 5 7 12 3 4 11 5] - [12 6 11 12 4 3 3 8 9 5] - [ 4 2 10 5 10 1 1 1 6 9] - [12 8 5 5 11 4 1 2 8 11] - [ 2 6 9 11 4 7 1 0 12 2] - [ 8 9 0 7 7 7 10 4 1 4] - [ 0 8 2 6 7 5 7 12 2 3] - [ 2 11 12 3 4 7 2 9 6 1] - [ 0 11 5 9 4 5 5 8 7 10] - + sage: A = random_matrix(GF(13), 10, 10) + sage: while A.rank() != 10: + ....: A = random_matrix(GF(13), 10, 10) sage: MS = parent(A) sage: B = A.augment(MS(1)) sage: B.echelonize() sage: A.rank() 10 - sage: C = B.submatrix(0,10,10,10); C - [ 4 9 4 4 0 4 7 11 9 11] - [11 7 6 8 2 8 6 11 9 5] - [ 3 9 9 2 4 8 9 2 9 4] - [ 7 0 11 4 0 9 6 11 8 1] - [12 12 4 12 3 12 6 1 7 12] - [12 2 11 6 6 6 7 0 10 6] - [ 0 7 3 4 7 11 10 12 4 6] - [ 5 11 0 5 3 11 4 12 5 12] - [ 6 7 3 5 1 4 11 7 4 1] - [ 4 9 6 7 11 1 2 12 6 7] - + sage: C = B.submatrix(0,10,10,10) sage: ~A == C True @@ -1794,28 +1620,12 @@ cdef class Matrix_modn_dense_template(Matrix_dense): :: - sage: A = random_matrix(GF(16007), 10, 20); A - [15455 1177 10072 4693 3887 4102 10746 15265 6684 14559 4535 13921 9757 9525 9301 8566 2460 9609 3887 6205] - [ 8602 10035 1242 9776 162 7893 12619 6660 13250 1988 14263 11377 2216 1247 7261 8446 15081 14412 7371 7948] - [12634 7602 905 9617 13557 2694 13039 4936 12208 15480 3787 11229 593 12462 5123 14167 6460 3649 5821 6736] - [10554 2511 11685 12325 12287 6534 11636 5004 6468 3180 3607 11627 13436 5106 3138 13376 8641 9093 2297 5893] - [ 1025 11376 10288 609 12330 3021 908 13012 2112 11505 56 5971 338 2317 2396 8561 5593 3782 7986 13173] - [ 7607 588 6099 12749 10378 111 2852 10375 8996 7969 774 13498 12720 4378 6817 6707 5299 9406 13318 2863] - [15545 538 4840 1885 8471 1303 11086 14168 1853 14263 3995 12104 1294 7184 1188 11901 15971 2899 4632 711] - [ 584 11745 7540 15826 15027 5953 7097 14329 10889 12532 13309 15041 6211 1749 10481 9999 2751 11068 21 2795] - [ 761 11453 3435 10596 2173 7752 15941 14610 1072 8012 9458 5440 612 10581 10400 101 11472 13068 7758 7898] - [10658 4035 6662 655 7546 4107 6987 1877 4072 4221 7679 14579 2474 8693 8127 12999 11141 605 9404 10003] - sage: A.echelon_form() - [ 1 0 0 0 0 0 0 0 0 0 8416 8364 10318 1782 13872 4566 14855 7678 11899 2652] - [ 0 1 0 0 0 0 0 0 0 0 4782 15571 3133 10964 5581 10435 9989 14303 5951 8048] - [ 0 0 1 0 0 0 0 0 0 0 15688 6716 13819 4144 257 5743 14865 15680 4179 10478] - [ 0 0 0 1 0 0 0 0 0 0 4307 9488 2992 9925 13984 15754 8185 11598 14701 10784] - [ 0 0 0 0 1 0 0 0 0 0 927 3404 15076 1040 2827 9317 14041 10566 5117 7452] - [ 0 0 0 0 0 1 0 0 0 0 1144 10861 5241 6288 9282 5748 3715 13482 7258 9401] - [ 0 0 0 0 0 0 1 0 0 0 769 1804 1879 4624 6170 7500 11883 9047 874 597] - [ 0 0 0 0 0 0 0 1 0 0 15591 13686 5729 11259 10219 13222 15177 15727 5082 11211] - [ 0 0 0 0 0 0 0 0 1 0 8375 14939 13471 12221 8103 4212 11744 10182 2492 11068] - [ 0 0 0 0 0 0 0 0 0 1 6534 396 6780 14734 1206 3848 7712 9770 10755 410] + sage: A = random_matrix(GF(16007), 10, 20) + sage: E = A.echelon_form() + sage: A.row_space() == E.row_space() + True + sage: all(r[r.nonzero_positions()[0]] == 1 for r in E.rows() if r) + True :: @@ -1936,29 +1746,13 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(7), 10, 20); A - [3 1 6 6 4 4 2 2 3 5 4 5 6 2 2 1 2 5 0 5] - [3 2 0 5 0 1 5 4 2 3 6 4 5 0 2 4 2 0 6 3] - [2 2 4 2 4 5 3 4 4 4 2 5 2 5 4 5 1 1 1 1] - [0 6 3 4 2 2 3 5 1 1 4 2 6 5 6 3 4 5 5 3] - [5 2 4 3 6 2 3 6 2 1 3 3 5 3 4 2 2 1 6 2] - [0 5 6 3 2 5 6 6 3 2 1 4 5 0 2 6 5 2 5 1] - [4 0 4 2 6 3 3 5 3 0 0 1 2 5 5 1 6 0 0 3] - [2 0 1 0 0 3 0 2 4 2 2 4 4 4 5 4 1 2 3 4] - [2 4 1 4 3 0 6 2 2 5 2 5 3 6 4 2 2 6 4 4] - [0 0 2 2 1 6 2 0 5 0 4 3 1 6 0 6 0 4 6 5] - - sage: A._echelonize_linbox(); A - [1 0 0 0 0 0 0 0 0 0 6 2 6 0 1 1 2 5 6 2] - [0 1 0 0 0 0 0 0 0 0 0 4 5 4 3 4 2 5 1 2] - [0 0 1 0 0 0 0 0 0 0 6 3 4 6 1 0 3 6 5 6] - [0 0 0 1 0 0 0 0 0 0 0 3 5 2 3 4 0 6 5 3] - [0 0 0 0 1 0 0 0 0 0 0 6 3 4 5 3 0 4 3 2] - [0 0 0 0 0 1 0 0 0 0 1 1 0 2 4 2 5 5 5 0] - [0 0 0 0 0 0 1 0 0 0 1 0 1 3 2 0 0 0 5 3] - [0 0 0 0 0 0 0 1 0 0 4 4 2 6 5 4 3 4 1 0] - [0 0 0 0 0 0 0 0 1 0 1 0 4 2 3 5 4 6 4 0] - [0 0 0 0 0 0 0 0 0 1 2 0 5 0 5 5 3 1 1 4] + sage: A = random_matrix(GF(7), 10, 20) + sage: B = copy(A) + sage: A._echelonize_linbox() + sage: A.row_space() == B.row_space() + True + sage: all(r[r.nonzero_positions()[0]] == 1 for r in A.rows() if r) + True """ self.check_mutability() self.clear_cache() @@ -1982,29 +1776,13 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(7), 10, 20); A - [3 1 6 6 4 4 2 2 3 5 4 5 6 2 2 1 2 5 0 5] - [3 2 0 5 0 1 5 4 2 3 6 4 5 0 2 4 2 0 6 3] - [2 2 4 2 4 5 3 4 4 4 2 5 2 5 4 5 1 1 1 1] - [0 6 3 4 2 2 3 5 1 1 4 2 6 5 6 3 4 5 5 3] - [5 2 4 3 6 2 3 6 2 1 3 3 5 3 4 2 2 1 6 2] - [0 5 6 3 2 5 6 6 3 2 1 4 5 0 2 6 5 2 5 1] - [4 0 4 2 6 3 3 5 3 0 0 1 2 5 5 1 6 0 0 3] - [2 0 1 0 0 3 0 2 4 2 2 4 4 4 5 4 1 2 3 4] - [2 4 1 4 3 0 6 2 2 5 2 5 3 6 4 2 2 6 4 4] - [0 0 2 2 1 6 2 0 5 0 4 3 1 6 0 6 0 4 6 5] - - sage: A._echelon_in_place_classical(); A - [1 0 0 0 0 0 0 0 0 0 6 2 6 0 1 1 2 5 6 2] - [0 1 0 0 0 0 0 0 0 0 0 4 5 4 3 4 2 5 1 2] - [0 0 1 0 0 0 0 0 0 0 6 3 4 6 1 0 3 6 5 6] - [0 0 0 1 0 0 0 0 0 0 0 3 5 2 3 4 0 6 5 3] - [0 0 0 0 1 0 0 0 0 0 0 6 3 4 5 3 0 4 3 2] - [0 0 0 0 0 1 0 0 0 0 1 1 0 2 4 2 5 5 5 0] - [0 0 0 0 0 0 1 0 0 0 1 0 1 3 2 0 0 0 5 3] - [0 0 0 0 0 0 0 1 0 0 4 4 2 6 5 4 3 4 1 0] - [0 0 0 0 0 0 0 0 1 0 1 0 4 2 3 5 4 6 4 0] - [0 0 0 0 0 0 0 0 0 1 2 0 5 0 5 5 3 1 1 4] + sage: A = random_matrix(GF(7), 10, 20) + sage: B = copy(A) + sage: A._echelon_in_place_classical() + sage: A.row_space() == B.row_space() + True + sage: all(r[r.nonzero_positions()[0]] == 1 for r in A.rows() if r) + True """ self.check_mutability() self.clear_cache() @@ -2153,28 +1931,13 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(17), 10, 10, density=0.1); A - [ 0 0 0 0 12 0 0 0 0 0] - [ 0 0 0 4 0 0 0 0 0 0] - [ 0 0 0 0 2 0 0 0 0 0] - [ 0 14 0 0 0 0 0 0 0 0] - [ 0 0 0 0 0 10 0 0 0 0] - [ 0 0 0 0 0 16 0 0 0 0] - [ 0 0 0 0 0 0 6 0 0 0] - [15 0 0 0 0 0 0 0 0 0] - [ 0 0 0 16 0 0 0 0 0 0] - [ 0 5 0 0 0 0 0 0 0 0] - sage: A.hessenbergize(); A - [ 0 0 0 0 0 0 0 12 0 0] - [15 0 0 0 0 0 0 0 0 0] - [ 0 0 0 0 0 0 0 2 0 0] - [ 0 0 0 0 14 0 0 0 0 0] - [ 0 0 0 4 0 0 0 0 0 0] - [ 0 0 0 0 5 0 0 0 0 0] - [ 0 0 0 0 0 0 6 0 0 0] - [ 0 0 0 0 0 0 0 0 0 10] - [ 0 0 0 0 0 0 0 0 0 0] - [ 0 0 0 0 0 0 0 0 0 16] + sage: A = random_matrix(GF(17), 10, 10, density=0.1) + sage: B = copy(A) + sage: A.hessenbergize() + sage: all(A[i,j] == 0 for j in range(10) for i in range(j+2, 10)) + True + sage: A.charpoly() == B.charpoly() + True """ self.check_mutability() x = self.fetch('in_hessenberg_form') @@ -2250,22 +2013,11 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(17), 10, 10, density=0.1); A - [ 0 0 0 0 12 0 0 0 0 0] - [ 0 0 0 4 0 0 0 0 0 0] - [ 0 0 0 0 2 0 0 0 0 0] - [ 0 14 0 0 0 0 0 0 0 0] - [ 0 0 0 0 0 10 0 0 0 0] - [ 0 0 0 0 0 16 0 0 0 0] - [ 0 0 0 0 0 0 6 0 0 0] - [15 0 0 0 0 0 0 0 0 0] - [ 0 0 0 16 0 0 0 0 0 0] - [ 0 5 0 0 0 0 0 0 0 0] - sage: A.characteristic_polynomial() - x^10 + 12*x^9 + 6*x^8 + 8*x^7 + 13*x^6 + sage: A = random_matrix(GF(17), 10, 10, density=0.1) + sage: B = copy(A) sage: P. = GF(17)[] - sage: A._charpoly_hessenberg('x') - x^10 + 12*x^9 + 6*x^8 + 8*x^7 + 13*x^6 + sage: A._charpoly_hessenberg('x') == B.charpoly() + True """ if self._nrows != self._ncols: raise ArithmeticError("charpoly not defined for non-square matrix.") @@ -2325,14 +2077,13 @@ cdef class Matrix_modn_dense_template(Matrix_dense): sage: A = random_matrix(GF(3), 100, 100) sage: B = copy(A) - sage: A.rank() - 99 + sage: _ = A.rank() sage: B == A True sage: A = random_matrix(GF(3), 100, 100, density=0.01) - sage: A.rank() - 63 + sage: A.transpose().rank() == A.rank() + True sage: A = matrix(GF(3), 100, 100) sage: A.rank() @@ -2399,68 +2150,38 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(7), 10, 10); A - [3 1 6 6 4 4 2 2 3 5] - [4 5 6 2 2 1 2 5 0 5] - [3 2 0 5 0 1 5 4 2 3] - [6 4 5 0 2 4 2 0 6 3] - [2 2 4 2 4 5 3 4 4 4] - [2 5 2 5 4 5 1 1 1 1] - [0 6 3 4 2 2 3 5 1 1] - [4 2 6 5 6 3 4 5 5 3] - [5 2 4 3 6 2 3 6 2 1] - [3 3 5 3 4 2 2 1 6 2] - - sage: A.determinant() - 6 + sage: s = set() + sage: while s != set(GF(7)): + ....: A = random_matrix(GF(7), 10, 10) + ....: s.add(A.determinant()) :: sage: A = random_matrix(GF(7), 100, 100) - sage: A.determinant() - 2 - - sage: A.transpose().determinant() - 2 + sage: A.determinant() == A.transpose().determinant() + True sage: B = random_matrix(GF(7), 100, 100) - sage: B.determinant() - 4 - sage: (A*B).determinant() == A.determinant() * B.determinant() True :: - sage: A = random_matrix(GF(16007), 10, 10); A - [ 5037 2388 4150 1400 345 5945 4240 14022 10514 700] - [15552 8539 1927 3870 9867 3263 11637 609 15424 2443] - [ 3761 15836 12246 15577 10178 13602 13183 15918 13942 2958] - [ 4526 10817 6887 6678 1764 9964 6107 1705 5619 5811] - [13537 15004 8307 11846 14779 550 14113 5477 7271 7091] - [13338 4927 11406 13065 5437 12431 6318 5119 14198 496] - [ 1044 179 12881 353 12975 12567 1092 10433 12304 954] - [10072 8821 14118 13895 6543 13484 10685 14363 2612 11070] - [15113 237 2612 14127 11589 5808 117 9656 15957 14118] - [15233 11080 5716 9029 11402 9380 13045 13986 14544 5771] - - sage: A.determinant() - 10207 + sage: A = random_matrix(GF(16007), 10, 10) + sage: A.determinant().parent() is GF(16007) + True :: sage: A = random_matrix(GF(16007), 100, 100) - sage: A.determinant() - 3576 + sage: A.determinant().parent() is GF(16007) + True - sage: A.transpose().determinant() - 3576 + sage: A.determinant() == A.transpose().determinant() + True sage: B = random_matrix(GF(16007), 100, 100) - sage: B.determinant() - 4075 - sage: (A*B).determinant() == A.determinant() * B.determinant() True @@ -2669,46 +2390,22 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(37), 10, 10); A - [24 15 7 27 32 34 16 32 25 23] - [11 3 22 13 35 33 1 10 33 25] - [33 9 25 3 15 27 30 30 7 12] - [10 0 35 4 12 34 32 16 19 17] - [36 4 21 17 3 34 11 10 10 17] - [32 15 23 2 23 32 5 8 18 11] - [24 5 28 13 21 22 29 18 33 30] - [26 18 10 26 17 31 35 18 25 30] - [21 1 4 14 11 17 29 16 18 12] - [34 19 14 11 35 30 35 34 25 33] - - sage: A[2] + 10*A[3] - (22, 9, 5, 6, 24, 34, 17, 5, 12, 34) + sage: A = random_matrix(GF(37), 10, 10) + sage: B = copy(A) sage: A.add_multiple_of_row(2, 3, 10) - sage: A - [24 15 7 27 32 34 16 32 25 23] - [11 3 22 13 35 33 1 10 33 25] - [22 9 5 6 24 34 17 5 12 34] - [10 0 35 4 12 34 32 16 19 17] - [36 4 21 17 3 34 11 10 10 17] - [32 15 23 2 23 32 5 8 18 11] - [24 5 28 13 21 22 29 18 33 30] - [26 18 10 26 17 31 35 18 25 30] - [21 1 4 14 11 17 29 16 18 12] - [34 19 14 11 35 30 35 34 25 33] + sage: all(A[i] == B[i] for i in range(10) if not i == 2) + True + sage: A[2] == B[2] + 10*B[3] + True sage: A.add_multiple_of_row(2, 3, 10, 4) - sage: A - [24 15 7 27 32 34 16 32 25 23] - [11 3 22 13 35 33 1 10 33 25] - [22 9 5 6 33 4 4 17 17 19] - [10 0 35 4 12 34 32 16 19 17] - [36 4 21 17 3 34 11 10 10 17] - [32 15 23 2 23 32 5 8 18 11] - [24 5 28 13 21 22 29 18 33 30] - [26 18 10 26 17 31 35 18 25 30] - [21 1 4 14 11 17 29 16 18 12] - [34 19 14 11 35 30 35 34 25 33] + sage: all(A[i] == B[i] for i in range(10) if not i == 2) + True + sage: A[2][:4] == B[2][:4] + 10*B[3][:4] + True + sage: A[2][4:] == B[2][4:] + 20*B[3][4:] + True """ cdef celement p cdef celement *v_from @@ -2730,46 +2427,22 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: - sage: A = random_matrix(GF(37), 10, 10); A - [24 15 7 27 32 34 16 32 25 23] - [11 3 22 13 35 33 1 10 33 25] - [33 9 25 3 15 27 30 30 7 12] - [10 0 35 4 12 34 32 16 19 17] - [36 4 21 17 3 34 11 10 10 17] - [32 15 23 2 23 32 5 8 18 11] - [24 5 28 13 21 22 29 18 33 30] - [26 18 10 26 17 31 35 18 25 30] - [21 1 4 14 11 17 29 16 18 12] - [34 19 14 11 35 30 35 34 25 33] - - sage: A.column(2) + 10*A.column(3) - (18, 4, 18, 1, 6, 6, 10, 11, 33, 13) + sage: A = random_matrix(GF(37), 10, 10) + sage: B = copy(A) sage: A.add_multiple_of_column(2, 3, 10) - sage: A - [24 15 18 27 32 34 16 32 25 23] - [11 3 4 13 35 33 1 10 33 25] - [33 9 18 3 15 27 30 30 7 12] - [10 0 1 4 12 34 32 16 19 17] - [36 4 6 17 3 34 11 10 10 17] - [32 15 6 2 23 32 5 8 18 11] - [24 5 10 13 21 22 29 18 33 30] - [26 18 11 26 17 31 35 18 25 30] - [21 1 33 14 11 17 29 16 18 12] - [34 19 13 11 35 30 35 34 25 33] + sage: all(A.column(i) == B.column(i) for i in range(10) if not i == 2) + True + sage: A.column(2) == B.column(2) + 10*B.column(3) + True sage: A.add_multiple_of_column(2, 3, 10, 4) - sage: A - [24 15 18 27 32 34 16 32 25 23] - [11 3 4 13 35 33 1 10 33 25] - [33 9 18 3 15 27 30 30 7 12] - [10 0 1 4 12 34 32 16 19 17] - [36 4 28 17 3 34 11 10 10 17] - [32 15 26 2 23 32 5 8 18 11] - [24 5 29 13 21 22 29 18 33 30] - [26 18 12 26 17 31 35 18 25 30] - [21 1 25 14 11 17 29 16 18 12] - [34 19 12 11 35 30 35 34 25 33] + sage: all(A.column(i) == B.column(i) for i in range(10) if not i == 2) + True + sage: A.column(2)[:4] == B.column(2)[:4] + 10*B.column(3)[:4] + True + sage: A.column(2)[4:] == B.column(2)[4:] + 20*B.column(3)[4:] + True """ cdef celement p cdef celement **m @@ -2839,29 +2512,62 @@ cdef class Matrix_modn_dense_template(Matrix_dense): EXAMPLES:: sage: A = matrix(GF(5), 5, 5, 0) - sage: A.randomize(0.5); A - [0 0 0 2 0] - [0 3 0 0 2] - [4 0 0 0 0] - [4 0 0 0 0] - [0 1 0 0 0] - - sage: A.randomize(); A - [3 3 2 1 2] - [4 3 3 2 2] - [0 3 3 3 3] - [3 3 2 2 4] - [2 2 2 1 4] + sage: total_count = 0 + sage: from collections import defaultdict + sage: dic = defaultdict(Integer) + sage: def add_samples(density): + ....: global dic, total_count + ....: for _ in range(100): + ....: A = Matrix(GF(5), 5, 5, 0) + ....: A.randomize(density) + ....: for a in A.list(): + ....: dic[a] += 1 + ....: total_count += 1.0 + + sage: add_samples(1.0) + sage: while not all(abs(dic[a]/total_count - 1/5) < 0.01 for a in dic): + ....: add_samples(1.0) + + sage: def add_sample(density): + ....: global density_sum, total_count + ....: total_count += 1.0 + ....: density_sum += random_matrix(GF(5), 1000, 1000, density=density).density() + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(0.5) + sage: expected_density = 1.0 - (999/1000)^500 + sage: expected_density + 0.3936... + sage: while abs(density_sum/total_count - expected_density) > 0.001: + ....: add_sample(0.5) The matrix is updated instead of overwritten:: - sage: A = random_matrix(GF(5), 100, 100, density=0.1) - sage: A.density() - 961/10000 - - sage: A.randomize(density=0.1) - sage: A.density() - 801/5000 + sage: def add_sample(density): + ....: global density_sum, total_count + ....: total_count += 1.0 + ....: A = random_matrix(GF(5), 1000, 1000, density=density) + ....: A.randomize(density=density, nonzero=True) + ....: density_sum += A.density() + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(0.5) + sage: expected_density = 1.0 - (999/1000)^1000 + sage: expected_density + 0.6323... + sage: while abs(density_sum/total_count - expected_density) > 0.001: + ....: add_sample(0.5) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(0.1) + sage: expected_density = 1.0 - (999/1000)^200 + sage: expected_density + 0.1813... + sage: while abs(density_sum/total_count - expected_density) > 0.001: + ....: add_sample(0.1) """ density = float(density) if density <= 0: diff --git a/src/sage/matrix/matrix_modn_sparse.pyx b/src/sage/matrix/matrix_modn_sparse.pyx index 8ea6db2190e..9762f857e7c 100644 --- a/src/sage/matrix/matrix_modn_sparse.pyx +++ b/src/sage/matrix/matrix_modn_sparse.pyx @@ -215,13 +215,19 @@ cdef class Matrix_modn_sparse(matrix_sparse.Matrix_sparse): EXAMPLES:: sage: MS = MatrixSpace(GF(13), 50, 50, sparse=True) - sage: m = MS.random_element(density=0.002) - sage: m._dict() - {(4, 44): 7, (5, 25): 4, (26, 9): 9, (43, 43): 6, (44, 38): 1} + sage: m = MS._random_nonzero_element(density=0.002) + sage: d = m._dict() + sage: for i in range(50): + ....: for j in range(50): + ....: if m[i, j] != 0: + ....: assert m[i, j] == d[i, j] + ....: else: + ....: assert (i, j) not in d TESTS:: - sage: parent(m._dict()[26,9]) + sage: [i, j] = list(d.keys())[0] + sage: parent(m._dict()[i, j]) Finite Field of size 13 """ d = self.fetch('dict') @@ -526,9 +532,16 @@ cdef class Matrix_modn_sparse(matrix_sparse.Matrix_sparse): :: - sage: A = random_matrix(GF(127),200,200,density=0.3, sparse=True) - sage: A.density() - 2073/8000 + sage: A = random_matrix(GF(127), 200, 200, density=0.3, sparse=True) + sage: density_sum = float(A.density()) + sage: total = 1 + sage: expected_density = 1.0 - (199/200)^60 + sage: expected_density + 0.2597... + sage: while abs(density_sum/total - expected_density) > 0.001: + ....: A = random_matrix(GF(127), 200, 200, density=0.3, sparse=True) + ....: density_sum += float(A.density()) + ....: total += 1 """ cdef Py_ssize_t i, nonzero_entries diff --git a/src/sage/matrix/matrix_mpolynomial_dense.pyx b/src/sage/matrix/matrix_mpolynomial_dense.pyx index 607a0a49333..d6645c04dec 100644 --- a/src/sage/matrix/matrix_mpolynomial_dense.pyx +++ b/src/sage/matrix/matrix_mpolynomial_dense.pyx @@ -447,10 +447,12 @@ cdef class Matrix_mpolynomial_dense(Matrix_generic_dense): sage: R. = QQ[] sage: C = random_matrix(R, 2, 2, terms=2) + sage: while C.rank() != 2: + ....: C = random_matrix(R, 2, 2, terms=2) sage: C.swapped_columns() sage: E = C.echelon_form('bareiss') - sage: E.swapped_columns() - (0, 1) + sage: sorted(E.swapped_columns()) + [0, 1] """ return self.fetch('swapped_columns') diff --git a/src/sage/matrix/matrix_rational_dense.pyx b/src/sage/matrix/matrix_rational_dense.pyx index 549c5c46305..d67529c0a7e 100644 --- a/src/sage/matrix/matrix_rational_dense.pyx +++ b/src/sage/matrix/matrix_rational_dense.pyx @@ -2243,20 +2243,109 @@ cdef class Matrix_rational_dense(Matrix_dense): - None, the matrix is modified in-space - EXAMPLES:: + EXAMPLES: - sage: a = matrix(QQ,2,4); a.randomize(); a - [ 0 -1 2 -2] - [ 1 -1 2 1] - sage: a = matrix(QQ,2,4); a.randomize(density=0.5); a - [ -1 -2 0 0] - [ 0 0 1/2 0] - sage: a = matrix(QQ,2,4); a.randomize(num_bound=100, den_bound=100); a - [ 14/27 21/25 43/42 -48/67] - [-19/55 64/67 -11/51 76] - sage: a = matrix(QQ,2,4); a.randomize(distribution='1/n'); a - [ 3 1/9 1/2 1/4] - [ 1 1/39 2 -1955/2] + The default distribution:: + + sage: from collections import defaultdict + sage: total_count = 0 + sage: dic = defaultdict(Integer) + sage: def add_samples(distribution=None): + ....: global dic, total_count + ....: for _ in range(100): + ....: A = Matrix(QQ, 2, 4, 0) + ....: A.randomize(distribution=distribution) + ....: for a in A.list(): + ....: dic[a] += 1 + ....: total_count += 1.0 + + sage: expected = {-2: 1/9, -1: 3/18, -1/2: 1/18, 0: 3/9, + ....: 1/2: 1/18, 1: 3/18, 2: 1/9} + sage: add_samples() + sage: while not all(abs(dic[a]/total_count - expected[a]) < 0.001 for a in dic): + ....: add_samples() + + The distribution ``'1/n'``:: + + sage: def mpq_randomize_entry_recip_uniform(): + ....: r = 2*random() - 1 + ....: if r == 0: r = 1 + ....: num = int(4/(5*r)) + ....: r = random() + ....: if r == 0: r = 1 + ....: den = int(1/random()) + ....: return Integer(num)/Integer(den) + + sage: total_count = 0 + sage: dic = defaultdict(Integer) + sage: dic2 = defaultdict(Integer) + sage: add_samples('1/n') + sage: for _ in range(8): + ....: dic2[mpq_randomize_entry_recip_uniform()] += 1 + sage: while not all(abs(dic[a] - dic2[a])/total_count < 0.005 for a in dic): + ....: add_samples('1/n') + ....: for _ in range(800): + ....: dic2[mpq_randomize_entry_recip_uniform()] += 1 + + The default can be used to obtain matrices of different rank:: + + sage: ranks = [False]*11 + sage: while not all(ranks): + ....: for dens in (0.05, 0.1, 0.2, 0.5): + ....: A = Matrix(QQ, 10, 10, 0) + ....: A.randomize(dens) + ....: ranks[A.rank()] = True + + The default density is `6/9`:: + + sage: def add_sample(density, num_rows, num_cols): + ....: global density_sum, total_count + ....: total_count += 1.0 + ....: A = Matrix(QQ, num_rows, num_cols, 0) + ....: A.randomize(density) + ....: density_sum += float(A.density()) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: expected_density = 6/9 + sage: add_sample(1.0, 100, 100) + sage: while abs(density_sum/total_count - expected_density) > 0.001: + ....: add_sample(1.0, 100, 100) + + The modified density depends on the number of columns:: + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: expected_density = 6/9*0.5 + sage: add_sample(0.5, 100, 2) + sage: while abs(density_sum/total_count - expected_density) > 0.001: + ....: add_sample(0.5, 100, 2) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: expected_density = 6/9*(1.0 - (99/100)^50) + sage: expected_density + 0.263... + + sage: add_sample(0.5, 100, 100) + sage: while abs(density_sum/total_count - expected_density) > 0.001: + ....: add_sample(0.5, 100, 100) + + Modifying the bounds for numerator and denominator:: + + sage: num_dic = defaultdict(Integer) + sage: den_dic = defaultdict(Integer) + sage: while not (all(num_dic[i] for i in range(-200, 201)) + ....: and all(den_dic[i] for i in range(1, 101))): + ....: a = matrix(QQ, 2, 4) + ....: a.randomize(num_bound=200, den_bound=100) + ....: for q in a.list(): + ....: num_dic[q.numerator()] += 1 + ....: den_dic[q.denominator()] += 1 + sage: len(num_dic) + 401 + sage: len(den_dic) + 100 TESTS: diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index f1b265162fa..223988344b3 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -2022,20 +2022,17 @@ def random_element(self, density=None, *args, **kwds): EXAMPLES:: - sage: Mat(ZZ,2,5).random_element() - [ -8 2 0 0 1] - [ -1 2 1 -95 -1] - sage: Mat(QQ,2,5).random_element(density=0.5) - [ 2 0 0 0 1] - [ 0 0 0 1/2 0] - sage: Mat(QQ,3,sparse=True).random_element() - [ -1 1/3 1] - [ 0 -1 0] - [ -1 1 -1/4] - sage: Mat(GF(9,'a'),3,sparse=True).random_element() - [ 1 2 1] - [ a + 2 2*a 2] - [ 2 2*a + 2 1] + sage: M = Mat(ZZ, 2, 5).random_element() + sage: TestSuite(M).run() + + sage: M = Mat(QQ, 2, 5).random_element(density=0.5) + sage: TestSuite(M).run() + + sage: M = Mat(QQ, 3, sparse=True).random_element() + sage: TestSuite(M).run() + + sage: M = Mat(GF(9,'a'), 3, sparse=True).random_element() + sage: TestSuite(M).run() """ Z = self.zero_matrix().__copy__() if density is None: @@ -2198,12 +2195,18 @@ def _random_nonzero_element(self, *args, **kwds): EXAMPLES:: sage: M = MatrixSpace(ZZ, 4) - sage: M._random_nonzero_element() - [ -8 2 0 0] - [ 1 -1 2 1] - [-95 -1 -2 -12] - [ 0 0 1 -1] + sage: A = M._random_nonzero_element() + sage: A.is_zero() + False + + sage: M = MatrixSpace(ZZ, 0) + sage: A = M._random_nonzero_element() + Traceback (most recent call last): + ... + ValueError: Full MatrixSpace of 0 by 0 dense matrices over Integer Ring only has zero elements """ + if 0 in self.dims(): + raise ValueError("{} only has zero elements".format(self)) rand_matrix = self.random_element(*args, **kwds) while rand_matrix.is_zero(): rand_matrix = self.random_element(*args, **kwds) diff --git a/src/sage/matrix/matrix_sparse.pyx b/src/sage/matrix/matrix_sparse.pyx index e8cd45ad86c..ac795c9b71e 100644 --- a/src/sage/matrix/matrix_sparse.pyx +++ b/src/sage/matrix/matrix_sparse.pyx @@ -888,9 +888,13 @@ cdef class Matrix_sparse(matrix.Matrix): sage: A = random_matrix(ZZ, 100000, density=.00005, sparse=True) # long time (4s on sage.math, 2012) sage: B = A[50000:,:50000] # long time - sage: len(B.nonzero_positions()) # long time - 17550 # 32-bit - 125449 # 64-bit + sage: count = 0 + sage: for i, j in A.nonzero_positions(): # long time + ....: if i >= 50000 and j < 50000: + ....: assert B[i-50000, j] == A[i, j] + ....: count += 1 + sage: count == sum(1 for _ in B.nonzero_positions()) # long time + True We must pass in a list of indices:: diff --git a/src/sage/matrix/matrix_symbolic_dense.pyx b/src/sage/matrix/matrix_symbolic_dense.pyx index 3edfbb69c32..742ee7553b3 100644 --- a/src/sage/matrix/matrix_symbolic_dense.pyx +++ b/src/sage/matrix/matrix_symbolic_dense.pyx @@ -166,17 +166,29 @@ cdef maxima from sage.calculus.calculus import symbolic_expression_from_maxima_string, maxima cdef class Matrix_symbolic_dense(Matrix_generic_dense): - def eigenvalues(self): + def eigenvalues(self, extend=True): """ Compute the eigenvalues by solving the characteristic polynomial in maxima. + The argument ``extend`` is ignored but kept for compatibility with + other matrix classes. + EXAMPLES:: sage: a=matrix(SR,[[1,2],[3,4]]) sage: a.eigenvalues() [-1/2*sqrt(33) + 5/2, 1/2*sqrt(33) + 5/2] + TESTS: + + Check for :trac:`31700`:: + + sage: m = matrix([[cos(pi/5), sin(pi/5)], [-sin(pi/5), cos(pi/5)]]) + sage: t = linear_transformation(m) + sage: t.eigenvalues() + [1/4*sqrt(5) - 1/4*sqrt(2*sqrt(5) - 10) + 1/4, + 1/4*sqrt(5) + 1/4*sqrt(2*sqrt(5) - 10) + 1/4] """ maxima_evals = self._maxima_(maxima).eigenvalues()._sage_() if not len(maxima_evals): diff --git a/src/sage/matrix/special.py b/src/sage/matrix/special.py index 1f249b4da5f..dbac2df7a9e 100644 --- a/src/sage/matrix/special.py +++ b/src/sage/matrix/special.py @@ -282,68 +282,102 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation Random integer matrices. With no arguments, the majority of the entries are zero, -1, and 1, and rarely "large." :: - sage: random_matrix(ZZ, 5, 5) - [ -8 2 0 0 1] - [ -1 2 1 -95 -1] - [ -2 -12 0 0 1] - [ -1 1 -1 -2 -1] - [ 4 -4 -6 5 0] + sage: from collections import defaultdict + sage: total_count = 0 + sage: dic = defaultdict(Integer) + sage: def add_samples(*args, **kwds): + ....: global dic, total_count + ....: for _ in range(100): + ....: A = random_matrix(*args, **kwds) + ....: for a in A.list(): + ....: dic[a] += 1 + ....: total_count += 1.0 + + sage: expected = lambda n : 2 / (5*abs(n)*(abs(n) + 1)) if n != 0 else 1/5 + sage: expected(0) + 1/5 + sage: expected(0) == expected(1) == expected(-1) + True + sage: expected(100) + 1/25250 + sage: add_samples(ZZ, 5, 5) + sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): + ....: add_samples(ZZ, 5, 5) The ``distribution`` keyword set to ``uniform`` will limit values between -2 and 2. :: - sage: random_matrix(ZZ, 5, 5, distribution='uniform') - [ 1 0 -2 1 1] - [ 1 0 0 0 2] - [-1 -2 0 2 -2] - [-1 -1 1 1 2] - [ 0 -2 -1 0 0] + sage: expected = lambda n : 1/5 if n in range(-2, 3) else 0 + sage: total_count = 0 + sage: dic = defaultdict(Integer) + sage: add_samples(ZZ, 5, 5, distribution='uniform') + sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): + ....: add_samples(ZZ, 5, 5, distribution='uniform') The ``x`` and ``y`` keywords can be used to distribute entries uniformly. When both are used ``x`` is the minimum and ``y`` is one greater than the maximum. :: - sage: random_matrix(ZZ, 4, 8, x=70, y=100) - [81 82 70 81 78 71 79 94] - [80 98 89 87 91 94 94 77] - [86 89 85 92 95 94 72 89] - [78 80 89 82 94 72 90 92] + sage: expected = lambda n : 1/30 if n in range(70, 100) else 0 + sage: total_count = 0 + sage: dic = defaultdict(Integer) + sage: add_samples(ZZ, 4, 8, x=70, y=100) + sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): + ....: add_samples(ZZ, 4, 8, x=70, y=100) - sage: random_matrix(ZZ, 3, 7, x=-5, y=5) - [-3 3 1 -5 3 1 2] - [ 3 3 0 3 -5 -2 1] - [ 0 -2 -2 2 -3 -4 -2] + sage: expected = lambda n : 1/10 if n in range(-5, 5) else 0 + sage: total_count = 0 + sage: dic = defaultdict(Integer) + sage: add_samples(ZZ, 3, 7, x=-5, y=5) + sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): + ....: add_samples(ZZ, 3, 7, x=-5, y=5) If only ``x`` is given, then it is used as the upper bound of a range starting at 0. :: - sage: random_matrix(ZZ, 5, 5, x=25) - [20 16 8 3 8] - [ 8 2 2 14 5] - [18 18 10 20 11] - [19 16 17 15 7] - [ 0 24 3 17 24] + sage: expected = lambda n : 1/25 if n in range(25) else 0 + sage: total_count = 0 + sage: dic = defaultdict(Integer) + sage: add_samples(ZZ, 5, 5, x=25) + sage: while not all(abs(dic[a]/total_count - expected(a)) < 0.001 for a in dic): + ....: add_samples(ZZ, 5, 5, x=25) To control the number of nonzero entries, use the ``density`` keyword at a value strictly below the default of 1.0. The ``density`` keyword - is used to compute the number of entries that will be nonzero, but the + is used to compute the number of entries per row that will be nonzero, but the same entry may be selected more than once. So the value provided will be an upper bound for the density of the created matrix. Note that for a square matrix it is only necessary to set a single dimension. :: - sage: random_matrix(ZZ, 5, x=-10, y=10, density=0.75) - [-6 1 0 0 0] - [ 9 0 0 4 1] - [-6 0 0 -8 0] - [ 0 4 0 6 0] - [ 1 -9 0 0 -8] - - sage: random_matrix(ZZ, 5, x=20, y=30, density=0.75) - [ 0 28 0 27 0] - [25 28 20 0 0] - [ 0 21 0 21 0] - [ 0 28 22 0 0] - [ 0 0 0 26 24] + sage: def add_sample(*args, **kwds): + ....: global density_sum, total_count + ....: total_count += 1.0 + ....: A = random_matrix(*args, **kwds) + ....: density_sum += float(A.density()) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(ZZ, 5, x=-10, y=10, density=0.75) + sage: expected_density = (1 - (4/5)^3) + sage: float(expected_density) + 0.488 + sage: while abs(density_sum/total_count - expected_density) > 0.001: + ....: add_sample(ZZ, 5, x=-10, y=10, density=0.75) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(ZZ, 5, x=20, y=30, density=0.75) + sage: while abs(density_sum/total_count - expected_density) > 0.001: + ....: add_sample(ZZ, 5, x=20, y=30, density=0.75) + + sage: density_sum = 0.0 + sage: total_count = 0.0 + sage: add_sample(ZZ, 100, x=20, y=30, density=0.75) + sage: expected_density = (1 - (99/100)^75) + sage: float(expected_density) + 0.529... + sage: while abs(density_sum/total_count - expected_density) > 0.001: + ....: add_sample(ZZ, 100, x=20, y=30, density=0.75) For a matrix with low density it may be advisable to insist on a sparse representation, as this representation is not selected automatically. :: @@ -355,13 +389,6 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation sage: A.is_sparse() True - sage: random_matrix(ZZ, 5, 5, density=0.3, sparse=True) - [ 4 0 0 0 -1] - [ 0 0 0 0 -7] - [ 0 0 2 0 0] - [ 0 0 1 0 -4] - [ 0 0 0 0 0] - For algorithm testing you might want to control the number of bits, say 10,000 entries, each limited to 16 bits. :: @@ -381,31 +408,45 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation numerators and denominators (respectively). Entries will be positive and negative (map the absolute value function through the entries to get all positive values). If either the numerator or denominator bound (or both) - is not used, then the values default to the distribution for ``ZZ`` - described above. :: - - sage: random_matrix(QQ, 2, 8, num_bound=20, den_bound=4) - [ -1/4 5 5 -9/2 5/3 19 15/2 19/2] - [ 20/3 -13/4 0 16 -5 -20 -11 -7/3] + is not used, then the values default to ``2``:: + + sage: A = random_matrix(QQ, 2, 8, num_bound=20, den_bound=4) + sage: A.dimensions() + (2, 8) + sage: type(A) + + sage: all(a.numerator() in range(-20, 21) and + ....: a.denominator() in range(1, 5) + ....: for a in A.list()) + True - sage: random_matrix(QQ, 4, density = 0.5, sparse=True) - [ 0 1 0 -1] - [ 0 0 0 0] - [ 6 0 3 0] - [ 1 1/3 0 0] + sage: A = random_matrix(QQ, 4, density = 0.5, sparse=True) + sage: type(A) + + sage: A.density() <= 0.5 + True sage: A = random_matrix(QQ, 3, 10, num_bound = 99, den_bound = 99) sage: positives = list(map(abs, A.list())) - sage: matrix(QQ, 3, 10, positives) - [ 2/45 40/21 45/46 17/22 1 70/79 97/71 7/24 12/5 13/8] - [ 8/25 1/3 61/14 92/45 4/85 3/38 95/16 82/71 1/5 41/16] - [55/76 19 28/41 52/51 14/3 43 76/13 8/77 13/38 37/21] + sage: A1 = matrix(QQ, 3, 10, positives) + sage: all(abs(A.list()[i]) == A1.list()[i] for i in range(30)) + True + sage: all(a.numerator() in range(100) and + ....: a.denominator() in range(1, 100) + ....: for a in A1.list()) + True + + sage: A = random_matrix(QQ, 4, 10, den_bound = 10) + sage: all(a.numerator() in range(-2, 3) and + ....: a.denominator() in range(1, 11) + ....: for a in A.list()) + True - sage: random_matrix(QQ, 4, 10, den_bound = 10) - [ 1/9 1/5 -1 2/9 1/4 -1/7 1/8 -1/9 0 2] - [ 2/3 2 1/8 -2 0 0 -2 2 0 -1/2] - [ 0 2 1 -2/3 0 0 1/6 0 -1/3 -2/9] - [ 0 0 2/5 1/9 0 0 1/6 1/10 0 1] + sage: A = random_matrix(QQ, 4, 10) + sage: all(a.numerator() in range(-2, 3) and + ....: a.denominator() in range(1, 3) + ....: for a in A.list()) + True Random matrices over other rings. Several classes of matrices have specialized ``randomize()`` methods. You can locate these with the Sage command:: @@ -419,32 +460,35 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation that we use the default implementation in this test:: sage: K.=FiniteField(3^2) - sage: random_matrix(K, 2, 5, implementation='generic') - [ a + 1 a + 1 0 2*a + 2 a + 1] - [ a + 2 a + 1 2 0 0] - - sage: random_matrix(RR, 3, 4, density=0.66) - [ 0.000000000000000 0.0869697644118808 -0.232952499486647 0.000000000000000] - [-0.793158962467820 0.000000000000000 0.318853016385637 0.000000000000000] - [-0.220342454156035 0.000000000000000 0.000000000000000 0.914890766754157] - - sage: A = random_matrix(ComplexField(32), 3, density=0.8, sparse=True); A - [ 0.000000000 -0.443499553 - 0.406854867*I 0.000000000] - [ 0.171578609 + 0.644048756*I 0.518523841 + 0.794429291*I -0.341030168 - 0.507791873*I] - [ 0.000000000 0.000000000 0.782759943 + 0.236288982*I] + sage: A = random_matrix(K, 2, 5, implementation='generic') + sage: type(A) + + sage: A.base_ring() is K + True + sage: TestSuite(A).run() + + sage: A = random_matrix(RR, 3, 4, density=0.66) + sage: type(A) + + sage: A.base_ring() is RR + True + sage: TestSuite(A).run() + + sage: A = random_matrix(ComplexField(32), 3, density=0.8, sparse=True) sage: A.is_sparse() True + sage: type(A) + + sage: A.base_ring() is ComplexField(32) + True + sage: TestSuite(A).run() Random matrices in echelon form. The ``algorithm='echelon_form'`` keyword, along with a requested number of non-zero rows (``num_pivots``) will return a random matrix in echelon form. When the base ring is ``QQ`` the result has integer entries. Other exact rings may be also specified. :: - sage: A = random_matrix(QQ, 4, 8, algorithm='echelon_form', num_pivots=3); A # random - [ 1 -5 0 -2 0 1 1 -2] - [ 0 0 1 -5 0 -3 -1 0] - [ 0 0 0 0 1 2 -2 1] - [ 0 0 0 0 0 0 0 0] + sage: A = random_matrix(QQ, 4, 8, algorithm='echelon_form', num_pivots=3) sage: A.base_ring() Rational Field sage: (A.nrows(), A.ncols()) @@ -472,7 +516,7 @@ def random_matrix(ring, nrows, ncols=None, algorithm='randomize', implementation of full rank generated by this function always have determinant one, and can be constructed with the ``unimodular`` keyword. :: - sage: A = random_matrix(QQ, 4, 8, algorithm='echelonizable', rank=3, upper_bound=60); A # random + sage: A = random_matrix(QQ, 4, 8, algorithm='echelonizable', rank=3, upper_bound=60) sage: A.base_ring() Rational Field sage: (A.nrows(), A.ncols()) @@ -2546,12 +2590,7 @@ def random_echelonizable_matrix(parent, rank, upper_bound=None, max_tries=100): sage: from sage.matrix.constructor import random_echelonizable_matrix sage: matrix_space = sage.matrix.matrix_space.MatrixSpace(QQ, 5, 6) - sage: A = random_echelonizable_matrix(matrix_space, rank=4, upper_bound=40); A - [ 3 4 12 39 18 22] - [ -1 -3 -9 -27 -16 -19] - [ 1 3 10 31 18 21] - [ -1 0 0 -2 2 2] - [ 0 1 2 8 4 5] + sage: A = random_echelonizable_matrix(matrix_space, rank=4, upper_bound=40) sage: A.rank() 4 sage: max(map(abs,A.list()))<40 @@ -2561,13 +2600,7 @@ def random_echelonizable_matrix(parent, rank, upper_bound=None, max_tries=100): An example with default settings (i.e. no entry size control). :: - sage: C=random_matrix(QQ, 6, 7, algorithm='echelonizable', rank=5); C - [ 1 -5 -8 16 6 65 30] - [ 3 -14 -22 42 17 178 84] - [ -5 24 39 -79 -31 -320 -148] - [ 4 -15 -26 55 27 224 106] - [ -1 0 -6 29 8 65 17] - [ 3 -20 -32 72 14 250 107] + sage: C=random_matrix(QQ, 6, 7, algorithm='echelonizable', rank=5) sage: C.rank() 5 sage: C.rref() == C.rref().change_ring(ZZ) @@ -2575,7 +2608,7 @@ def random_echelonizable_matrix(parent, rank, upper_bound=None, max_tries=100): A matrix without size control may have very large entry sizes. :: - sage: D=random_matrix(ZZ, 7, 8, algorithm='echelonizable', rank=6); D + sage: D=random_matrix(ZZ, 7, 8, algorithm='echelonizable', rank=6); D # random [ 1 2 8 -35 -178 -239 -284 778] [ 4 9 37 -163 -827 -1111 -1324 3624] [ 5 6 21 -88 -454 -607 -708 1951] @@ -2587,24 +2620,18 @@ def random_echelonizable_matrix(parent, rank, upper_bound=None, max_tries=100): Matrices can be generated over any exact ring. :: sage: F.=GF(2^3) - sage: B = random_matrix(F, 4, 5, algorithm='echelonizable', rank=4, upper_bound=None); B - [ 1 a + 1 0 a^2 + a + 1 1] - [ a a^2 + a + 1 a^2 + 1 a^2 + a 0] - [ a^2 + a 1 1 a^2 + a a + 1] - [a^2 + a + 1 a^2 + a + 1 a^2 0 a^2 + a] + sage: B = random_matrix(F, 4, 5, algorithm='echelonizable', rank=4, upper_bound=None) sage: B.rank() 4 + sage: B.base_ring() is F + True Square matrices over ZZ or QQ with full rank are always unimodular. :: - sage: E=random_matrix(QQ, 7, 7, algorithm='echelonizable', rank=7); E - [ 1 1 7 -29 139 206 413] - [ -2 -1 -10 41 -197 -292 -584] - [ 2 5 27 -113 541 803 1618] - [ 4 0 14 -55 268 399 798] - [ 3 1 8 -32 152 218 412] - [ -3 -2 -18 70 -343 -506 -1001] - [ 1 -2 -1 1 -2 9 52] + sage: E = random_matrix(QQ, 7, 7, algorithm='echelonizable', rank=7) + sage: det(E) + 1 + sage: E = random_matrix(ZZ, 7, 7, algorithm='echelonizable', rank=7) sage: det(E) 1 @@ -2765,13 +2792,7 @@ def random_subspaces_matrix(parent, rank=None): sage: from sage.matrix.constructor import random_subspaces_matrix sage: matrix_space = sage.matrix.matrix_space.MatrixSpace(QQ, 6, 8) - sage: B = random_subspaces_matrix(matrix_space, rank=3); B - [ -15 -4 83 35 -24 47 -74 50] - [ -16 -7 94 34 -25 38 -75 50] - [ 89 34 -513 -196 141 -235 426 -285] - [ 17 6 -97 -38 27 -47 82 -55] - [ 7 3 -41 -15 11 -17 33 -22] - [ -5 -2 29 11 -8 13 -24 16] + sage: B = random_subspaces_matrix(matrix_space, rank=3) sage: B.rank() 3 sage: B.nullity() @@ -2783,13 +2804,6 @@ def random_subspaces_matrix(parent, rank=None): sage: B_expanded = B.augment(identity_matrix(6)).rref() sage: all(x in ZZ for x in B_expanded.list()) True - sage: B_expanded - [ 1 0 -5 0 -1 1 0 -1 0 0 0 3 10 24] - [ 0 1 -2 0 1 2 1 0 0 0 0 -2 -3 -11] - [ 0 0 0 1 -1 2 -2 1 0 0 0 1 4 9] - [ 0 0 0 0 0 0 0 0 1 0 0 2 -2 1] - [ 0 0 0 0 0 0 0 0 0 1 0 0 3 1] - [ 0 0 0 0 0 0 0 0 0 0 1 -3 -4 2] Check that we fixed :trac:`10543` (echelon forms should be immutable):: @@ -2799,24 +2813,9 @@ def random_subspaces_matrix(parent, rank=None): We want to modify B_expanded, so replace it with a copy:: sage: B_expanded = copy(B_expanded) - sage: B_expanded.subdivide(B.nrows()-B.nullity(),B.ncols());B_expanded - [ 1 0 -5 0 -1 1 0 -1| 0 0 0 3 10 24] - [ 0 1 -2 0 1 2 1 0| 0 0 0 -2 -3 -11] - [ 0 0 0 1 -1 2 -2 1| 0 0 0 1 4 9] - [-------------------------------+-----------------------] - [ 0 0 0 0 0 0 0 0| 1 0 0 2 -2 1] - [ 0 0 0 0 0 0 0 0| 0 1 0 0 3 1] - [ 0 0 0 0 0 0 0 0| 0 0 1 -3 -4 2] - sage: C=B_expanded.subdivision(0,0) - sage: C - [ 1 0 -5 0 -1 1 0 -1] - [ 0 1 -2 0 1 2 1 0] - [ 0 0 0 1 -1 2 -2 1] - sage: L=B_expanded.subdivision(1,1) - sage: L - [ 1 0 0 2 -2 1] - [ 0 1 0 0 3 1] - [ 0 0 1 -3 -4 2] + sage: B_expanded.subdivide(B.nrows()-B.nullity(), B.ncols()) + sage: C = B_expanded.subdivision(0, 0) + sage: L = B_expanded.subdivision(1, 1) sage: B.right_kernel() == C.right_kernel() True sage: B.row_space() == C.row_space() @@ -2828,29 +2827,16 @@ def random_subspaces_matrix(parent, rank=None): A matrix to show that the null space of the L matrix is the column space of the starting matrix. :: - sage: A = random_matrix(QQ, 5, 7, algorithm='subspaces', rank=None); A - [ -63 13 -71 29 -163 150 -268] - [ 24 -5 27 -11 62 -57 102] - [ 14 -3 16 -7 37 -34 60] - [ -4 1 -4 1 -9 8 -16] - [ 9 -2 10 -4 23 -21 38] + sage: A = random_matrix(QQ, 5, 7, algorithm='subspaces', rank=None) sage: (A.nrows(), A.ncols()) (5, 7) sage: all(x in ZZ for x in A.list()) True - sage: A.nullity() - 2 sage: A_expanded=A.augment(identity_matrix(5)).rref() - sage: A_expanded - [ 1 0 0 2 -1 1 2 0 2 0 -4 -7] - [ 0 1 0 1 -1 0 0 0 4 0 -3 -12] - [ 0 0 1 -2 3 -3 2 0 -1 0 3 4] - [ 0 0 0 0 0 0 0 1 3 0 0 -1] - [ 0 0 0 0 0 0 0 0 0 1 -1 -2] sage: all(x in ZZ for x in A_expanded.list()) True - sage: C=A_expanded.submatrix(0,0,A.nrows()-A.nullity(),A.ncols()) - sage: L=A_expanded.submatrix(A.nrows()-A.nullity(),A.ncols()) + sage: C = A_expanded.submatrix(0,0,A.nrows()-A.nullity(), A.ncols()) + sage: L = A_expanded.submatrix(A.nrows()-A.nullity(), A.ncols()) sage: A.right_kernel() == C.right_kernel() True sage: A.row_space() == C.row_space() @@ -2963,38 +2949,27 @@ def random_unimodular_matrix(parent, upper_bound=None, max_tries=100): sage: from sage.matrix.constructor import random_unimodular_matrix sage: matrix_space = sage.matrix.matrix_space.MatrixSpace(QQ, 5) - sage: A = random_unimodular_matrix(matrix_space); A - [ 0 3 8 -30 -30] - [ 0 1 4 -18 -13] - [ -1 0 0 3 0] - [ 4 16 71 -334 -222] - [ -1 -1 -9 50 24] + sage: A = random_unimodular_matrix(matrix_space) sage: det(A) 1 A matrix size 6 with entries no larger than 50. :: - sage: B = random_matrix(ZZ, 7, algorithm='unimodular', upper_bound=50);B - [-14 17 14 -31 43 24 46] - [ -5 6 5 -11 15 9 18] - [ -2 5 3 -7 15 -3 -16] - [ 1 -2 -3 4 -3 -7 -21] - [ -1 4 1 -4 14 -10 -37] - [ 3 -3 -1 6 -12 4 25] - [ 4 -4 -2 7 -13 -2 1] + sage: B = random_matrix(ZZ, 7, algorithm='unimodular', upper_bound=50) sage: det(B) 1 + sage: all(abs(b) < 50 for b in B.list()) + True A matrix over the number Field in `y` with defining polynomial `y^2-2y-2`. :: sage: y = var('y') - sage: K=NumberField(y^2-2*y-2,'y') - sage: C=random_matrix(K, 3, algorithm='unimodular');C - [ -5*y + 11 10*y - 30 -695*y + 2366] - [ 5 5*y - 9 -535*y + 588] - [ y - 1 3*y - 1 -35*y - 273] + sage: K = NumberField(y^2-2*y-2, 'y') + sage: C = random_matrix(K, 3, algorithm='unimodular') sage: det(C) 1 + sage: C.base_ring() is K + True TESTS: @@ -3071,57 +3046,29 @@ def random_diagonalizable_matrix(parent,eigenvalues=None,dimensions=None): sage: from sage.matrix.constructor import random_diagonalizable_matrix sage: matrix_space = sage.matrix.matrix_space.MatrixSpace(QQ, 5) - sage: A = random_diagonalizable_matrix(matrix_space); A - [ 90 -80 56 -448 -588] - [ 60 0 28 -324 -204] - [ 60 -72 32 -264 -432] - [ 30 -16 16 -152 -156] - [ -10 -8 -4 60 8] - sage: sorted(A.eigenvalues()) - [-10, -8, -4, 0, 0] - sage: S=A.right_eigenmatrix()[1]; S - [ 1 1 1 1 0] - [ 1/2 0 2/3 0 1] - [ 4/7 9/10 2/3 6/7 -3/7] - [ 2/7 1/5 1/3 3/14 1/7] - [-1/14 1/10 -1/9 1/14 -2/7] - sage: S_inverse=S.inverse(); S_inverse - [ 0 0 -14 42 42] - [ 0 10 0 -10 30] - [ -9 0 0 36 18] - [ 10 -10 14 -68 -90] - [ 6 1 7 -45 -33] - sage: S_inverse*A*S - [ -4 0 0 0 0] - [ 0 -8 0 0 0] - [ 0 0 -10 0 0] - [ 0 0 0 0 0] - [ 0 0 0 0 0] + sage: A = random_diagonalizable_matrix(matrix_space) + sage: eigenvalues = A.eigenvalues() + sage: S = A.right_eigenmatrix()[1] + sage: eigenvalues2 = (S.inverse()*A*S).diagonal() + sage: sorted(eigenvalues) == sorted(eigenvalues2) + True A diagonalizable matrix with eigenvalues and dimensions designated, with a check that if eigenvectors were calculated by hand entries would all be integers. :: - sage: B = random_matrix(QQ, 6, algorithm='diagonalizable', eigenvalues=[-12,4,6],dimensions=[2,3,1]); B - [ 2 -64 16 206 56 -142] - [ 14 -28 -64 46 40 -14] - [ -4 -16 4 44 32 -28] - [ 6 0 -32 -22 8 26] - [ 0 -16 0 48 20 -32] - [ 2 0 -16 -14 8 18] + sage: eigenvalues = [ZZ.random_element() for _ in range(3)] + sage: B = random_matrix(QQ, 6, algorithm='diagonalizable', eigenvalues=eigenvalues, dimensions=[2,3,1]) sage: all(x in ZZ for x in (B-(-12*identity_matrix(6))).rref().list()) True sage: all(x in ZZ for x in (B-(4*identity_matrix(6))).rref().list()) True sage: all(x in ZZ for x in (B-(6*identity_matrix(6))).rref().list()) True - sage: S=B.right_eigenmatrix()[1]; S_inverse=S.inverse(); S_inverse*B*S - [ 6 0 0 0 0 0] - [ 0 -12 0 0 0 0] - [ 0 0 -12 0 0 0] - [ 0 0 0 4 0 0] - [ 0 0 0 0 4 0] - [ 0 0 0 0 0 4] + sage: S = B.right_eigenmatrix()[1] + sage: eigenvalues2 = (S.inverse()*B*S).diagonal() + sage: all(e in eigenvalues for e in eigenvalues2) + True TESTS: diff --git a/src/sage/matroids/matroid.pyx b/src/sage/matroids/matroid.pyx index 61693cc330a..c0fd7fc8191 100644 --- a/src/sage/matroids/matroid.pyx +++ b/src/sage/matroids/matroid.pyx @@ -7800,7 +7800,7 @@ cdef class Matroid(SageObject): Simplicial complex with vertex set (1, 2, 3, 4, 5) and facets {(1, 3, 5), (1, 4, 5), (2, 3, 5), (2, 4, 5)} """ - from sage.homology.simplicial_complex import SimplicialComplex + from sage.topology.simplicial_complex import SimplicialComplex return SimplicialComplex(self.no_broken_circuits_sets(ordering)) def union(self, matroids): diff --git a/src/sage/misc/all.py b/src/sage/misc/all.py index f8b17511ace..d108d4e5f74 100644 --- a/src/sage/misc/all.py +++ b/src/sage/misc/all.py @@ -62,7 +62,8 @@ lazy_import('sage.misc.sagedoc', ['browse_sage_doc', 'search_src', 'search_def', 'search_doc', 'tutorial', 'reference', 'manual', 'developer', - 'constructions', 'python_help', 'help']) + 'constructions', 'help']) +lazy_import('pydoc', 'help', 'python_help') from .classgraph import class_graph diff --git a/src/sage/misc/html.py b/src/sage/misc/html.py index c4a6a32c71a..7dacc5f2748 100644 --- a/src/sage/misc/html.py +++ b/src/sage/misc/html.py @@ -379,18 +379,21 @@ def _repr_(self): """ return 'Create HTML output (see html? for details)' - def __call__(self, obj, concatenate=True): + def __call__(self, obj, concatenate=True, strict=False): r""" Construct a HTML fragment INPUT: - - ``obj`` -- anything. An object for which you want a HTML + - ``obj`` -- anything. An object for which you want an HTML representation. - ``concatenate`` -- if ``True``, combine HTML representations of elements of the container ``obj`` + - ``strict`` -- if ``True``, construct an HTML representation of + ``obj`` even if ``obj`` is a string + OUTPUT: A :class:`HtmlFragment` instance. @@ -407,8 +410,12 @@ def __call__(self, obj, concatenate=True): sage: html('sagemath') sagemath + + sage: html('sagemath', strict=True) + \[\newcommand{\Bold}[1]{\mathbf{#1}}\verb|sagemath|\] """ - if isinstance(obj, str): + # string obj is interpreted as an HTML in not strict mode + if isinstance(obj, str) and not strict: return HtmlFragment(math_parse(obj)) # prefer dedicated _html_() method diff --git a/src/sage/misc/lazy_import.pyx b/src/sage/misc/lazy_import.pyx index 336567e22ca..cc91cbf0c19 100644 --- a/src/sage/misc/lazy_import.pyx +++ b/src/sage/misc/lazy_import.pyx @@ -9,7 +9,6 @@ use. EXAMPLES:: - sage: from sage.misc.lazy_import import lazy_import sage: lazy_import('sage.rings.all', 'ZZ') sage: type(ZZ) @@ -124,7 +123,6 @@ cpdef test_fake_startup(): EXAMPLES:: sage: sage.misc.lazy_import.test_fake_startup() - sage: from sage.misc.lazy_import import lazy_import sage: lazy_import('sage.rings.all', 'ZZ', 'my_ZZ') sage: my_ZZ(123) Resolving lazy import ZZ during startup @@ -166,11 +164,13 @@ cdef class LazyImport(object): EXAMPLES:: sage: from sage.misc.lazy_import import LazyImport - sage: my_isprime = LazyImport('sage.all', 'is_prime') - sage: my_isprime(5) + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: type(lazy_ZZ) + + sage: lazy_ZZ._get_object() is ZZ True - sage: my_isprime(55) - False + sage: type(lazy_ZZ) + """ self._object = None self._module = module @@ -219,12 +219,14 @@ cdef class LazyImport(object): print(f"Resolving lazy import {self._name} during startup") elif self._at_startup and not startup_guard: print(f"Option ``at_startup=True`` for lazy import {self._name} not needed anymore") + try: self._object = getattr(__import__(self._module, {}, {}, [self._name]), self._name) except ImportError as e: if self._feature: raise FeatureNotPresentError(self._feature, reason=f'Importing {self._name} failed: {e}') raise + name = self._as_name if self._deprecation is not None: from sage.misc.superseded import deprecation @@ -338,8 +340,8 @@ cdef class LazyImport(object): EXAMPLES:: sage: from sage.misc.lazy_import import LazyImport - sage: my_ZZ = LazyImport('sage.rings.all', 'ZZ') - sage: dir(my_ZZ) == dir(ZZ) + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: dir(lazy_ZZ) == dir(ZZ) True """ return dir(self.get_object()) @@ -352,9 +354,9 @@ cdef class LazyImport(object): sage: from sage.misc.lazy_import import LazyImport sage: my_isprime = LazyImport('sage.all', 'is_prime') - sage: my_isprime(12) - False - sage: my_isprime(13) + sage: is_prime(12) == my_isprime(12) + True + sage: is_prime(13) == my_isprime(13) True """ return self.get_object()(*args, **kwds) @@ -363,13 +365,10 @@ cdef class LazyImport(object): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: type(lazy_ZZ) - - sage: lazy_ZZ - Integer Ring - sage: repr(lazy_ZZ) - 'Integer Ring' + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: repr(lazy_ZZ) == repr(ZZ) + True """ try: obj = self.get_object() @@ -381,9 +380,10 @@ cdef class LazyImport(object): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: str(lazy_ZZ) - 'Integer Ring' + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: str(lazy_ZZ) == str(ZZ) + True """ return str(self.get_object()) @@ -391,28 +391,31 @@ cdef class LazyImport(object): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: str(lazy_ZZ) - u'Integer Ring' + 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 __nonzero__(self): + def __bool__(self): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: not lazy_ZZ + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: bool(lazy_ZZ) == bool(ZZ) True """ - return not self.get_object() + return bool(self.get_object()) def __hash__(self): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ - sage: hash(lazy_ZZ) == hash(1.parent()) + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: hash(lazy_ZZ) == hash(ZZ) True """ return hash(self.get_object()) @@ -421,11 +424,12 @@ cdef class LazyImport(object): """ TESTS:: - sage: lazy_import('sage.all', 'ZZ'); lazy_ZZ = ZZ + sage: from sage.misc.lazy_import import LazyImport + sage: lazy_ZZ = LazyImport('sage.rings.all', 'ZZ') + sage: lazy_ZZ == ZZ + True sage: lazy_ZZ == RR False - sage: lazy_ZZ == 1.parent() - True """ return PyObject_RichCompare(obj(left), obj(right), op) @@ -529,8 +533,6 @@ cdef class LazyImport(object): sage: import sys sage: py_version = sys.version_info[0] sage: lazy_import('sys', 'version_info') - sage: type(version_info) - sage: version_info[0] == py_version True """ @@ -540,12 +542,13 @@ cdef class LazyImport(object): """ TESTS:: + sage: from sage.misc.lazy_import import LazyImport sage: sage.all.foo = list(range(10)) - sage: lazy_import('sage.all', 'foo') - sage: type(foo) - - sage: foo[1] = 100 - sage: print(foo) + sage: lazy_foo = LazyImport('sage.all', 'foo') + sage: lazy_foo[1] = 100 + sage: print(lazy_foo) + [0, 100, 2, 3, 4, 5, 6, 7, 8, 9] + sage: sage.all.foo [0, 100, 2, 3, 4, 5, 6, 7, 8, 9] """ self.get_object()[key] = value @@ -554,12 +557,13 @@ cdef class LazyImport(object): """ TESTS:: + sage: from sage.misc.lazy_import import LazyImport sage: sage.all.foo = list(range(10)) - sage: lazy_import('sage.all', 'foo') - sage: type(foo) - - sage: del foo[1] - sage: print(foo) + sage: lazy_foo = LazyImport('sage.all', 'foo') + sage: del lazy_foo[1] + sage: print(lazy_foo) + [0, 2, 3, 4, 5, 6, 7, 8, 9] + sage: print(sage.all.foo) [0, 2, 3, 4, 5, 6, 7, 8, 9] """ del self.get_object()[key] @@ -569,8 +573,6 @@ cdef class LazyImport(object): TESTS:: sage: lazy_import('sys', 'version_info') - sage: type(version_info) - sage: iter(version_info) <...iterator object at ...> """ @@ -583,14 +585,10 @@ cdef class LazyImport(object): sage: import sys sage: py_version = sys.version_info[0] sage: lazy_import('sys', 'version_info') - sage: type(version_info) - sage: py_version in version_info True sage: lazy_import('sys', 'version_info') - sage: type(version_info) - sage: 2000 not in version_info True """ @@ -602,8 +600,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo + 1 11 """ @@ -615,8 +611,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo - 1 9 """ @@ -628,8 +622,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo * 2 20 """ @@ -642,8 +634,6 @@ cdef class LazyImport(object): sage: from sympy import Matrix sage: sage.all.foo = Matrix([[1,1],[0,1]]) sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo.__matmul__(foo) Matrix([ [1, 2], @@ -657,8 +647,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo // 3 3 """ @@ -670,8 +658,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: operator.truediv(foo, 3) 10/3 """ @@ -683,8 +669,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo ** 2 100 """ @@ -696,8 +680,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo % 7 3 """ @@ -709,8 +691,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo << 3 80 """ @@ -722,8 +702,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo >> 2 2 """ @@ -735,8 +713,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo & 7 2 """ @@ -748,8 +724,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo | 7 15 """ @@ -761,8 +735,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: foo ^^ 7 13 """ @@ -774,8 +746,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: -foo -10 """ @@ -787,8 +757,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: +foo 10 """ @@ -800,8 +768,6 @@ cdef class LazyImport(object): sage: sage.all.foo = -1000 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: abs(foo) 1000 """ @@ -813,8 +779,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: ~foo 1/10 """ @@ -826,8 +790,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: complex(foo) (10+0j) """ @@ -839,8 +801,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: int(foo) 10 """ @@ -852,8 +812,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: float(foo) 10.0 """ @@ -865,8 +823,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: oct(foo) # py2 doctest:warning...: DeprecationWarning: use the method .oct instead @@ -883,8 +839,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: hex(foo) # py2 doctest:warning...: DeprecationWarning: use the method .hex instead @@ -901,8 +855,6 @@ cdef class LazyImport(object): sage: sage.all.foo = 10 sage: lazy_import('sage.all', 'foo') - sage: type(foo) - sage: list(range(100))[foo] 10 """ @@ -914,14 +866,20 @@ cdef class LazyImport(object): TESTS:: - sage: sage.all.foo = 10 - sage: lazy_import('sage.all', 'foo') - sage: type(foo) - - sage: copy(foo) - 10 + + sage: from sage.misc.lazy_import import LazyImport + sage: sage.all.foo = [[1,2], 3] + sage: lazy_foo = LazyImport('sage.all', 'foo') + sage: a = copy(lazy_foo) + sage: a is sage.all.foo # copy + False + sage: a[0] is sage.all.foo[0] # copy but not deep + True + sage: type(lazy_foo) is LazyImport + True """ - return self.get_object() + import copy + return copy.copy(self.get_object()) def __deepcopy__(self, memo=None): """ @@ -929,14 +887,19 @@ cdef class LazyImport(object): TESTS:: - sage: sage.all.foo = 10 - sage: lazy_import('sage.all', 'foo') - sage: type(foo) - - sage: deepcopy(foo) - 10 + sage: from sage.misc.lazy_import import LazyImport + sage: sage.all.foo = [[1,2], 3] + sage: lazy_foo = LazyImport('sage.all', 'foo') + sage: a = deepcopy(lazy_foo) + sage: a is sage.all.foo # copy + False + sage: a[0] is sage.all.foo[0] # deep copy + False + sage: type(lazy_foo) is LazyImport + True """ - return self.get_object() + import copy + return copy.deepcopy(self.get_object()) def __instancecheck__(self, x): """ @@ -1000,7 +963,6 @@ def lazy_import(module, names, as_=None, *, EXAMPLES:: - sage: from sage.misc.lazy_import import lazy_import sage: lazy_import('sage.rings.all', 'ZZ') sage: type(ZZ) diff --git a/src/sage/misc/package.py b/src/sage/misc/package.py index 51d0ae61bf7..1afd054a50f 100644 --- a/src/sage/misc/package.py +++ b/src/sage/misc/package.py @@ -356,8 +356,6 @@ def installed_packages(exclude_pip=True): [...'alabaster', ...'sage_conf', ...] sage: installed_packages()['alabaster'] # optional - build, random '0.7.12' - sage: installed_packages()['sage_conf'] # optional - build - 'none' .. SEEALSO:: diff --git a/src/sage/misc/parser.pyx b/src/sage/misc/parser.pyx index 366bdfa8bfe..b4b43c8a16f 100644 --- a/src/sage/misc/parser.pyx +++ b/src/sage/misc/parser.pyx @@ -508,6 +508,30 @@ cdef class Parser: self.callable_constructor = make_function self.implicit_multiplication = implicit_multiplication + def _variable_constructor(self): + """ + Return the variable constructor of this parser. + + EXAMPLES:: + + sage: from sage.calculus.calculus import SR_parser + sage: SR_parser._variable_constructor() + numerical_approx(prec=None, digits=None, algorithm=None)... + numerical_approx(prec=None, digits=None, algorithm=None)... Check that sphinx is not imported at Sage start-up:: diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index 17ab74a0374..35125721f27 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -2276,11 +2276,10 @@ def sage_getsourcelines(obj): ([...'class MPolynomialIdeal( MPolynomialIdeal_singular_repr, \\\n', ...) sage: x = var('x') - sage: sage_getsourcelines(x) - (['cdef class Expression(CommutativeRingElement):\n', - ' cpdef object pyobject(self):\n', - ...) - sage: sage_getsourcelines(x)[0][-1] # last line + sage: lines, lineno = sage_getsourcelines(x); lines[0:2] + ['cdef class Expression(CommutativeRingElement):\n', + ' cpdef object pyobject(self):\n'] + sage: lines[-1] # last line ' return S\n' We show some enhancements provided by :trac:`11768`. First, we diff --git a/src/sage/misc/temporary_file.py b/src/sage/misc/temporary_file.py index 25bf6d1372e..a6368f53a36 100644 --- a/src/sage/misc/temporary_file.py +++ b/src/sage/misc/temporary_file.py @@ -335,7 +335,8 @@ def __init__(self, target_filename, append=False, mode=0o666, self.tmpdir = os.path.dirname(self.target) self.append = append # Remove umask bits from mode - umask = os.umask(0); os.umask(umask) + umask = os.umask(0) + os.umask(umask) self.mode = mode & (~umask) # 'binary' mode is the default on Python 2, whereas 'text' mode is the diff --git a/src/sage/modular/etaproducts.py b/src/sage/modular/etaproducts.py index b656a0f7de0..4a90b2cecfe 100644 --- a/src/sage/modular/etaproducts.py +++ b/src/sage/modular/etaproducts.py @@ -146,7 +146,7 @@ def _mul_(self, other): sage: eta1, eta2 = EtaGroup(4).basis() # indirect doctest sage: eta1 * eta2 - Eta product of level 4 : (eta_1)^8 (eta_4)^-8 + Eta product of level 4 : (eta_1)^24 (eta_2)^-48 (eta_4)^24 """ newdict = {d: self._rdict.get(d, 0) + other._rdict.get(d, 0) for d in union(self._rdict, other._rdict)} @@ -161,7 +161,7 @@ def _div_(self, other): sage: eta1, eta2 = EtaGroup(4).basis() sage: eta1 / eta2 # indirect doctest - Eta product of level 4 : (eta_1)^-24 (eta_2)^48 (eta_4)^-24 + Eta product of level 4 : (eta_1)^-8 (eta_4)^8 sage: (eta1 / eta2) * eta2 == eta1 True """ @@ -509,16 +509,16 @@ def basis(self, reduce=True): sage: EtaGroup(5).basis() [Eta product of level 5 : (eta_1)^6 (eta_5)^-6] sage: EtaGroup(12).basis() - [Eta product of level 12 : (eta_1)^2 (eta_2)^1 (eta_3)^2 (eta_4)^-1 (eta_6)^-7 (eta_12)^3, + [Eta product of level 12 : (eta_1)^-3 (eta_2)^2 (eta_3)^1 (eta_4)^-1 (eta_6)^-2 (eta_12)^3, Eta product of level 12 : (eta_1)^-4 (eta_2)^2 (eta_3)^4 (eta_6)^-2, + Eta product of level 12 : (eta_1)^6 (eta_2)^-9 (eta_3)^-2 (eta_4)^3 (eta_6)^3 (eta_12)^-1, Eta product of level 12 : (eta_1)^-1 (eta_2)^3 (eta_3)^3 (eta_4)^-2 (eta_6)^-9 (eta_12)^6, - Eta product of level 12 : (eta_1)^1 (eta_2)^-1 (eta_3)^-3 (eta_4)^-2 (eta_6)^7 (eta_12)^-2, - Eta product of level 12 : (eta_1)^-6 (eta_2)^9 (eta_3)^2 (eta_4)^-3 (eta_6)^-3 (eta_12)^1] + Eta product of level 12 : (eta_1)^3 (eta_3)^-1 (eta_4)^-3 (eta_12)^1] sage: EtaGroup(12).basis(reduce=False) # much bigger coefficients - [Eta product of level 12 : (eta_2)^24 (eta_12)^-24, - Eta product of level 12 : (eta_1)^-336 (eta_2)^576 (eta_3)^696 (eta_4)^-216 (eta_6)^-576 (eta_12)^-144, - Eta product of level 12 : (eta_1)^-8 (eta_2)^-2 (eta_6)^2 (eta_12)^8, - Eta product of level 12 : (eta_1)^1 (eta_2)^9 (eta_3)^13 (eta_4)^-4 (eta_6)^-15 (eta_12)^-4, + [Eta product of level 12 : (eta_1)^384 (eta_2)^-576 (eta_3)^-696 (eta_4)^216 (eta_6)^576 (eta_12)^96, + Eta product of level 12 : (eta_2)^24 (eta_12)^-24, + Eta product of level 12 : (eta_1)^-40 (eta_2)^116 (eta_3)^96 (eta_4)^-30 (eta_6)^-80 (eta_12)^-62, + Eta product of level 12 : (eta_1)^-4 (eta_2)^-33 (eta_3)^-4 (eta_4)^1 (eta_6)^3 (eta_12)^37, Eta product of level 12 : (eta_1)^15 (eta_2)^-24 (eta_3)^-29 (eta_4)^9 (eta_6)^24 (eta_12)^5] ALGORITHM: An eta product of level `N` is uniquely @@ -1030,7 +1030,7 @@ def _eta_relations_helper(eta1, eta2, degree, qexp_terms, labels, verbose): sage: from sage.modular.etaproducts import _eta_relations_helper sage: r,s = EtaGroup(4).basis() sage: _eta_relations_helper(r,s,4,100,['a','b'],False) - [a*b - a + 16] + [a + 1/16*b - 1/16] sage: _eta_relations_helper(EtaProduct(26, {2:2,13:2,26:-2,1:-2}),EtaProduct(26, {2:4,13:2,26:-4,1:-2}),3,12,['a','b'],False) # not enough terms, will return rubbish [1] """ diff --git a/src/sage/modular/hecke/element.py b/src/sage/modular/hecke/element.py index 20b7852a34e..35053b87675 100644 --- a/src/sage/modular/hecke/element.py +++ b/src/sage/modular/hecke/element.py @@ -89,21 +89,19 @@ def _compute_element(self): EXAMPLES:: - sage: f = EllipticCurve('11a').modular_form() - sage: hasattr(f, '_HeckeModuleElement__element') - False + sage: f = CuspForms(11, 2).gen(0) sage: f._compute_element() - (1, 0) - sage: f.element() - (1, 0) - sage: hasattr(f, '_HeckeModuleElement__element') - True + Traceback (most recent call last): + ... + NotImplementedError: _compute_element *must* be defined... """ # You have to define this in the derived class if you ever set # x=None in __init__ for your element class. # The main reason for this is it allows for lazy constructors who # compute the representation of an element (e.g., a q-expansion) in # terms of the basis only when needed. + + # Not in use at present. raise NotImplementedError("_compute_element *must* be defined in the derived class if element is set to None in constructor") def element(self): @@ -138,7 +136,8 @@ def _vector_(self, R=None): sage: type(vector(v, GF(2))) """ - if R is None: return self.__element + if R is None: + return self.__element return self.__element.change_ring(R) def _richcmp_(self, other, op): @@ -267,7 +266,7 @@ def is_eisenstein(self): True sage: ModularSymbols(19,4).0.is_eisenstein() False - sage: EllipticCurve('37a1').newform().is_eisenstein() + sage: EllipticCurve('37a1').newform().element().is_eisenstein() False """ return (self in self.parent().ambient().eisenstein_submodule()) diff --git a/src/sage/modular/hypergeometric_misc.pyx b/src/sage/modular/hypergeometric_misc.pyx index 0faa55e3701..7f3c8ee494d 100644 --- a/src/sage/modular/hypergeometric_misc.pyx +++ b/src/sage/modular/hypergeometric_misc.pyx @@ -24,12 +24,20 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, sage: D = 1 sage: hgm_coeffs(7, 1, 2, gamma, [0]*6, D, gtable, prec, False) [7, 2*7, 6*7, 7, 6, 4*7] + + Check issue from :trac:`28404`:: + + sage: H = Hyp(cyclotomic=[[10,2],[1,1,1,1,1]]) + sage: u = H.euler_factor(2,79) # indirect doctest + sage: u.reverse().is_weil_polynomial() + True + """ from sage.rings.padics.factory import Zp cdef int gl, j, k, l, v, gv cdef long long i, q1, w, w1, w2, q2, r, r1 - cdef bint flip + cdef bint flip, need_lift q1 = p ** f - 1 gl = len(gamma) @@ -55,6 +63,14 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, gtab2 = array.array('l', [0]) * q1 for r in range(q1): gtab2[r] = gtable[r].lift() % q2 + else: + gtab2 = array.array('q', [0]) * q1 + try: + for r in range(q1): + gtab2[r] = gtable[r] + except TypeError: + for r in range(q1): + gtab2[r] = gtable[r].lift() if f == 1: for r in range(q1): digit_count[r] = r @@ -108,7 +124,7 @@ cpdef hgm_coeffs(long long p, int f, int prec, gamma, m, int D, for j in range(-gv): w1 = w1 * w2 % q2 else: - w2 = gtable[r1] + w2 = gtab2[r1] if gv > 0: for j in range(gv): u *= w2 diff --git a/src/sage/modular/local_comp/liftings.py b/src/sage/modular/local_comp/liftings.py index 91bfd2448d3..f629bc659b0 100644 --- a/src/sage/modular/local_comp/liftings.py +++ b/src/sage/modular/local_comp/liftings.py @@ -222,9 +222,9 @@ def lift_for_SL(A, N=None): TESTS:: sage: lift_for_SL(matrix(3,3,[1,2,0,3,4,0,0,0,1]),3) - [10 14 3] - [ 9 10 3] - [ 3 3 1] + [-2 -1 0] + [ 0 1 -3] + [ 3 0 4] sage: A = matrix(Zmod(7), 2, [1,0,0,1]) sage: L = lift_for_SL(A) diff --git a/src/sage/modular/local_comp/local_comp.py b/src/sage/modular/local_comp/local_comp.py index 01676fcbc83..a00268ee911 100644 --- a/src/sage/modular/local_comp/local_comp.py +++ b/src/sage/modular/local_comp/local_comp.py @@ -20,15 +20,16 @@ """ from sage.structure.sage_object import SageObject -from sage.rings.all import ZZ, Zmod, QQbar, PolynomialRing, polygen -from sage.modular.modform.element import Newform -from sage.modular.dirichlet import DirichletGroup -from sage.misc.cachefunc import cached_method +from sage.rings.all import ZZ, QQbar, PolynomialRing, polygen from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.misc.verbose import verbose +from sage.misc.flatten import flatten +from sage.modular.modform.element import Newform from sage.structure.sequence import Sequence from .type_space import TypeSpace -from .smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic +from .smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic, SmoothCharacterGroupRamifiedQuadratic def LocalComponent(f, p, twist_factor=None): r""" @@ -58,7 +59,7 @@ def LocalComponent(f, p, twist_factor=None): We also adopt a slightly unusual definition of the principal series: we define `\pi(\chi_1, \chi_2)` to be the induction from the Borel subgroup of the character of the maximal torus `\begin{pmatrix} x & \\ & y - \end{pmatrix} \mapsto \chi_1(a) \chi_2(b) |b|`, so its central character is + \end{pmatrix} \mapsto \chi_1(a) \chi_2(b) |a|`, so its central character is `z \mapsto \chi_1(z) \chi_2(z) |z|`. Thus `\chi_1 \chi_2` is the restriction to `\QQ_p^\times` of the unique character of the id\'ele class group mapping `\ell` to `\ell^{k-1} \varepsilon(\ell)` for almost all `\ell`. @@ -73,11 +74,9 @@ def LocalComponent(f, p, twist_factor=None): Character of Q_7*, of level 0, mapping 7 |--> 1 sage: Pi.species() 'Supercuspidal' - sage: Pi.characters() - [ - Character of unramified extension Q_7(s)* (s^2 + 6*s + 3 = 0), of level 1, mapping s |--> d, 7 |--> 1, - Character of unramified extension Q_7(s)* (s^2 + 6*s + 3 = 0), of level 1, mapping s |--> -d, 7 |--> 1 - ] + sage: set(Pi.characters()) + {Character of unramified extension Q_7(s)* (s^2 + 6*s + 3 = 0), of level 1, mapping s |--> -d, 7 |--> 1, + Character of unramified extension Q_7(s)* (s^2 + 6*s + 3 = 0), of level 1, mapping s |--> d, 7 |--> 1} """ p = ZZ(p) if not p.is_prime(): @@ -98,11 +97,13 @@ def LocalComponent(f, p, twist_factor=None): return PrimitivePrincipalSeries(f, p, twist_factor) if c == 0 and r == 1: return PrimitiveSpecial(f, p, twist_factor) - Xf = TypeSpace(f, p) - if Xf.is_minimal(): + + g, chi = f.minimal_twist(p) + if g == f: return PrimitiveSupercuspidal(f, p, twist_factor) - else: - raise NotImplementedError( "Form %s is not %s-primitive" % (f, p) ) + + mintwist = LocalComponent(g, p, twist_factor) + return ImprimitiveLocalComponent(f, p, twist_factor, mintwist, chi) class LocalComponentBase(SageObject): r""" @@ -258,18 +259,9 @@ def central_character(self): sage: LocalComponent(Newforms(DirichletGroup(24)([1, -1,-1]), 3, names='a')[0], 2).central_character() Character of Q_2*, of level 3, mapping 7 |--> 1, 5 |--> -1, 2 |--> -2 """ - from sage.arith.all import crt - chi = self.newform().character() - f = self.prime() ** self.conductor() - N = self.newform().level() // f - G = DirichletGroup(f, self.coefficient_field()) - chip = G([chi(crt(ZZ(x), 1, f, N)) for x in G.unit_gens()]).primitive_character() - a = crt(1, self.prime(), f, N) - - if chip.conductor() == 1: - return SmoothCharacterGroupQp(self.prime(), self.coefficient_field()).character(0, [chi(a) * self.prime()**self.twist_factor()]) - else: - return SmoothCharacterGroupQp(self.prime(), self.coefficient_field()).character(chip.conductor().valuation(self.prime()), list((~chip).values_on_gens()) + [chi(a) * self.prime()**self.twist_factor()]) + G = SmoothCharacterGroupQp(self.prime(), self.coefficient_field()) + eps = G.from_dirichlet(self.newform().character()) + return eps / G.norm_character()**self.twist_factor() def __eq__(self, other): r""" @@ -314,8 +306,37 @@ def __ne__(self, other): """ return not (self == other) +class PrimitiveLocalComponent(LocalComponentBase): + r""" + Base class for primitive (twist-minimal) local components. + """ + + def is_primitive(self): + r""" + Return True if this local component is primitive (has minimal level + among its character twists). + + EXAMPLES:: + + sage: Newform("50a").local_component(5).is_primitive() + True + """ + return True + + def minimal_twist(self): + r""" + Return a twist of this local component which has the minimal possible + conductor. + + EXAMPLES:: + + sage: Pi = Newform("50a").local_component(5) + sage: Pi.minimal_twist() == Pi + True + """ + return self -class PrincipalSeries(LocalComponentBase): +class PrincipalSeries(PrimitiveLocalComponent): r""" A principal series representation. This is an abstract base class, not to be instantiated directly; see the subclasses @@ -464,11 +485,12 @@ def characters(self): ] """ G = SmoothCharacterGroupQp(self.prime(), self.coefficient_field()) - chi1 = G.character(0, [self.newform()[self.prime()]]) + t = ZZ((self.newform().weight() - 2 - self.twist_factor()) / 2) + chi1 = G.character(0, [self.newform()[self.prime()]]) * G.norm_character()**t chi2 = G.character(0, [self.prime()]) * self.central_character() / chi1 return Sequence([chi1, chi2], cr=True, universe=G) -class PrimitiveSpecial(LocalComponentBase): +class PrimitiveSpecial(PrimitiveLocalComponent): r""" A primitive special representation: that is, the Steinberg representation twisted by an unramified character. All such representations have conductor @@ -554,14 +576,13 @@ def check_tempered(self): for sigma in K.embeddings(QQbar): assert sigma(c1(p)).abs() == w -class PrimitiveSupercuspidal(LocalComponentBase): +class PrimitiveSupercuspidal(PrimitiveLocalComponent): r""" A primitive supercuspidal representation. - Except for some exceptional cases - when `p = 2` which we do not implement here, such representations are - parametrized by smooth characters of tamely ramified quadratic extensions - of `\QQ_p`. + Except for some exceptional cases when `p = 2` which we do not implement + here, such representations are parametrized by smooth characters of tamely + ramified quadratic extensions of `\QQ_p`. EXAMPLES:: @@ -603,10 +624,9 @@ def type_space(self): def characters(self): r""" Return the two conjugate characters of `K^\times`, where `K` is some - quadratic extension of `\QQ_p`, defining this representation. This is - fully implemented only in the case where the power of `p` dividing the - level of the form is even, in which case `K` is the unique unramified - quadratic extension of `\QQ_p`. + quadratic extension of `\QQ_p`, defining this representation. An error + will be raised in some 2-adic cases, since not all 2-adic supercuspidal + representations arise in this way. EXAMPLES: @@ -614,11 +634,9 @@ def characters(self): sage: f = Newform('50a') sage: Pi = LocalComponent(f, 5) - sage: chars = Pi.characters(); chars - [ - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 1, - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1, 5 |--> 1 - ] + sage: chars = Pi.characters(); set(chars) + {Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1, 5 |--> 1, + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 1} sage: chars[0].base_ring() Number Field in d with defining polynomial x^2 + x + 1 @@ -632,13 +650,11 @@ def characters(self): sage: f = Newforms(GammaH(25, [6]), 3, names='j')[0]; f q + j0*q^2 + 1/3*j0^3*q^3 - 1/3*j0^2*q^4 + O(q^6) sage: Pi = LocalComponent(f, 5) - sage: Pi.characters() - [ - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 5, - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1/3*j0^3, 5 |--> 5 - ] + sage: set(Pi.characters()) + {Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> 1/3*j0^2*d - 1/3*j0^3, 5 |--> 5, + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1/3*j0^2*d, 5 |--> 5} sage: Pi.characters()[0].base_ring() - Number Field in d with defining polynomial x^2 + 1/3*j0^3*x - 1/3*j0^2 over its base field + Number Field in d with defining polynomial x^2 - j0*x + 1/3*j0^2 over its base field .. warning:: @@ -651,28 +667,53 @@ def characters(self): sage: f = Newform('81a', names='j'); f q + j0*q^2 + q^4 - j0*q^5 + O(q^6) - sage: LocalComponent(f, 3).characters() # long time (12s on sage.math, 2012) + sage: set(LocalComponent(f, 3).characters()) # long time (12s on sage.math, 2012) + {Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> -2*d + j0, 4 |--> 1, 3*s + 1 |--> -j0*d + 1, 3 |--> 1, + Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> 2*d - j0, 4 |--> 1, 3*s + 1 |--> j0*d - 2, 3 |--> 1} + + Some ramified examples:: + + sage: Newform('27a').local_component(3).characters() + [ + Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> -d, s |--> -1, + Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> d - 1, s |--> -1 + ] + sage: LocalComponent(Newform('54a'), 3, twist_factor=4).characters() [ - Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> -2*d - j0, 4 |--> 1, 3*s + 1 |--> -j0*d - 2, 3 |--> 1, - Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> 2*d + j0, 4 |--> 1, 3*s + 1 |--> j0*d + 1, 3 |--> 1 + Character of ramified extension Q_3(s)* (s^2 - 3 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> -1/9*d, s |--> -9, + Character of ramified extension Q_3(s)* (s^2 - 3 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> 1/9*d - 1, s |--> -9 ] - In the ramified case, it's not fully implemented, and just returns a - string indicating which ramified extension is being considered:: + A 2-adic non-example:: - sage: Pi = LocalComponent(Newform('27a'), 3) - sage: Pi.characters() - 'Character of Q_3(sqrt(-3))' - sage: Pi = LocalComponent(Newform('54a'), 3) - sage: Pi.characters() - 'Character of Q_3(sqrt(3))' + sage: Newform('24a').local_component(2).characters() + Traceback (most recent call last): + ... + ValueError: Totally ramified 2-adic representations are not classified by characters + + Examples where `K^\times / \QQ_p^\times` is not topologically cyclic + (which complicates the computations greatly):: + + sage: Newforms(DirichletGroup(64, QQ).1, 2, names='a')[0].local_component(2).characters() # long time, random + [ + Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> 1, -1 |--> 1, 2 |--> 1, + Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> -1, -1 |--> 1, 2 |--> 1 + ] + sage: set(Newform('243a',names='a').local_component(3).characters()) # long time + {Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -2*s - 1 |--> -d - 1, 4 |--> 1, 3*s + 1 |--> -d - 1, s |--> 1, + Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -2*s - 1 |--> d, 4 |--> 1, 3*s + 1 |--> d, s |--> 1} """ T = self.type_space() + p = self.prime() if self.conductor() % 2 == 0: G = SmoothCharacterGroupUnramifiedQuadratic(self.prime(), self.coefficient_field()) n = self.conductor() // 2 - g = G.quotient_gen(n) + + gs = G.quotient_gens(n) + g = gs[-1] + + assert g.valuation(G.ideal(1)) == 0 m = g.matrix().change_ring(ZZ).list() tr = (~T.rho(m)).trace() @@ -681,10 +722,87 @@ def characters(self): X = polygen(self.coefficient_field()) theta_poly = X**2 - (-1)**n*tr*X + self.central_character()(g.norm()) + verbose("theta_poly for %s is %s" % (g, theta_poly), level=1) if theta_poly.is_irreducible(): F = self.coefficient_field().extension(theta_poly, "d") G = G.base_extend(F) - chi1, chi2 = [G.extend_character(n, self.central_character(), x[0]) for x in theta_poly.roots(G.base_ring())] + + # roots with repetitions allowed + gvals = flatten([[y[0]]*y[1] for y in theta_poly.roots(G.base_ring())]) + + if len(gs) == 1: + # This is always the case if p != 2 + chi1, chi2 = [G.extend_character(n, self.central_character(), [x]) for x in gvals] + else: + # 2-adic cases, conductor >= 64. Here life is complicated + # because the quotient (O_K* / p^n)^* / (image of Z_2^*) is not + # cyclic. + g0 = gs[0] + try: + G._reduce_Qp(1, g0) + raise ArithmeticError("Bad generators returned") + except ValueError: + pass + + tr = (~T.rho(g0.matrix().list())).trace() + X = polygen(G.base_ring()) + theta0_poly = X**2 - (-1)**n*tr*X + self.central_character()(g0.norm()) + verbose("theta_poly for %s is %s" % (g0, theta_poly), level=1) + if theta0_poly.is_irreducible(): + F = theta0_poly.base_ring().extension(theta_poly, "e") + G = G.base_extend(F) + g0vals = flatten([[y[0]]*y[1] for y in theta0_poly.roots(G.base_ring())]) + + pairA = [ [g0vals[0], gvals[0]], [g0vals[1], gvals[1]] ] + pairB = [ [g0vals[0], gvals[1]], [g0vals[1], gvals[0]] ] + + A_fail = 0 + B_fail = 0 + try: + chisA = [G.extend_character(n, self.central_character(), [y, x]) for (y, x) in pairA] + except ValueError: + A_fail = 1 + try: + chisB = [G.extend_character(n, self.central_character(), [y, x]) for (y, x) in pairB] + except ValueError: + B_fail = 1 + + if chisA == chisB or chisA == reversed(chisB): + # repeated roots -- break symmetry arbitrarily + B_fail = 1 + + # check the character relation from LW12 + if (not A_fail and not B_fail): + for x in G.ideal(n).invertible_residues(): + try: + # test if G mod p is in Fp + flag = G._reduce_Qp(1, x) + except ValueError: + flag = None + if flag is not None: + verbose("skipping x=%s as congruent to %s mod p" % (x, flag)) + continue + + verbose("testing x = %s" % x, level=1) + ti = (-1)**n * (~T.rho(x.matrix().list())).trace() + verbose(" trace of matrix is %s" % ti, level=1) + if ti != chisA[0](x) + chisA[1](x): + verbose(" chisA FAILED", level=1) + A_fail = 1 + break + if ti != chisB[0](x) + chisB[1](x): + verbose(" chisB FAILED", level=1) + B_fail = 1 + break + else: + verbose(" Trace identity check works for both", level=1) + + if B_fail and not A_fail: + chi1, chi2 = chisA + elif A_fail and not B_fail: + chi1, chi2 = chisB + else: + raise ValueError("Something went wrong: can't identify the characters") # Consistency checks assert chi1.restrict_to_Qp() == chi2.restrict_to_Qp() == self.central_character() @@ -695,33 +813,28 @@ def characters(self): else: # The ramified case. - p = self.prime() - + n = self.conductor() - 1 if p == 2: # The ramified 2-adic representations aren't classified by admissible pairs. Die. - raise NotImplementedError( "Computation with ramified 2-adic representations not implemented" ) - - if p % 4 == 3: - a = ZZ(-1) - else: - a = ZZ(Zmod(self.prime()).quadratic_nonresidue()) - - tr1 = (~T.rho([0,1,a*p, 0])).trace() - tr2 = (~T.rho([0,1,p,0])).trace() - - if tr1 == tr2 == 0: - # This *can* happen. E.g. if the central character satisfies - # chi(-1) = -1, then we have theta(pi) + theta(-pi) = theta(pi) - # * (1 + -1) = 0. In this case, one can presumably identify - # the character and the extension by some more subtle argument - # but I don't know of a good way to automate the process. - raise NotImplementedError( "Can't identify ramified quadratic extension -- both traces zero" ) - elif tr1 == 0: - return "Character of Q_%s(sqrt(%s))" % (p, p) - - elif tr2 == 0: - return "Character of Q_%s(sqrt(%s))" % (p, a*p) - + raise ValueError("Totally ramified 2-adic representations are not classified by characters") + + G0 = SmoothCharacterGroupRamifiedQuadratic(p, 0, self.coefficient_field()) + G1 = SmoothCharacterGroupRamifiedQuadratic(p, 1, self.coefficient_field()) + q0 = G0.quotient_gens(n) + assert all(x.valuation(G0.ideal(1)) == 1 for x in q0) + q1 = G1.quotient_gens(n) + assert all(x.valuation(G1.ideal(1)) == 1 for x in q1) + + t0 = [(~T.rho(q.matrix().list())).trace() for q in q0] + t1 = [(~T.rho(q.matrix().list())).trace() for q in q1] + + if all(x == 0 for x in t0 + t1): + # Can't happen? + raise NotImplementedError( "Can't identify ramified quadratic extension -- all traces zero" ) + elif all(x == 0 for x in t1): + G, qs, ts = G0, q0, t0 + elif all(x == 0 for x in t0): + G, qs, ts = G1, q1, t1 else: # At least one of the traces is *always* 0, since the type # space has to be isomorphic to its twist by the (ramified @@ -729,6 +842,81 @@ def characters(self): # extension. raise RuntimeError( "Can't get here!" ) + q = qs[0] + t = ts[0] + k = self.newform().weight() + t *= p**ZZ( (k - 2 + self.twist_factor() ) / 2) + + X = polygen(self.coefficient_field()) + theta_poly = X**2 - X * t + self.central_character()(q.norm()) + verbose("theta_poly is %s" % theta_poly, level=1) + if theta_poly.is_irreducible(): + F = self.coefficient_field().extension(theta_poly, "d") + G = G.base_extend(F) + c1q, c2q = flatten([[x]*e for x,e in theta_poly.roots(G.base_ring())]) + + if len(qs) == 1: + chi1, chi2 = [G.extend_character(n, self.central_character(), [x]) for x in [c1q, c2q]] + + else: + assert p == 3 + q = qs[1] + t = ts[1] + t *= p**ZZ( (k - 2 + self.twist_factor() ) / 2) + + X = polygen(G.base_ring()) + theta_poly = X**2 - X * t + self.central_character()(q.norm()) + verbose("theta_poly is %s" % theta_poly, level=1) + if theta_poly.is_irreducible(): + F = G.base_ring().extension(theta_poly, "e") + G = G.base_extend(F) + c1q2, c2q2 = flatten([[x]*e for x,e in theta_poly.roots(G.base_ring())]) + + + pairA = [ [c1q, c1q2], [c2q,c2q2] ] + pairB = [ [c1q, c2q2], [c2q, c1q2] ] + + A_fail = 0 + B_fail = 0 + try: + chisA = [G.extend_character(n, self.central_character(), [x, y]) for (x, y) in pairA] + except ValueError: + verbose('A failed to create', level=1) + A_fail = 1 + try: + chisB = [G.extend_character(n, self.central_character(), [x, y]) for (x, y) in pairB] + except ValueError: + verbose('A failed to create', level=1) + B_fail = 1 + + if c1q == c2q or c1q2 == c2q2: + B_fail = 1 + + for u in G.ideal(n).invertible_residues(): + if A_fail or B_fail: + break + x = q*u + verbose("testing x = %s" % x, level=1) + ti = (~T.rho(x.matrix().list())).trace() * p**ZZ((k-2+self.twist_factor())/2) + verbose("trace of matrix is %s" % ti, level=1) + if chisA[0](x) + chisA[1](x) != ti: + A_fail = 1 + if chisB[0](x) + chisB[1](x) != ti: + B_fail = 1 + + if B_fail and not A_fail: + chi1, chi2 = chisA + elif A_fail and not B_fail: + chi1, chi2 = chisB + else: + raise ValueError("Something went wrong: can't identify the characters") + + # Consistency checks + assert chi1.restrict_to_Qp() == chi2.restrict_to_Qp() == self.central_character() + assert chi1*chi2 == chi1.parent().compose_with_norm(self.central_character()) + + return Sequence([chi1, chi2], check=False, cr=True) + def check_tempered(self): r""" Check that this representation is tempered (after twisting by @@ -743,13 +931,128 @@ def check_tempered(self): EXAMPLES:: sage: LocalComponent(Newform("50a"), 5).check_tempered() - sage: LocalComponent(Newform("27a"), 3).check_tempered() # not tested + sage: LocalComponent(Newform("27a"), 3).check_tempered() """ - if self.conductor() % 2: - raise NotImplementedError c1, c2 = self.characters() K = c1.base_ring() p = self.prime() - w = QQbar(p)**(self.twist_factor() / ZZ(2)) + w = QQbar(p)**self.twist_factor() for sigma in K.embeddings(QQbar): - assert c1(p).abs() == c2(p).abs() == w + assert sigma(c1(p)).abs() == sigma(c2(p)).abs() == w + +class ImprimitiveLocalComponent(LocalComponentBase): + r""" + A smooth representation which is not of minimal level among its character + twists. Internally, this is stored as a pair consisting of a minimal local + component and a character to twist by. + """ + + def __init__(self,newform, prime, twist_factor, min_twist, chi): + r""" + EXAMPLES:: + + sage: Newform("45a").local_component(3) # indirect doctest + Smooth representation of GL_2(Q_3) with conductor 3^2, twist of representation of conductor 3^1 + """ + LocalComponentBase.__init__(self, newform, prime, twist_factor) + self._min_twist = min_twist + self._chi = chi + + def is_primitive(self): + r""" + Return True if this local component is primitive (has minimal level + among its character twists). + + EXAMPLES:: + + sage: Newform("45a").local_component(3).is_primitive() + False + """ + return False + + def minimal_twist(self): + r""" + Return a twist of this local component which has the minimal possible + conductor. + + EXAMPLES:: + + sage: Pi = Newform("75b").local_component(5) + sage: Pi.minimal_twist() + Smooth representation of GL_2(Q_5) with conductor 5^1 + """ + return self._min_twist + + def twisting_character(self): + r""" + Return the character giving the minimal twist of this representation. + + EXAMPLES:: + + sage: Pi = Newform("45a").local_component(3) + sage: Pi.twisting_character() + Dirichlet character modulo 3 of conductor 3 mapping 2 |--> -1 + """ + return self._chi + + def species(self): + r""" + The species of this local component, which is either 'Principal + Series', 'Special' or 'Supercuspidal'. + + EXAMPLES:: + + sage: Pi = Newform("45a").local_component(3) + sage: Pi.species() + 'Special' + """ + return self._min_twist.species() + + def _repr_(self): + r""" + EXAMPLES:: + + sage: Pi = Newform("45a").local_component(3) + sage: Pi # indirect doctest + Smooth representation of GL_2(Q_3) with conductor 3^2, twist of representation of conductor 3^1 + """ + return LocalComponentBase._repr_(self) + ', twist of representation of conductor %s^%s' % (self.prime(), self._min_twist.conductor()) + + def characters(self): + r""" + Return the pair of characters (either of `\QQ_p^*` or of some quadratic + extension) corresponding to this representation. + + EXAMPLES:: + + sage: f = [f for f in Newforms(63, 4, names='a') if f[2] == 1][0] + sage: f.local_component(3).characters() + [ + Character of Q_3*, of level 1, mapping 2 |--> -1, 3 |--> d, + Character of Q_3*, of level 1, mapping 2 |--> -1, 3 |--> -d - 2 + ] + """ + minchars = self._min_twist.characters() + G = minchars[0].parent() + chi = self._chi + if self.species() == "Supercuspidal": + H = SmoothCharacterGroupQp(self.prime(), chi.base_ring()) + Hchi = H.from_dirichlet(~chi) + Gchi = G.compose_with_norm(Hchi) + else: + Gchi = G.from_dirichlet(~chi) + return Sequence([c*Gchi for c in minchars], cr=True, universe=G) + + def check_tempered(self): + r""" + Check that this representation is quasi-tempered, i.e. `\pi \otimes + |\det|^{j/2}` is tempered. It is well known that local components of + modular forms are *always* tempered, so this serves as a useful check + on our computations. + + EXAMPLES:: + + sage: f = [f for f in Newforms(63, 4, names='a') if f[2] == 1][0] + sage: f.local_component(3).check_tempered() + """ + self.minimal_twist().check_tempered() diff --git a/src/sage/modular/local_comp/smoothchar.py b/src/sage/modular/local_comp/smoothchar.py index 8bb3d46a009..92135f7f71d 100644 --- a/src/sage/modular/local_comp/smoothchar.py +++ b/src/sage/modular/local_comp/smoothchar.py @@ -41,7 +41,6 @@ +Infinity """ import operator - from sage.structure.element import MultiplicativeGroupElement, parent from sage.structure.parent_base import ParentWithBase from sage.structure.sequence import Sequence @@ -50,9 +49,12 @@ from sage.misc.cachefunc import cached_method from sage.misc.abstract_method import abstract_method from sage.misc.misc_c import prod +from sage.arith.misc import crt from sage.categories.groups import Groups from sage.categories.rings import Rings from sage.misc.mrange import xmrange +from sage.misc.verbose import verbose +from sage.modular.dirichlet import DirichletGroup class SmoothCharacterGeneric(MultiplicativeGroupElement): @@ -79,13 +81,13 @@ def __init__(self, parent, c, values_on_gens): """ MultiplicativeGroupElement.__init__(self, parent) self._c = c - self._values_on_gens = values_on_gens + self._values_on_gens = Sequence(values_on_gens, universe=self.base_ring(), immutable=True) self._check_level() def _check_level(self): r""" Checks that this character has the level it claims to have, and if not, - decrement the level by 1. This is called by :meth:`__init__`. + decrement the level appropriately. This is called by :meth:`__init__`. EXAMPLES:: @@ -103,6 +105,17 @@ def _check_level(self): self._c = self._c - 1 self._check_level() + def __hash__(self): + r""" + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp + sage: chi = SmoothCharacterGroupQp(5, QQ).character(5, [-1, 7]) + sage: D = {chi: 7}; D[chi] # indirect doctest + 7 + """ + return hash( (self._c, self._values_on_gens) ) + def _richcmp_(self, other, op): r""" Compare ``self`` and ``other``. @@ -720,6 +733,21 @@ def character(self, level, values_on_gens): raise ValueError( "value on uniformiser %s (=%s) should be a unit" % (self.unit_gens(level)[i], S[i]) ) return self.element_class(self, level, S) + def norm_character(self): + r""" + Return the normalised absolute value character in this group (mapping a + uniformiser to `1/q` where `q` is the order of the residue field). + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic + sage: SmoothCharacterGroupQp(5, QQ).norm_character() + Character of Q_5*, of level 0, mapping 5 |--> 1/5 + sage: SmoothCharacterGroupUnramifiedQuadratic(2, QQ).norm_character() + Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 0, mapping 2 |--> 1/4 + """ + return self.character(0, [1/self.ideal(1).residue_field().cardinality()]) + def _an_element_(self): r""" Return an element of this group. Required by the coercion machinery. @@ -1012,8 +1040,350 @@ def subgroup_gens(self, level): else: return [1 + self.prime()**(level - 1)] + def from_dirichlet(self, chi): + r""" + Given a Dirichlet character `\chi`, return the factor at p of the + adelic character `\phi` which satisfies `\phi(\varpi_\ell) = + \chi(\ell)` for almost all `\ell`, where `\varpi_\ell` is a uniformizer + at `\ell`. + + More concretely, if we write `\chi = \chi_p \chi_M` as a product of + characters of p-power, resp prime-to-p, conductor, then this function + returns the character of `\QQ_p^\times` sending `p` to `\chi_M(p)` and + agreeing with `\chi_p^{-1}` on integers that are 1 modulo M and coprime + to `p`. + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp + sage: G = SmoothCharacterGroupQp(3, CyclotomicField(6)) + sage: G.from_dirichlet(DirichletGroup(9).0) + Character of Q_3*, of level 2, mapping 2 |--> -zeta6 + 1, 3 |--> 1 + """ + p = self.prime() + chi = chi.primitive_character() + c = chi.level().valuation(p) + M = chi.level().prime_to_m_part(p) + return self.character(chi.conductor().valuation(p), [~chi(crt(1, x, M, p**c)) for x in self.unit_gens(c)[:-1]] + [chi(crt(p, 1, M, p**c))]) + + def quadratic_chars(self): + r""" + Return a list of the (non-trivial) quadratic characters in this group. + This will be a list of 3 characters, unless `p = 2` when there are 7. + + EXAMPLES:: -class SmoothCharacterGroupUnramifiedQuadratic(SmoothCharacterGroupGeneric): + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp + sage: SmoothCharacterGroupQp(7, QQ).quadratic_chars() + [Character of Q_7*, of level 0, mapping 7 |--> -1, + Character of Q_7*, of level 1, mapping 3 |--> -1, 7 |--> -1, + Character of Q_7*, of level 1, mapping 3 |--> -1, 7 |--> 1] + sage: SmoothCharacterGroupQp(2, QQ).quadratic_chars() + [Character of Q_2*, of level 0, mapping 2 |--> -1, + Character of Q_2*, of level 2, mapping 3 |--> -1, 2 |--> -1, + Character of Q_2*, of level 2, mapping 3 |--> -1, 2 |--> 1, + Character of Q_2*, of level 3, mapping 7 |--> -1, 5 |--> -1, 2 |--> -1, + Character of Q_2*, of level 3, mapping 7 |--> -1, 5 |--> -1, 2 |--> 1, + Character of Q_2*, of level 3, mapping 7 |--> 1, 5 |--> -1, 2 |--> -1, + Character of Q_2*, of level 3, mapping 7 |--> 1, 5 |--> -1, 2 |--> 1] + """ + if self.prime() == 2: + q = 3 + else: + q = 1 + ram = [self.from_dirichlet(chi) for chi in DirichletGroup(self.prime() ** q, QQ) if not chi.is_trivial()] + nr = self.character(0, [-1]) + return sorted([nr] + [f for f in ram] + [f*nr for f in ram]) + +class SmoothCharacterGroupQuadratic(SmoothCharacterGroupGeneric): + r""" + The group of smooth characters of `E^\times`, where `E` is a quadratic extension of `\QQ_p`. + """ + + def discrete_log(self, level, x, gens=None): + r""" + Express the class of `x` in `F^\times / (1 + \mathfrak{p}^c)^\times` in + terms of the generators returned by ``self.unit_gens(level)``, or a + custom set of generators if given. + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic + sage: G = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) + sage: G.discrete_log(0, 12) + [2] + sage: G.discrete_log(1, 12) + [0, 2] + sage: v = G.discrete_log(5, 12); v + [0, 2, 0, 1, 2] + sage: g = G.unit_gens(5); prod([g[i]**v[i] for i in [0..4]])/12 - 1 in G.ideal(5) + True + sage: G.discrete_log(3,G.number_field()([1,1])) + [2, 0, 0, 1, 0] + sage: H = SmoothCharacterGroupUnramifiedQuadratic(5, QQ) + sage: x = H.number_field()([1,1]); x + s + 1 + sage: v = H.discrete_log(5, x); v + [22, 263, 379, 0] + sage: h = H.unit_gens(5); prod([h[i]**v[i] for i in [0..3]])/x - 1 in H.ideal(5) + True + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupRamifiedQuadratic + sage: G = SmoothCharacterGroupRamifiedQuadratic(3, 1, QQ) + sage: s = G.number_field().gen() + sage: dl = G.discrete_log(4, 3 + 2*s) + sage: gs = G.unit_gens(4); gs[0]^dl[0] * gs[1]^dl[1] * gs[2]^dl[2] * gs[3]^dl[3] - (3 + 2*s) in G.ideal(4) + True + + An example with a custom generating set:: + + sage: G.discrete_log(2, s+3, gens=[s, s+1, 2]) + [1, 2, 0] + """ + x = self.number_field().coerce(x) + if x == 0: + raise ValueError( "cannot evaluate at zero" ) + if gens is None: + n1 = x.valuation(self.ideal(1)) + x1 = x / self.unit_gens(0)[-1] ** n1 + if level == 0: + return [n1] + else: + return self.ideal(level).ideallog(x1, self.unit_gens(level)[:-1]) + [n1] + else: + P = self.ideal(1) + I = self.ideal(level) + gens = [self.number_field().coerce(g) for g in gens] + i = min(i for i in range(len(gens)) if gens[i].valuation(P) == 1) # lazy! + pi = gens[i] + genvals = [] + genunits = [] + for g in gens: + genvals.append(g.valuation(P)) + gu = g / pi**genvals[-1] + gu *= gu.denominator_ideal().element_1_mod(I) + genunits.append(I.reduce(gu)) + xunit = x / pi**x.valuation(P) + xunit = I.reduce(xunit * xunit.denominator_ideal().element_1_mod(I)) + verbose("computing log of %s in basis %s" % (xunit, genunits), level=1) + dl = I.ideallog(xunit, genunits) + pi_term = x.valuation(P) - sum(dl[j] * genvals[j] for j in range(len(gens))) + dl[i] += pi_term + X = prod(gens[j] ** dl[j] for j in range(len(gens))) + assert (X/x - 1).valuation(P) >= level + return dl + + @cached_method + def quotient_gens(self, n): + r""" + Return a list of elements of `E` which are a generating set for the + quotient `E^\times / \QQ_p^\times`, consisting of elements which are + "minimal" in the sense of [LW12]. + + In the examples we implement here, this quotient is almost always + cyclic: the exceptions are the unramified quadratic extension of + `\QQ_2` for `n \ge 3`, and the extension `\QQ_3(\sqrt{-3})` for `n \ge + 4`. + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic + sage: G = SmoothCharacterGroupUnramifiedQuadratic(7,QQ) + sage: G.quotient_gens(1) + [2*s - 2] + sage: G.quotient_gens(2) + [15*s + 21] + sage: G.quotient_gens(3) + [-75*s + 33] + + A ramified case:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupRamifiedQuadratic + sage: G = SmoothCharacterGroupRamifiedQuadratic(7, 0, QQ) + sage: G.quotient_gens(3) + [22*s + 21] + + An example where the quotient group is not cyclic:: + + sage: G = SmoothCharacterGroupUnramifiedQuadratic(2,QQ) + sage: G.quotient_gens(1) + [s + 1] + sage: G.quotient_gens(2) + [-s + 2] + sage: G.quotient_gens(3) + [-17*s - 14, 3*s - 2] + """ + + # silly special case + if n == 0: + if self.ideal(1).norm().is_prime(): + return [self.unit_gens(0), [2]] + else: + return [[], []] + + p = self.prime() + I = self.ideal(n) + gs = self.unit_gens(n) + es = self.exponents(n) + d = len(es) + + A = ZZ**d + R = [A.gen(i)*es[i] for i in range(d)] + r = I.smallest_integer() + S = [self.discrete_log(n, ZZ(s)) for s in Zmod(r).unit_gens() + (p,)] + Q = A / A.span(R + S) + t = None + qgs = [] + for v in Q.gens(): + # choose a "nice" representative + vv = v.lift() + if vv[-1] < 0: + vv *= -1 + while vv[-1] not in [0, 1]: + if t is None: + t = self.discrete_log(n, p) + vv = [vv[i] - t[i] for i in range(d)] + assert (Q(A(vv)) == v or Q(A(vv)) == -v) + qgs.append( I.reduce(prod(gs[i] ** (vv[i] % es[i]) for i in range(d-1))) * gs[-1]**vv[-1] ) + + if len(qgs) == 2: + x, y = qgs + return [x * y, y] + else: + return qgs + + def _reduce_Qp(self, level, x): + r""" + Utility function: given an element `x` of the number field of self, + return an element of `\QQ_p^\times` which is congruent to `x` modulo a + given power of the maximal ideal. An error will be raised if no such + element exists. + + EXAMPLES:: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic + sage: G = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) + sage: s = G.number_field().gen() + sage: G._reduce_Qp(3, -2520*s-1007) + 9 + sage: G._reduce_Qp(3, s) + Traceback (most recent call last): + ... + ValueError: s not congruent mod Fractional ideal (8) to an elt of Qp + """ + p = self.prime() + r = ZZ(x.norm().valuation(p) / 2) + y = x / p**r + if p==2 and y.trace().valuation(2) < 1: + raise ValueError("%s not congruent mod %s to an elt of Qp" % (x, self.ideal(level))) + Y = (y.trace() / 2) % self.ideal(level).smallest_integer() + X = p**r * Y + if not (X/x - 1).valuation(self.ideal(1)) >= level: + if p != 2: + raise ValueError("%s not congruent mod %s to an elt of Qp" % (x, self.ideal(level))) + else: + X += ZZ(2)**(r + level - 1) + if not (X/x - 1).valuation(self.ideal(1)) >= level: + raise ValueError("%s not congruent mod %s to an elt of Qp" % (x, self.ideal(level))) + return X + + def extend_character(self, level, chi, vals, check=True): + r""" + Return the unique character of `F^\times` which coincides with `\chi` + on `\QQ_p^\times` and maps the generators of the quotient returned by + :meth:`quotient_gens` to ``vals``. + + INPUT: + + - ``chi``: a smooth character of `\QQ_p`, where `p` is the residue + characteristic of `F`, with values in the base ring of self (or some + other ring coercible to it) + - ``level``: the level of the new character (which should be at least + the level of ``chi``) + - ``vals``: a list of elements of the base ring of self (or some other + ring coercible to it), specifying values on the quotients returned by + :meth:`quotient_gens`. + + A ``ValueError`` will be raised if `x^t \ne \chi(\alpha^t)`, where `t` + is the smallest integer such that `\alpha^t` is congruent modulo + `p^{\rm level}` to an element of `\QQ_p`. + + EXAMPLES: + + We extend an unramified character of `\QQ_3^\times` to the unramified + quadratic extension in various ways. + + :: + + sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic + sage: chi = SmoothCharacterGroupQp(5, QQ).character(0, [7]); chi + Character of Q_5*, of level 0, mapping 5 |--> 7 + sage: G = SmoothCharacterGroupUnramifiedQuadratic(5, QQ) + sage: G.extend_character(1, chi, [-1]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1, 5 |--> 7 + sage: G.extend_character(2, chi, [-1]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1, 5 |--> 7 + sage: G.extend_character(3, chi, [1]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 0, mapping 5 |--> 7 + sage: K. = CyclotomicField(6); G.base_extend(K).extend_character(1, chi, [z]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -z + 1, 5 |--> 7 + + We extend the nontrivial quadratic character:: + + sage: chi = SmoothCharacterGroupQp(5, QQ).character(1, [-1, 7]) + sage: K. = CyclotomicField(24); G.base_extend(K).extend_character(1, chi, [z^6]) + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -z^6, 5 |--> 7 + + Extensions of higher level:: + + sage: K. = CyclotomicField(20); rho = G.base_extend(K).extend_character(2, chi, [z]); rho + Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 2, mapping 11*s - 10 |--> z^5, 6 |--> 1, 5*s + 1 |--> z^4, 5 |--> 7 + sage: rho(3) + -1 + + Examples where it doesn't work:: + + sage: G.extend_character(1, chi, [1]) + Traceback (most recent call last): + ... + ValueError: Invalid values for extension + + sage: G = SmoothCharacterGroupQp(2, QQ); H = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) + sage: chi = G.character(3, [1, -1, 7]) + sage: H.extend_character(2, chi, [-1]) + Traceback (most recent call last): + ... + ValueError: Level of extended character cannot be smaller than level of character of Qp + """ + chi = chi.base_extend(self.base_ring()) + + qs = self.quotient_gens(level) + assert len(vals) == len(qs) + + # initial sanity checks + r = self.ideal(level).smallest_integer().valuation(self.prime()) + if chi.level() > r: + raise ValueError("Level of extended character cannot be smaller than level of character of Qp") + + # now do the calculation + standard_gens = self.unit_gens(level) + values_on_standard_gens = [] + + custom_gens = qs + chi.parent().unit_gens(r) + values_on_custom_gens = vals + [chi(x) for x in chi.parent().unit_gens(r)] + verbose("want to send %s to %s" % (custom_gens, values_on_custom_gens), level=1) + + for x in standard_gens: + d = self.discrete_log(level, x, custom_gens) + chix = prod(values_on_custom_gens[i]**d[i] for i in range(len(d))) + values_on_standard_gens.append(chix) + + chiE = self.character(level, values_on_standard_gens) + if not all( chiE(qs[i]) == vals[i] for i in range(len(qs)) ) or chiE.restrict_to_Qp() != chi: + raise ValueError("Invalid values for extension") + return chiE + +class SmoothCharacterGroupUnramifiedQuadratic(SmoothCharacterGroupQuadratic): r""" The group of smooth characters of `\QQ_{p^2}^\times`, where `\QQ_{p^2}` is the unique unramified quadratic extension of `\QQ_p`. We represent @@ -1230,183 +1600,8 @@ def subgroup_gens(self, level): else: return [1 + self.prime()**(level - 1), 1 + self.prime()**(level - 1) * self.number_field().gen()] - def quotient_gen(self, level): - r""" - Find an element generating the quotient - - .. MATH:: - - \mathcal{O}_F^\times / \ZZ_p^\times \cdot (1 + p^c \mathcal{O}_F), - - where `c` is the given level. - - EXAMPLES:: - - sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic - sage: G = SmoothCharacterGroupUnramifiedQuadratic(7,QQ) - sage: G.quotient_gen(1) - s - sage: G.quotient_gen(2) - -20*s - 21 - sage: G.quotient_gen(3) - -69*s - 70 - - For `p = 2` an error will be raised for level `\ge 3`, as the quotient is not cyclic:: - - sage: G = SmoothCharacterGroupUnramifiedQuadratic(2,QQ) - sage: G.quotient_gen(1) - s - sage: G.quotient_gen(2) - -s + 2 - sage: G.quotient_gen(3) - Traceback (most recent call last): - ... - ValueError: Quotient group not cyclic - """ - if level == 0: - raise ValueError( "Quotient group is trivial" ) - elif self.prime() == 2 and level >= 3: - raise ValueError( "Quotient group not cyclic" ) - elif level == 1: - return self.unit_gens(level)[0] - else: - return self.ideal(level).reduce(self.unit_gens(level)[0] * (1 + self.prime() * self.number_field().gen())) - def extend_character(self, level, chi, x, check=True): - r""" - Return the unique character of `F^\times` which coincides with `\chi` - on `\QQ_p^\times` and maps the generator `\alpha` returned by - :meth:`quotient_gen` to `x`. - - INPUT: - - - ``chi``: a smooth character of `\QQ_p`, where `p` is the residue - characteristic of `F`, with values in the base ring of self (or some - other ring coercible to it) - - ``level``: the level of the new character (which should be at least - the level of ``chi``) - - ``x``: an element of the base ring of self (or some other ring - coercible to it). - - A ``ValueError`` will be raised if `x^t \ne \chi(\alpha^t)`, where `t` - is the smallest integer such that `\alpha^t` is congruent modulo - `p^{\rm level}` to an element of `\QQ_p`. - - EXAMPLES: - - We extend an unramified character of `\QQ_3^\times` to the unramified - quadratic extension in various ways. - - :: - - sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupQp, SmoothCharacterGroupUnramifiedQuadratic - sage: chi = SmoothCharacterGroupQp(5, QQ).character(0, [7]); chi - Character of Q_5*, of level 0, mapping 5 |--> 7 - sage: G = SmoothCharacterGroupUnramifiedQuadratic(5, QQ) - sage: G.extend_character(1, chi, -1) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1, 5 |--> 7 - sage: G.extend_character(2, chi, -1) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1, 5 |--> 7 - sage: G.extend_character(3, chi, 1) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 0, mapping 5 |--> 7 - sage: K. = CyclotomicField(6); G.base_extend(K).extend_character(1, chi, z) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> z, 5 |--> 7 - - We extend the nontrivial quadratic character:: - - sage: chi = SmoothCharacterGroupQp(5, QQ).character(1, [-1, 7]) - sage: K. = CyclotomicField(24); G.base_extend(K).extend_character(1, chi, z^6) - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> z^6, 5 |--> 7 - - Extensions of higher level:: - - sage: K. = CyclotomicField(20); rho = G.base_extend(K).extend_character(2, chi, z); rho - Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 2, mapping 11*s - 10 |--> z^5, 6 |--> 1, 5*s + 1 |--> -z^6, 5 |--> 7 - sage: rho(3) - -1 - - Examples where it doesn't work:: - - sage: G.extend_character(1, chi, 1) - Traceback (most recent call last): - ... - ValueError: Value at s must satisfy x^6 = chi(2) = -1, but it does not - - sage: G = SmoothCharacterGroupQp(2, QQ); H = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) - sage: chi = G.character(3, [1, -1, 7]) - sage: H.extend_character(2, chi, -1) - Traceback (most recent call last): - ... - ValueError: Level of extended character cannot be smaller than level of character of Qp - """ - chi = chi.base_extend(self.base_ring()) - if chi.level() > level: - raise ValueError("Level of extended character cannot be smaller than level of character of Qp") - - # check it makes sense - e = (self.prime() + 1) * (self.prime()**(level - 1)) - v = self.ideal(level).reduce(self.quotient_gen(level) ** e) - - v = QQ(v) - if x**e != chi(v): - raise ValueError( "Value at %s must satisfy x^%s = chi(%s) = %s, but it does not" % (self.quotient_gen(level), e, v, chi(v)) ) - - # now do the calculation - values_on_standard_gens = [] - other_gens = [self.quotient_gen(level)] + [ZZ(z) for z in Zmod(self.prime()**level).unit_gens()] - values_on_other_gens = [x] + [chi(u) for u in other_gens[1:]] - for s in self.unit_gens(level)[:-1]: - t = self.ideal(level).ideallog(s, other_gens) - values_on_standard_gens.append( prod([values_on_other_gens[i] ** t[i] for i in range(len(t))]) ) - values_on_standard_gens.append(chi(self.prime())) - chiE = self.character(level, values_on_standard_gens) - - # check it makes sense (optional but on by default) - if check: - assert chiE(self.quotient_gen(level)) == x - assert chiE.restrict_to_Qp() == chi - - return chiE - - def discrete_log(self, level, x): - r""" - Express the class of `x` in `F^\times / (1 + \mathfrak{p}^c)^\times` in - terms of the generators returned by ``self.unit_gens(level)``. - - EXAMPLES:: - - sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupUnramifiedQuadratic - sage: G = SmoothCharacterGroupUnramifiedQuadratic(2, QQ) - sage: G.discrete_log(0, 12) - [2] - sage: G.discrete_log(1, 12) - [0, 2] - sage: v = G.discrete_log(5, 12); v - [0, 2, 0, 1, 2] - sage: g = G.unit_gens(5); prod([g[i]**v[i] for i in [0..4]])/12 - 1 in G.ideal(5) - True - sage: G.discrete_log(3,G.number_field()([1,1])) - [2, 0, 0, 1, 0] - sage: H = SmoothCharacterGroupUnramifiedQuadratic(5, QQ) - sage: x = H.number_field()([1,1]); x - s + 1 - sage: v = H.discrete_log(5, x); v - [22, 263, 379, 0] - sage: h = H.unit_gens(5); prod([h[i]**v[i] for i in [0..3]])/x - 1 in H.ideal(5) - True - """ - x = self.number_field().coerce(x) - if x == 0: - raise ValueError("cannot evaluate at zero") - n1 = x.valuation(self.number_field().ideal(self.prime())) - x1 = x / self.prime() ** n1 - if level == 0: - return [n1] - else: - return self.ideal(level).ideallog(x1, self.unit_gens(level)[:-1]) + [n1] - - -class SmoothCharacterGroupRamifiedQuadratic(SmoothCharacterGroupGeneric): +class SmoothCharacterGroupRamifiedQuadratic(SmoothCharacterGroupQuadratic): r""" The group of smooth characters of `K^\times`, where `K` is a ramified quadratic extension of `\QQ_p`, and `p \ne 2`. @@ -1433,7 +1628,7 @@ def __init__(self, prime, flag, base_ring, names='s'): sage: G1 = SmoothCharacterGroupRamifiedQuadratic(3, 0, QQ); G1 Group of smooth characters of ramified extension Q_3(s)* (s^2 - 3 = 0) with values in Rational Field sage: G2 = SmoothCharacterGroupRamifiedQuadratic(3, 1, QQ); G2 - Group of smooth characters of ramified extension Q_3(s)* (s^2 + 3 = 0) with values in Rational Field + Group of smooth characters of ramified extension Q_3(s)* (s^2 - 6 = 0) with values in Rational Field sage: G3 = SmoothCharacterGroupRamifiedQuadratic(5, 1, QQ); G3 Group of smooth characters of ramified extension Q_5(s)* (s^2 - 10 = 0) with values in Rational Field @@ -1447,20 +1642,25 @@ def __init__(self, prime, flag, base_ring, names='s'): sage: TestSuite(G2).run() sage: TestSuite(G3).run() """ + prime = ZZ(prime) if prime == 2: - raise NotImplementedError("Wildly ramified extensions not supported") + raise NotImplementedError( "Wildly ramified extensions not supported" ) SmoothCharacterGroupGeneric.__init__(self, prime, base_ring) self._name = names if flag not in [0, 1]: raise ValueError("Flag must be 0 (for Qp(sqrt(p)) ) or 1 (for the other ramified extension)") self._flag = flag - if flag == 0: - self._unif_sqr = self.prime() + + # Find an integer a such that sqrt(a*p) generates the right field and ZZ(sqrt(a*p)) is integrally closed + for a in range(4 * prime): + if (not a % prime) or (not ZZ(a).is_squarefree()) or ((a * prime) % 4 == 1): + continue + if (flag == 0 and Zmod(prime)(a).is_square()) or \ + (flag == 1 and not Zmod(prime)(a).is_square()): + self._unif_sqr = a * prime + break else: - if self.prime() % 4 == 3: - self._unif_sqr = -self.prime() - else: - self._unif_sqr = ZZ(Zmod(self.prime()).quadratic_nonresidue()) * self.prime() + raise ValueError("Can't get here") def change_ring(self, ring): r""" @@ -1474,7 +1674,7 @@ def change_ring(self, ring): sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupRamifiedQuadratic sage: SmoothCharacterGroupRamifiedQuadratic(7, 1, Zmod(3), names='foo').change_ring(CC) - Group of smooth characters of ramified extension Q_7(foo)* (foo^2 + 7 = 0) with values in Complex Field with 53 bits of precision + Group of smooth characters of ramified extension Q_7(foo)* (foo^2 - 35 = 0) with values in Complex Field with 53 bits of precision """ return SmoothCharacterGroupRamifiedQuadratic(self.prime(), self._flag, ring, self._name) @@ -1502,7 +1702,7 @@ def number_field(self): sage: SmoothCharacterGroupRamifiedQuadratic(5, 1, QQ, 'b').number_field() Number Field in b with defining polynomial x^2 - 10 sage: SmoothCharacterGroupRamifiedQuadratic(7, 1, Zmod(6), 'c').number_field() - Number Field in c with defining polynomial x^2 + 7 + Number Field in c with defining polynomial x^2 - 35 """ from sage.rings.all import PolynomialRing R, x = PolynomialRing(QQ, 'x').objgen() @@ -1613,27 +1813,3 @@ def subgroup_gens(self, level): return self.unit_gens(level)[:-1] else: return [1 + self.number_field().gen()**(level - 1)] - - def discrete_log(self, level, x): - r""" - Solve the discrete log problem in the unit group. - - EXAMPLES:: - - sage: from sage.modular.local_comp.smoothchar import SmoothCharacterGroupRamifiedQuadratic - sage: G = SmoothCharacterGroupRamifiedQuadratic(3, 1, QQ) - sage: s = G.number_field().gen() - sage: G.discrete_log(4, 3 + 2*s) - [5, 1, 1, 1] - sage: gs = G.unit_gens(4); gs[0]^5 * gs[1] * gs[2] * gs[3] - (3 + 2*s) in G.ideal(4) - True - """ - x = self.number_field().coerce(x) - if x == 0: - raise ValueError("cannot evaluate at zero") - n1 = x.valuation(self.ideal(1)) - x1 = x / self.number_field().gen()**n1 - if level == 0: - return [n1] - else: - return self.ideal(level).ideallog(x1, self.unit_gens(level)[:-1]) + [n1] diff --git a/src/sage/modular/local_comp/type_space.py b/src/sage/modular/local_comp/type_space.py index f1a292c69df..ce78d301979 100644 --- a/src/sage/modular/local_comp/type_space.py +++ b/src/sage/modular/local_comp/type_space.py @@ -307,15 +307,17 @@ def group(self): sage: from sage.modular.local_comp.type_space import example_type_space sage: example_type_space().group() - Congruence Subgroup Gamma_H(98) with H generated by [43] + Congruence Subgroup Gamma_H(98) with H generated by [15, 29, 43] """ + # Implementation here is not the most efficient but this is heavily not + # time-critical, and getting it wrong can lead to subtle bugs. p = self.prime() r = self.conductor() d = max(self.character_conductor(), r//2) n = self.tame_level() chi = self.form().character() tame_H = [i for i in chi.kernel() if (i % p**r) == 1] - wild_H = [crt(1 + p**d, 1, p**r, n)] + wild_H = [crt(x, 1, p**r, n) for x in range(p**r) if x % (p**d) == 1] return GammaH(n * p**r, tame_H + wild_H) ############################################################################### @@ -390,11 +392,11 @@ def minimal_twist(self): V = A.submodule(VV, check=False) D = V.decomposition()[0] - if len(D.star_eigenvalues()) == 2: - D = D.sign_submodule(1) - D._set_sign(D.star_eigenvalues()[0]) - M = ModularForms(D.group(), D.weight()) - ff = Newform(M, D, names='a') + #if len(D.star_eigenvalues()) == 2: + # D = D.sign_submodule(1) + D1 = D.modular_symbols_of_sign(1) + M = ModularForms(D1.group(), D1.weight(), D1.base_ring()) + ff = Newform(M, D1, names='a') return ff ##################################### @@ -615,9 +617,10 @@ def _discover_torus_action(self): [ 1 -1 -2 2] """ f = self.prime() ** self.u() - if len(Zmod(f).unit_gens()) != 1: - raise NotImplementedError - a = ZZ(Zmod(f).unit_gens()[0]) + if not (f % 8): + a = ZZ(5) + else: + a = ZZ(Zmod(f).unit_gens()[0]) mats = self._intertwining_basis(a) V = self.t_space.nonembedded_free_module() @@ -683,18 +686,25 @@ def rho(self, g): except AttributeError: self._discover_torus_action() a = self._a + + if not (f % 8): + if d % 4 == 3: + return (self.rho([-g[0], g[1], -g[2], g[3]]) * + self.t_space.star_involution().matrix().transpose()) + i = 0 while (d * a**i) % f != 1: i += 1 - if i > f: raise ArithmeticError + if i > f: + raise ArithmeticError return self._rho_s([a**i*g[0], g[1], a**i*g[2], g[3]]) * self._amat**(-i) - # funny business + # det(g) is not a unit if (self.conductor() % 2 == 0): if all(x.valuation(p) > 0 for x in g): eps = self.form().character()(crt(1, p, f, self.tame_level())) - return ~eps * self.rho([x // p for x in g]) + return ~eps * p**(self.form().weight() - 2) * self.rho([x // p for x in g]) else: raise ArithmeticError( "g(={0}) not in K".format(g) ) @@ -718,4 +728,9 @@ def _unif_ramified(self): [ 0 -1] """ - return self.t_space.atkin_lehner_operator(self.prime()).matrix().transpose() * self.prime() ** (-1 + self.form().weight() // 2) + p = self.prime() + k = self.form().weight() + return (self.t_space.atkin_lehner_operator(p).matrix().transpose() + * p ** ( -(k-2)*self.u() ) + * self.t_space.diamond_bracket_matrix( + crt(1, p**self.u(), p**self.u(), self.tame_level())).transpose()) diff --git a/src/sage/modular/modform/element.py b/src/sage/modular/modform/element.py index d1206fd3539..2954c4eada6 100644 --- a/src/sage/modular/modform/element.py +++ b/src/sage/modular/modform/element.py @@ -8,10 +8,10 @@ - :class:`Newform` - - :class:`ModularFormElement` - - :class:`ModularFormElement_elliptic_curve` + - :class:`ModularFormElement` + - :class:`EisensteinSeries` """ @@ -1260,6 +1260,9 @@ def cm_discriminant(self): return -self.__cm_char.conductor() class Newform(ModularForm_abstract): + # The reasons why Newform does not inherit from ModularFormElement + # should really be documented somewhere. + def __init__(self, parent, component, names, check=True): r""" Initialize a Newform object. @@ -2077,14 +2080,19 @@ def twist(self, chi, level=None, check=True): determine a common base field into which both the Hecke eigenvalue field of self, and the field of values of ``chi``, can be embedded. - - ``level`` -- (optional) the level `N` of the twisted form. - By default, the algorithm tries to compute `N` using - [AL1978]_, Theorem 3.1. + - ``level`` -- (optional) the level `N` of the twisted form. If `N` is + not given, the algorithm tries to compute `N` using [AL1978]_, + Theorem 3.1; if this is not possible, it returns an error. If `N` is + given but incorrect, i.e. the twisted form does not have level `N`, + then this function will attempt to detect this and return an error, + but it may sometimes return an incorrect answer (a newform of level + `N` whose first few coefficients agree with those of `f \otimes + \chi`). - ``check`` -- (optional) boolean; if ``True`` (default), ensure that the space of modular symbols that is computed is genuinely simple and - new. This makes it less likely that a wrong result is returned if an - incorrect ``level`` is specified. + new. This makes it less likely, but not impossible, that a wrong + result is returned if an incorrect ``level`` is specified. OUTPUT: @@ -2173,7 +2181,7 @@ def twist(self, chi, level=None, check=True): # pull out the eigenspace for p in prime_range(500): - if p.divides(N) or p.divides(chi.level()): + if p.divides(chi.level()): continue D = (D.hecke_operator(p) - self[p]*chi(p)).kernel() if D.rank() == 1: @@ -2184,6 +2192,115 @@ def twist(self, chi, level=None, check=True): raise RuntimeError('unable to identify modular symbols for twist of %s by %s' % (self, chi)) return Newform(S, D, names='_', check=check) + def minimal_twist(self, p=None): + r""" + Compute a pair `(g, chi)` such that `g = f \otimes \chi`, where `f` is + this newform and `\chi` is a Dirichlet character, such that `g` has + level as small as possible. If the optional argument `p` is given, + consider only twists by Dirichlet characters of `p`-power conductor. + + EXAMPLES:: + + sage: f = Newforms(575, 2, names='a')[4] + sage: g, chi = f.minimal_twist(5) + sage: g + q + a*q^2 - a*q^3 - 2*q^4 + (1/2*a + 2)*q^5 + O(q^6) + sage: chi + Dirichlet character modulo 5 of conductor 5 mapping 2 |--> 1/2*a + sage: f.twist(chi, level=g.level()) == g + True + """ + if p is None: + # test local minimality at all primes + for p in self.level().prime_divisors(): + (g, chi) = self.minimal_twist(p) + if g.level() < self.level(): + h, tau = g.minimal_twist(p=None) + M = chi.modulus().lcm(tau.modulus()) + return (h, chi.extend(M)*tau.extend(M)) + else: + # f locally minimal at all p, hence globally minimal + return (self, DirichletGroup(1, self.base_ring())(1)) + + p = ZZ(p) + N = self.level() + r = N.valuation(p) + c = self.character().conductor().valuation(p) + if not (p.is_prime() and p.divides(N)): + raise ValueError("p should be prime factor of N") + + if (r == c) or (r == 1 and c == 0): + # easy cases + return (self, DirichletGroup(1, self.base_ring())(1)) + elif r < 2*c: + # In this case we know that there is a unique chi of conductor p^u + # such that self x chi has level N/p^u, where u = r-c, and this + # twist is minimal. + candidates = [] + for chi in DirichletGroup(p**(r-c), self.base_ring()): + if not chi.is_primitive(): + continue + try: + g = self.twist(chi, level=N//p**(r-c)) + candidates.append( (g, chi) ) + except ValueError: + continue + + l = ZZ(1) + while len(candidates) > 1: + l = l.next_prime() + if l == p: + continue + candidates = [(h, chi) for (h, chi) in candidates if h[l] == chi(l)*self[l] ] + if l > 10000 or len(candidates) == 0: + raise RuntimeError("bug finding minimal twist") + return candidates[0] + else: + # The hard case. Now f might be ramified principal series, twist of + # Steinberg, or supercuspidal, and the minimal twist is not unique + # any more. So we use the slow, but very general, type-space + # algorithm. + from sage.modular.local_comp.type_space import TypeSpace + T = TypeSpace(self, p) + if T.is_minimal(): + return (self, DirichletGroup(1, self.base_ring())(1)) + else: + g = T.minimal_twist() + epsg = g.character().extend(N) + chisq = (epsg / self.character()).restrict(p**(r//2)) + K = coercion_model.common_parent(self.base_ring(), g.base_ring()) + chis = [chi for chi in DirichletGroup(p**(r//2), K) if chi**2 == chisq] + + if g.has_cm() and g.cm_discriminant().prime_divisors() == [p]: + # Quicker to test g than self, because g has smaller level. + t = 2 + else: + t = 1 + l = ZZ(1) + while len(chis) > t: + l = l.next_prime() + if l == p: + continue + chis = [chi for chi in chis if g[l] == chi(l) * self[l] ] + if l > 10000 or len(chis) == 0: + raise RuntimeError("bug finding minimal twist") + return (g, chis[0]) + + def local_component(self, p, twist_factor=None): + """ + Calculate the local component at the prime `p` of the automorphic + representation attached to this newform. For more information, see the + documentation of the :func:`LocalComponent` function. + + EXAMPLES:: + + sage: f = Newform("49a") + sage: f.local_component(7) + Smooth representation of GL_2(Q_7) with conductor 7^2 + """ + from sage.modular.local_comp.local_comp import LocalComponent + return LocalComponent(self, p, twist_factor) + class ModularFormElement(ModularForm_abstract, element.HeckeModuleElement): def __init__(self, parent, x, check=True): r""" @@ -2497,7 +2614,7 @@ def twist(self, chi, level=None): return M(f_twist) -class ModularFormElement_elliptic_curve(ModularFormElement): +class ModularFormElement_elliptic_curve(Newform): r""" A modular form attached to an elliptic curve over `\QQ`. """ @@ -2523,11 +2640,9 @@ def __init__(self, parent, E): sage: f == loads(dumps(f)) True """ - ModularFormElement.__init__(self, parent, None) -## parent.find_in_space( E.q_expansion(parent.hecke_bound()) )) + Newform.__init__(self, parent, E.modular_symbol_space(), names=None) self.__E = E - def elliptic_curve(self): """ Return elliptic curve associated to self. diff --git a/src/sage/modular/modform/find_generators.py b/src/sage/modular/modform/find_generators.py index 18055ef00f8..40f4f7c8580 100644 --- a/src/sage/modular/modform/find_generators.py +++ b/src/sage/modular/modform/find_generators.py @@ -400,8 +400,7 @@ def generators(self, maxweight=8, prec=10, start_gens=[], start_weight=2): sage: ModularFormsRing(Gamma0(13)).generators(maxweight=12, prec=4) [(2, 1 + 2*q + 6*q^2 + 8*q^3 + O(q^4)), (4, 1 + O(q^4)), (4, q + O(q^4)), (4, q^2 + O(q^4)), (4, q^3 + O(q^4)), (6, 1 + O(q^4)), (6, q + O(q^4))] sage: ModularFormsRing(Gamma0(13),base_ring=ZZ).generators(maxweight=12, prec=4) - [(2, 1 + 2*q + 6*q^2 + 8*q^3 + O(q^4)), (4, O(q^4)), (4, q^3 + O(q^4)), (4, q^2 + O(q^4)), (4, q + O(q^4)), (6, O(q^4)), (6, O(q^4)), (12, O(q^4))] - + [(2, 1 + 2*q + 6*q^2 + 8*q^3 + O(q^4)), (4, q + 4*q^2 + 10*q^3 + O(q^4)), (4, 2*q^2 + 5*q^3 + O(q^4)), (4, q^2 + O(q^4)), (4, -2*q^3 + O(q^4)), (6, O(q^4)), (6, O(q^4)), (12, O(q^4))] sage: [k for k,f in ModularFormsRing(1, QQ).generators(maxweight=12)] [4, 6] sage: [k for k,f in ModularFormsRing(1, ZZ).generators(maxweight=12)] diff --git a/src/sage/modular/modsym/p1list_nf.py b/src/sage/modular/modsym/p1list_nf.py index 709a91035a7..91f7efada6f 100644 --- a/src/sage/modular/modsym/p1list_nf.py +++ b/src/sage/modular/modsym/p1list_nf.py @@ -957,7 +957,7 @@ def apply_J_epsilon(self, i, e1, e2=1): sage: N = k.ideal(a + 1) sage: P = P1NFList(N) sage: u = k.unit_group().gens_values(); u - [-1, a^3 + a^2 + a + 12, a^3 + 3*a^2 - 1] + [-1, -a^3 - a^2 - a - 12, -a^3 - 3*a^2 + 1] sage: P.apply_J_epsilon(3, u[2]^2)==P.apply_J_epsilon(P.apply_J_epsilon(3, u[2]),u[2]) True """ diff --git a/src/sage/modular/multiple_zeta.py b/src/sage/modular/multiple_zeta.py index 67c46766c80..d1f1f550247 100644 --- a/src/sage/modular/multiple_zeta.py +++ b/src/sage/modular/multiple_zeta.py @@ -458,7 +458,7 @@ def reset(self, max_weight=8, prec=1024): """ self.prec = int(prec) self.max_weight = int(max_weight) - self._data = pari.zetamultall(self.max_weight, self.prec) + self._data = pari.zetamultall(self.max_weight, precision=self.prec) def update(self, max_weight, prec): """ diff --git a/src/sage/modular/overconvergent/hecke_series.py b/src/sage/modular/overconvergent/hecke_series.py index 3320b6464f6..fe6147b0a07 100644 --- a/src/sage/modular/overconvergent/hecke_series.py +++ b/src/sage/modular/overconvergent/hecke_series.py @@ -446,7 +446,7 @@ def complementary_spaces_modp(N,p,k0,n,elldash,LWBModp,bound): """ CompSpacesCode = [] ell = dimension_modular_forms(N,k0 + n*(p-1)) - TotalBasisModp = matrix(GF(p),ell,elldash); # zero matrix + TotalBasisModp = matrix(GF(p), ell, elldash) # zero matrix for i in range(n+1): NewBasisCodemi = random_new_basis_modp(N,p,k0 + i*(p-1),LWBModp,TotalBasisModp,elldash,bound) @@ -751,8 +751,8 @@ def higher_level_UpGj(p, N, klist, m, modformsring, bound, extra_data=False): T = matrix(S,ell,elldash) for i in range(ell): ei = R(e[i].list()) - Gkdivei = Gkdiv*ei; # act by G^kdiv - for j in range(0, elldash): + Gkdivei = Gkdiv*ei # act by G^kdiv + for j in range(elldash): T[i,j] = Gkdivei[p*j] verbose("done steps 4b and 5", t) diff --git a/src/sage/modular/quatalg/brandt.py b/src/sage/modular/quatalg/brandt.py index 568f28e6649..5fbde461d12 100644 --- a/src/sage/modular/quatalg/brandt.py +++ b/src/sage/modular/quatalg/brandt.py @@ -2,82 +2,90 @@ Brandt Modules Introduction -============ +------------ -This tutorial outlines the construction of Brandt modules in Sage. The -importance of this construction is that it provides us with a method -to compute modular forms on `\Gamma_0(N)` as outlined in Pizer's paper -[Piz1980]_. In fact there exists a non-canonical Hecke algebra isomorphism -between the Brandt modules and a certain subspace of -`S_{2}(\Gamma_0(pM))` which contains all the newforms. +The construction of Brandt modules provides us with a method to +compute modular forms, as outlined in Pizer's paper [Piz1980]_. -The Brandt module is the free abelian group on right ideal classes of -a quaternion order together with a natural Hecke action determined by -Brandt matrices. +Given a prime number `p` and a positive integer `M` with `p\nmid M`, +the *Brandt module* `B(p, M)` is the free abelian group on right ideal +classes of a quaternion order of level `pM` in the quaternion algebra +ramified precisely at the places `p` and `\infty`. This Brandt module +carries a natural Hecke action given by Brandt matrices. There exists +a non-canonical Hecke algebra isomorphism between `B(p, M)` and a +certain subspace of `S_{2}(\Gamma_0(pM))` containing the newforms. Quaternion Algebras ------------------- A quaternion algebra over `\QQ` is a central simple algebra of -dimension 4 over `\QQ`. Such an algebra `A` is said to be -ramified at a place `v` of `\QQ` if and only if `A_v=A\otimes -\QQ_v` is a division algebra. Otherwise `A` is said to be split -at `v`. +dimension 4 over `\QQ`. Such an algebra `A` is said to be ramified at +a place `v` of `\QQ` if and only if `A \otimes \QQ_v` is a division +algebra. Otherwise `A` is said to be split at `v`. ``A = QuaternionAlgebra(p)`` returns the quaternion algebra `A` over `\QQ` ramified precisely at the places `p` and `\infty`. -``A = QuaternionAlgebra(k,a,b)`` returns a quaternion algebra with basis -`\{1,i,j,j\}` over `\mathbb{K}` such that `i^2=a`, `j^2=b` and `ij=k.` +``A = QuaternionAlgebra(a, b)`` returns the quaternion algebra `A` +over `\QQ` with basis `\{1, i, j, k\}` such that `i^2 = a`, `j^2 = b` +and `ij = -ji = k.` -An order `R` in a quaternion algebra is a 4-dimensional lattice on `A` -which is also a subring containing the identity. +An order `R` in a quaternion algebra `A` over `\QQ` is a 4-dimensional +lattice in `A` which is also a subring containing the identity. A +maximal order is one that is not properly contained in another order. + +A particularly important kind of orders are those that have a level; +see Definition 1.2 in [Piz1980]_. This is a positive integer `N` such +that every prime that ramifies in `A` divides `N` to an odd power. +The maximal orders are those that have level equal to the discriminant +of `A`. ``R = A.maximal_order()`` returns a maximal order `R` in the quaternion algebra `A.` -An Eichler order `\mathcal{O}` in a quaternion algebra is the -intersection of two maximal orders. The level of `\mathcal{O}` is its -index in any maximal order containing it. +A right `\mathcal{O}`-ideal `I` is a lattice in `A` such that for +every prime `p` there exists `a_p\in A_p^*` with `I_p = +a_p\mathcal{O}_p`. Two right `\mathcal{O}`-ideals `I` and `J` are said +to belong to the same class if `I=aJ` for some `a \in A^*`. Left +`\mathcal{O}`-ideals are defined in a similar fashion. -``O = A.order_of_level_N`` returns an Eichler order `\mathcal{O}` in `A` -of level `N` where `p` does not divide `N`. +The right order of `I` is the subring of `A` consisting of elements +`a` with `Ia \subseteq I`. +Brandt Modules +-------------- -A right `\mathcal{O}`-ideal `I` is a lattice on `A` such that -`I_p=a_p\mathcal{O}` (for some `a_p\in A_p^*`) for all `p<\infty`. Two -right `\mathcal{O}`-ideals `I` and `J` are said to belong to the same -class if `I=aJ` for some `a \in A^*`. (Left `\mathcal{O}`-ideals are -defined in a similar fashion.) +``B = BrandtModule(p, M=1)`` returns the Brandt module associated to +the prime number `p` and the integer `M`, with `p` not dividing `M`. -The right order of `I` is defined to be the set of elements in `A` -which fix `I` under right multiplication. +``A = B.quaternion_algebra()`` returns the quaternion algebra attached +to `B`; this is the quaternion algebra over `\QQ` ramified exactly at +`p` and `\infty`. -``right_order(R, basis)`` returns the right ideal of `I` in `R` given a -basis for the right ideal `I` contained in the maximal order `R.` +``O = B.order_of_level_N()`` returns an order `\mathcal{O}` of level +`N = pM` in `A`. -``ideal_classes(self)`` returns a tuple of all right ideal classes in self -which, for the purpose of constructing the Brandt module B(p,M), is -taken to be an Eichler order of level M. +``B.right_ideals()`` returns a tuple of representatives for all right +ideal classes of `\mathcal{O}`. The implementation of this method is especially interesting. It depends on the construction of a Hecke module defined as a free abelian group on right ideal classes of a quaternion algebra with the -following action +following action: .. MATH:: T_n[I] = \sum_{\phi} [J] where `(n,pM)=1` and the sum is over cyclic `\mathcal{O}`-module -homomorphisms `\phi :I\rightarrow J` of degree `n` up to isomorphism -of `J`. Equivalently one can sum over the inclusions of the submodules -`J \rightarrow n^{-1}I`. The rough idea is to start with the trivial -ideal class containing the order `\mathcal{O}` itself. Using the -method ``cyclic_submodules(self, I, p)`` one computes `T_p([\mathcal{O}])` -for some prime integer `p` not dividing the level of the order -`\mathcal{O}`. Apply this method repeatedly and test for equivalence -among resulting ideals. A theorem of Serre asserts that one gets a +homomorphisms `\phi\colon I\rightarrow J` of degree `n` up to +isomorphism of `J`. Equivalently one can sum over the inclusions of +the submodules `J \rightarrow n^{-1}I`. The rough idea is to start +with the trivial ideal class containing the order `\mathcal{O}` +itself. Using the method ``cyclic_submodules(self, I, q)`` one then +repeatedly computes `T_q([\mathcal{O}])` for some prime `q` not +dividing the level of `\mathcal{O}` and tests for equivalence among +the resulting ideals. A theorem of Serre asserts that one gets a complete set of ideal class representatives after a finite number of repetitions. @@ -356,7 +364,7 @@ def maximal_order(A): sage: A = BrandtModule(17).quaternion_algebra() sage: sage.modular.quatalg.brandt.maximal_order(A) - Order of Quaternion Algebra (-17, -3) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, -1/3*j - 1/3*k, k) + Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, -1/3*i + 1/3*k, -k) sage: A = QuaternionAlgebra(17,names='i,j,k') sage: A.maximal_order() @@ -382,9 +390,9 @@ def basis_for_left_ideal(R, gens): sage: B = BrandtModule(17); A = B.quaternion_algebra(); i,j,k = A.gens() sage: sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), [i+j,i-j,2*k,A(3)]) - [1/2 + 1/6*j + 2/3*k, 1/2*i + 1/2*k, 1/3*j + 1/3*k, k] + [1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k] sage: sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), [3*(i+j),3*(i-j),6*k,A(3)]) - [3/2 + 1/2*j + 2*k, 3/2*i + 3/2*k, j + k, 3*k] + [3/2 + 1/2*i + k, i + 2*k, 3/2*j + 3/2*k, 3*k] """ return basis_for_quaternion_lattice([b * g for b in R.basis() for g in gens]) @@ -409,14 +417,14 @@ def right_order(R, basis): sage: B = BrandtModule(17); basis = sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), B.maximal_order().basis()) sage: sage.modular.quatalg.brandt.right_order(B.maximal_order(), basis) - Order of Quaternion Algebra (-17, -3) with base ring Rational Field with basis (1/2 + 1/6*j + 2/3*k, 1/2*i + 1/2*k, 1/3*j + 1/3*k, k) + Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k) sage: basis - [1/2 + 1/6*j + 2/3*k, 1/2*i + 1/2*k, 1/3*j + 1/3*k, k] + [1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k] sage: B = BrandtModule(17); A = B.quaternion_algebra(); i,j,k = A.gens() sage: basis = sage.modular.quatalg.brandt.basis_for_left_ideal(B.maximal_order(), [i*j-j]) sage: sage.modular.quatalg.brandt.right_order(B.maximal_order(), basis) - Order of Quaternion Algebra (-17, -3) with base ring Rational Field with basis (1/2 + 1/2*i + 1/2*j + 17/2*k, i, j + 8*k, 9*k) + Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k) """ # Compute matrix of multiplication by each element of the basis. B = R.basis() @@ -790,26 +798,9 @@ def quaternion_algebra(self): sage: BrandtModule(5).quaternion_algebra() Quaternion Algebra (-2, -5) with base ring Rational Field sage: BrandtModule(17).quaternion_algebra() - Quaternion Algebra (-17, -3) with base ring Rational Field + Quaternion Algebra (-3, -17) with base ring Rational Field """ - p = self.N() - assert p.is_prime(), "we have only implemented the prime case" - if p == 2: - QA = -1 - QB = -1 - elif p % 4 == 3: - QA = -1 - QB = -p - elif p % 8 == 5: - QA = -2 - QB = -p - elif p % 8 == 1: - q = 3 - while q % 4 != 3 or kronecker(p, q) != -1: - q = next_prime(q) - QA = -p - QB = -q - return QuaternionAlgebra(QQ, QA, QB) + return QuaternionAlgebra(self.N()) @cached_method def maximal_order(self): @@ -819,7 +810,7 @@ def maximal_order(self): EXAMPLES:: sage: BrandtModule(17).maximal_order() - Order of Quaternion Algebra (-17, -3) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, -1/3*j - 1/3*k, k) + Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, -1/3*i + 1/3*k, -k) sage: BrandtModule(17).maximal_order() is BrandtModule(17).maximal_order() True """ @@ -828,7 +819,7 @@ def maximal_order(self): @cached_method def order_of_level_N(self): """ - Return Eichler order of level `N = p^{2 r + 1} M` in the + Return an order of level `N = p^{2 r + 1} M` in the quaternion algebra. EXAMPLES:: diff --git a/src/sage/modules/fg_pid/fgp_element.py b/src/sage/modules/fg_pid/fgp_element.py index 53857d31a07..f6f03acd16c 100644 --- a/src/sage/modules/fg_pid/fgp_element.py +++ b/src/sage/modules/fg_pid/fgp_element.py @@ -39,7 +39,7 @@ class FGP_Element(ModuleElement): sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) sage: Q = V/W sage: x = Q(V.0-V.1); x #indirect doctest - (0, 3) + (0, 9) sage: isinstance(x, sage.modules.fg_pid.fgp_element.FGP_Element) True sage: type(x) @@ -76,7 +76,8 @@ def __init__(self, parent, x, check=DEBUG): For full documentation, see :class:`FGP_Element`. """ - if check: assert x in parent.V(), 'The argument x='+str(x)+' is not in the covering module!' + if check: + assert x in parent.V(), 'The argument x='+str(x)+' is not in the covering module!' ModuleElement.__init__(self, parent) self._x = x @@ -94,14 +95,14 @@ def lift(self): sage: Q.1 (0, 1) sage: Q.0.lift() - (0, 0, 1) + (0, 6, 1) sage: Q.1.lift() - (0, 2, 0) + (0, -2, 0) sage: x = Q(V.0); x - (0, 4) + (0, 8) sage: x.lift() (1/2, 0, 0) - sage: x == 4*Q.1 + sage: x == 8*Q.1 True sage: x.lift().parent() == V True @@ -158,9 +159,9 @@ def _add_(self, other): We test canonical coercion from V and W. sage: Q.0 + V.0 - (1, 4) + (1, 8) sage: V.0 + Q.0 - (1, 4) + (1, 8) sage: W.0 + Q.0 (1, 0) sage: W.0 + Q.0 == Q.0 @@ -291,7 +292,7 @@ def _repr_(self): sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) sage: Q = V/W sage: Q(V.1)._repr_() - '(0, 1)' + '(0, 11)' """ return repr(self.vector()) @@ -332,7 +333,8 @@ def vector(self): sage: x.vector().parent() Ambient free module of rank 2 over the principal ideal domain Integer Ring """ - try: return self.__vector + try: + return self.__vector except AttributeError: self.__vector = self.parent().coordinate_vector(self, reduce=True) self.__vector.set_immutable() diff --git a/src/sage/modules/fg_pid/fgp_module.py b/src/sage/modules/fg_pid/fgp_module.py index 38b95d40a39..db8fd8cabb7 100644 --- a/src/sage/modules/fg_pid/fgp_module.py +++ b/src/sage/modules/fg_pid/fgp_module.py @@ -70,17 +70,17 @@ sage: M0.optimized()[0].V() Free module of degree 3 and rank 2 over Integer Ring User basis matrix: - [0 0 1] - [0 2 0] + [ 0 8 1] + [ 0 -2 0] Create elements of M0 either by coercing in elements of V0, getting generators, or coercing in a list or tuple or coercing in 0. Finally, one can express an element as a linear combination of the smith form generators :: sage: M0(V0.0) - (0, 14) + (0, 2) sage: M0(V0.0 + W0.0) # no difference modulo W0 - (0, 14) + (0, 2) sage: M0.linear_combination_of_smith_form_gens([3,20]) (3, 4) sage: 3*M0.0 + 20*M0.1 @@ -93,9 +93,9 @@ sage: x = M0.0 - M0.1; x (1, 15) sage: x.lift() - (0, -2, 1) + (0, 10, 1) sage: M0(vector([1/2,0,0])) - (0, 14) + (0, 2) sage: x.additive_order() 16 @@ -142,9 +142,9 @@ Finitely generated module V/W over Integer Ring with invariants (2, 16) sage: M0(K.0) - (2, 0) + (2, 8) sage: M0(K.1) - (3, 1) + (1, 5) sage: f(M0(K.0)) (0) sage: f(M0(K.1)) @@ -180,7 +180,7 @@ sage: Q.linear_combination_of_smith_form_gens([1,3]) (1, 3) sage: Q(V([1,3,4])) - (0, 11) + (0, 1) sage: Q(W([1,16,0])) (0, 0) sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],QQ) @@ -632,7 +632,7 @@ def _element_constructor_(self, x, check=True): sage: W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) sage: Q = V/W sage: x = Q(V.0-V.1); x # indirect doctest - (0, 3) + (0, 9) sage: type(x) sage: x is Q(x) @@ -931,9 +931,9 @@ def _smith_form(self): sage: Q = V/W sage: Q._smith_form() ( - [ 1 0 0] [1 0 0] [ 1 0 -8] - [ 0 4 0] [0 0 1] [ 0 0 1] - [ 0 0 12], [0 1 0], [ 0 1 0] + [ 1 0 0] [ 1 0 0] [ 1 0 8] + [ 0 4 0] [ 0 1 1] [ 0 0 -1] + [ 0 0 12], [ 0 -1 0], [ 0 1 3] ) """ return self._relative_matrix().smith_form() @@ -1026,7 +1026,7 @@ def smith_form_gens(self): sage: Q.smith_form_gens() ((1, 0), (0, 1)) sage: [x.lift() for x in Q.smith_form_gens()] - [(0, 0, 1), (0, 1, 0)] + [(0, 3, 1), (0, -1, 0)] """ # Get the rightmost transformation in the Smith form _, _, X = self._smith_form() @@ -1069,15 +1069,15 @@ def gens_to_smith(self): sage: D.gens_to_smith() [0 3 0] [0 0 3] - [0 2 0] - [1 0 0] + [0 4 0] + [1 2 0] [0 0 4] sage: T = D.gens_to_smith()*D.smith_to_gens() sage: T - [ 3 0 15 0 0] + [ 3 0 3 0 0] [ 0 33 0 0 3] - [ 2 0 10 0 0] - [ 0 0 0 1 0] + [ 4 0 4 0 0] + [ 2 0 3 1 0] [ 0 44 0 0 4] The matrix `T` now satisfies a certain congruence:: @@ -1120,13 +1120,13 @@ def smith_to_gens(self): [ 0 0 0 1/3 0] [ 0 0 0 0 2/3] sage: D.smith_to_gens() - [ 0 0 0 1 0] - [ 1 0 5 0 0] + [ 0 0 1 1 0] + [ 1 0 1 0 0] [ 0 11 0 0 1] sage: T = D.smith_to_gens()*D.gens_to_smith() sage: T - [ 1 0 0] - [ 0 13 0] + [ 1 6 0] + [ 0 7 0] [ 0 0 37] This matrix satisfies the congruence:: @@ -1148,7 +1148,7 @@ def smith_to_gens(self): of the user defined generators that is x:: sage: x.vector() * D.smith_to_gens() - (2, 33, 10, 1, 3) + (2, 33, 3, 1, 3) """ if self.base_ring() != ZZ: # it is not @@ -1196,7 +1196,7 @@ def gens_vector(self, x, reduce=False): sage: gens = [V(g) for g in gens] sage: D = FGP_with_gens(V, W, gens) sage: D.gens() - ((0, 3, 0), (0, 0, 3), (0, 2, 0), (1, 0, 0), (0, 0, 8)) + ((0, 3, 0), (0, 0, 3), (0, 4, 0), (1, 2, 0), (0, 0, 8)) We create some element of D:: @@ -1209,12 +1209,12 @@ def gens_vector(self, x, reduce=False): sage: v = D.gens_vector(x) sage: v - (2, 9, 10, 1, 33) + (2, 9, 3, 1, 33) The output can be further reduced:: sage: D.gens_vector(x, reduce=True) - (0, 1, 1, 1, 0) + (0, 1, 0, 1, 0) Let us check:: @@ -1262,44 +1262,49 @@ def coordinate_vector(self, x, reduce=False): If x is not in self, it is coerced in:: sage: Q.coordinate_vector(V.0) - (1, 0, -3) + (1, -3, 0) sage: Q.coordinate_vector(Q(V.0)) - (1, 0, -3) + (1, -3, 0) TESTS:: sage: V = span([[1/2,0,0],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) sage: Q = V/W; Q Finitely generated module V/W over Integer Ring with invariants (4, 12) - sage: Q.coordinate_vector(Q.0 - Q.1) - (1, -1) + sage: Q.coordinate_vector(Q.0 - Q.1, reduce=True) + (1, 11) + sage: a, b = Q.coordinate_vector(Q.0 - Q.1) + sage: (a % 4, b % 12) + (1, 11) sage: O, X = Q.optimized() sage: O.V() Free module of degree 3 and rank 2 over Integer Ring User basis matrix: - [0 0 1] - [0 2 0] + [ 0 6 1] + [ 0 -2 0] sage: phi = Q.hom([Q.0, 4*Q.1]) sage: x = Q(V.0); x - (0, 4) + (0, 8) sage: Q.coordinate_vector(x, reduce=True) + (0, 8) + sage: a, b = Q.coordinate_vector(-x, reduce=False) + sage: (a % 4, b % 12) (0, 4) - sage: Q.coordinate_vector(-x, reduce=False) # random - (0, -4) - sage: x == 4*Q.1 + sage: x == 8*Q.1 True sage: x = Q(V.1); x - (0, 1) - sage: Q.coordinate_vector(x) - (0, 1) - sage: x == Q.1 + (0, 11) + sage: a, b = Q.coordinate_vector(x) + sage: (a % 4, b % 12) + (0, 11) + sage: x == -Q.1 True sage: x = Q(V.2); x - (1, 0) + (1, 3) sage: Q.coordinate_vector(x) - (1, 0) - sage: x == Q.0 + (1, 3) + sage: x == Q.0 + 3*Q.1 True """ try: @@ -1407,8 +1412,8 @@ def optimized(self): sage: O.V() Free module of degree 3 and rank 2 over Integer Ring User basis matrix: - [0 0 1] - [0 1 0] + [ 0 3 1] + [ 0 -1 0] sage: O.W() Free module of degree 3 and rank 2 over Integer Ring Echelon basis matrix: @@ -1698,8 +1703,13 @@ def random_element(self, *args, **kwds): sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ); W = V.span([2*V.0+4*V.1, 9*V.0+12*V.1, 4*V.2]) sage: Q = V/W - sage: Q.random_element() - (1, 10) + sage: Q.random_element().parent() is Q + True + sage: Q.cardinality() + 48 + sage: S = set() + sage: while len(S) < 48: + ....: S.add(Q.random_element()) """ return self(self._V.random_element(*args, **kwds)) @@ -1921,7 +1931,23 @@ def random_fgp_module(n, R=ZZ, finite=False): sage: import sage.modules.fg_pid.fgp_module as fgp sage: fgp.random_fgp_module(4) - Finitely generated module V/W over Integer Ring with invariants (4) + Finitely generated module V/W over Integer Ring with invariants (...) + + In most cases the cardinality is small or infinite:: + + sage: for g in (1, 2, 3, +Infinity): + ....: while fgp.random_fgp_module(4).cardinality() != 1: + ....: pass + + One can force a finite module:: + + sage: fgp.random_fgp_module(4, finite=True).is_finite() + True + + Larger finite modules appear:: + + sage: while fgp.random_fgp_module(4, finite=True).cardinality() < 100: + ....: pass """ K = R.fraction_field() V = K**n @@ -1948,9 +1974,18 @@ def random_fgp_morphism_0(*args, **kwds): EXAMPLES:: sage: import sage.modules.fg_pid.fgp_module as fgp - sage: fgp.random_fgp_morphism_0(4) - Morphism from module over Integer Ring with invariants (4,) to module with invariants (4,) that sends the generators to [(0)] + sage: mor = fgp.random_fgp_morphism_0(4) + sage: mor.domain() == mor.codomain() + True + sage: fgp.is_FGP_Module(mor.domain()) + True + + Each generator is sent to a random multiple of itself:: + sage: gens = mor.domain().gens() + sage: im_gens = mor.im_gens() + sage: all(im_gens[i] == sum(im_gens[i])*gens[i] for i in range(len(gens))) + True """ A = random_fgp_module(*args, **kwds) return A.hom([ZZ.random_element() * g for g in A.smith_form_gens()]) diff --git a/src/sage/modules/fg_pid/fgp_morphism.py b/src/sage/modules/fg_pid/fgp_morphism.py index fbbd479c6e9..2bca2484d8a 100644 --- a/src/sage/modules/fg_pid/fgp_morphism.py +++ b/src/sage/modules/fg_pid/fgp_morphism.py @@ -258,20 +258,20 @@ def __call__(self, x): sage: O.V() Free module of degree 3 and rank 2 over Integer Ring User basis matrix: - [0 0 1] - [0 2 0] + [ 0 6 1] + [ 0 -2 0] sage: phi = Q.hom([Q.0, 4*Q.1]) sage: x = Q(V.0); x - (0, 4) - sage: x == 4*Q.1 + (0, 8) + sage: x == 8*Q.1 True sage: x in O.V() False sage: phi(x) - (0, 4) - sage: phi(4*Q.1) - (0, 4) - sage: phi(4*Q.1) == phi(x) + (0, 8) + sage: phi(8*Q.1) + (0, 8) + sage: phi(8*Q.1) == phi(x) True """ from .fgp_module import is_FGP_Module @@ -457,6 +457,8 @@ def lift(self, x): import sage.misc.weak_dict _fgp_homset = sage.misc.weak_dict.WeakValueDictionary() + + def FGP_Homset(X, Y): """ EXAMPLES:: @@ -469,9 +471,11 @@ def FGP_Homset(X, Y): sage: type(Q.Hom(Q)) """ - key = (X,Y) - try: return _fgp_homset[key] - except KeyError: pass + key = (X, Y) + try: + return _fgp_homset[key] + except KeyError: + pass H = FGP_Homset_class(X, Y) # Caching breaks tests in fgp_module. # _fgp_homset[key] = H diff --git a/src/sage/modules/filtered_vector_space.py b/src/sage/modules/filtered_vector_space.py index fa67153e75b..4d0e451806e 100644 --- a/src/sage/modules/filtered_vector_space.py +++ b/src/sage/modules/filtered_vector_space.py @@ -1254,10 +1254,15 @@ def random_deformation(self, epsilon=None): [1 0 0] sage: G = F.random_deformation(1/50); G QQ^3 >= QQ^1 >= QQ^1 >= 0 - sage: G.get_degree(2) - Vector space of degree 3 and dimension 1 over Rational Field - Basis matrix: - [ 1 -15/304 0] + sage: D = G.get_degree(2) + sage: D.degree() + 3 + sage: v = D.basis_matrix()[0] + sage: v[0] + 1 + + sage: while F.random_deformation(1/50).get_degree(2).matrix() == matrix([1, 0, 0]): + ....: pass """ from sage.modules.free_module_element import random_vector R = self.base_ring() diff --git a/src/sage/modules/free_module.py b/src/sage/modules/free_module.py index b4ce5196e48..85dea128651 100644 --- a/src/sage/modules/free_module.py +++ b/src/sage/modules/free_module.py @@ -1570,7 +1570,8 @@ def __iter__(self): return R = self.base_ring() iters = [iter(R) for _ in range(len(G))] - for x in iters: next(x) # put at 0 + for x in iters: + next(x) # put at 0 zero = R(0) v = [zero for _ in range(len(G))] n = 0 @@ -2362,18 +2363,28 @@ def random_element(self, prob=1.0, *args, **kwds): EXAMPLES:: - sage: M = FreeModule(ZZ, 2).span([[1,1]]) - sage: M.random_element() - (-1, -1) - sage: M.random_element() - (2, 2) - sage: M.random_element() - (1, 1) + sage: M = FreeModule(ZZ, 2).span([[1, 1]]) + sage: v = M.random_element() + sage: v.parent() is M + True + sage: v in M + True + + Small entries are likely:: + + sage: for i in [-2, -1, 0, 1, 2]: + ....: while vector([i, i]) != M.random_element(): + ....: pass + + Large entries appear as well:: + + sage: while abs(M.random_element()[0]) < 100: + ....: pass Passes extra positional or keyword arguments through:: - sage: M.random_element(5,10) - (9, 9) + sage: all(i in range(5, 10) for i in M.random_element(1.0, 5, 10)) + True """ rand = current_randstate().python_random().random R = self.base_ring() @@ -3001,9 +3012,11 @@ def intersection(self, other): # standard algorithm for computing intersection of general submodule if self.dimension() <= other.dimension(): - V1 = self; V2 = other + V1 = self + V2 = other else: - V1 = other; V2 = self + V1 = other + V2 = self A1 = V1.basis_matrix() A2 = V2.basis_matrix() S = A1.stack(A2) @@ -3846,13 +3859,15 @@ def intersection(self, other): # standard algorithm for computing intersection of general subspaces if self.dimension() <= other.dimension(): - V1 = self; V2 = other + V1 = self + V2 = other else: - V1 = other; V2 = self + V1 = other + V2 = self A1 = V1.basis_matrix() A2 = V2.basis_matrix() - S = A1.stack(A2) - K = S.kernel() + S = A1.stack(A2) + K = S.kernel() n = int(V1.dimension()) B = [A1.linear_combination_of_rows(v.list()[:n]) for v in K.basis()] return self.ambient_vector_space().submodule(B, check=False) @@ -5226,26 +5241,46 @@ def random_element(self, prob=1.0, *args, **kwds): EXAMPLES:: sage: M = FreeModule(ZZ, 3) - sage: M.random_element() - (-1, 2, 1) - sage: M.random_element() - (-95, -1, -2) - sage: M.random_element() - (-12, 0, 0) + sage: M.random_element().parent() is M + True Passes extra positional or keyword arguments through:: - sage: M.random_element(5,10) - (5, 5, 5) - + sage: all(i in range(5, 10) for i in M.random_element(1.0, 5, 10)) + True :: sage: M = FreeModule(ZZ, 16) - sage: M.random_element() - (-6, 5, 0, 0, -2, 0, 1, -4, -6, 1, -1, 1, 1, -1, 1, -1) - sage: M.random_element(prob=0.3) - (0, 0, 0, 0, -3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, -3) + sage: M.random_element().parent() is M + True + + sage: def add_sample(**kwds): + ....: global total, zeros + ....: v = M.random_element(**kwds) + ....: total += M.rank() + ....: zeros += sum(i == 0 for i in v) + + sage: total = 0 + sage: zeros = 0 + sage: add_sample() + sage: expected = 1/5 + sage: while abs(zeros/total - expected) > 0.01: + ....: add_sample() + + sage: total = 0 + sage: zeros = 0 + sage: add_sample(prob=0.3) + sage: expected = 1/5 * 3/10 + 7/10 + sage: while abs(zeros/total - expected) > 0.01: + ....: add_sample(prob=0.3) + + sage: total = 0 + sage: zeros = 0 + sage: add_sample(prob=0.7) + sage: expected = 1/5 * 7/10 + 3/10 + sage: while abs(zeros/total - expected) > 0.01: + ....: add_sample(prob=0.7) """ rand = current_randstate().python_random().random R = self.base_ring() @@ -5306,6 +5341,22 @@ def gen(self, i=0): v.set_immutable() return v + def _sympy_(self): + """ + Return a SymPy ``ProductSet`` corresponding to ``self``. + + EXAMPLES:: + + sage: sZZ3 = (ZZ^3)._sympy_(); sZZ3 + ProductSet(Integers, Integers, Integers) + sage: (1, 2, 3) in sZZ3 + True + """ + from sympy import ProductSet + from sage.interfaces.sympy import sympy_init + sympy_init() + return ProductSet(*([self.coordinate_ring()] * self.rank())) + ############################################################################### # diff --git a/src/sage/modules/free_module_element.pyx b/src/sage/modules/free_module_element.pyx index 1bbe475695c..b61ed658e58 100644 --- a/src/sage/modules/free_module_element.pyx +++ b/src/sage/modules/free_module_element.pyx @@ -776,58 +776,66 @@ def random_vector(ring, degree=None, *args, **kwds): :meth:`sage.rings.integer_ring.IntegerRing_class.random_element` for several other variants. :: - sage: random_vector(10) - (-8, 2, 0, 0, 1, -1, 2, 1, -95, -1) + sage: random_vector(10).parent() + Ambient free module of rank 10 over the principal ideal domain Integer Ring + sage: random_vector(20).parent() + Ambient free module of rank 20 over the principal ideal domain Integer Ring - sage: sorted(random_vector(20)) - [-12, -6, -4, -4, -2, -2, -2, -1, -1, -1, 0, 0, 0, 0, 0, 1, 1, 1, 4, 5] - - sage: random_vector(ZZ, 20, x=4) - (2, 0, 3, 0, 1, 0, 2, 0, 2, 3, 0, 3, 1, 2, 2, 2, 1, 3, 2, 3) - - sage: random_vector(ZZ, 20, x=-20, y=100) - (43, 47, 89, 31, 56, -20, 23, 52, 13, 53, 49, -12, -2, 94, -1, 95, 60, 83, 28, 63) + sage: v = random_vector(ZZ, 20, x=4) + sage: all(i in range(4) for i in v) + True - sage: random_vector(ZZ, 20, distribution="1/n") - (0, -1, -2, 0, -1, -2, 0, 0, 27, -1, 1, 1, 0, 2, -1, 1, -1, -2, -1, 3) + sage: v = random_vector(ZZ, 20, x=-20, y=100) + sage: all(i in range(-20, 100) for i in v) + True If the ring is not specified, the default is the integers, and parameters for the random distribution may be passed without using keywords. This is a random vector with 20 entries uniformly distributed between -20 and 100. :: - sage: random_vector(20, -20, 100) - (70, 19, 98, 2, -18, 88, 36, 66, 76, 52, 82, 99, 55, -17, 82, -15, 36, 28, 79, 18) + sage: random_vector(20, -20, 100).parent() + Ambient free module of rank 20 over the principal ideal domain Integer Ring Now over the rationals. Note that bounds on the numerator and denominator may be specified. See :meth:`sage.rings.rational_field.RationalField.random_element` for documentation. :: - sage: random_vector(QQ, 10) - (0, -1, -4/3, 2, 0, -13, 2/3, 0, -4/5, -1) + sage: random_vector(QQ, 10).parent() + Vector space of dimension 10 over Rational Field - sage: random_vector(QQ, 10, num_bound = 15, den_bound = 5) - (-12/5, 9/4, -13/3, -1/3, 1, 5/4, 4, 1, -15, 10/3) + sage: v = random_vector(QQ, 10, num_bound=15, den_bound=5) + sage: v.parent() + Vector space of dimension 10 over Rational Field + sage: all(q.numerator() <= 15 and q.denominator() <= 5 for q in v) + True Inexact rings may be used as well. The reals have uniform distributions, with the range `(-1,1)` as the default. More at: :meth:`sage.rings.real_mpfr.RealField_class.random_element` :: - sage: random_vector(RR, 5) - (0.248997268533725, -0.112200126330480, 0.776829203293064, -0.899146461031406, 0.534465018743125) + sage: v = random_vector(RR, 5) + sage: v.parent() + Vector space of dimension 5 over Real Field with 53 bits of precision + sage: all(-1 <= r <= 1 for r in v) + True - sage: random_vector(RR, 5, min = 8, max = 14) - (8.43260944052606, 8.34129413391087, 8.92391495103829, 11.5784799413416, 11.0973561568002) + sage: v = random_vector(RR, 5, min=8, max=14) + sage: v.parent() + Vector space of dimension 5 over Real Field with 53 bits of precision + sage: all(8 <= r <= 14 for r in v) + True Any ring with a ``random_element()`` method may be used. :: sage: F = FiniteField(23) sage: hasattr(F, 'random_element') True - sage: random_vector(F, 10) - (21, 6, 5, 2, 6, 2, 18, 9, 9, 7) + sage: v = random_vector(F, 10) + sage: v.parent() + Vector space of dimension 10 over Finite Field of size 23 The default implementation is a dense representation, equivalent to setting ``sparse=False``. :: @@ -840,6 +848,25 @@ def random_vector(ring, degree=None, *args, **kwds): sage: w.is_sparse() True + The elements are chosen using the ring's ``random_element`` method:: + + sage: from sage.misc.randstate import current_randstate + sage: seed = current_randstate().seed() + sage: set_random_seed(seed) + sage: v1 = random_vector(ZZ, 20, distribution="1/n") + sage: v2 = random_vector(ZZ, 15, x=-1000, y=1000) + sage: v3 = random_vector(QQ, 10) + sage: v4 = random_vector(FiniteField(17), 10) + sage: v5 = random_vector(RR, 10) + sage: set_random_seed(seed) + sage: w1 = vector(ZZ.random_element(distribution="1/n") for _ in range(20)) + sage: w2 = vector(ZZ.random_element(x=-1000, y=1000) for _ in range(15)) + sage: w3 = vector(QQ.random_element() for _ in range(10)) + sage: w4 = vector(FiniteField(17).random_element() for _ in range(10)) + sage: w5 = vector(RR.random_element() for _ in range(10)) + sage: [v1, v2, v3, v4, v5] == [w1, w2, w3, w4, w5] + True + Inputs get checked before constructing the vector. :: sage: random_vector('junk') @@ -912,7 +939,7 @@ cdef class FreeModuleElement(Vector): # abstract base class sage: P. = ZZ[] sage: v = vector(P, 3, [x^2 + 2, 2*x + 1, -2*x^2 + 4*x]) sage: giac(v) - [x^2+2,2*x+1,-2*x^2+4*x] + [sageVARx^2+2,2*sageVARx+1,-2*sageVARx^2+4*sageVARx] """ return self.list() @@ -2570,7 +2597,7 @@ cdef class FreeModuleElement(Vector): # abstract base class sage: u.dot_product(w) 0 - The cross product is defined for degree seven vectors as well: + The cross product is defined for degree seven vectors as well: see :wikipedia:`Cross_product`. The 3-D cross product is achieved using the quaternions, whereas the 7-D cross product is achieved using the octonions. :: @@ -3543,6 +3570,72 @@ cdef class FreeModuleElement(Vector): # abstract base class """ return '{' + ', '.join(x._mathematica_init_() for x in self.list()) + '}' + def _sympy_(self): + """ + Return a SymPy column vector (matrix) corresponding to ``self``. + + OUTPUT: + + - An instance of either an ``ImmutableMatrix`` or ``ImmutableSparseMatrix``, + regardless of whether ``self`` is mutable or not. + + EXAMPLES:: + + sage: v = vector([1, 2, 3]); v + (1, 2, 3) + sage: sv = v._sympy_(); sv + Matrix([ + [1], + [2], + [3]]) + sage: type(sv) + + + sage: w = vector({1: 1, 5: -1}, sparse=True) + sage: sw = w._sympy_(); sw + Matrix([ + [ 0], + [ 1], + [ 0], + [ 0], + [ 0], + [-1]]) + sage: type(sw) + + + If ``self`` was immutable, then converting the result to Sage gives + back ``self``:: + + sage: immv = vector([1, 2, 3], immutable=True) + sage: immv._sympy_()._sage_() is immv + True + + If ``self`` was mutable, then converting back to Sage creates a new + matrix (column vector):: + + sage: sv._sage_() + [1] + [2] + [3] + sage: sv._sage_() is v + False + sage: sv._sage_() == v + False + """ + from sage.interfaces.sympy import sympy_init + sympy_init() + from sympy.matrices import ImmutableMatrix, ImmutableSparseMatrix + if self.is_sparse(): + matrix = ImmutableSparseMatrix(self._degree, 1, + {(i, 0): v + for i, v in self.dict(copy=False).items()}) + else: + matrix = ImmutableMatrix(self._degree, 1, + self.list(copy=False)) + if self.is_immutable(): + matrix._sage_object = self + return matrix + def nonzero_positions(self): """ Return the sorted list of integers ``i`` such that ``self[i] != 0``. diff --git a/src/sage/modules/free_module_homspace.py b/src/sage/modules/free_module_homspace.py index 77cb4ff9891..e296c40e087 100644 --- a/src/sage/modules/free_module_homspace.py +++ b/src/sage/modules/free_module_homspace.py @@ -171,6 +171,26 @@ def __call__(self, A, check=True): Codomain: Free module of degree 3 and rank 3 over Integer Ring Echelon ... + The following tests the bug fixed in :trac:`31818`. If there is no + coercion between base rings, one can only define the zero morphism, + as morphism of additive groups. Before one could for example use an + integer matrix to define a morphism from the rational numbers to the + integers. :: + + sage: V = QQ^2; W = ZZ^2; m = identity_matrix(2) + sage: H = V.Hom(W); H(m) + Traceback (most recent call last): + ... + TypeError: nontrivial morphisms require a coercion map from the base ring of the domain to the base ring of the codomain + sage: n = zero_matrix(2); + sage: h = H(n); h + Free module morphism defined by the matrix + [0 0] + [0 0] + Domain: Vector space of dimension 2 over Rational Field + Codomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring + sage: [h(v) for v in V.gens()] + [(0, 0), (0, 0)] """ from . import free_module_morphism if not is_Matrix(A): @@ -187,6 +207,8 @@ def __call__(self, A, check=True): # Let us hope that FreeModuleMorphism knows to handle # that case pass + if not self.codomain().base_ring().has_coerce_map_from(self.domain().base_ring()) and not A.is_zero(): + raise TypeError("nontrivial morphisms require a coercion map from the base ring of the domain to the base ring of the codomain") return free_module_morphism.FreeModuleMorphism(self, A) @cached_method diff --git a/src/sage/modules/free_module_integer.py b/src/sage/modules/free_module_integer.py index 443083a3e11..a3b201657a6 100644 --- a/src/sage/modules/free_module_integer.py +++ b/src/sage/modules/free_module_integer.py @@ -254,14 +254,10 @@ def __init__(self, ambient, basis, check=True, echelonize=False, [ 1 -2 0] [ 2 2 1] - sage: IntegerLattice(random_matrix(ZZ, 5, 5, x=-2^20, y=2^20)) - Free module of degree 5 and rank 5 over Integer Ring - User basis matrix: - [ -7945 -381123 85872 -225065 12924] - [-158254 120252 189195 -262144 -345323] - [ 232388 -49556 306585 -31340 401528] - [-353460 213748 310673 158140 172810] - [-287787 333937 -145713 -482137 186529] + sage: M = random_matrix(ZZ, 5, 5, x=-2^20, y=2^20) + sage: L = IntegerLattice(M) + sage: M.row_space() == L.matrix().row_space() + True sage: K. = NumberField(x^8+1) sage: O = K.ring_of_integers() @@ -307,32 +303,16 @@ def reduced_basis(self): EXAMPLES:: sage: from sage.modules.free_module_integer import IntegerLattice - sage: L = IntegerLattice(random_matrix(ZZ, 10, 10), lll_reduce=False) - sage: L.reduced_basis - [ -8 2 0 0 1 -1 2 1 -95 -1] - [ -2 -12 0 0 1 -1 1 -1 -2 -1] - [ 4 -4 -6 5 0 0 -2 0 1 -4] - [ -6 1 -1 1 1 -1 1 -1 -3 1] - [ 1 0 0 -3 2 -2 0 -2 1 0] - [ -1 1 0 0 1 -1 4 -1 1 -1] - [ 14 1 -5 4 -1 0 2 4 1 1] - [ -2 -1 0 4 -3 1 -5 0 -2 -1] - [ -9 -1 -1 3 2 1 -1 1 -2 1] - [ -1 2 -7 1 0 2 3 -1955 -22 -1] - - sage: _ = L.LLL() - sage: L.reduced_basis - [ 1 0 0 -3 2 -2 0 -2 1 0] - [ -1 1 0 0 1 -1 4 -1 1 -1] - [ -2 0 0 1 0 -2 -1 -3 0 -2] - [ -2 -2 0 -1 3 0 -2 0 2 0] - [ 1 1 1 2 3 -2 -2 0 3 1] - [ -4 1 -1 0 1 1 2 2 -3 3] - [ 1 -3 -7 2 3 -1 0 0 -1 -1] - [ 1 -9 1 3 1 -3 1 -1 -1 0] - [ 8 5 19 3 27 6 -3 8 -25 -22] - [ 172 -25 57 248 261 793 76 -839 -41 376] + sage: M = random_matrix(ZZ, 10, 10) + sage: while M.rank() < 10: + ....: M = random_matrix(ZZ, 10, 10) + sage: L = IntegerLattice(M, lll_reduce=False) + sage: L.reduced_basis == M + True + sage: LLL = L.LLL() + sage: LLL == L.reduced_basis or bool(LLL[0].norm() >= M[0].norm()) + True """ return self._reduced_basis @@ -376,36 +356,19 @@ def LLL(self, *args, **kwds): sage: from sage.modules.free_module_integer import IntegerLattice sage: A = random_matrix(ZZ, 10, 10, x=-2000, y=2000) + sage: while A.rank() < 10: + ....: A = random_matrix(ZZ, 10, 10) sage: L = IntegerLattice(A, lll_reduce=False); L Free module of degree 10 and rank 10 over Integer Ring User basis matrix: - [ -645 -1037 -1775 -1619 1721 -1434 1766 1701 1669 1534] - [ 1303 960 1998 -1838 1683 -1332 149 327 -849 -1562] - [-1113 -1366 1379 669 54 1214 -1750 -605 -1566 1626] - [-1367 1651 926 1731 -913 627 669 -1437 -132 1712] - [ -549 1327 -1353 68 1479 -1803 -456 1090 -606 -317] - [ -221 -1920 -1361 1695 1139 111 -1792 1925 -656 1992] - [-1934 -29 88 890 1859 1820 -1912 -1614 -1724 1606] - [ -590 -1380 1768 774 656 760 -746 -849 1977 -1576] - [ 312 -242 -1732 1594 -439 -1069 458 -1195 1715 35] - [ 391 1229 -1815 607 -413 -860 1408 1656 1651 -628] - sage: min(v.norm().n() for v in L.reduced_basis) - 3346.57... - - sage: L.LLL() - [ -888 53 -274 243 -19 431 710 -83 928 347] - [ 448 -330 370 -511 242 -584 -8 1220 502 183] - [ -524 -460 402 1338 -247 -279 -1038 -28 -159 -794] - [ 166 -190 -162 1033 -340 -77 -1052 1134 -843 651] - [ -47 -1394 1076 -132 854 -151 297 -396 -580 -220] - [-1064 373 -706 601 -587 -1394 424 796 -22 -133] - [-1126 398 565 -1418 -446 -890 -237 -378 252 247] - [ -339 799 295 800 425 -605 -730 -1160 808 666] - [ 755 -1206 -918 -192 -1063 -37 -525 -75 338 400] - [ 382 -199 -1839 -482 984 -15 -695 136 682 563] - sage: L.reduced_basis[0].norm().n() - 1613.74... - + ... + sage: L.reduced_basis == A + True + sage: old_min = min(v.norm().n() for v in L.reduced_basis) + sage: _ = L.LLL() + sage: new_min = L.reduced_basis[0].norm().n() + sage: new_min <= old_min + True """ basis = self.reduced_basis basis = [v for v in basis.LLL(*args, **kwds) if v] diff --git a/src/sage/modules/free_quadratic_module_integer_symmetric.py b/src/sage/modules/free_quadratic_module_integer_symmetric.py index 7de14e8d774..d09578682ea 100644 --- a/src/sage/modules/free_quadratic_module_integer_symmetric.py +++ b/src/sage/modules/free_quadratic_module_integer_symmetric.py @@ -549,8 +549,8 @@ def IntegralLatticeGluing(Lattices, glue, return_embeddings=False): sage: [L, phi] = IntegralLatticeGluing([L1, L2], [[f1, g1], [f2, 2 * g2]], True) sage: phi[0] Free module morphism defined by the matrix - [ 2 2 -1 -2] - [ 0 2 0 -1] + [ 2 2 -2 -1] + [ 0 2 -1 0] Domain: Lattice of degree 4 and rank 2 over Integer Ring Basis matrix: [1 1 0 0] @@ -563,7 +563,7 @@ def IntegralLatticeGluing(Lattices, glue, return_embeddings=False): Codomain: Lattice of degree 10 and rank 4 over Integer Ring Basis matrix: [ 1/2 0 -1/2 0 0 1/2 0 0 1/2 1/2] - [ 0 1/2 1/2 0 0 0 0 0 1/2 1/2] + [ 0 1/2 1/2 0 0 1/2 0 0 0 0] [ 0 0 0 0 0 1 0 0 0 0] [ 0 0 0 0 0 0 0 0 1 1] Inner product matrix: @@ -784,7 +784,7 @@ def discriminant_group(self, s=0): Finite quadratic module over Integer Ring with invariants (2, 10) Gram matrix of the quadratic form with values in Q/2Z: [ 1 1/2] - [1/2 9/5] + [1/2 1/5] sage: L.discriminant_group(2) Finite quadratic module over Integer Ring with invariants (2, 2) Gram matrix of the quadratic form with values in Q/2Z: @@ -793,7 +793,7 @@ def discriminant_group(self, s=0): sage: L.discriminant_group(5) Finite quadratic module over Integer Ring with invariants (5,) Gram matrix of the quadratic form with values in Q/2Z: - [6/5] + [4/5] TESTS:: @@ -1463,8 +1463,8 @@ def local_modification(M, G, p, check=True): sage: local_modification(M, L.gram_matrix(), 2) Lattice of degree 4 and rank 4 over Integer Ring Basis matrix: - [1/3 0 1/3 2/3] - [ 0 1/3 1/3 2/3] + [1/3 0 2/3 2/3] + [ 0 1/3 0 2/3] [ 0 0 1 0] [ 0 0 0 1] Inner product matrix: diff --git a/src/sage/modules/multi_filtered_vector_space.py b/src/sage/modules/multi_filtered_vector_space.py index 05973ccf7ed..0d0e4b316e7 100644 --- a/src/sage/modules/multi_filtered_vector_space.py +++ b/src/sage/modules/multi_filtered_vector_space.py @@ -717,10 +717,16 @@ def random_deformation(self, epsilon=None): Vector space of degree 2 and dimension 1 over Rational Field Basis matrix: [1 0] - sage: V.random_deformation(1/100).get_degree('b',1) - Vector space of degree 2 and dimension 1 over Rational Field - Basis matrix: - [ 1 8/1197] + sage: D = V.random_deformation(1/100).get_degree('b',1) + sage: D.degree() + 2 + sage: D.dimension() + 1 + sage: D.matrix()[0, 0] + 1 + + sage: while V.random_deformation(1/100).get_degree('b',1).matrix() == matrix([1, 0]): + ....: pass """ filtrations = {key: value.random_deformation(epsilon) for key, value in self._filt.items()} diff --git a/src/sage/modules/torsion_quadratic_module.py b/src/sage/modules/torsion_quadratic_module.py index 27a019554e4..f922c0c2787 100644 --- a/src/sage/modules/torsion_quadratic_module.py +++ b/src/sage/modules/torsion_quadratic_module.py @@ -62,7 +62,7 @@ def TorsionQuadraticForm(q): TESTS:: - sage: TorsionQuadraticForm(matrix.diagonal([3/8,3/8,3/4])) + sage: TorsionQuadraticForm(matrix.diagonal([3/4,3/8,3/8])) Finite quadratic module over Integer Ring with invariants (4, 8, 8) Gram matrix of the quadratic form with values in Q/2Z: [3/4 0 0] @@ -1030,10 +1030,10 @@ def normal_form(self, partial=False): sage: T Finite quadratic module over Integer Ring with invariants (6, 6, 12, 12) Gram matrix of the quadratic form with values in Q/(1/3)Z: - [1/18 5/36 0 0] - [5/36 1/18 5/36 5/36] - [ 0 5/36 1/36 1/72] - [ 0 5/36 1/72 1/36] + [ 1/18 1/12 5/36 1/36] + [ 1/12 1/6 1/36 1/9] + [ 5/36 1/36 1/36 11/72] + [ 1/36 1/9 11/72 1/36] sage: T.normal_form() Finite quadratic module over Integer Ring with invariants (6, 6, 12, 12) Gram matrix of the quadratic form with values in Q/(1/3)Z: diff --git a/src/sage/modules/vector_integer_dense.pyx b/src/sage/modules/vector_integer_dense.pyx index 114c8868264..9b24aecd965 100644 --- a/src/sage/modules/vector_integer_dense.pyx +++ b/src/sage/modules/vector_integer_dense.pyx @@ -316,10 +316,9 @@ cdef class Vector_integer_dense(free_module_element.FreeModuleElement): sage: A = random_matrix(ZZ,1,3) sage: v = A.row(0) - sage: vs = singular(v); vs - -8, - 2, - 0 + sage: vs = singular(v) + sage: vs._repr_() == '{},\n{},\n{}'.format(*v) + True sage: vs.type() 'intvec' """ diff --git a/src/sage/modules/vector_mod2_dense.pyx b/src/sage/modules/vector_mod2_dense.pyx index d9bd624078a..3c08d14f2d7 100644 --- a/src/sage/modules/vector_mod2_dense.pyx +++ b/src/sage/modules/vector_mod2_dense.pyx @@ -14,12 +14,13 @@ AUTHOR: EXAMPLES:: sage: VS = GF(2)^3 - sage: e = VS.random_element(); e - (1, 0, 0) - sage: f = VS.random_element(); f - (0, 1, 1) - sage: e + f - (1, 1, 1) + sage: e = VS.random_element() + sage: e.parent() is VS + True + sage: S = set(vector(v, immutable=True) for v in VS) + sage: S1 = set() + sage: while S != S1: + ....: S1.add(vector(VS.random_element(), immutable=True)) TESTS:: @@ -94,10 +95,11 @@ cdef class Vector_mod2_dense(free_module_element.FreeModuleElement): sage: w = copy(v) sage: w == v True - sage: v[:10] - (1, 0, 0, 0, 1, 1, 1, 0, 0, 1) - sage: w[:10] - (1, 0, 0, 0, 1, 1, 1, 0, 0, 1) + sage: v[:10] == w[:10] + True + sage: v[5] += 1 + sage: v == w + False """ cdef Vector_mod2_dense y = self._new_c() if self._degree: @@ -274,11 +276,12 @@ cdef class Vector_mod2_dense(free_module_element.FreeModuleElement): EXAMPLES:: sage: VS = VectorSpace(GF(2),4) - sage: v = VS.random_element(); v - (1, 0, 0, 0) - sage: v[0] = 0; v - (0, 0, 0, 0) - sage: v[1:3] = [1, 1]; v + sage: v = VS.random_element() + sage: v[0] = 0; v[0] + 0 + sage: v[1:3] = [1, 1]; v[1:3] + (1, 1) + sage: v[3] = 0; v (0, 1, 1, 0) sage: v[4] = 0 Traceback (most recent call last): @@ -398,12 +401,11 @@ cdef class Vector_mod2_dense(free_module_element.FreeModuleElement): EXAMPLES:: sage: VS = VectorSpace(GF(2),10) - sage: e = VS.random_element(); e - (1, 0, 0, 0, 1, 1, 1, 0, 0, 1) - sage: f = VS.random_element(); f - (1, 1, 0, 1, 1, 1, 0, 0, 0, 1) - sage: e.pairwise_product(f) #indirect doctest - (1, 0, 0, 0, 1, 1, 0, 0, 0, 1) + sage: e = VS.random_element() + sage: f = VS.random_element() + sage: g = e.pairwise_product(f) #indirect doctest + sage: all(g[i] == e[i]*f[i] for i in range(10)) + True """ cdef Vector_mod2_dense z, r r = right @@ -418,26 +420,24 @@ cdef class Vector_mod2_dense(free_module_element.FreeModuleElement): EXAMPLES:: sage: VS = VectorSpace(GF(2),10) - sage: e = VS.random_element(); e - (1, 0, 0, 0, 1, 1, 1, 0, 0, 1) + sage: e = VS.random_element() sage: 0 * e (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - sage: 1 * e - (1, 0, 0, 0, 1, 1, 1, 0, 0, 1) - sage: 2 * e #indirect doctest + sage: 1 * e == e + True + sage: 2 * e # indirect doctest (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) :: - sage: VS = VectorSpace(GF(2),10) - sage: e = VS.random_element(); e - (1, 1, 0, 1, 1, 1, 0, 0, 0, 1) - sage: e * 0 #indirect doctest - (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - sage: e * 1 - (1, 1, 0, 1, 1, 1, 0, 0, 0, 1) - sage: e * 2 - (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + sage: VS = VectorSpace(GF(2), 100) + sage: e = VS.random_element() + sage: e * 0 == 0 # indirect doctest + True + sage: e * 1 == e + True + sage: e * 2 == 0 + True """ cdef IntegerMod_int a @@ -467,11 +467,11 @@ cdef class Vector_mod2_dense(free_module_element.FreeModuleElement): EXAMPLES:: - sage: VS = VectorSpace(GF(2),10) - sage: e = VS.random_element(); e - (1, 0, 0, 0, 1, 1, 1, 0, 0, 1) - sage: e.list() - [1, 0, 0, 0, 1, 1, 1, 0, 0, 1] + sage: VS = VectorSpace(GF(2), 10) + sage: entries = [GF(2).random_element() for _ in range(10)] + sage: e = VS(entries) + sage: e.list() == entries + True """ cdef Py_ssize_t d = self._degree cdef Py_ssize_t i diff --git a/src/sage/modules/with_basis/representation.py b/src/sage/modules/with_basis/representation.py index 3057de93f6c..27fffed163d 100644 --- a/src/sage/modules/with_basis/representation.py +++ b/src/sage/modules/with_basis/representation.py @@ -152,7 +152,7 @@ class Representation(Representation_abstract): - :wikipedia:`Group_representation` """ - def __init__(self, semigroup, module, on_basis, side="left"): + def __init__(self, semigroup, module, on_basis, side="left", **kwargs): """ Initialize ``self``. @@ -164,18 +164,66 @@ def __init__(self, semigroup, module, on_basis, side="left"): sage: on_basis = lambda g,m: M.term(m, g.sign()) sage: R = Representation(G, M, on_basis) sage: R._test_representation() - """ + + sage: G = CyclicPermutationGroup(3) + sage: M = algebras.Exterior(QQ, 'x', 3) + sage: from sage.modules.with_basis.representation import Representation + sage: on_basis = lambda g,m: M.prod([M.monomial((g(j+1)-1,)) for j in m]) #cyclically permute generators + sage: from sage.categories.algebras import Algebras + sage: R = Representation(G, M, on_basis, category=Algebras(QQ).WithBasis().FiniteDimensional()) + sage: r = R.an_element(); r + 1 + 2*x0 + x0*x1 + 3*x1 + sage: r*r + 1 + 4*x0 + 2*x0*x1 + 6*x1 + sage: x0, x1, x2 = M.gens() + sage: s = R(x0*x1) + sage: g = G.an_element() + sage: g*s + x1*x2 + sage: g*R(x1*x2) + -x0*x2 + sage: g*r + 1 + 2*x1 + x1*x2 + 3*x2 + sage: g^2*r + 1 + 3*x0 - x0*x2 + 2*x2 + + sage: G = SymmetricGroup(4) + sage: A = SymmetricGroup(4).algebra(QQ) + sage: from sage.categories.algebras import Algebras + sage: from sage.modules.with_basis.representation import Representation + sage: action = lambda g,x: A.monomial(g*x) + sage: category = Algebras(QQ).WithBasis().FiniteDimensional() + sage: R = Representation(G, A, action, 'left', category=category) + sage: r = R.an_element(); r + () + (2,3,4) + 2*(1,3)(2,4) + 3*(1,4)(2,3) + sage: r^2 + 14*() + 2*(2,3,4) + (2,4,3) + 12*(1,2)(3,4) + 3*(1,2,4) + 2*(1,3,2) + 4*(1,3)(2,4) + 5*(1,4,3) + 6*(1,4)(2,3) + sage: g = G.an_element(); g + (2,3,4) + sage: g*r + (2,3,4) + (2,4,3) + 2*(1,3,2) + 3*(1,4,3) + """ + try: + self.product_on_basis = module.product_on_basis + except AttributeError: + pass + + category = kwargs.pop('category', Modules(module.base_ring()).WithBasis()) + if side not in ["left", "right"]: raise ValueError('side must be "left" or "right"') + self._left_repr = (side == "left") self._on_basis = on_basis self._module = module + indices = module.basis().keys() - cat = Modules(module.base_ring()).WithBasis() + if 'FiniteDimensional' in module.category().axioms(): - cat = cat.FiniteDimensional() + category = category.FiniteDimensional() + Representation_abstract.__init__(self, semigroup, module.base_ring(), indices, - category=cat, **module.print_options()) + category=category, **module.print_options()) def _test_representation(self, **options): """ @@ -273,6 +321,51 @@ def _element_constructor_(self, x): return self._from_dict(x.monomial_coefficients(copy=False), remove_zeros=False) return super(Representation, self)._element_constructor_(x) + def product_by_coercion(self, left, right): + """ + Return the product of ``left`` and ``right`` by passing to ``self._module`` + and then building a new element of ``self``. + + EXAMPLES:: + + sage: G = groups.permutation.KleinFour() + sage: E = algebras.Exterior(QQ,'e',4) + sage: on_basis = lambda g,m: E.monomial(m) # the trivial representation + sage: from sage.modules.with_basis.representation import Representation + sage: R = Representation(G, E, on_basis) + sage: r = R.an_element(); r + 1 + 2*e0 + 3*e1 + e1*e2 + sage: g = G.an_element(); + sage: g*r == r + True + sage: r*r + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for *: + 'Representation of The Klein 4 group of order 4, as a permutation + group indexed by Subsets of {0, 1, 2, 3} over Rational Field' and + 'Representation of The Klein 4 group of order 4, as a permutation + group indexed by Subsets of {0, 1, 2, 3} over Rational Field' + + sage: from sage.categories.algebras import Algebras + sage: category = Algebras(QQ).FiniteDimensional().WithBasis() + sage: T = Representation(G, E, on_basis, category=category) + sage: t = T.an_element(); t + 1 + 2*e0 + 3*e1 + e1*e2 + sage: g*t == t + True + sage: t*t + 1 + 4*e0 + 4*e0*e1*e2 + 6*e1 + 2*e1*e2 + + """ + M = self._module + + # Multiply in self._module + p = M._from_dict(left._monomial_coefficients, False, False) * M._from_dict(right._monomial_coefficients, False, False) + + # Convert from a term in self._module to a term in self + return self._from_dict(p.monomial_coefficients(copy=False), False, False) + def side(self): """ Return whether ``self`` is a left or a right representation. @@ -293,6 +386,7 @@ def side(self): """ return "left" if self._left_repr else "right" + class Element(CombinatorialFreeModule.Element): def _acted_upon_(self, scalar, self_on_left=False): """ @@ -369,6 +463,7 @@ def _acted_upon_(self, scalar, self_on_left=False): ret += P.linear_combination(((P._on_basis(ms, m), cs*c) for m,c in self), not self_on_left) return ret + return CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) _rmul_ = _lmul_ = _acted_upon_ diff --git a/src/sage/numerical/backends/glpk_backend.pyx b/src/sage/numerical/backends/glpk_backend.pyx index d490818ce75..26f037647ed 100644 --- a/src/sage/numerical/backends/glpk_backend.pyx +++ b/src/sage/numerical/backends/glpk_backend.pyx @@ -19,17 +19,17 @@ AUTHORS: # http://www.gnu.org/licenses/ #***************************************************************************** +from libc.float cimport DBL_MAX +from libc.limits cimport INT_MAX from cysignals.memory cimport sig_malloc, sig_free from cysignals.signals cimport sig_on, sig_off +from memory_allocator cimport MemoryAllocator from sage.cpython.string cimport char_to_str, str_to_bytes from sage.cpython.string import FS_ENCODING -from sage.ext.memory_allocator cimport MemoryAllocator from sage.numerical.mip import MIPSolverException from sage.libs.glpk.constants cimport * from sage.libs.glpk.lp cimport * -from libc.float cimport DBL_MAX -from libc.limits cimport INT_MAX cdef class GLPKBackend(GenericBackend): @@ -2790,7 +2790,7 @@ cdef class GLPKBackend(GenericBackend): raise ValueError("The variable's index j must satisfy 0 <= j < number_of_variables") glp_set_col_stat(self.lp, j+1, stat) - + cpdef int warm_up(self): r""" Warm up the basis using current statuses assigned to rows and cols. @@ -2800,7 +2800,7 @@ cdef class GLPKBackend(GenericBackend): - Returns the warming up status * 0 The operation has been successfully performed. - * GLP_EBADB The basis matrix is invalid. + * GLP_EBADB The basis matrix is invalid. * GLP_ESING The basis matrix is singular within the working precision. * GLP_ECOND The basis matrix is ill-conditioned. diff --git a/src/sage/numerical/gauss_legendre.pyx b/src/sage/numerical/gauss_legendre.pyx index fba5a931d2e..1234405acaa 100644 --- a/src/sage/numerical/gauss_legendre.pyx +++ b/src/sage/numerical/gauss_legendre.pyx @@ -1,18 +1,31 @@ r""" -Gauss-Legendre integration for vector-valued functions +Gauss-Legendre Integration for Vector-Valued Functions Routine to perform Gauss-Legendre integration for vector-functions. +EXAMPLES: + +We verify that `\int_0^1 n x^{n-1} \, dx = 1` for `n=1, \dots, 4`:: + + sage: from sage.numerical.gauss_legendre import integrate_vector + sage: prec = 100 + sage: K = RealField(prec) + sage: N = 4 + sage: V = VectorSpace(K, N) + sage: f = lambda x: V([(n+1)*x^n for n in range(N)]) + sage: I = integrate_vector(f, prec) + sage: max([c.abs() for c in I-V(N*[1])]) + 0.00000000000000000000000000000 + AUTHORS: - Nils Bruin (2017-06-06): initial version + - Linden Disney-Hogg (2021-06-17): documentation and integrate_vector method changes -EXAMPLES:: +.. NOTE:: -NOTE: - -The code here is directly based on mpmath (see http://mpmath.org), but has a highly -optimized routine to compute the nodes. + The code here is directly based on mpmath (see http://mpmath.org), but has a highly + optimized routine to compute the nodes. """ # **************************************************************************** @@ -39,19 +52,24 @@ from sage.misc.cachefunc import cached_function from sage.rings.real_mpfr cimport RealNumber, RealField_class @cached_function -def nodes(degree,prec): +def nodes(degree, prec): r""" - Compute the integration nodes and weights for the Gauss-Legendre quadrature scheme. + Compute the integration nodes and weights for the Gauss-Legendre quadrature + scheme + + We use the recurrence relations for Legendre polynomials to compute their values. + This is a version of the algorithm that in [Neu2018]_ is called the REC algorithm. INPUT: - ``degree`` -- integer. The number of nodes. Must be 3 or even. - - ``prec`` -- integer (minimal value 53). Binary precision with which the nodes and weights are computed. + - ``prec`` -- integer (minimal value 53). Binary precision with which the + nodes and weights are computed. OUTPUT: - A list of (node,weight) pairs. + A list of (node, weight) pairs. EXAMPLES: @@ -61,12 +79,20 @@ def nodes(degree,prec): from this routine are actually more accurate than what the values the closed formula produces):: sage: from sage.numerical.gauss_legendre import nodes - sage: L1=nodes(24,53) - sage: P=RR['x'](sage.functions.orthogonal_polys.legendre_P(24,x)) - sage: Pdif=P.diff() - sage: L2=[( (r+1)/2,1/(1-r^2)/Pdif(r)^2) for r,_ in RR['x'](P).roots()] - sage: all((a[0]-b[0]).abs() < 10^-15 and (a[1]-b[1]).abs() < 10^-9 for a,b in zip(L1,L2)) + sage: L1 = nodes(24, 53) + sage: P = RR['x'](sage.functions.orthogonal_polys.legendre_P(24, x)) + sage: Pdif = P.diff() + sage: L2 = [((r + 1)/2, 1/(1 - r^2)/Pdif(r)^2) + ....: for r, _ in RR['x'](P).roots()] + sage: all((a[0] - b[0]).abs() < 1e-15 and (a[1] - b[1]).abs() < 1e-9 + ....: for a, b in zip(L1, L2)) True + + .. TODO:: + + It may be worth testing if using the Arb algorithm for finding the + nodes and weights in ``arb/acb_calc/integrate_gl_auto_deg.c`` has better + performance. """ cdef long j,j1,n cdef RealNumber r,t1,t2,t3,t4,a,w @@ -121,7 +147,7 @@ def nodes(degree,prec): mpfr_clear(v) return nodes -def estimate_error(results,prec,epsilon): +def estimate_error(results, prec, epsilon): r""" Routine to estimate the error in a list of quadrature approximations. @@ -150,11 +176,11 @@ def estimate_error(results,prec,epsilon): sage: from sage.numerical.gauss_legendre import estimate_error sage: prec = 200 sage: K = RealField(prec) - sage: V = VectorSpace(K,2) - sage: a = V([1,-1]) - sage: b = V([1,1/2]) - sage: L = [a+2^(-2^i)*b for i in [0..5]] - sage: estimate_error(L,prec,K(2^(-prec))) + sage: V = VectorSpace(K, 2) + sage: a = V([1, -1]) + sage: b = V([1, 1/2]) + sage: L = [a + 2^(-2^i)*b for i in [0..5]] + sage: estimate_error(L, prec, K(2^(-prec))) 2.328235...e-10 """ if len(results)==2: @@ -174,20 +200,20 @@ def estimate_error(results,prec,epsilon): e.append(D4.exp()) return max(e) -def integrate_vector(f,prec,epsilon=None): +def integrate_vector(f, prec, epsilon=None): r""" Integrate a one-argument vector-valued function numerically using Gauss-Legendre. This function uses the Gauss-Legendre quadrature scheme to approximate - the integral of f(t) for t=0..1. + the integral `\int_0^1 f(t) \, dt`. INPUT: - - `f` -- callable. Vector-valued integrand. + - ``f`` -- callable. Vector-valued integrand. - - `prec` -- integer. Binary precision to be used. + - ``prec`` -- integer. Binary precision to be used. - - `epsilon` -- Multiprecision float. Target error bound. + - ``epsilon`` -- multiprecision float (default: `2^{(-\text{prec}+3)}`). Target error bound. OUTPUT: @@ -196,38 +222,39 @@ def integrate_vector(f,prec,epsilon=None): EXAMPLES:: sage: from sage.numerical.gauss_legendre import integrate_vector - sage: prec=200 - sage: K=RealField(prec) - sage: V=VectorSpace(K,2) - sage: epsilon=K(2^(-prec+4)) - sage: f=lambda t:V((1+t^2,1/(1+t^2))) - sage: I=integrate_vector(f,prec,epsilon) - sage: J=V((4/3,pi/4)) - sage: max(c.abs() for c in (I-J)) < epsilon + sage: prec = 200 + sage: K = RealField(prec) + sage: V = VectorSpace(K, 2) + sage: epsilon = K(2^(-prec + 4)) + sage: f = lambda t:V((1 + t^2, 1/(1 + t^2))) + sage: I = integrate_vector(f, prec, epsilon=epsilon) + sage: J = V((4/3, pi/4)) + sage: max(c.abs() for c in (I - J)) < epsilon True We can also use complex-valued integrands:: - sage: prec=200 - sage: Kreal=RealField(prec) - sage: K=ComplexField(prec) - sage: V=VectorSpace(K,2) - sage: epsilon=Kreal(2^(-prec+4)) - sage: f=lambda t: V((t,K(exp(2*pi*t*K.0)))) - sage: I=integrate_vector(f,prec,epsilon) - sage: J=V((1/2,0)) - sage: max(c.abs() for c in (I-J)) < epsilon + sage: prec = 200 + sage: Kreal = RealField(prec) + sage: K = ComplexField(prec) + sage: V = VectorSpace(K, 2) + sage: epsilon = Kreal(2^(-prec + 4)) + sage: f = lambda t: V((t, K(exp(2*pi*t*K.0)))) + sage: I = integrate_vector(f, prec, epsilon=epsilon) + sage: J = V((1/2, 0)) + sage: max(c.abs() for c in (I - J)) < epsilon True """ results = [] cdef long degree = 3 Rout = RealField(prec) + if epsilon is None: epsilon = Rout(2)**(-prec+3) while True: nodelist = nodes(degree,prec) I = nodelist[0][1]*f(nodelist[0][0]) - for i in xrange(1,len(nodelist)): + for i in range(1,len(nodelist)): I += nodelist[i][1]*f(nodelist[i][0]) results.append(I) if degree > 3: diff --git a/src/sage/numerical/mip.pyx b/src/sage/numerical/mip.pyx index 7a4a312711b..67414403b99 100644 --- a/src/sage/numerical/mip.pyx +++ b/src/sage/numerical/mip.pyx @@ -132,7 +132,7 @@ or by the following special syntax:: sage: mip. = MixedIntegerLinearProgram(solver='GLPK') sage: a - MIPVariable of dimension 1 + MIPVariable a with 0 real components sage: 5 + a[1] + 2*b[3] 5 + x_0 + 2*x_1 @@ -744,7 +744,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: x = p.new_variable(); x - MIPVariable of dimension 1 + MIPVariable with 0 real components sage: x0 = x[0]; x0 x_0 @@ -796,7 +796,7 @@ cdef class MixedIntegerLinearProgram(SageObject): shorthand for generating new variables with default settings:: sage: mip. = MixedIntegerLinearProgram(solver='GLPK') - sage: mip.add_constraint(x[0] + y[1] + z[2] <= 10) + sage: mip.add_constraint(x[0] + y[1] + z[2] <= 10) sage: mip.show() Maximization: @@ -835,7 +835,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: p.default_variable() - MIPVariable of dimension 1 + MIPVariable with 0 real components """ if self._default_mipvariable is None: self._default_mipvariable = self.new_variable() @@ -1309,7 +1309,7 @@ cdef class MixedIntegerLinearProgram(SageObject): name = varid_explainer[i] lb, ub = b.col_bounds(i) print(' {0} is {1} variable (min={2}, max={3})'.format( - name, var_type, + name, var_type, lb if lb is not None else "-oo", ub if ub is not None else "+oo")) @@ -1636,7 +1636,7 @@ cdef class MixedIntegerLinearProgram(SageObject): sage: p.add_constraint(x[5] + 3*x[7] == x[6] + 3) sage: p.add_constraint(x[5] + 3*x[7] <= x[6] + 3 <= x[8] + 27) - + Using this notation, the previous program can be written as:: sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK') @@ -1819,8 +1819,8 @@ cdef class MixedIntegerLinearProgram(SageObject): raise ValueError('min and max must not be specified for (in)equalities') relation = linear_function M = relation.parent().linear_tensors().free_module() - self.add_constraint(relation.lhs() - relation.rhs(), - min=M(0) if relation.is_equation() else None, + self.add_constraint(relation.lhs() - relation.rhs(), + min=M(0) if relation.is_equation() else None, max=M(0), name=name) else: raise ValueError('argument must be a linear function or constraint, got '+str(linear_function)) @@ -2712,7 +2712,7 @@ cdef class MixedIntegerLinearProgram(SageObject): if back_end.variable_lower_bound(i) != 0: raise ValueError('Problem variables must have 0 as lower bound') if back_end.variable_upper_bound(i) is not None: - raise ValueError('Problem variables must not have upper bound') + raise ValueError('Problem variables must not have upper bound') # Construct 'A' coef_matrix = [] @@ -2847,7 +2847,7 @@ cdef class MIPVariable(SageObject): underlying linear program. - ``vtype`` (integer) -- Defines the type of the variables - (default is ``REAL``). + (default is ``REAL``, i.e., ``vtype=-1``). - ``name`` -- A name for the ``MIPVariable``. @@ -2869,7 +2869,7 @@ cdef class MIPVariable(SageObject): sage: p = MixedIntegerLinearProgram(solver='GLPK') sage: p.new_variable(nonnegative=True) - MIPVariable of dimension 1 + MIPVariable with 0 real components, >= 0 """ self._dict = {} self._p = mip @@ -2957,7 +2957,7 @@ cdef class MIPVariable(SageObject): sage: x[11] Traceback (most recent call last): ... - IndexError: 11 does not index a component of MIPVariable of dimension 1 + IndexError: 11 does not index a component of MIPVariable with 7 real components Indices can be more than just integers:: @@ -2979,7 +2979,7 @@ cdef class MIPVariable(SageObject): sage: x[0] Traceback (most recent call last): ... - IndexError: 0 does not index a component of MIPVariable of dimension 1 + IndexError: 0 does not index a component of MIPVariable with 0 real components """ cdef int j @@ -3041,7 +3041,7 @@ cdef class MIPVariable(SageObject): sage: qv[5] Traceback (most recent call last): ... - IndexError: 5 does not index a component of MIPVariable of dimension 1 + IndexError: 5 does not index a component of MIPVariable with 2 real components """ cdef MIPVariable cp = type(self)(mip, self._vtype, self._name, @@ -3131,12 +3131,38 @@ cdef class MIPVariable(SageObject): EXAMPLES:: - sage: p=MixedIntegerLinearProgram(solver='GLPK') - sage: v=p.new_variable() + sage: p = MixedIntegerLinearProgram(solver='GLPK') + sage: v = p.new_variable() sage: v - MIPVariable of dimension 1 - """ - return "MIPVariable of dimension 1" + MIPVariable with 0 real components + sage: x = p.new_variable(integer=True, nonnegative=True, name="x") + sage: x[0] + x_0 + sage: x + MIPVariable x with 1 integer component, >= 0 + sage: x[1] + x_1 + sage: x + MIPVariable x with 2 integer components, >= 0 + sage: y = p.new_variable(real=True, name="y", indices=range(5)) + sage: y.set_min(0) + sage: y.set_max(17) + sage: y + MIPVariable y with 5 real components, >= 0, <= 17 + sage: z = p.new_variable(binary=True, name="z", indices=range(7)) + sage: z + MIPVariable z with 7 binary components + """ + s = 'MIPVariable{0} with {1} {2} component{3}'.format( + " " + self._name if self._name else "", + len(self._dict), + {0:"binary", -1:"real", 1:"integer"}[self._vtype], + "s" if len(self._dict) != 1 else "") + if (self._vtype != 0) and (self._lower_bound is not None): + s += ', >= {0}'.format(self._lower_bound) + if (self._vtype != 0) and (self._upper_bound is not None): + s += ', <= {0}'.format(self._upper_bound) + return s def keys(self): r""" diff --git a/src/sage/numerical/optimize.py b/src/sage/numerical/optimize.py index 6c6b1667b3d..f09634c3bf2 100644 --- a/src/sage/numerical/optimize.py +++ b/src/sage/numerical/optimize.py @@ -105,7 +105,8 @@ def find_root(f, a, b, xtol=10e-13, rtol=2.0**-50, maxiter=100, full_output=Fals return f.find_root(a=a,b=b,xtol=xtol,rtol=rtol,maxiter=maxiter,full_output=full_output) except AttributeError: pass - a = float(a); b = float(b) + a = float(a) + b = float(b) if a > b: a, b = b, a left = f(a) diff --git a/src/sage/parallel/map_reduce.py b/src/sage/parallel/map_reduce.py index d51d5d872ee..18ec7f41fe0 100644 --- a/src/sage/parallel/map_reduce.py +++ b/src/sage/parallel/map_reduce.py @@ -50,8 +50,8 @@ How can I use all that stuff? ----------------------------- -First, you need to set the environment variable `SAGE_NUM_THREADS` to the -desired number of parallel threads to be used: +First, you need to set the environment variable ``SAGE_NUM_THREADS`` to the +desired number of parallel threads to be used:: sage: import os # not tested sage: os.environ["SAGE_NUM_THREADS"] = '8' # not tested diff --git a/src/sage/plot/arrow.py b/src/sage/plot/arrow.py index 0247872298d..63de0ad5dd7 100644 --- a/src/sage/plot/arrow.py +++ b/src/sage/plot/arrow.py @@ -397,7 +397,6 @@ def __init__(self, patch, n): self._n = n def get_paths(self, renderer): - self._patch.set_dpi_cor(renderer.points_to_pixels(1.)) paths, fillables = self._patch.get_path_in_displaycoord() return paths diff --git a/src/sage/plot/hyperbolic_arc.py b/src/sage/plot/hyperbolic_arc.py index 57661907545..1acc1e1fdba 100644 --- a/src/sage/plot/hyperbolic_arc.py +++ b/src/sage/plot/hyperbolic_arc.py @@ -54,7 +54,7 @@ def __init__(self, A, B, options): if B.imag()<0: raise ValueError("%s is not a valid point in the UHP model"%(B)) self.path = [] - self._hyperbolic_arc(A, B, True); + self._hyperbolic_arc(A, B, True) BezierPath.__init__(self, self.path, options) self.A, self.B = (A, B) @@ -100,12 +100,13 @@ def _hyperbolic_arc(self, z0, z3, first=False): self.path.append([(z0.real(), z0.imag()), (z1.real(), z1.imag()), (z2.real(), z2.imag()), - (z3.real(), z3.imag())]); + (z3.real(), z3.imag())]) first = False else: self.path.append([(z1.real(), z1.imag()), (z2.real(), z2.imag()), - (z3.real(), z3.imag())]); + (z3.real(), z3.imag())]) + @rename_keyword(color='rgbcolor') @options(alpha=1, fill=False, thickness=1, rgbcolor="blue", zorder=2, linestyle='solid') diff --git a/src/sage/plot/polygon.py b/src/sage/plot/polygon.py index 33090764311..82ea3197162 100644 --- a/src/sage/plot/polygon.py +++ b/src/sage/plot/polygon.py @@ -1,7 +1,7 @@ """ Polygons """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2006 Alex Clemesha , # William Stein , # 2008 Mike Hansen , @@ -16,7 +16,7 @@ # The full text of the GPL is available at: # # http://www.gnu.org/licenses/ -#***************************************************************************** +# **************************************************************************** from sage.plot.primitive import GraphicPrimitive_xydata from sage.misc.decorators import options, rename_keyword @@ -61,6 +61,11 @@ class Polygon(GraphicPrimitive_xydata): sage: polygon([(0,0,1), (1,1,1), (2,0,1)]) Graphics3d Object + + :: + + sage: polygon2d([(1, 1), (0, 1), (1, 0)], fill=False, linestyle="dashed") + Graphics object consisting of 1 graphics primitive """ def __init__(self, xdata, ydata, options): """ @@ -88,11 +93,11 @@ def _repr_(self): sage: p=P[0]; p Polygon defined by 3 points """ - return "Polygon defined by %s points"%len(self) + return "Polygon defined by %s points" % len(self) def __getitem__(self, i): """ - Returns `i`th vertex of Polygon primitive, starting count + Return `i`th vertex of Polygon primitive, starting count from 0th vertex. EXAMPLES:: @@ -126,7 +131,7 @@ def __setitem__(self, i, point): def __len__(self): """ - Returns number of vertices of Polygon primitive. + Return number of vertices of Polygon primitive. EXAMPLES:: @@ -153,6 +158,7 @@ def _allowed_options(self): 'fill': 'Whether or not to fill the polygon.', 'legend_label': 'The label for this item in the legend.', 'legend_color': 'The color of the legend text.', + 'linestyle': 'The style of the enclosing line.', 'rgbcolor': 'The color as an RGB tuple.', 'hue': 'The color given as a hue.', 'zorder': 'The layer level in which to draw'} @@ -231,7 +237,7 @@ def plot3d(self, z=0, **kwds): if isinstance(z, list): zdata = z else: - zdata = [z]*len(self.xdata) + zdata = [z] * len(self.xdata) if len(zdata) == len(self.xdata): return IndexFaceSet([list(zip(self.xdata, self.ydata, zdata))], **options) @@ -249,6 +255,8 @@ def _render_on_subplot(self, subplot): p = patches.Polygon([(self.xdata[i], self.ydata[i]) for i in range(len(self.xdata))]) p.set_linewidth(float(options['thickness'])) + if 'linestyle' in options: + p.set_linestyle(options['linestyle']) a = float(options['alpha']) z = int(options.pop('zorder', 1)) p.set_alpha(a) @@ -271,7 +279,7 @@ def _render_on_subplot(self, subplot): def polygon(points, **options): """ - Returns either a 2-dimensional or 3-dimensional polygon depending + Return either a 2-dimensional or 3-dimensional polygon depending on value of points. For information regarding additional arguments, see either @@ -305,13 +313,14 @@ def polygon(points, **options): from sage.plot.plot3d.shapes2 import polygon3d return polygon3d(points, **options) + @rename_keyword(color='rgbcolor') -@options(alpha=1, rgbcolor=(0,0,1), edgecolor=None, thickness=None, +@options(alpha=1, rgbcolor=(0, 0, 1), edgecolor=None, thickness=None, legend_label=None, legend_color=None, aspect_ratio=1.0, fill=True) def polygon2d(points, **options): r""" - Returns a 2-dimensional polygon defined by ``points``. + Return a 2-dimensional polygon defined by ``points``. Type ``polygon2d.options`` for a dictionary of the default options for polygons. You can change this to change the diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py index a853f46651d..4a143e41487 100755 --- a/src/sage/quadratic_forms/binary_qf.py +++ b/src/sage/quadratic_forms/binary_qf.py @@ -1664,7 +1664,8 @@ def BinaryQF_reduced_representatives(D, primitive_only=False, proper=True): a4 = 4*a s = D + a*a4 w = 1+(s-1).isqrt() if s > 0 else 0 - if w%2 != D%2: w += 1 + if w%2 != D%2: + w += 1 for b in xsrange(w, a+1, 2): t = b*b-D if t % a4 == 0: diff --git a/src/sage/quadratic_forms/extras.py b/src/sage/quadratic_forms/extras.py index 37c30e9afa5..2baa4e3cfa7 100644 --- a/src/sage/quadratic_forms/extras.py +++ b/src/sage/quadratic_forms/extras.py @@ -94,12 +94,12 @@ def extend_to_primitive(A_input): sage: A = Matrix(ZZ, 3, 2, range(6)) sage: extend_to_primitive(A) - [ 0 1 0] + [ 0 1 -1] [ 2 3 0] - [ 4 5 -1] + [ 4 5 0] sage: extend_to_primitive([vector([1,2,3])]) - [(1, 2, 3), (0, 1, 0), (0, 0, 1)] + [(1, 2, 3), (0, 1, 1), (-1, 0, 0)] """ ## Deal with a list of vectors diff --git a/src/sage/quadratic_forms/genera/genus.py b/src/sage/quadratic_forms/genera/genus.py index f0718593ea8..35de7eea8d7 100644 --- a/src/sage/quadratic_forms/genera/genus.py +++ b/src/sage/quadratic_forms/genera/genus.py @@ -2897,18 +2897,18 @@ def discriminant_form(self): sage: GS.discriminant_form() Finite quadratic module over Integer Ring with invariants (2, 2, 4, 24) Gram matrix of the quadratic form with values in Q/2Z: - [ 1/2 0 0 0] - [ 0 3/2 0 0] - [ 0 0 7/4 0] - [ 0 0 0 7/24] + [ 1/2 0 1/2 0] + [ 0 3/2 0 0] + [ 1/2 0 3/4 0] + [ 0 0 0 25/24] sage: A = matrix.diagonal(ZZ, [1, -4, 6, 8]) sage: GS = Genus(A) sage: GS.discriminant_form() Finite quadratic module over Integer Ring with invariants (2, 4, 24) Gram matrix of the quadratic form with values in Q/Z: - [ 1/2 0 0] - [ 0 3/4 0] - [ 0 0 7/24] + [ 1/2 1/2 0] + [ 1/2 3/4 0] + [ 0 0 1/24] """ from sage.modules.torsion_quadratic_module import TorsionQuadraticForm qL = [] diff --git a/src/sage/quadratic_forms/genera/normal_form.py b/src/sage/quadratic_forms/genera/normal_form.py index 394835cc53e..db46afe0d76 100644 --- a/src/sage/quadratic_forms/genera/normal_form.py +++ b/src/sage/quadratic_forms/genera/normal_form.py @@ -259,14 +259,12 @@ def p_adic_normal_form(G, p, precision=None, partial=False, debug=False): precision = G.det().valuation(p) + 4 R = Zp(p, prec=precision, type='fixed-mod') G = G.change_ring(R) - G.set_immutable() # is not changed during computation - D = copy(G) # is transformed into jordan form + G.set_immutable() # is not changed during computation n = G.ncols() # The trivial case if n == 0: return G.parent().zero(), G.parent().zero() # the transformation matrix is called B - B = Matrix.identity(R, n) if p == 2: D, B = _jordan_2_adic(G) else: diff --git a/src/sage/quadratic_forms/quadratic_form__local_normal_form.py b/src/sage/quadratic_forms/quadratic_form__local_normal_form.py index 73140207264..695929a1687 100644 --- a/src/sage/quadratic_forms/quadratic_form__local_normal_form.py +++ b/src/sage/quadratic_forms/quadratic_form__local_normal_form.py @@ -160,8 +160,8 @@ def local_normal_form(self, p): # 1x1 => make upper left the smallest if (p != 2): - block_size = 1; - Q.add_symmetric(1, 0, 1, in_place = True) + block_size = 1 + Q.add_symmetric(1, 0, 1, in_place=True) # 2x2 => replace it with the appropriate 2x2 matrix else: block_size = 2 diff --git a/src/sage/quadratic_forms/quadratic_form__siegel_product.py b/src/sage/quadratic_forms/quadratic_form__siegel_product.py index d380daaeb5b..fb2e72a0f4a 100644 --- a/src/sage/quadratic_forms/quadratic_form__siegel_product.py +++ b/src/sage/quadratic_forms/quadratic_form__siegel_product.py @@ -83,10 +83,10 @@ def siegel_product(self, u): n = self.dim() d = self.det() ## ??? Warning: This is a factor of 2^n larger than it should be! - ## DIAGNOSTIC + # DIAGNOSTIC verbose("n = " + str(n)) verbose("d = " + str(d)) - verbose("In siegel_product: d = " + str(d) + "\n"); + verbose("In siegel_product: d = " + str(d) + "\n") ## Product of "bad" places to omit S = 2 * d * u diff --git a/src/sage/quadratic_forms/quadratic_form__split_local_covering.py b/src/sage/quadratic_forms/quadratic_form__split_local_covering.py index 92102fcbd1e..afd1ee350c3 100644 --- a/src/sage/quadratic_forms/quadratic_form__split_local_covering.py +++ b/src/sage/quadratic_forms/quadratic_form__split_local_covering.py @@ -186,11 +186,11 @@ def vectors_by_length(self, bound): ## (So theta_vec[i] will have all vectors v with Q(v) = i.) theta_vec = [[] for i in range(bound + 1)] - ## Initialize Q with zeros and Copy the Cholesky array into Q + # Initialize Q with zeros and Copy the Cholesky array into Q Q = self.cholesky_decomposition() - ## 1. Initialize + # 1. Initialize T = n * [RDF(0)] ## Note: We index the entries as 0 --> n-1 U = n * [RDF(0)] i = n-1 @@ -199,36 +199,21 @@ def vectors_by_length(self, bound): L = n * [0] x = n * [0] - Z = RDF(0) - ## 2. Compute bounds + # 2. Compute bounds Z = (T[i] / Q[i][i]).sqrt(extend=False) L[i] = ( Z - U[i]).floor() x[i] = (-Z - U[i]).ceil() done_flag = False - Q_val_double = RDF(0) Q_val = 0 ## WARNING: Still need a good way of checking overflow for this value... - ## Big loop which runs through all vectors + # Big loop which runs through all vectors while not done_flag: ## 3b. Main loop -- try to generate a complete vector x (when i=0) while (i > 0): - #print " i = ", i - #print " T[i] = ", T[i] - #print " Q[i][i] = ", Q[i][i] - #print " x[i] = ", x[i] - #print " U[i] = ", U[i] - #print " x[i] + U[i] = ", (x[i] + U[i]) - #print " T[i-1] = ", T[i-1] - T[i-1] = T[i] - Q[i][i] * (x[i] + U[i]) * (x[i] + U[i]) - - #print " T[i-1] = ", T[i-1] - #print " x = ", x - #print - i = i - 1 U[i] = 0 for j in range(i+1, n): @@ -247,29 +232,20 @@ def vectors_by_length(self, bound): i += 1 x[i] += 1 - ## 4. Solution found (This happens when i = 0) - #print "-- Solution found! --" - #print " x = ", x - #print " Q_val = Q(x) = ", Q_val + # 4. Solution found (This happens when i = 0) Q_val_double = Theta_Precision - T[0] + Q[0][0] * (x[0] + U[0]) * (x[0] + U[0]) Q_val = Q_val_double.round() - ## SANITY CHECK: Roundoff Error is < 0.001 + # SANITY CHECK: Roundoff Error is < 0.001 if abs(Q_val_double - Q_val) > 0.001: print(" x = ", x) print(" Float = ", Q_val_double, " Long = ", Q_val) raise RuntimeError("The roundoff error is bigger than 0.001, so we should use more precision somewhere...") - #print " Float = ", Q_val_double, " Long = ", Q_val, " XX " - #print " The float value is ", Q_val_double - #print " The associated long value is ", Q_val - if (Q_val <= bound): - #print " Have vector ", x, " with value ", Q_val theta_vec[Q_val].append(deepcopy(x)) - - ## 5. Check if x = 0, for exit condition. =) + # 5. Check if x = 0, for exit condition. =) j = 0 done_flag = True while (j < n): @@ -277,20 +253,15 @@ def vectors_by_length(self, bound): done_flag = False j += 1 - ## 3a. Increment (and carry if we go out of bounds) x[i] += 1 while (x[i] > L[i]) and (i < n-1): i += 1 x[i] += 1 - - #print " Leaving ThetaVectors()" return theta_vec - - def complementary_subform_to_vector(self, v): """ Finds the `(n-1)`-dim'l quadratic form orthogonal to the vector `v`. @@ -318,25 +289,25 @@ def complementary_subform_to_vector(self, v): sage: Q1 = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q1.complementary_subform_to_vector([1,0,0,0]) Quadratic form in 3 variables over Integer Ring with coefficients: - [ 3 0 0 ] + [ 7 0 0 ] [ * 5 0 ] - [ * * 7 ] + [ * * 3 ] :: sage: Q1.complementary_subform_to_vector([1,1,0,0]) Quadratic form in 3 variables over Integer Ring with coefficients: - [ 12 0 0 ] + [ 7 0 0 ] [ * 5 0 ] - [ * * 7 ] + [ * * 12 ] :: sage: Q1.complementary_subform_to_vector([1,1,1,1]) Quadratic form in 3 variables over Integer Ring with coefficients: - [ 624 -480 -672 ] - [ * 880 -1120 ] - [ * * 1008 ] + [ 880 -480 -160 ] + [ * 624 -96 ] + [ * * 240 ] """ n = self.dim() @@ -417,8 +388,8 @@ def split_local_cover(self): sage: Q1.split_local_cover() Quadratic form in 3 variables over Integer Ring with coefficients: [ 3 0 0 ] - [ * 7 0 ] - [ * * 5 ] + [ * 5 0 ] + [ * * 7 ] """ ## 0. If a split local cover already exists, then return it. diff --git a/src/sage/repl/display/formatter.py b/src/sage/repl/display/formatter.py index 307da60c8d8..59456cb094a 100644 --- a/src/sage/repl/display/formatter.py +++ b/src/sage/repl/display/formatter.py @@ -104,6 +104,10 @@ def __init__(self, *args, **kwds): from sage.repl.rich_output.backend_ipython import BackendIPython self.dm.check_backend_class(BackendIPython) + pt_formatter = self.formatters[PLAIN_TEXT] + pt_formatter.observe(self._ipython_float_precision_changed, + names=['float_precision']) + def format(self, obj, include=None, exclude=None): r""" Use the Sage rich output instead of IPython @@ -195,6 +199,41 @@ def format(self, obj, include=None, exclude=None): ipy_metadata[PLAIN_TEXT] = sage_metadata[PLAIN_TEXT] return ipy_format, ipy_metadata + @staticmethod + def _ipython_float_precision_changed(change): + """ + Update the current float precision for the display of matrices in Sage. + + This function is called when the IPython ``%precision`` magic is + invoked. + + TESTS:: + + sage: from sage.repl.interpreter import get_test_shell + sage: shell = get_test_shell() + sage: shell.run_cell('%precision 4') + '%.4f' + sage: shell.run_cell('matrix.options.precision') # indirect doctest + 4 + sage: shell.run_cell('%precision') + '%r' + sage: shell.run_cell('matrix.options.precision') # indirect doctest + None + """ + from sage.matrix.constructor import options + s = change.new + if not s: + # unset the precision + options.precision = None + else: + try: + prec = int(s) + if prec >= 0: + options.precision = prec + # otherwise ignore the change + except ValueError: + pass + class SagePlainTextFormatter(PlainTextFormatter): diff --git a/src/sage/repl/ipython_kernel/install.py b/src/sage/repl/ipython_kernel/install.py index a1d3ec7efa7..2143c221ed3 100644 --- a/src/sage/repl/ipython_kernel/install.py +++ b/src/sage/repl/ipython_kernel/install.py @@ -123,12 +123,12 @@ def use_local_threejs(self): sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec(prefix=tmp_dir()) sage: spec.use_local_threejs() - sage: threejs = os.path.join(spec.nbextensions_dir, 'threejs') + sage: threejs = os.path.join(spec.nbextensions_dir, 'threejs-sage') sage: os.path.isdir(threejs) True """ src = THREEJS_DIR - dst = os.path.join(self.nbextensions_dir, 'threejs') + dst = os.path.join(self.nbextensions_dir, 'threejs-sage') self.symlink(src, dst) def _kernel_cmd(self): diff --git a/src/sage/repl/rich_output/backend_base.py b/src/sage/repl/rich_output/backend_base.py index d62ab376a19..bd89fb9774c 100644 --- a/src/sage/repl/rich_output/backend_base.py +++ b/src/sage/repl/rich_output/backend_base.py @@ -475,7 +475,7 @@ def latex_formatter(self, obj, **kwds): concatenate = kwds.get('concatenate', False) from sage.misc.html import html from sage.repl.rich_output.output_browser import OutputHtml - return OutputHtml(html(obj, concatenate=concatenate)) + return OutputHtml(html(obj, concatenate=concatenate, strict=True)) def set_underscore_variable(self, obj): """ diff --git a/src/sage/repl/rich_output/backend_ipython.py b/src/sage/repl/rich_output/backend_ipython.py index b290e74ecfa..43835b422b0 100644 --- a/src/sage/repl/rich_output/backend_ipython.py +++ b/src/sage/repl/rich_output/backend_ipython.py @@ -413,8 +413,9 @@ def threejs_offline_scripts(self): '...', r'<\/script>').replace('\n', ' \\\n') return """ - + - """.format(CDN_script.replace('', r'<\/script>').replace('\n', ' \\\n')) + """.format(_required_threejs_version(), CDN_script) diff --git a/src/sage/repl/rich_output/display_manager.py b/src/sage/repl/rich_output/display_manager.py index f91c56efb68..eb042a9de47 100644 --- a/src/sage/repl/rich_output/display_manager.py +++ b/src/sage/repl/rich_output/display_manager.py @@ -45,6 +45,20 @@ ) from sage.repl.rich_output.preferences import DisplayPreferences +def _required_threejs_version(): + """ + Return the version of threejs that Sage requires. + + EXAMPLES:: + + sage: from sage.repl.rich_output.display_manager import _required_threejs_version + sage: _required_threejs_version() + 'r...' + """ + import os + import sage.env + with open(os.path.join(sage.env.SAGE_EXTCODE, 'threejs', 'threejs-version.txt')) as f: + return f.read().strip() class DisplayException(Exception): """ @@ -749,10 +763,7 @@ def threejs_scripts(self, online): offline threejs graphics """ if online: - import sage.env - import os - with open(os.path.join(sage.env.THREEJS_DIR, 'version')) as f: - version = f.read().strip() + version = _required_threejs_version() return """ """.format(version) diff --git a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py index 1975374e2ac..9bd8b03df28 100644 --- a/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py +++ b/src/sage/rings/asymptotic/asymptotics_multivariate_generating_functions.py @@ -3681,6 +3681,7 @@ def diff_prod(f_derivs, u, g, X, interval, end, uderivs, atc): D = {} rhs = [] lhs = [] + new_vars = [] for t in combinations_with_replacement(X, l): t = list(t) s = t + end @@ -3689,13 +3690,15 @@ def diff_prod(f_derivs, u, g, X, interval, end, uderivs, atc): # Since Sage's solve command can't take derivatives as variable # names, make new variables based on t to stand in for # diff(u, t) and store them in D. - D[diff(u, t).subs(atc)] = var('zing' + - ''.join(str(x) for x in t)) + new_var = SR.temp_var() + new_vars.append(new_var) + D[diff(u, t).subs(atc)] = new_var eqns = [lhs[i] == rhs[i].subs(uderivs).subs(D) for i in range(len(lhs))] variables = D.values() sol = solve(eqns, *variables, solution_dict=True) uderivs.update(subs_all(D, sol[ZZ.zero()])) + SR.cleanup_var(new_vars) return uderivs diff --git a/src/sage/rings/complex_arb.pyx b/src/sage/rings/complex_arb.pyx index 1112255a6d9..224fd1eb165 100644 --- a/src/sage/rings/complex_arb.pyx +++ b/src/sage/rings/complex_arb.pyx @@ -820,6 +820,13 @@ class ComplexBallField(UniqueRepresentation, Field): Traceback (most recent call last): ... ValueError: unable to determine which roots are real + + TESTS:: + + sage: CBF._roots_univariate_polynomial(CBF['x'].zero(), CBF, False, None) + Traceback (most recent call last): + ... + ArithmeticError: taking the roots of the zero polynomial """ if algorithm is not None: raise NotImplementedError @@ -853,6 +860,8 @@ class ComplexBallField(UniqueRepresentation, Field): cdef ComplexBall cb acb_poly_init(rounded_poly) cdef long deg = acb_poly_degree(poly.__poly) + if deg < 0: + raise ArithmeticError("taking the roots of the zero polynomial") cdef acb_ptr roots = _acb_vec_init(deg) try: sig_on() diff --git a/src/sage/rings/complex_double.pyx b/src/sage/rings/complex_double.pyx index 411f5a3dea4..15c738eac0d 100644 --- a/src/sage/rings/complex_double.pyx +++ b/src/sage/rings/complex_double.pyx @@ -277,7 +277,7 @@ cdef class ComplexDoubleField_class(sage.rings.ring.Field): """ return r"\Bold{C}" - def __call__(self, x, im=None): + def __call__(self, x=None, im=None): """ Create a complex double using ``x`` and optionally an imaginary part ``im``. @@ -329,8 +329,19 @@ cdef class ComplexDoubleField_class(sage.rings.ring.Field): True sage: b == CC(a) True + + TESTS: + + Check that :trac:`31836` is fixed:: + + sage: a = CDF() ; a + 0.0 + sage: a.parent() + Complex Double Field """ # We implement __call__ to gracefully accept the second argument. + if x is None: + return self.zero() if im is not None: x = x, im return Parent.__call__(self, x) diff --git a/src/sage/rings/complex_mpc.pyx b/src/sage/rings/complex_mpc.pyx index 3bcb2a3790d..debfac174d0 100644 --- a/src/sage/rings/complex_mpc.pyx +++ b/src/sage/rings/complex_mpc.pyx @@ -89,10 +89,6 @@ from sage.misc.superseded import deprecated_function_alias cimport gmpy2 gmpy2.import_gmpy2() -NumberFieldElement_quadratic = None -AlgebraicNumber_base = None -AlgebraicNumber = None -AlgebraicReal = None AA = None QQbar = None CDF = CLF = RLF = None @@ -105,19 +101,10 @@ def late_import(): sage: sage.rings.complex_mpc.late_import() """ - global NumberFieldElement_quadratic - global AlgebraicNumber_base - global AlgebraicNumber - global AlgebraicReal global AA, QQbar global CLF, RLF, CDF - if NumberFieldElement_quadratic is None: - import sage.rings.number_field.number_field_element_quadratic as nfeq - NumberFieldElement_quadratic = nfeq.NumberFieldElement_quadratic + if AA is None: import sage.rings.qqbar - AlgebraicNumber_base = sage.rings.qqbar.AlgebraicNumber_base - AlgebraicNumber = sage.rings.qqbar.AlgebraicNumber - AlgebraicReal = sage.rings.qqbar.AlgebraicReal AA = sage.rings.qqbar.AA QQbar = sage.rings.qqbar.QQbar from .real_lazy import CLF, RLF diff --git a/src/sage/rings/complex_mpfr.pyx b/src/sage/rings/complex_mpfr.pyx index 695a1048a4e..637e3f3990c 100644 --- a/src/sage/rings/complex_mpfr.pyx +++ b/src/sage/rings/complex_mpfr.pyx @@ -1227,6 +1227,13 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): 5 sage: a^5 -38.0000000000000 + 41.0000000000000*I + + TESTS: + + Check that :trac:`11323` is fixed:: + + sage: float(5)^(0.5 + 14.1347251*i) + -1.62414637645790 - 1.53692828324508*I """ self._multiplicative_order = Integer(n) @@ -1690,7 +1697,7 @@ cdef class ComplexNumber(sage.structure.element.FieldElement): try: return (self.log()*right).exp() - except TypeError: + except (AttributeError, TypeError): pass try: diff --git a/src/sage/rings/finite_rings/finite_field_base.pyx b/src/sage/rings/finite_rings/finite_field_base.pyx index a49373492eb..caff023cbd3 100644 --- a/src/sage/rings/finite_rings/finite_field_base.pyx +++ b/src/sage/rings/finite_rings/finite_field_base.pyx @@ -873,8 +873,25 @@ cdef class FiniteField(Field): sage: GF(7^2,'a').factored_unit_order() (2^4 * 3,) + + TESTS: + + Check that :trac:`31686` is fixed:: + + sage: p = 1100585370631 + sage: F = GF(p^24, 'a') + sage: F.factored_unit_order() + (2^6 * 3^2 * 5 * 7 * 11 * 13 * 17 * 53 * 97 * 229 * 337 * 421 + * 3929 * 215417 * 249737 * 262519 * 397897 * 59825761 * 692192057 + * 12506651939 * 37553789761 * 46950147799 * 172462808473 * 434045140817 + * 81866093016401 * 617237859576697 * 659156729361017707 + * 268083135725348991493995910983015600019336657 + * 90433843562394341719266736354746485652016132372842876085423636587989263202299569913,) """ - F = (self.order() - 1).factor() + from sage.structure.factorization import Factorization + from sage.rings.polynomial.cyclotomic import cyclotomic_value as cv + p, d = self.characteristic(), self.degree() + F = Factorization(f for n in d.divisors() for f in cv(n, p).factor()) return (F,) def cardinality(self): @@ -1621,14 +1638,14 @@ cdef class FiniteField(Field): We check that :trac:`23801` is resolved:: - sage: k. = GF(3^240) + sage: k. = GF(5^240) sage: l, inc = k.subfield(3, 'z', map=True); l - Finite Field in z of size 3^3 + Finite Field in z of size 5^3 sage: inc Ring morphism: - From: Finite Field in z of size 3^3 - To: Finite Field in a of size 3^240 - Defn: z |--> a^239 + a^238 + ... + a^3 + 2 + From: Finite Field in z of size 5^3 + To: Finite Field in a of size 5^240 + Defn: z |--> 2*a^235 + a^231 + ... + a + 4 There is no coercion since we can't ensure compatibility with larger fields in this case:: @@ -1639,7 +1656,7 @@ cdef class FiniteField(Field): But there is still a compatibility among the generators chosen for the subfields:: sage: ll, iinc = k.subfield(12, 'w', map=True) - sage: x = iinc(ll.gen())^((3^12-1)/(3^3-1)) + sage: x = iinc(ll.gen())^((5^12-1)/(5^3-1)) sage: x.minimal_polynomial() == l.modulus() True diff --git a/src/sage/rings/finite_rings/finite_field_constructor.py b/src/sage/rings/finite_rings/finite_field_constructor.py index e05b7fc9fa7..219b4a778ae 100644 --- a/src/sage/rings/finite_rings/finite_field_constructor.py +++ b/src/sage/rings/finite_rings/finite_field_constructor.py @@ -285,14 +285,14 @@ class FiniteFieldFactory(UniqueFactory): (a generator of the multiplicative group), use ``modulus="primitive"`` if you need this:: - sage: K. = GF(5^40) + sage: K. = GF(5^45) sage: a.multiplicative_order() - 189478062869360049565633138 + 7105427357601001858711242675781 sage: a.is_square() True - sage: K. = GF(5^40, modulus="primitive") + sage: K. = GF(5^45, modulus="primitive") sage: b.multiplicative_order() - 9094947017729282379150390624 + 28421709430404007434844970703124 The modulus must be irreducible:: diff --git a/src/sage/rings/finite_rings/galois_group.py b/src/sage/rings/finite_rings/galois_group.py index cd953380011..2d62e31b0fb 100644 --- a/src/sage/rings/finite_rings/galois_group.py +++ b/src/sage/rings/finite_rings/galois_group.py @@ -65,7 +65,6 @@ def __init__(self, field): sage: TestSuite(GF(9).galois_group()).run() """ - self._field = field GaloisGroup_cyc.__init__(self, field, (field.degree(),), gen_names="Frob") def _repr_(self): diff --git a/src/sage/rings/finite_rings/integer_mod_ring.py b/src/sage/rings/finite_rings/integer_mod_ring.py index 0dcef0d21ae..8380acc6533 100644 --- a/src/sage/rings/finite_rings/integer_mod_ring.py +++ b/src/sage/rings/finite_rings/integer_mod_ring.py @@ -626,7 +626,7 @@ def multiplicative_subgroups(self): sage: Integers(5).multiplicative_subgroups() ((2,), (4,), ()) sage: Integers(15).multiplicative_subgroups() - ((11, 7), (4, 11), (8,), (11,), (14,), (7,), (4,), ()) + ((11, 7), (11, 4), (2,), (11,), (14,), (7,), (4,), ()) sage: Integers(2).multiplicative_subgroups() ((),) sage: len(Integers(341).multiplicative_subgroups()) diff --git a/src/sage/rings/finite_rings/residue_field.pyx b/src/sage/rings/finite_rings/residue_field.pyx index cd4c2212c3b..007a68e4c0f 100644 --- a/src/sage/rings/finite_rings/residue_field.pyx +++ b/src/sage/rings/finite_rings/residue_field.pyx @@ -1055,7 +1055,7 @@ cdef class ReductionMap(Map): sage: f = k.convert_map_from(K) sage: s = f.section(); s Lifting map: - From: Residue field in abar of Fractional ideal (14*a^4 - 24*a^3 - 26*a^2 + 58*a - 15) + From: Residue field in abar of Fractional ideal (-14*a^4 + 24*a^3 + 26*a^2 - 58*a + 15) To: Number Field in a with defining polynomial x^5 - 5*x + 2 sage: s(k.gen()) a @@ -1268,7 +1268,7 @@ cdef class ResidueFieldHomomorphism_global(RingHomomorphism): sage: f = k.coerce_map_from(K.ring_of_integers()) sage: s = f.section(); s Lifting map: - From: Residue field in abar of Fractional ideal (14*a^4 - 24*a^3 - 26*a^2 + 58*a - 15) + From: Residue field in abar of Fractional ideal (-14*a^4 + 24*a^3 + 26*a^2 - 58*a + 15) To: Maximal Order in Number Field in a with defining polynomial x^5 - 5*x + 2 sage: s(k.gen()) a @@ -1371,10 +1371,10 @@ cdef class LiftingMap(Section): sage: F = K.factor(7)[0][0].residue_field() sage: L = F.lift_map(); L Lifting map: - From: Residue field in abar of Fractional ideal (-2*a^4 + a^3 - 4*a^2 + 2*a - 1) + From: Residue field in abar of Fractional ideal (2*a^4 - a^3 + 4*a^2 - 2*a + 1) To: Maximal Order in Number Field in a with defining polynomial x^5 + 2 sage: L.domain() - Residue field in abar of Fractional ideal (-2*a^4 + a^3 - 4*a^2 + 2*a - 1) + Residue field in abar of Fractional ideal (2*a^4 - a^3 + 4*a^2 - 2*a + 1) sage: K. = CyclotomicField(7) sage: F = K.factor(5)[0][0].residue_field() @@ -1498,7 +1498,7 @@ cdef class LiftingMap(Section): sage: F. = K.factor(7)[0][0].residue_field() sage: F.lift_map() #indirect doctest Lifting map: - From: Residue field in tmod of Fractional ideal (-3*theta_12^2 + 1) + From: Residue field in tmod of Fractional ideal (theta_12^2 + 2) To: Maximal Order in Cyclotomic Field of order 12 and degree 4 """ return "Lifting" @@ -1515,7 +1515,7 @@ class ResidueFiniteField_prime_modn(ResidueField_generic, FiniteField_prime_modn sage: P = K.ideal(29).factor()[1][0] sage: k = ResidueField(P) sage: k - Residue field of Fractional ideal (a^2 + 2*a + 2) + Residue field of Fractional ideal (-a^2 - 2*a - 2) sage: k.order() 29 sage: OK = K.maximal_order() @@ -1597,7 +1597,7 @@ class ResidueFiniteField_prime_modn(ResidueField_generic, FiniteField_prime_modn sage: P = K.ideal(29).factor()[1][0] sage: k = ResidueField(P) sage: k - Residue field of Fractional ideal (a^2 + 2*a + 2) + Residue field of Fractional ideal (-a^2 - 2*a - 2) sage: OK = K.maximal_order() sage: c = OK(a) sage: b = k(a); b diff --git a/src/sage/rings/fraction_field_element.pyx b/src/sage/rings/fraction_field_element.pyx index 3f32d2e1c52..4206b1dd4d8 100644 --- a/src/sage/rings/fraction_field_element.pyx +++ b/src/sage/rings/fraction_field_element.pyx @@ -753,34 +753,97 @@ cdef class FractionFieldElement(FieldElement): else: raise TypeError("denominator must equal 1") - def _integer_(self, Z=ZZ): + def __float__(self): """ EXAMPLES:: + sage: K. = Frac(ZZ['x,y']) + sage: float(x/x + y/y) + 2.0 + """ + return float(self.__numerator) / float(self.__denominator) + + def __complex__(self): + """ + EXAMPLES:: + + sage: K. = Frac(I.parent()['x,y']) + sage: complex(x/(I*x) + (I*y)/y) + 0j + """ + return complex(self.__numerator) / complex(self.__denominator) + + def _rational_(self): + r""" + TESTS:: + + sage: K = Frac(ZZ['x']) + sage: QQ(K(x) / K(2*x)) + 1/2 + """ + return self._conversion(QQ) + + def _conversion(self, R): + r""" + Generic conversion + + TESTS:: + sage: K = Frac(ZZ['x']) - sage: K(5)._integer_() + sage: ZZ(K(5)) # indirect doctest 5 + sage: ZZ(K(1) / K(2)) + Traceback (most recent call last): + ... + ArithmeticError: inverse does not exist + sage: RDF(K(1) / K(2)) + 0.5 + sage: K. = Frac(RR['x']) sage: ZZ(2*x/x) 2 - """ - if self.__denominator != 1: - self.reduce() - if self.__denominator == 1: - return Z(self.__numerator) - raise TypeError("no way to coerce to an integer.") - - def _rational_(self, Q=QQ): - """ - EXAMPLES:: + sage: RDF(x) + Traceback (most recent call last): + ... + TypeError: cannot convert nonconstant polynomial sage: K. = Frac(QQ['x']) - sage: K(1/2)._rational_() + sage: QQ(K(1/2)) 1/2 - sage: K(1/2 + x/x)._rational_() + sage: QQ(K(1/2 + x/x)) 3/2 + + sage: x = polygen(QQ) + sage: A. = NumberField(x^3 - 2) + sage: A((x+3) / (2*x - 1)) + 14/15*u^2 + 7/15*u + 11/15 + + sage: B = A['y'].fraction_field() + sage: A(B(u)) + u + sage: C = A['x,y'].fraction_field() + sage: A(C(u)) + u """ - return Q(self.__numerator) / Q(self.__denominator) + if self.__denominator.is_one(): + return R(self.__numerator) + else: + self.reduce() + num = R(self.__numerator) + inv_den = R(self.__denominator).inverse_of_unit() + return num * inv_den + + _real_double_ = _conversion + _complex_double_ = _conversion + _mpfr_ = _conversion + _complex_mpfr_ = _conversion + _real_mpfi_ = _conversion + _complex_mpfi_ = _conversion + _arb_ = _conversion + _acb_ = _conversion + _integer_ = _conversion + _algebraic_ = _conversion + _number_field_ = _conversion def __pow__(self, right, dummy): r""" @@ -871,16 +934,6 @@ cdef class FractionFieldElement(FieldElement): return self.__class__(self._parent, self.__denominator, self.__numerator, coerce=False, reduce=False) - def __float__(self): - """ - EXAMPLES:: - - sage: K. = Frac(ZZ['x,y']) - sage: float(x/x + y/y) - 2.0 - """ - return float(self.__numerator) / float(self.__denominator) - cpdef _richcmp_(self, other, int op): """ EXAMPLES:: diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py index d0ad9e582a2..b7d7c1127fb 100644 --- a/src/sage/rings/function_field/function_field.py +++ b/src/sage/rings/function_field/function_field.py @@ -1870,7 +1870,7 @@ def free_module(self, base=None, basis=None, map=True): if base is None: base = self.base_field() degree = self.degree(base) - V = base**degree; + V = base**degree if not map: return V from_V = MapVectorSpaceToFunctionField(V, self) @@ -2074,7 +2074,7 @@ def hom(self, im_gens, base_morphism=None): base_morphism = self.base_field().hom(im_gens[1:], base_morphism) # the codomain of this morphism is the field containing all the im_gens - codomain = im_gens[0].parent(); + codomain = im_gens[0].parent() if base_morphism is not None: from sage.categories.pushout import pushout codomain = pushout(codomain, base_morphism.codomain()) @@ -3534,6 +3534,7 @@ def _maximal_order_basis(self): in some algorithms. """ from sage.matrix.constructor import matrix + from .hermite_form_polynomial import reversed_hermite_form k = self.constant_base_field() K = self.base_field() # rational function field @@ -3608,14 +3609,8 @@ def _maximal_order_basis(self): basis_V = [to_V(bvec) for bvec in _basis] l = lcm([vvec.denominator() for vvec in basis_V]) - # Why do we have 'reversed' here? I don't know. But without it, the - # time to get hermite_form_reversed dramatically increases. - _mat = matrix([[coeff.numerator() for coeff in l*v] for v in reversed(basis_V)]) - - # compute the reversed hermite form - _mat.reverse_rows_and_columns() - _mat._hermite_form_euclidean(normalization=lambda p: ~p.lc()) - _mat.reverse_rows_and_columns() + _mat = matrix([[coeff.numerator() for coeff in l*v] for v in basis_V]) + reversed_hermite_form(_mat) basis = [fr_V(v) / l for v in _mat if not v.is_zero()] return basis diff --git a/src/sage/rings/function_field/hermite_form_polynomial.pyx b/src/sage/rings/function_field/hermite_form_polynomial.pyx new file mode 100644 index 00000000000..823ae0b55fa --- /dev/null +++ b/src/sage/rings/function_field/hermite_form_polynomial.pyx @@ -0,0 +1,187 @@ +r""" +Hermite form computation for function fields + +This module provides an optimized implementation of the algorithm computing +Hermite forms of matrices over polynomials. This is the workhorse of the +function field machinery of Sage. + +EXAMPLES:: + + sage: P. = PolynomialRing(QQ) + sage: A = matrix(P,3,[-(x-1)^((i-j+1) % 3) for i in range(3) for j in range(3)]) + sage: A + [ -x + 1 -1 -x^2 + 2*x - 1] + [-x^2 + 2*x - 1 -x + 1 -1] + [ -1 -x^2 + 2*x - 1 -x + 1] + sage: from sage.rings.function_field.hermite_form_polynomial import reversed_hermite_form + sage: B = copy(A) + sage: U = reversed_hermite_form(B, transformation=True) + sage: U * A == B + True + sage: B + [x^3 - 3*x^2 + 3*x - 2 0 0] + [ 0 x^3 - 3*x^2 + 3*x - 2 0] + [ x^2 - 2*x + 1 x - 1 1] + +The function :func:`reversed_hermite_form` computes the reversed hermite form, +which is reversed both row-wise and column-wise from the usual hermite form. +Let us check it:: + + sage: A.reverse_rows_and_columns() + sage: C = copy(A.hermite_form()) + sage: C.reverse_rows_and_columns() + sage: C + [x^3 - 3*x^2 + 3*x - 2 0 0] + [ 0 x^3 - 3*x^2 + 3*x - 2 0] + [ x^2 - 2*x + 1 x - 1 1] + sage: C == B + True + +AUTHORS: + +- Kwankyu Lee (2021-05-21): initial version + +""" +#***************************************************************************** +# Copyright (C) 2021 Kwankyu Lee +# +# 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 sage.matrix.matrix cimport Matrix +from sage.rings.polynomial.polynomial_element cimport Polynomial +from sage.matrix.constructor import identity_matrix + +def reversed_hermite_form(Matrix mat, bint transformation=False): + """ + Transform the matrix in place to reversed hermite normal form and + optionally return the transformation matrix. + + INPUT: + + - ``transformation`` -- boolean (default: ``False``); if ``True``, + return the transformation matrix + + EXAMPLES:: + + sage: from sage.rings.function_field.hermite_form_polynomial import reversed_hermite_form + sage: P. = PolynomialRing(QQ) + sage: A = matrix(P,3,[-(x-1)^((i-2*j) % 4) for i in range(3) for j in range(3)]) + sage: A + [ -1 -x^2 + 2*x - 1 -1] + [ -x + 1 -x^3 + 3*x^2 - 3*x + 1 -x + 1] + [ -x^2 + 2*x - 1 -1 -x^2 + 2*x - 1] + sage: B = copy(A) + sage: U = reversed_hermite_form(B, transformation=True) + sage: U * A == B + True + sage: B + [ 0 0 0] + [ 0 x^4 - 4*x^3 + 6*x^2 - 4*x 0] + [ 1 x^2 - 2*x + 1 1] + """ + cdef Matrix A = mat + cdef Matrix U + + cdef Py_ssize_t m = A.nrows() + cdef Py_ssize_t n = A.ncols() + cdef Py_ssize_t i = m - 1 + cdef Py_ssize_t j = n - 1 + cdef Py_ssize_t k, l, c, ip + + cdef Polynomial a, b, d, p, q, e, f, Aic, Alc, Uic, Ulc + cdef Polynomial zero = A.base_ring().zero() + + cdef int di, dk + + cdef list pivot_cols = [] + + if transformation: + U = identity_matrix(A.base_ring(), m) + + while j >= 0: + # find the first row with nonzero entry in jth column + k = i + while k >= 0 and not A.get_unsafe(k, j): + k -= 1 + if k >= 0: + # swap the kth row with the ith row + if k < i: + A.swap_rows_c(i, k) + if transformation: + U.swap_rows_c(i, k) + k -= 1 + # put the row with the smalllest degree to the ith row + di = A.get_unsafe(i, j).degree() + while k >= 0 and A.get_unsafe(k, j): + dk = A.get_unsafe(k, j).degree() + if dk < di: + A.swap_rows_c(i, k) + di = dk + if transformation: + U.swap_rows_c(i, k) + k -= 1 + l = i - 1 + while True: + # find a row with nonzero entry in the jth column + while l >= 0 and not A.get_unsafe(l, j): + l -= 1 + if l < 0: + break + + a = A.get_unsafe(i, j) + b = A.get_unsafe(l, j) + d, p, q = a.xgcd(b) # p * a + q * b = d = gcd(a,b) + e = a // d + f = -(b // d) + + A.set_unsafe(i, j, d) + A.set_unsafe(l, j, zero) + + for c in range(j): + Aic = A.get_unsafe(i, c) + Alc = A.get_unsafe(l, c) + A.set_unsafe(i, c, p * Aic + q * Alc) + A.set_unsafe(l, c, f * Aic + e * Alc) + if transformation: + for c in range(m): + Uic = U.get_unsafe(i, c) + Ulc = U.get_unsafe(l, c) + U.set_unsafe(i, c, p * Uic + q * Ulc) + U.set_unsafe(l, c, f * Uic + e * Ulc) + pivot_cols.append(j) + i -= 1 + j -= 1 + + # reduce entries below pivots + for i in range(len(pivot_cols)): + j = pivot_cols[i] + ip = m - 1 - i + pivot = A.get_unsafe(ip, j) + + # normalize the leading coefficient to one + coeff = ~pivot.lc() + for c in range(j + 1): + A.set_unsafe(ip, c, A.get_unsafe(ip, c) * coeff) + if transformation: + for c in range(m): + U.set_unsafe(ip, c, U.get_unsafe(ip, c) * coeff) + + pivot = A.get_unsafe(ip,j) + for k in range(ip + 1, m): + q = -( A.get_unsafe(k, j) // pivot) + if q: + for c in range(j + 1): + A.set_unsafe(k, c, A.get_unsafe(k, c) + + q * A.get_unsafe(ip, c)) + if transformation: + for c in range(m): + U.set_unsafe(k, c, U.get_unsafe(k, c) + + q * U.get_unsafe(ip, c)) + + if transformation: + return U + diff --git a/src/sage/rings/function_field/ideal.py b/src/sage/rings/function_field/ideal.py index da29c8963c0..a4245d53be4 100644 --- a/src/sage/rings/function_field/ideal.py +++ b/src/sage/rings/function_field/ideal.py @@ -110,6 +110,8 @@ from .divisor import divisor +from .hermite_form_polynomial import reversed_hermite_form + class FunctionFieldIdeal(Element): """ @@ -1591,10 +1593,7 @@ def intersect(self, other): M = block_matrix([[I,I],[A,O],[O,B]]) # reversed Hermite form - M.reverse_rows_and_columns() - U = M._hermite_form_euclidean(transformation=True, - normalization=lambda p: ~p.lc()) - U.reverse_rows_and_columns() + U = reversed_hermite_form(M, transformation=True) vecs = [U[i][:n] for i in range(n)] diff --git a/src/sage/rings/function_field/order.py b/src/sage/rings/function_field/order.py index 58ae8bf092a..fd6a09ff9cb 100644 --- a/src/sage/rings/function_field/order.py +++ b/src/sage/rings/function_field/order.py @@ -137,6 +137,8 @@ FunctionFieldIdealInfinite_rational, FunctionFieldIdealInfinite_polymod) +from .hermite_form_polynomial import reversed_hermite_form + class FunctionFieldOrder_base(CachedRepresentation, Parent): """ @@ -1475,10 +1477,8 @@ def _ideal_from_vectors_and_denominator(self, vecs, d=1, check=True): # so that we get a unique hnf. Here the hermite form # algorithm also makes the pivots monic. - # compute the reverse hermite form with zero rows deleted - mat.reverse_rows_and_columns() - mat._hermite_form_euclidean(normalization=lambda p: ~p.lc()) - mat.reverse_rows_and_columns() + # compute the reversed hermite form with zero rows deleted + reversed_hermite_form(mat) i = 0 while i < mat.nrows() and mat.row(i).is_zero(): i += 1 @@ -2605,9 +2605,7 @@ def ideal_with_gens_over_base(self, gens): k = x * k h2 = block_matrix([[h],[k]]) - h2.reverse_rows_and_columns() - h2._hermite_form_euclidean(normalization=lambda p: ~p.lc()) - h2.reverse_rows_and_columns() + reversed_hermite_form(h2) i = 0 while i < h2.nrows() and h2.row(i).is_zero(): i += 1 diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 56cf1161d0e..14eec0c8c3b 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -5483,7 +5483,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): sage: 3._bnfisnorm(QuadraticField(-1, 'i')) (1, 3) sage: 7._bnfisnorm(CyclotomicField(7)) - (-zeta7^5 - zeta7^4 - 2*zeta7^3 - zeta7^2 - zeta7 - 1, 1) + (zeta7^5 - zeta7^2, 1) """ from sage.rings.rational_field import QQ return QQ(self)._bnfisnorm(K, proof=proof, extra_primes=extra_primes) diff --git a/src/sage/rings/integer_ring.pyx b/src/sage/rings/integer_ring.pyx index ae0b0f41327..2d4aa4eb8af 100644 --- a/src/sage/rings/integer_ring.pyx +++ b/src/sage/rings/integer_ring.pyx @@ -1467,6 +1467,20 @@ cdef class IntegerRing_class(PrincipalIdealDomain): """ return '"Integer"' + def _sympy_(self): + r""" + Return the SymPy set ``Integers``. + + EXAMPLES:: + + sage: ZZ._sympy_() + Integers + """ + from sympy import Integers + from sage.interfaces.sympy import sympy_init + sympy_init() + return Integers + def _sage_input_(self, sib, coerced): r""" Produce an expression which will reproduce this value when diff --git a/src/sage/rings/multi_power_series_ring.py b/src/sage/rings/multi_power_series_ring.py index 925b8842cd9..2b8639ed2a3 100644 --- a/src/sage/rings/multi_power_series_ring.py +++ b/src/sage/rings/multi_power_series_ring.py @@ -1045,9 +1045,7 @@ def _send_to_bg(self,f): f = self._poly_ring(f) except TypeError: raise TypeError("Cannot coerce input to polynomial ring.") - fg_to_bg_dict = dict((v,v*self._bg_ps_ring().gen()) - for v in self._poly_ring().gens()) - return self._bg_ps_ring(f.subs(fg_to_bg_dict)) + return self._bg_ps_ring(f.homogeneous_components()) def _send_to_fg(self,f): """ @@ -1072,7 +1070,7 @@ def _send_to_fg(self,f): sage: fg = M._send_to_fg(bg.add_bigoh(2)); fg 4 + 4*f0 + 4*f2 """ - return self._poly_ring(f.polynomial().subs({self._bg_indeterminate:1})) + return self._poly_ring_.sum(f.polynomial().coefficients()) def unpickle_multi_power_series_ring_v0(base_ring, num_gens, names, order, default_prec, sparse): diff --git a/src/sage/rings/number_field/S_unit_solver.py b/src/sage/rings/number_field/S_unit_solver.py index 5c0a33eb74f..cabc8eb3215 100644 --- a/src/sage/rings/number_field/S_unit_solver.py +++ b/src/sage/rings/number_field/S_unit_solver.py @@ -24,10 +24,10 @@ sage: from sage.rings.number_field.S_unit_solver import solve_S_unit_equation, eq_up_to_order sage: K. = NumberField(x^2+x+1) sage: S = K.primes_above(3) - sage: expected = [((2, 1), (4, 0), xi + 2, -xi - 1), - ....: ((5, -1), (4, -1), 1/3*xi + 2/3, -1/3*xi + 1/3), - ....: ((5, 0), (1, 0), -xi, xi + 1), - ....: ((1, 1), (2, 0), -xi + 1, xi)] + sage: expected = [((0, 1), (4, 0), xi + 2, -xi - 1), + ....: ((1, -1), (0, -1), 1/3*xi + 2/3, -1/3*xi + 1/3), + ....: ((1, 0), (5, 0), xi + 1, -xi), + ....: ((2, 0), (5, 1), xi, -xi + 1)] sage: sols = solve_S_unit_equation(K, S, 200) sage: eq_up_to_order(sols, expected) True @@ -55,7 +55,7 @@ from sage.rings.all import Infinity -from sage.calculus.var import var +from sage.symbolic.ring import SR from sage.rings.integer import Integer from sage.rings.integer_ring import ZZ from sage.rings.real_mpfr import RealField, RR @@ -675,7 +675,7 @@ def Yu_bound(SUK, v, prec=106): else: # K and v don't satisfy the theorem hypotheses, and we must move to a quadratic extension L. # For justification of this next bound, see [AKMRVW]. - x = var('x') + x = SR.var('x') if p == 2: L_over_K = K.extension(x**2 + x + 1, 'xi0') else: @@ -1422,11 +1422,11 @@ def defining_polynomial_for_Kp(prime, prec=106): # We are going to find which factor of f is related to the prime ideal 'prime' L = [g.change_ring(ZZ) for g, _ in factors] - A = [g for g in L if (g(theta)).valuation(prime) >= e*N/2]; + A = [g for g in L if (g(theta)).valuation(prime) >= e*N/2] # We narrow down the list unitl only one value remains - if len(A) == 1: + if len(A) == 1: return A[0].change_ring(Integers(p**prec)).change_ring(ZZ) else: N += 1 @@ -1780,20 +1780,20 @@ def sieve_ordering(SUK, q): sage: SUK = K.S_unit_group(S=3) sage: sieve_data = list(sieve_ordering(SUK, 19)) sage: sieve_data[0] - (Fractional ideal (-2*xi^2 + 3), - Fractional ideal (xi - 3), - Fractional ideal (2*xi + 1)) + (Fractional ideal (xi - 3), + Fractional ideal (-2*xi^2 + 3), + Fractional ideal (2*xi + 1)) sage: sieve_data[1] - (Residue field of Fractional ideal (-2*xi^2 + 3), - Residue field of Fractional ideal (xi - 3), - Residue field of Fractional ideal (2*xi + 1)) + (Residue field of Fractional ideal (xi - 3), + Residue field of Fractional ideal (-2*xi^2 + 3), + Residue field of Fractional ideal (2*xi + 1)) sage: sieve_data[2] - ([18, 9, 16, 8], [18, 7, 10, 4], [18, 3, 12, 10]) + ([18, 7, 16, 4], [18, 9, 12, 8], [18, 3, 10, 10]) sage: sieve_data[3] - (972, 972, 3888) + (486, 648, 11664) """ K = SUK.number_field() @@ -2654,10 +2654,10 @@ def sieve_below_bound(K, S, bound=10, bump=10, split_primes_list=[], verbose=Fal sage: S = SUK.primes() sage: sols = sieve_below_bound(K, S, 10) sage: expected = [ - ....: ((5, -1), (4, -1), 1/3*xi + 2/3, -1/3*xi + 1/3), - ....: ((2, 1), (4, 0), xi + 2, -xi - 1), - ....: ((2, 0), (1, 1), xi, -xi + 1), - ....: ((5, 0), (1, 0), -xi, xi + 1)] + ....: ((1, -1), (0, -1), 1/3*xi + 2/3, -1/3*xi + 1/3), + ....: ((0, 1), (4, 0), xi + 2, -xi - 1), + ....: ((2, 0), (5, 1), xi, -xi + 1), + ....: ((1, 0), (5, 0), xi + 1, -xi)] sage: eq_up_to_order(sols, expected) True """ @@ -2715,10 +2715,10 @@ def solve_S_unit_equation(K, S, prec=106, include_exponents=True, include_bound= sage: S = K.primes_above(3) sage: sols = solve_S_unit_equation(K, S, 200) sage: expected = [ - ....: ((2, 1), (4, 0), xi + 2, -xi - 1), - ....: ((5, -1), (4, -1), 1/3*xi + 2/3, -1/3*xi + 1/3), - ....: ((5, 0), (1, 0), -xi, xi + 1), - ....: ((1, 1), (2, 0), -xi + 1, xi)] + ....: ((0, 1), (4, 0), xi + 2, -xi - 1), + ....: ((1, -1), (0, -1), 1/3*xi + 2/3, -1/3*xi + 1/3), + ....: ((1, 0), (5, 0), xi + 1, -xi), + ....: ((2, 0), (5, 1), xi, -xi + 1)] sage: eq_up_to_order(sols, expected) True @@ -2726,7 +2726,7 @@ def solve_S_unit_equation(K, S, prec=106, include_exponents=True, include_bound= sage: solutions, bound = solve_S_unit_equation(K, S, 100, include_bound=True) sage: bound - 6 + 7 You can omit the exponent vectors:: diff --git a/src/sage/rings/number_field/class_group.py b/src/sage/rings/number_field/class_group.py index 46d0ca8c9d3..1ad6d583a8c 100644 --- a/src/sage/rings/number_field/class_group.py +++ b/src/sage/rings/number_field/class_group.py @@ -157,7 +157,7 @@ def __pow__(self, n): sage: C=K.class_group() sage: c = C(2, a) sage: c^2 - Fractional ideal class (2, a^2 + 2*a - 1) + Fractional ideal class (4, a) sage: c^3 Trivial principal fractional ideal class sage: c^1000 @@ -467,7 +467,7 @@ def _element_constructor_(self, *args, **kwds): sage: CK = K.class_group() sage: CL = L.class_group() sage: [CL(I).exponents() for I in CK] - [(0,), (4,), (2,)] + [(0,), (2,), (4,)] """ if isinstance(args[0], FractionalIdealClass): return self.element_class(self, None, self._number_field.ideal(args[0].ideal())) diff --git a/src/sage/rings/number_field/galois_group.py b/src/sage/rings/number_field/galois_group.py index 0335450b224..45d6bf21d4c 100644 --- a/src/sage/rings/number_field/galois_group.py +++ b/src/sage/rings/number_field/galois_group.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Galois Groups of Number Fields @@ -17,8 +18,8 @@ """ from sage.structure.sage_object import SageObject -from sage.groups.galois_group import _alg_key, GaloisGroup_perm -from sage.groups.perm_gps.permgroup import PermutationGroup_generic, standardize_generator +from sage.groups.galois_group import _alg_key, GaloisGroup_perm, GaloisSubgroup_perm +from sage.groups.perm_gps.permgroup import standardize_generator from sage.groups.perm_gps.permgroup_element import PermutationGroupElement from sage.misc.superseded import deprecation @@ -29,6 +30,7 @@ from sage.rings.number_field.number_field import refine_embedding from sage.rings.number_field.morphism import NumberFieldHomomorphism_im_gens from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ class GaloisGroup_v1(SageObject): @@ -215,6 +217,34 @@ class GaloisGroup_v2(GaloisGroup_perm): The 'arithmetical' features (decomposition and ramification groups, Artin symbols etc) are only available for Galois fields. + + EXAMPLES:: + + sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() + sage: G.subgroup([G([(1,2,3),(4,5,6)])]) + Subgroup generated by [(1,2,3)(4,5,6)] of (Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23) + + Subgroups can be specified using generators (:trac:`26816`):: + + sage: K. = NumberField(x^6 - 6*x^4 + 9*x^2 + 23) + sage: G = K.galois_group() + sage: list(G) + [(), + (1,2,3)(4,5,6), + (1,3,2)(4,6,5), + (1,4)(2,6)(3,5), + (1,5)(2,4)(3,6), + (1,6)(2,5)(3,4)] + sage: g = G[1] + sage: h = G[3] + sage: list(G.subgroup([])) + [()] + sage: list(G.subgroup([g])) + [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] + sage: list(G.subgroup([h])) + [(), (1,4)(2,6)(3,5)] + sage: sorted(G.subgroup([g,h])) == sorted(G) + True """ def __init__(self, number_field, algorithm='pari', names=None, gc_numbering=None, _type=None): @@ -712,40 +742,6 @@ def __iter__(self): """ return iter(self._elts) - def subgroup(self, elts): - r""" - Return the subgroup of self with the given elements. Mostly for internal use. - - EXAMPLES:: - - sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() - sage: G.subgroup([ G(1), G([(1,2,3),(4,5,6)]), G([(1,3,2),(4,6,5)]) ]) - Subgroup [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23 - - Subgroups can be specified using generators (:trac:`26816`):: - - sage: K. = NumberField(x^6 - 6*x^4 + 9*x^2 + 23) - sage: G = K.galois_group() - sage: list(G) - [(), - (1,2,3)(4,5,6), - (1,3,2)(4,6,5), - (1,4)(2,6)(3,5), - (1,5)(2,4)(3,6), - (1,6)(2,5)(3,4)] - sage: g = G[1] - sage: h = G[3] - sage: list(G.subgroup([])) - [()] - sage: list(G.subgroup([g])) - [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] - sage: list(G.subgroup([h])) - [(), (1,4)(2,6)(3,5)] - sage: list(G.subgroup([g,h])) == list(G) - True - """ - return GaloisGroup_subgroup(self, elts) - # Proper number theory starts here. All the functions below make no sense # unless the field is Galois. @@ -796,7 +792,7 @@ def decomposition_group(self, P): sage: P = K.ideal([17, a^2]) sage: G = K.galois_group() sage: G.decomposition_group(P) - Subgroup [(), (1,8)(2,7)(3,6)(4,5)] of Galois group 8T4 ([4]2) with order 8 of x^8 - 20*x^6 + 104*x^4 - 40*x^2 + 1156 + Subgroup generated by [(1,8)(2,7)(3,6)(4,5)] of (Galois group 8T4 ([4]2) with order 8 of x^8 - 20*x^6 + 104*x^4 - 40*x^2 + 1156) sage: G.decomposition_group(P^2) Traceback (most recent call last): ... @@ -818,9 +814,9 @@ def decomposition_group(self, P): if isinstance(P, NumberFieldHomomorphism_im_gens): if self.number_field().is_totally_real(): - return self.subgroup([self.identity()]) + return self.subgroup([]) else: - return self.subgroup([self.identity(), self.complex_conjugation(P)]) + return self.subgroup([self.complex_conjugation(P)]) else: return self.ramification_group(P, -1) @@ -879,9 +875,9 @@ def ramification_group(self, P, v): sage: G=K.galois_group() sage: P = K.primes_above(3)[0] sage: G.ramification_group(P, 3) - Subgroup [(), (1,2,4)(3,5,6), (1,4,2)(3,6,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 + 243 + Subgroup generated by [(1,2,4)(3,5,6)] of (Galois group 6T2 ([3]2) with order 6 of x^6 + 243) sage: G.ramification_group(P, 5) - Subgroup [()] of Galois group 6T2 ([3]2) with order 6 of x^6 + 243 + Subgroup generated by [()] of (Galois group 6T2 ([3]2) with order 6 of x^6 + 243) """ if not self.is_galois(): raise TypeError("Ramification groups only defined for Galois extensions") @@ -903,9 +899,9 @@ def inertia_group(self, P): sage: K. = NumberField(x^2 - 3,'a') sage: G = K.galois_group() sage: G.inertia_group(K.primes_above(2)[0]) - Subgroup [(), (1,2)] of Galois group 2T1 (S2) with order 2 of x^2 - 3 + Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 - 3) sage: G.inertia_group(K.primes_above(5)[0]) - Subgroup [()] of Galois group 2T1 (S2) with order 2 of x^2 - 3 + Subgroup generated by [()] of (Galois group 2T1 (S2) with order 2 of x^2 - 3) """ if not self.is_galois(): raise TypeError("Inertia groups only defined for Galois extensions") @@ -977,72 +973,90 @@ def artin_symbol(self, P): raise ValueError("%s is ramified" % P) return t[0] -class GaloisGroup_subgroup(GaloisGroup_v2): +class GaloisGroup_subgroup(GaloisSubgroup_perm): r""" A subgroup of a Galois group, as returned by functions such as ``decomposition_group``. - """ - def __init__(self, ambient, elts): - r""" - Return the subgroup of this Galois group generated by the - given elements. + INPUT: - It is generally better to use the :meth:`subgroup` method of - the parent group. + - ``ambient`` -- the ambient Galois group - EXAMPLES:: + - ``gens`` -- a list of generators for the group - sage: from sage.rings.number_field.galois_group import GaloisGroup_subgroup - sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() - sage: GaloisGroup_subgroup( G, [ G(1), G([(1,2,3),(4,5,6)]), G([(1,3,2),(4,6,5)])]) - Subgroup [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23 + - ``gap_group`` -- a gap or libgap permutation group, or a string + defining one (default: ``None``) - TESTS: + - ``domain`` -- a set on which this permutation group acts; extracted from ``ambient`` if not specified - Check that :trac:`17664` is fixed:: + - ``category`` -- the category for this object - sage: L. = QuadraticField(-1) - sage: P = L.primes_above(5)[0] - sage: G = L.galois_group() - sage: H = G.decomposition_group(P) - sage: H.domain() - {1, 2} - sage: G.artin_symbol(P) - () - """ - # XXX This should be fixed so that this can use GaloisGroup_v2.__init__ - PermutationGroup_generic.__init__(self, elts, canonicalize=True, - domain=ambient.domain()) - self._ambient = ambient - self._field = ambient.number_field() - self._galois_closure = ambient._galois_closure - self._pari_data = ambient._pari_data - self._gc_map = ambient._gc_map - self._default_algorithm = ambient._default_algorithm - self._type = None # Backward compatibility - self._elts = sorted(self.iteration()) + - ``canonicalize`` -- if true, sorts and removes duplicates - def order(self): + - ``check`` -- whether to check that generators actually lie in the ambient group + + EXAMPLES:: + + sage: from sage.rings.number_field.galois_group import GaloisGroup_subgroup + sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() + sage: GaloisGroup_subgroup( G, [G([(1,2,3),(4,5,6)])]) + Subgroup generated by [(1,2,3)(4,5,6)] of (Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23) + + sage: K. = NumberField(x^6-3*x^2-1) + sage: L. = K.galois_closure() + sage: G = L.galois_group() + sage: P = L.primes_above(3)[0] + sage: H = G.decomposition_group(P) + sage: H.order() + 3 + + sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() + sage: H = G.subgroup([G([(1,2,3),(4,5,6)])]) + sage: H + Subgroup generated by [(1,2,3)(4,5,6)] of (Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23) + + TESTS: + + Check that :trac:`17664` is fixed:: + + sage: L. = QuadraticField(-1) + sage: P = L.primes_above(5)[0] + sage: G = L.galois_group() + sage: H = G.decomposition_group(P) + sage: H.domain() + {1, 2} + sage: G.artin_symbol(P) + () + """ + @lazy_attribute + def _pari_data(self): """ - Return the order of this subgroup. + Access to Pari information for the ambient Galois group. EXAMPLES:: - sage: K. = NumberField(x^6-3*x^2-1) - sage: L. = K.galois_closure() + sage: L. = NumberField(x^4 + 1) sage: G = L.galois_group() - sage: P = L.primes_above(3)[0] - sage: H = G.decomposition_group(P) - sage: H.order() - 3 + sage: H = G.decomposition_group(L.primes_above(3)[0]) + sage: H._pari_data + [y^4 + 1, ...] """ - return ZZ(len(self._elts)) + return self._ambient_group._pari_data - def fixed_field(self): + def fixed_field(self, name=None, polred=None, threshold=None): r""" Return the fixed field of this subgroup (as a subfield of the Galois closure of the number field associated to the ambient Galois group). + INPUT: + + - ``name`` -- a variable name for the new field. + + - ``polred`` -- whether to optimize the generator of the newly created field + for a simpler polynomial, using pari's polredbest. + Defaults to ``True`` when the degree of the fixed field is at most 8. + + - ``threshold`` -- positive number; polred only performed if the cost is at most this threshold + EXAMPLES:: sage: L. = NumberField(x^4 + 1) @@ -1055,35 +1069,60 @@ def fixed_field(self): To: Number Field in a with defining polynomial x^4 + 1 Defn: a0 |--> a^3 + a) - An embedding is returned also if the subgroup is trivial - (:trac:`26817`):: + You can use the ``polred`` option to get a simpler defining polynomial:: - sage: H = G.subgroup([G.identity()]) - sage: H.fixed_field() - (Number Field in a0 with defining polynomial x^4 + 1 with a0 = a, + sage: K. = NumberField(x^5 - 5*x^2 - 3) + sage: G = K.galois_group(); G + Galois group 5T2 (5:2) with order 10 of x^5 - 5*x^2 - 3 + sage: sigma, tau = G.gens() + sage: H = G.subgroup([tau]) + sage: H.fixed_field(polred=False) + (Number Field in a0 with defining polynomial x^2 + 84375 with a0 = 5*ac^5 + 25*ac^3, Ring morphism: - From: Number Field in a0 with defining polynomial x^4 + 1 with a0 = a - To: Number Field in a with defining polynomial x^4 + 1 - Defn: a0 |--> a) - """ - vecs = [pari(g.domain()).Vecsmall() for g in self._elts] - v = self._ambient._pari_data.galoisfixedfield(vecs) - x = self._galois_closure(v[1]) - return self._galois_closure.subfield(x) - - def _repr_(self): - r""" - String representation of self. + From: Number Field in a0 with defining polynomial x^2 + 84375 with a0 = 5*ac^5 + 25*ac^3 + To: Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375 + Defn: a0 |--> 5*ac^5 + 25*ac^3) + sage: H.fixed_field(polred=True) + (Number Field in a0 with defining polynomial x^2 - x + 4 with a0 = -1/30*ac^5 - 1/6*ac^3 + 1/2, + Ring morphism: + From: Number Field in a0 with defining polynomial x^2 - x + 4 with a0 = -1/30*ac^5 - 1/6*ac^3 + 1/2 + To: Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375 + Defn: a0 |--> -1/30*ac^5 - 1/6*ac^3 + 1/2) + sage: G.splitting_field() + Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375 - EXAMPLES:: - sage: G = NumberField(x^3 - x - 1, 'a').galois_closure('b').galois_group() - sage: H = G.subgroup([ G(1), G([(1,2,3),(4,5,6)]), G([(1,3,2),(4,6,5)])]) - sage: H._repr_() - 'Subgroup [(), (1,2,3)(4,5,6), (1,3,2)(4,6,5)] of Galois group 6T2 ([3]2) with order 6 of x^6 - 6*x^4 + 9*x^2 + 23' - """ - return "Subgroup %s of %s" % (self._elts, self._ambient) + An embedding is returned also if the subgroup is trivial + (:trac:`26817`):: + sage: H = G.subgroup([]) + sage: H.fixed_field() + (Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375, + Identity endomorphism of Number Field in ac with defining polynomial x^10 + 10*x^8 + 25*x^6 + 3375) + """ + G = self._ambient_group + L = G._galois_closure + if self.order() == G.order(): + return QQ, L.coerce_map_from(QQ) + elif self.order() == 1: + return L, L.coerce_map_from(L) + vecs = [pari(g.domain()).Vecsmall() for g in self.iteration()] + v = G._pari_data.galoisfixedfield(vecs) + x = v[1] + if polred is None: + index = G.order() // self.order() + polred = (index <= 8) + if polred: + f = x.minpoly() + bitsize = ZZ(QQ(f[0]).numerator().nbits() + QQ(f[0]).denominator().nbits()) + cost = 2 * bitsize.nbits() + 5 * ZZ(f.poldegree()).nbits() + # time(polredbest) ≈ b²d⁵ + if threshold is None or cost <= threshold: + f, elt_back = f.polredbest(flag=1) + x = elt_back.modreverse().lift()(x) + if name is None: + name = G._field.variable_name() + '0' + return L.subfield(x, name=name) class GaloisGroupElement(PermutationGroupElement): r""" @@ -1186,6 +1225,8 @@ def ramification_degree(self, P): return min(w) GaloisGroup_v2.Element = GaloisGroupElement +GaloisGroup_v2.Subgroup = GaloisGroup_subgroup +GaloisGroup_subgroup.Element = GaloisGroupElement # For unpickling purposes we rebind GaloisGroup as GaloisGroup_v1. diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index fe02c6e8242..03d38c67168 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -3592,7 +3592,7 @@ def fractional_ideal(self, *gens, **kwds): sage: L. = K.extension(x^2 - 3, x^2 + 1) sage: M. = L.extension(x^2 + 1) sage: L.ideal(K.ideal(2, a)) - Fractional ideal (a) + Fractional ideal (-a) sage: M.ideal(K.ideal(2, a)) == M.ideal(a*(b - c)/2) True @@ -4760,7 +4760,7 @@ def _S_class_group_and_units(self, S, proof=True): 1/13*a^2 + 7/13*a - 332/13, -1/13*a^2 + 6/13*a + 345/13, -1, - 2/13*a^2 + 1/13*a - 755/13] + -2/13*a^2 - 1/13*a + 755/13] sage: units[5] in (1/13*a^2 - 19/13*a - 7/13, 1/13*a^2 + 20/13*a - 7/13) True sage: len(units) == 6 @@ -4802,8 +4802,8 @@ def _S_class_group_quotient_matrix(self, S): sage: K. = QuadraticField(-105) sage: K._S_class_group_quotient_matrix((K.ideal(11, a + 4),)) [0 0] - [1 0] [0 1] + [1 0] TESTS: @@ -4948,7 +4948,7 @@ def selmer_generators(self, S, m, proof=True, orders=False): 1/13*a^2 + 7/13*a - 332/13, -1/13*a^2 + 6/13*a + 345/13, -1, - 2/13*a^2 + 1/13*a - 755/13] + -2/13*a^2 - 1/13*a + 755/13] sage: gens[5] in (1/13*a^2 - 19/13*a - 7/13, 1/13*a^2 + 20/13*a - 7/13) True sage: gens[6] in (-1/13*a^2 + 45/13*a - 97/13, 1/13*a^2 - 45/13*a + 97/13) @@ -6234,28 +6234,37 @@ def _pari_integral_basis(self, v=None, important=True): try: return self._integral_basis_dict[v] except (AttributeError, KeyError): - f = self.pari_polynomial("y") - if v: - B = f.nfbasis(fa=v) - elif self._assume_disc_small: - B = f.nfbasis(1) - elif not important: - # Trial divide the discriminant with primes up to 10^6 - m = self.pari_polynomial().poldisc().abs().factor(limit=10**6) - # Since we only need a *squarefree* factorization for - # primes with exponent 1, we need trial division up to D^(1/3) - # instead of D^(1/2). - trialdivlimit2 = pari(10**12) - trialdivlimit3 = pari(10**18) - if all(p < trialdivlimit2 or (e == 1 and p < trialdivlimit3) or p.isprime() for p, e in zip(m[0], m[1])): - B = f.nfbasis(fa = m) - else: - raise RuntimeError("Unable to factor discriminant with trial division") + pass + + f = self.pari_polynomial("y") + if v: + # NOTE: here we make pari know about potentially big primes factors of + # the discriminant, see + # https://pari.math.u-bordeaux.fr/cgi-bin/bugreport.cgi?bug=2257 + primelimit = pari.default("primelimit") + primes = [p for p in v if p > primelimit] + if primes: + pari.addprimes(primes) + B = f.nfbasis(fa=v) + elif self._assume_disc_small: + B = f.nfbasis(1) + elif not important: + # Trial divide the discriminant with primes up to 10^6 + m = self.pari_polynomial().poldisc().abs().factor(limit=10**6) + # Since we only need a *squarefree* factorization for + # primes with exponent 1, we need trial division up to D^(1/3) + # instead of D^(1/2). + trialdivlimit2 = pari(10**12) + trialdivlimit3 = pari(10**18) + if all(p < trialdivlimit2 or (e == 1 and p < trialdivlimit3) or p.isprime() for p, e in zip(m[0], m[1])): + B = f.nfbasis(fa = m) else: - B = f.nfbasis() + raise RuntimeError("Unable to factor discriminant with trial division") + else: + B = f.nfbasis() - self._integral_basis_dict[v] = B - return B + self._integral_basis_dict[v] = B + return B def reduced_basis(self, prec=None): r""" @@ -6882,7 +6891,7 @@ def units(self, proof=None): sage: A = x^4 - 10*x^3 + 20*5*x^2 - 15*5^2*x + 11*5^3 sage: K = NumberField(A, 'a') sage: K.units() - (8/275*a^3 - 12/55*a^2 + 15/11*a - 3,) + (-1/275*a^3 - 4/55*a^2 + 5/11*a - 3,) For big number fields, provably computing the unit group can take a very long time. In this case, one can ask for the @@ -6893,14 +6902,14 @@ def units(self, proof=None): sage: K.units(proof=True) # takes forever, not tested ... sage: K.units(proof=False) # result not independently verified - (a^9 + a - 1, - a^15 - a^12 + a^10 - a^9 - 2*a^8 + 3*a^7 + a^6 - 3*a^5 + a^4 + 4*a^3 - 3*a^2 - 2*a + 2, - a^16 - a^15 + a^14 - a^12 + a^11 - a^10 - a^8 + a^7 - 2*a^6 + a^4 - 3*a^3 + 2*a^2 - 2*a + 1, + (-a^9 - a + 1, + -a^16 + a^15 - a^14 + a^12 - a^11 + a^10 + a^8 - a^7 + 2*a^6 - a^4 + 3*a^3 - 2*a^2 + 2*a - 1, 2*a^16 - a^14 - a^13 + 3*a^12 - 2*a^10 + a^9 + 3*a^8 - 3*a^6 + 3*a^5 + 3*a^4 - 2*a^3 - 2*a^2 + 3*a + 4, - 2*a^16 - 3*a^15 + 3*a^14 - 3*a^13 + 3*a^12 - a^11 + a^9 - 3*a^8 + 4*a^7 - 5*a^6 + 6*a^5 - 4*a^4 + 3*a^3 - 2*a^2 - 2*a + 4, - a^16 - a^15 - 3*a^14 - 4*a^13 - 4*a^12 - 3*a^11 - a^10 + 2*a^9 + 4*a^8 + 5*a^7 + 4*a^6 + 2*a^5 - 2*a^4 - 6*a^3 - 9*a^2 - 9*a - 7, a^15 + a^14 + 2*a^11 + a^10 - a^9 + a^8 + 2*a^7 - a^5 + 2*a^3 - a^2 - 3*a + 1, - 5*a^16 - 6*a^14 + a^13 + 7*a^12 - 2*a^11 - 7*a^10 + 4*a^9 + 7*a^8 - 6*a^7 - 7*a^6 + 8*a^5 + 6*a^4 - 11*a^3 - 5*a^2 + 13*a + 4) + -a^16 - a^15 - a^14 - a^13 - a^12 - a^11 - a^10 - a^9 - a^8 - a^7 - a^6 - a^5 - a^4 - a^3 - a^2 + 2, + -2*a^16 + 3*a^15 - 3*a^14 + 3*a^13 - 3*a^12 + a^11 - a^9 + 3*a^8 - 4*a^7 + 5*a^6 - 6*a^5 + 4*a^4 - 3*a^3 + 2*a^2 + 2*a - 4, + a^15 - a^12 + a^10 - a^9 - 2*a^8 + 3*a^7 + a^6 - 3*a^5 + a^4 + 4*a^3 - 3*a^2 - 2*a + 2, + -a^14 - a^13 + a^12 + 2*a^10 + a^8 - 2*a^7 - 2*a^6 + 2*a^3 - a^2 + 2*a - 2) TESTS: @@ -6909,7 +6918,7 @@ def units(self, proof=None): sage: K. = NumberField(1/2*x^2 - 1/6) sage: K.units() - (3*a - 2,) + (-3*a + 2,) """ proof = proof_flag(proof) @@ -6988,7 +6997,7 @@ def unit_group(self, proof=None): sage: U.gens() (u0, u1, u2, u3, u4, u5, u6, u7, u8) sage: U.gens_values() # result not independently verified - [-1, a^9 + a - 1, a^15 - a^12 + a^10 - a^9 - 2*a^8 + 3*a^7 + a^6 - 3*a^5 + a^4 + 4*a^3 - 3*a^2 - 2*a + 2, a^16 - a^15 + a^14 - a^12 + a^11 - a^10 - a^8 + a^7 - 2*a^6 + a^4 - 3*a^3 + 2*a^2 - 2*a + 1, 2*a^16 - a^14 - a^13 + 3*a^12 - 2*a^10 + a^9 + 3*a^8 - 3*a^6 + 3*a^5 + 3*a^4 - 2*a^3 - 2*a^2 + 3*a + 4, 2*a^16 - 3*a^15 + 3*a^14 - 3*a^13 + 3*a^12 - a^11 + a^9 - 3*a^8 + 4*a^7 - 5*a^6 + 6*a^5 - 4*a^4 + 3*a^3 - 2*a^2 - 2*a + 4, a^16 - a^15 - 3*a^14 - 4*a^13 - 4*a^12 - 3*a^11 - a^10 + 2*a^9 + 4*a^8 + 5*a^7 + 4*a^6 + 2*a^5 - 2*a^4 - 6*a^3 - 9*a^2 - 9*a - 7, a^15 + a^14 + 2*a^11 + a^10 - a^9 + a^8 + 2*a^7 - a^5 + 2*a^3 - a^2 - 3*a + 1, 5*a^16 - 6*a^14 + a^13 + 7*a^12 - 2*a^11 - 7*a^10 + 4*a^9 + 7*a^8 - 6*a^7 - 7*a^6 + 8*a^5 + 6*a^4 - 11*a^3 - 5*a^2 + 13*a + 4] + [-1, -a^9 - a + 1, -a^16 + a^15 - a^14 + a^12 - a^11 + a^10 + a^8 - a^7 + 2*a^6 - a^4 + 3*a^3 - 2*a^2 + 2*a - 1, 2*a^16 - a^14 - a^13 + 3*a^12 - 2*a^10 + a^9 + 3*a^8 - 3*a^6 + 3*a^5 + 3*a^4 - 2*a^3 - 2*a^2 + 3*a + 4, a^15 + a^14 + 2*a^11 + a^10 - a^9 + a^8 + 2*a^7 - a^5 + 2*a^3 - a^2 - 3*a + 1, -a^16 - a^15 - a^14 - a^13 - a^12 - a^11 - a^10 - a^9 - a^8 - a^7 - a^6 - a^5 - a^4 - a^3 - a^2 + 2, -2*a^16 + 3*a^15 - 3*a^14 + 3*a^13 - 3*a^12 + a^11 - a^9 + 3*a^8 - 4*a^7 + 5*a^6 - 6*a^5 + 4*a^4 - 3*a^3 + 2*a^2 + 2*a - 4, a^15 - a^12 + a^10 - a^9 - 2*a^8 + 3*a^7 + a^6 - 3*a^5 + a^4 + 4*a^3 - 3*a^2 - 2*a + 2, -a^14 - a^13 + a^12 + 2*a^10 + a^8 - 2*a^7 - 2*a^6 + 2*a^3 - a^2 + 2*a - 2] """ proof = proof_flag(proof) @@ -7177,7 +7186,7 @@ def S_unit_solutions(self, S=[], prec=106, include_exponents=False, include_boun sage: solutions, bound = K.S_unit_solutions(S, prec=100, include_bound=True) sage: bound - 6 + 7 """ from .S_unit_solver import solve_S_unit_equation return solve_S_unit_equation(self, S, prec, include_exponents, include_bound, proof) @@ -10297,7 +10306,7 @@ def __init__(self, n, names, embedding=None, assume_disc_small=False, maximize_a :: sage: type(CyclotomicField(4).zero()) - + sage: type(CyclotomicField(6).one()) sage: type(CyclotomicField(6).an_element()) @@ -10318,8 +10327,8 @@ def __init__(self, n, names, embedding=None, assume_disc_small=False, maximize_a embedding = embedding, assume_disc_small=assume_disc_small, maximize_at_primes=maximize_at_primes) - if n%2: - self.__zeta_order = 2*n + if n % 2: + self.__zeta_order = 2 * n else: self.__zeta_order = n ## quadratic number fields require this: @@ -10335,12 +10344,13 @@ def __init__(self, n, names, embedding=None, assume_disc_small=False, maximize_a self._standard_embedding = not CDF.has_coerce_map_from(self) or CDF(self.gen()).imag() > 0 self._cache_an_element = None - self._element_class = number_field_element_quadratic.NumberFieldElement_quadratic if n == 4: + self._element_class = number_field_element_quadratic.NumberFieldElement_gaussian self._D = ZZ(-1) self._NumberField_generic__gen = self._element_class(self, (QQ(0), QQ(1))) else: ## n is 3 or 6 + self._element_class = number_field_element_quadratic.NumberFieldElement_quadratic self._D = ZZ(-3) one_half = ZZ(1)/ZZ(2) if n == 3: @@ -10997,9 +11007,9 @@ def _coerce_from_gap(self, x): EXAMPLES:: sage: k5. = CyclotomicField(5) - sage: gap('E(5)^7 + 3') + sage: w = libgap.eval('E(5)^7 + 3') + sage: w -3*E(5)-2*E(5)^2-3*E(5)^3-3*E(5)^4 - sage: w = gap('E(5)^7 + 3') sage: z^7 + 3 z^2 + 3 sage: k5(w) # indirect doctest @@ -11010,7 +11020,7 @@ def _coerce_from_gap(self, x): sage: F = CyclotomicField(8) sage: z = F.gen() - sage: a = gap(z+1/z); a + sage: a = libgap(z+1/z); a E(8)-E(8)^3 sage: F(a) -zeta8^3 + zeta8 @@ -11024,6 +11034,7 @@ def _coerce_from_gap(self, x): It also works with the old pexpect interface to GAP:: + sage: a = gap(z + 1/z) sage: b = gap(Matrix(F,[[z^2,1],[0,a+1]])); b [ [ E(4), 1 ], [ 0, 1+E(8)-E(8)^3 ] ] sage: b[1,2] @@ -11610,9 +11621,9 @@ def __init__(self, polynomial, name=None, latex_name=None, check=True, embedding sage: k. = QuadraticField(7) sage: type(k.zero()) - + sage: type(k.one()) - + sage: TestSuite(k).run() @@ -11627,16 +11638,22 @@ def __init__(self, polynomial, name=None, latex_name=None, check=True, embedding embedding=embedding, latex_name=latex_name, assume_disc_small=assume_disc_small, maximize_at_primes=maximize_at_primes, structure=structure) self._standard_embedding = True + + # set the generator and element class c, b, a = [QQ(t) for t in self.defining_polynomial().list()] - if a.is_one() and b.is_zero() and c.is_one(): - self._element_class = number_field_element_quadratic.NumberFieldElement_gaussian - else: - self._element_class = number_field_element_quadratic.NumberFieldElement_quadratic - # set the generator Dpoly = b*b - 4*a*c D = (Dpoly.numer() * Dpoly.denom()).squarefree_part(bound=10000) self._D = D parts = -b/(2*a), (Dpoly/D).sqrt()/(2*a) + + if a.is_one() and b.is_zero() and c.is_one(): + self._element_class = number_field_element_quadratic.NumberFieldElement_gaussian + else: + if number_field_element_quadratic.is_sqrt_disc(parts[0], parts[1]): + self._element_class = number_field_element_quadratic.NumberFieldElement_quadratic_sqrt + else: + self._element_class = number_field_element_quadratic.NumberFieldElement_quadratic + self._NumberField_generic__gen = self._element_class(self, parts) # we must set the flag _standard_embedding *before* any element creation diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 29932830f3c..3736c708044 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -357,7 +357,7 @@ cdef class NumberFieldElement(FieldElement): sage: cf4(one) 1 sage: type(cf4(1)) - + sage: cf33 = CyclotomicField(33) ; z33 = cf33.0 sage: cf66 = CyclotomicField(66) ; z66 = cf66.0 sage: z33._lift_cyclotomic_element(cf66) @@ -756,11 +756,11 @@ cdef class NumberFieldElement(FieldElement): return repr(self.__pari__(name=name)) def __getitem__(self, n): - """ - Return the n-th coefficient of this number field element, written + r""" + Return the ``n``-th coefficient of this number field element, written as a polynomial in the generator. - Note that `n` must be between 0 and `d-1`, where + Note that ``n`` must be between `0` and `d-1`, where `d` is the degree of the number field. EXAMPLES:: @@ -780,11 +780,11 @@ cdef class NumberFieldElement(FieldElement): sage: c[-1] Traceback (most recent call last): ... - IndexError: index must be between 0 and degree minus 1. + IndexError: index must be between 0 and degree minus 1 sage: c[4] Traceback (most recent call last): ... - IndexError: index must be between 0 and degree minus 1. + IndexError: index must be between 0 and degree minus 1 The list method implicitly calls ``__getitem__``:: @@ -794,8 +794,11 @@ cdef class NumberFieldElement(FieldElement): True """ if n < 0 or n >= self.number_field().degree(): # make this faster. - raise IndexError("index must be between 0 and degree minus 1.") - return self.polynomial()[n] + raise IndexError("index must be between 0 and degree minus 1") + cdef list coeffs = self._coefficients() + if n >= len(coeffs): + return QQ.zero() + return coeffs[n] cpdef _richcmp_(left, right, int op): r""" @@ -1632,7 +1635,7 @@ cdef class NumberFieldElement(FieldElement): sage: Q. = K[] sage: L. = NumberField(X^4 + a) sage: t = (-a).is_norm(L, element=True); t - (True, b^3 + 1) + (True, -b^3 - 1) sage: t[1].norm(K) -a @@ -1747,11 +1750,11 @@ cdef class NumberFieldElement(FieldElement): sage: Q. = K[] sage: L. = NumberField(X^4 + a) sage: t = (-a)._rnfisnorm(L); t - (b^3 + 1, 1) + (-b^3 - 1, 1) sage: t[0].norm(K) -a sage: t = K(3)._rnfisnorm(L); t - (-b^3 - a*b^2 - a^2*b + 1, 3*a^2 - 3*a + 6) + (b^3 + a*b^2 + a^2*b - 1, 3*a^2 - 3*a + 6) sage: t[0].norm(K)*t[1] 3 @@ -2494,9 +2497,9 @@ cdef class NumberFieldElement(FieldElement): x._reduce_c_() return x - #NOTES: In LiDIA, they build a multiplication table for the - #number field, so it's not necessary to reduce modulo the - #defining polynomial every time: + # NOTE: In LiDIA, they build a multiplication table for the + # number field, so it's not necessary to reduce modulo the + # defining polynomial every time: # src/number_fields/algebraic_num/order.cc: compute_table # but asymptotically fast poly multiplication means it's # actually faster to *not* build a table!?! @@ -4492,7 +4495,7 @@ cdef class NumberFieldElement(FieldElement): This means the different with respect to the base field `\QQ`. - EXAMPLE:: + EXAMPLES:: sage: K. = NumberFieldTower([x^2 - 17, x^3 - 2]) sage: a.absolute_different() @@ -4504,6 +4507,7 @@ cdef class NumberFieldElement(FieldElement): """ return self.different(K=QQ) + cdef class NumberFieldElement_absolute(NumberFieldElement): def _magma_init_(self, magma): @@ -5524,5 +5528,3 @@ cdef void _ntl_poly(f, ZZX_c *num, ZZ_c *den): for i from 0 <= i <= __num.degree(): mpz_to_ZZ(&coeff, (ZZ(__num[i])).value) ZZX_SetCoeff( num[0], i, coeff ) - - diff --git a/src/sage/rings/number_field/number_field_element_quadratic.pxd b/src/sage/rings/number_field/number_field_element_quadratic.pxd index 6344ceec552..7a4f063de73 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pxd +++ b/src/sage/rings/number_field/number_field_element_quadratic.pxd @@ -1,6 +1,7 @@ from sage.libs.gmp.types cimport mpz_t from sage.libs.arb.types cimport arb_t from sage.rings.integer cimport Integer +from sage.rings.rational cimport Rational from .number_field_element cimport NumberFieldElement, NumberFieldElement_absolute @@ -10,7 +11,6 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): cdef Integer D cdef bint standard_embedding cpdef NumberFieldElement galois_conjugate(self) - cdef bint is_sqrt_disc(self) cpdef list _coefficients(self) @@ -18,5 +18,17 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): cdef int arb_set_real(self, arb_t x, long prec) except -1 cdef void arb_set_imag(self, arb_t x, long prec) + cpdef tuple parts(self) + +cdef class NumberFieldElement_quadratic_sqrt(NumberFieldElement_quadratic): + pass + +cdef class NumberFieldElement_gaussian(NumberFieldElement_quadratic_sqrt): + cpdef real_part(self) + cpdef imag_part(self) + cdef class OrderElement_quadratic(NumberFieldElement_quadratic): pass + +cpdef bint is_sqrt_disc(Rational ad, Rational bd) + diff --git a/src/sage/rings/number_field/number_field_element_quadratic.pyx b/src/sage/rings/number_field/number_field_element_quadratic.pyx index 203f7520fe1..f1ad9519b54 100644 --- a/src/sage/rings/number_field/number_field_element_quadratic.pyx +++ b/src/sage/rings/number_field/number_field_element_quadratic.pyx @@ -112,6 +112,7 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): TESTS:: sage: from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_quadratic + sage: from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_quadratic_sqrt We set up some fields:: @@ -125,14 +126,14 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): We construct elements of these fields in various ways - firstly, from polynomials:: - sage: NumberFieldElement_quadratic(K, x-1) + sage: NumberFieldElement_quadratic_sqrt(K, x-1) a - 1 sage: NumberFieldElement_quadratic(F, x-1) b - 1 From triples of Integers:: - sage: NumberFieldElement_quadratic(K, (1,2,3)) + sage: NumberFieldElement_quadratic_sqrt(K, (1,2,3)) 2/3*a + 1/3 sage: NumberFieldElement_quadratic(F, (1,2,3)) 4/9*b + 1/9 @@ -141,7 +142,7 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): From pairs of Rationals:: - sage: NumberFieldElement_quadratic(K, (1/2,1/3)) + sage: NumberFieldElement_quadratic_sqrt(K, (1/2,1/3)) 1/3*a + 1/2 sage: NumberFieldElement_quadratic(F, (1/2,1/3)) 2/9*b + 7/18 @@ -150,7 +151,7 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): Direct from Rationals:: - sage: NumberFieldElement_quadratic(K, 2/3) + sage: NumberFieldElement_quadratic_sqrt(K, 2/3) 2/3 sage: NumberFieldElement_quadratic(F, 2/3) 2/3 @@ -171,7 +172,7 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): sage: F. = QuadraticField(-7) sage: c = a + 7 sage: type(c) # indirect doctest - + """ self.D = parent._D cdef Integer a, b, denom @@ -602,7 +603,7 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): def _real_mpfi_(self, R): r""" - Conversion to a real interval field + Conversion to a real interval field. TESTS:: @@ -661,7 +662,7 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): def _complex_mpfi_(self, R): r""" - Conversion to a complex interval field + Conversion to a complex interval field. TESTS:: @@ -867,8 +868,8 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): self.arb_set_imag(acb_imagref(res.value), R._prec) return res - def parts(self): - """ + cpdef tuple parts(self): + r""" This function returns a pair of rationals `a` and `b` such that self `= a+b\sqrt{D}`. @@ -912,19 +913,7 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): mpz_set(mpq_denref(bd.value), self.denom) mpq_canonicalize(bd.value) - return ad, bd - - cdef bint is_sqrt_disc(self): - r""" - Returns true if self is `\sqrt{D}`. - - EXAMPLES:: - - sage: F. = NumberField(x^2 - x + 7) - sage: b.denominator() # indirect doctest - 1 - """ - return mpz_cmp_ui(self.denom, 1)==0 and mpz_cmp_ui(self.a, 0)==0 and mpz_cmp_ui(self.b, 1)==0 + return (ad, bd) ######################################################### # Comparisons @@ -1726,9 +1715,9 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): sage: K.gen().is_one() False """ - return mpz_cmp_ui(self.a, 1) == 0 and \ - mpz_cmp_ui(self.b, 0) == 0 and \ - mpz_cmp_ui(self.denom, 1) == 0 + return (mpz_cmp_ui(self.a, 1) == 0 and + mpz_cmp_ui(self.b, 0) == 0 and + mpz_cmp_ui(self.denom, 1) == 0) cpdef bint is_rational(self): r""" @@ -1782,8 +1771,8 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): def real(self): r""" - Returns the real part of self, which is either self (if self lives - it a totally real field) or a rational number. + Return the real part of ``self``, which is either ``self`` (if + ``self`` lives in a totally real field) or a rational number. EXAMPLES:: @@ -1816,7 +1805,7 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): def imag(self): r""" - Returns the imaginary part of self. + Return the imaginary part of ``self``. EXAMPLES:: @@ -1905,52 +1894,32 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): """ EXAMPLES:: - sage: K. = NumberField(x^2+41) - sage: a._coefficients() - [0, 1] - sage: K. = NumberField(x^2+x+41) - sage: a._coefficients() - [0, 1] - sage: b = 3*a+1/5 + sage: F. = NumberField(x^2 - x + 7) sage: b._coefficients() - [1/5, 3] + [0, 1] """ # In terms of the generator... - cdef NumberFieldElement_quadratic gen = self.number_field().gen() # should this be cached? cdef Rational const = Rational.__new__(Rational) cdef Rational lin = Rational.__new__(Rational) - ad, bd = self.parts() if not self: return [] + ad, bd = self.parts() if not bd: return [ad] - if gen.is_sqrt_disc(): - return [ad,bd] - else: - alpha, beta = gen.parts() - scale = bd/beta - return [ad - scale*alpha, scale] + + cdef NumberFieldElement_quadratic gen = self.number_field().gen() # should this be cached? + alpha, beta = gen.parts() + scale = bd/beta + return [ad - scale*alpha, scale] def denominator(self): - """ - Return the denominator of self. This is the LCM of the denominators of - the coefficients of self, and thus it may well be `> 1` even when the - element is an algebraic integer. + r""" + Return the denominator of ``self``. - EXAMPLES:: + This is the LCM of the denominators of the coefficients of `self``, and + thus it may well be `> 1` even when the element is an algebraic integer. - sage: K. = NumberField(x^2+x+41) - sage: a.denominator() - 1 - sage: b = (2*a+1)/6 - sage: b.denominator() - 6 - sage: K(1).denominator() - 1 - sage: K(1/2).denominator() - 2 - sage: K(0).denominator() - 1 + EXAMPLES:: sage: K. = NumberField(x^2 - 5) sage: b = (a + 1)/2 @@ -1958,28 +1927,24 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): 2 sage: b.is_integral() True + + sage: K. = NumberField(x^2-x+7) + sage: c.denominator() + 1 """ - # In terms of the generator... - cdef NumberFieldElement_quadratic gen = self.number_field().gen() # should this be cached? - cdef Integer denom - if gen.is_sqrt_disc(): - denom = Integer.__new__(Integer) - mpz_set(denom.value, self.denom) - return denom + c = self._coefficients() + if len(c) == 2: + const, lin = c + elif len(c) == 1: + const = c[0] + lin = Rational(0) else: - c = self._coefficients() - if len(c) == 2: - const, lin = c - elif len(c) == 1: - const = c[0] - lin = Rational(0) - else: - const = lin = Rational(0) - return const.denominator().lcm(lin.denominator()) + const = lin = Rational(0) + return const.denominator().lcm(lin.denominator()) def numerator(self): - """ - Return self*self.denominator(). + r""" + Return ``self * self.denominator()``. EXAMPLES:: @@ -1990,7 +1955,7 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): sage: b.numerator() 2*a + 1 """ - return self*self.denominator() + return self * self.denominator() ######################################################### @@ -2355,7 +2320,109 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute): else: return n -cdef class NumberFieldElement_gaussian(NumberFieldElement_quadratic): + +cdef class NumberFieldElement_quadratic_sqrt(NumberFieldElement_quadratic): + r""" + A NumberFieldElement_quadratic object gives an efficient representation of + an element of a quadratic extension of `\QQ` for the case when + :func:`is_sqrt_disc()` is ``True``. + """ + def denominator(self): + r""" + Return the denominator of ``self``. + + This is the LCM of the denominators of the coefficients of `self``, and + thus it may well be `> 1` even when the element is an algebraic integer. + + EXAMPLES:: + + sage: K. = NumberField(x^2+x+41) + sage: a.denominator() + 1 + sage: b = (2*a+1)/6 + sage: b.denominator() + 6 + sage: K(1).denominator() + 1 + sage: K(1/2).denominator() + 2 + sage: K(0).denominator() + 1 + + sage: K. = NumberField(x^2 - 5) + sage: b = (a + 1)/2 + sage: b.denominator() + 2 + sage: b.is_integral() + True + """ + cdef Integer denom + denom = Integer.__new__(Integer) + mpz_set(denom.value, self.denom) + return denom + + cpdef list _coefficients(self): + """ + EXAMPLES:: + + sage: K. = NumberField(x^2+41) + sage: a._coefficients() + [0, 1] + sage: K.zero()._coefficients() + [] + sage: (3/2*K.one())._coefficients() + [3/2] + """ + # In terms of the generator... Rational const = Rational.__new__(Rational) + cdef Rational lin = Rational.__new__(Rational) + if not self: + return [] + cdef tuple parts = self.parts() + if not (parts[1]): + return [parts[0]] + return list(parts) + + def __getitem__(self, n): + """ + Return the ``n``-th coefficient of this number field element, + written as a polynomial in the generator. + + Note that ``n`` must be either ``0`` or ``1``. + + EXAMPLES:: + + sage: K. = NumberField(x^2-13) + sage: elt = a/4 + 1/3 + sage: elt[0] + 1/3 + sage: elt[1] + 1/4 + + sage: K.zero()[0] + 0 + sage: K.zero()[1] + 0 + + sage: K.one()[0] + 1 + sage: K.one()[1] + 0 + + sage: elt[2] + Traceback (most recent call last): + ... + IndexError: index must be either 0 or 1 + + sage: C. = CyclotomicField(3) + sage: list(z3) + [0, 1] + """ + try: + return self.parts()[n] + except IndexError: # So we have a better error message + raise IndexError("index must be either 0 or 1") + +cdef class NumberFieldElement_gaussian(NumberFieldElement_quadratic_sqrt): r""" An element of `\QQ[i]`. @@ -2445,7 +2512,14 @@ cdef class NumberFieldElement_gaussian(NumberFieldElement_quadratic): sage: (1 + 2*I).real().parent() Rational Field """ - return self[0] + cdef Rational ad = Rational.__new__(Rational) + if mpz_cmp_ui(self.a, 0) == 0: + mpq_set_ui(ad.value, 0, 1) + else: + mpz_set(mpq_numref(ad.value), self.a) + mpz_set(mpq_denref(ad.value), self.denom) + mpq_canonicalize(ad.value) + return ad real = real_part @@ -2464,10 +2538,18 @@ cdef class NumberFieldElement_gaussian(NumberFieldElement_quadratic): sage: (1 - mi).imag() 1 """ - if self.standard_embedding: - return self[1] - else: - return -self[1] + cdef Rational bd = Rational.__new__(Rational) + if mpz_cmp_ui(self.b, 0) == 0: + # It is 0, so all we need to do is initialize the result + mpq_set_ui(bd.value, 0, 1) + return bd + + mpz_set(mpq_numref(bd.value), self.b) + mpz_set(mpq_denref(bd.value), self.denom) + mpq_canonicalize(bd.value) + if not self.standard_embedding: + mpq_neg(bd.value, bd.value) + return bd imag = imag_part @@ -2681,6 +2763,66 @@ cdef class OrderElement_quadratic(NumberFieldElement_quadratic): R = self.parent() return R(_inverse_mod_generic(self, I)) + cpdef list _coefficients(self): + """ + EXAMPLES:: + + sage: K. = NumberField(x^2-27) + sage: R = K.ring_of_integers() + sage: aa = R.gen(1) + sage: aa._coefficients() + [0, 1/3] + """ + # In terms of the generator... + cdef Rational const = Rational.__new__(Rational) + cdef Rational lin = Rational.__new__(Rational) + if not self: + return [] + ad, bd = self.parts() + if not bd: + return [ad] + + cdef NumberFieldElement_quadratic gen = self.number_field().gen() + alpha, beta = gen.parts() + if is_sqrt_disc(alpha, beta): + return [ad, bd] + else: + scale = bd / beta + return [ad - scale*alpha, scale] + + def denominator(self): + r""" + Return the denominator of ``self``. + + This is the LCM of the denominators of the coefficients of `self``, and + thus it may well be `> 1` even when the element is an algebraic integer. + + EXAMPLES:: + + sage: K. = NumberField(x^2-27) + sage: R = K.ring_of_integers() + sage: aa = R.gen(1) + sage: aa.denominator() + 3 + """ + # In terms of the generator... + cdef NumberFieldElement_quadratic gen = self.number_field().gen() # should this be cached? + cdef Integer denom + cdef tuple parts = gen.parts() + cdef Rational alpha, beta, const, lin + alpha = (parts[0]) + beta = (parts[1]) + if is_sqrt_disc(alpha, beta): + denom = Integer.__new__(Integer) + mpz_set(denom.value, self.denom) + return denom + else: + parts = self.parts() + const = (parts[0]) + lin = (parts[1]) + scale = lin / beta + const = const - scale * alpha + return const.denominator().lcm(scale.denominator()) cdef class Z_to_quadratic_field_element(Morphism): """ @@ -2877,3 +3019,36 @@ cdef class Q_to_quadratic_field_element(Morphism): To: Cyclotomic Field of order 6 and degree 2 """ return "Natural" + +##################################################################### +## Helper function + +cpdef bint is_sqrt_disc(Rational ad, Rational bd): + r""" + Return ``True`` if the pair ``(ad, bd)`` is `\sqrt{D}`. + + EXAMPLES:: + + sage: F. = NumberField(x^2 - x + 7) + sage: b.denominator() # indirect doctest + 1 + """ + cdef mpz_t a, b, denom + mpz_init(a) + mpz_init(b) + mpz_init(denom) + + mpz_lcm(denom, mpq_denref(ad.value), mpq_denref(bd.value)) + mpz_divexact(a, denom, mpq_denref(ad.value)) + mpz_mul(a, a, mpq_numref(ad.value)) + mpz_divexact(b, denom, mpq_denref(bd.value)) + mpz_mul(b, b, mpq_numref(bd.value)) + + cdef bint ret = mpz_cmp_ui(denom, 1) == 0 and mpz_cmp_ui(a, 0) == 0 and mpz_cmp_ui(b, 1) == 0 + + mpz_clear(a) + mpz_clear(b) + mpz_clear(denom) + + return ret + diff --git a/src/sage/rings/number_field/number_field_ideal.py b/src/sage/rings/number_field/number_field_ideal.py index 619529f0eab..da9c90f3b2f 100644 --- a/src/sage/rings/number_field/number_field_ideal.py +++ b/src/sage/rings/number_field/number_field_ideal.py @@ -231,7 +231,7 @@ def _richcmp_(self, other, op): sage: K. = NumberField(x^2 + 3); K Number Field in a with defining polynomial x^2 + 3 sage: f = K.factor(15); f - (Fractional ideal (-a))^2 * (Fractional ideal (5)) + (Fractional ideal (1/2*a + 3/2))^2 * (Fractional ideal (5)) sage: (f[0][0] < f[1][0]) True sage: (f[0][0] == f[0][0]) @@ -620,7 +620,7 @@ def free_module(self): sage: K. = CyclotomicField(7) sage: I = K.factor(11)[0][0]; I - Fractional ideal (-2*z^4 - 2*z^2 - 2*z + 1) + Fractional ideal (-3*z^4 - 2*z^3 - 2*z^2 - 2) sage: A = I.free_module() sage: A # warning -- choice of basis can be somewhat random Free module of degree 6 and rank 6 over Integer Ring @@ -780,7 +780,6 @@ def gens_reduced(self, proof=None): sage: P = EllipticCurve(L, '57a1').lift_x(z_x) * 3 sage: ideal = L.fractional_ideal(P[0], P[1]) sage: ideal.is_principal(proof=False) - *** Warning: precision too low for generators, not given. True sage: len(ideal.gens_reduced(proof=False)) 1 @@ -789,7 +788,7 @@ def gens_reduced(self, proof=None): self._is_principal = True self._reduced_generators = self.gens() return self._reduced_generators - self._cache_bnfisprincipal(proof=proof, gens_needed=True) + self._cache_bnfisprincipal(proof=proof, gens=True) return self._reduced_generators def gens_two(self): @@ -828,19 +827,24 @@ def gens_two(self): try: return self.__two_generators except AttributeError: - if self.is_zero(): - self.__two_generators = (0,0) - return self.__two_generators - K = self.number_field() + pass + + K = self.number_field() + + if self.is_zero(): + self.__two_generators = (K.zero(), K.zero()) + else: HNF = self.pari_hnf() # Check whether the ideal is generated by an integer, i.e. # whether HNF is a multiple of the identity matrix if HNF.gequal(HNF[0,0]): - a = HNF[0,0]; alpha = 0 + a = HNF[0,0] + alpha = 0 else: a, alpha = K.pari_nf().idealtwoelt(HNF) self.__two_generators = (K(a), K(alpha)) - return self.__two_generators + + return self.__two_generators def integral_basis(self): r""" @@ -1030,7 +1034,7 @@ def pari_prime(self): raise ValueError("%s is not a prime ideal" % self) return self._pari_prime - def _cache_bnfisprincipal(self, proof=None, gens_needed=False): + def _cache_bnfisprincipal(self, proof=None, gens=False): r""" This function is essentially the implementation of :meth:`is_principal`, :meth:`gens_reduced` and @@ -1042,9 +1046,8 @@ def _cache_bnfisprincipal(self, proof=None, gens_needed=False): - ``proof`` -- proof flag. If ``proof=False``, assume GRH. - - ``gens_needed`` -- (default: True) if True, insist on computing - the reduced generators of the ideal. With ``gens=False``, they - may or may not be computed (depending on how big the ideal is). + - ``gens`` -- (default: False) if True, also computes the reduced + generators of the ideal. OUTPUT: @@ -1052,6 +1055,15 @@ def _cache_bnfisprincipal(self, proof=None, gens_needed=False): ``_ideal_class_log`` (see :meth:`ideal_class_log`), ``_is_principal`` (see :meth:`is_principal`) and ``_reduced_generators``. + + TESTS: + + Check that no warnings are triggered from PARI/GP (see :trac:`30801`):: + + sage: K. = NumberField(x^2 - x + 112941801) + sage: I = K.ideal((112941823, a + 49942513)) + sage: I.is_principal() + False """ # Since pari_bnf() is cached, this call to pari_bnf() should not # influence the run-time much. Also, this simplifies the handling @@ -1063,29 +1075,33 @@ def _cache_bnfisprincipal(self, proof=None, gens_needed=False): # If we already have _reduced_generators, no need to compute them again if hasattr(self, "_reduced_generators"): - gens_needed = False + gens = False # Is there something to do? - if hasattr(self, "_ideal_class_log") and not gens_needed: + if hasattr(self, "_ideal_class_log") and not gens: self._is_principal = not any(self._ideal_class_log) return - # Call bnfisprincipal(). - # If gens_needed, use flag=3 which will insist on computing - # the generator. Otherwise, use flag=1, where the generator - # may or may not be computed. - v = bnf.bnfisprincipal(self.pari_hnf(), 3 if gens_needed else 1) - self._ideal_class_log = list(v[0]) - self._is_principal = not any(self._ideal_class_log) - - if self._is_principal: - # Cache reduced generator if it was computed - if v[1]: - g = self.number_field()(v[1]) - self._reduced_generators = (g,) + if not gens: + v = bnf.bnfisprincipal(self.pari_hnf(), 0) + self._ideal_class_log = list(v) + self._is_principal = not any(self._ideal_class_log) else: - # Non-principal ideal, compute two generators if asked for - if gens_needed: + # TODO: this is a bit of a waste. We ask bnfisprincipal to compute the compact form and then + # convert this compact form back into an expanded form. + # (though calling with 3 instead of 5 most likely triggers an error with memory allocation failure) + v = bnf.bnfisprincipal(self.pari_hnf(), 5) + e = v[0] + t = v[1] + t = bnf.nfbasistoalg(bnf.nffactorback(t)) + self._ideal_class_log = list(e) + self._is_principal = not any(self._ideal_class_log) + + if self._is_principal: + g = self.number_field()(t) + self._reduced_generators = (g,) + elif gens: + # Non-principal ideal self._reduced_generators = self.gens_two() def is_principal(self, proof=None): @@ -1417,7 +1433,7 @@ def decomposition_group(self): EXAMPLES:: sage: QuadraticField(-23, 'w').primes_above(7)[0].decomposition_group() - Subgroup [(), (1,2)] of Galois group 2T1 (S2) with order 2 of x^2 + 23 + Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) """ return self.number_field().galois_group().decomposition_group(self) @@ -1433,9 +1449,9 @@ def ramification_group(self, v): EXAMPLES:: sage: QuadraticField(-23, 'w').primes_above(23)[0].ramification_group(0) - Subgroup [(), (1,2)] of Galois group 2T1 (S2) with order 2 of x^2 + 23 + Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) sage: QuadraticField(-23, 'w').primes_above(23)[0].ramification_group(1) - Subgroup [()] of Galois group 2T1 (S2) with order 2 of x^2 + 23 + Subgroup generated by [()] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) """ return self.number_field().galois_group().ramification_group(self, v) @@ -1451,7 +1467,7 @@ def inertia_group(self): EXAMPLES:: sage: QuadraticField(-23, 'w').primes_above(23)[0].inertia_group() - Subgroup [(), (1,2)] of Galois group 2T1 (S2) with order 2 of x^2 + 23 + Subgroup generated by [(1,2)] of (Galois group 2T1 (S2) with order 2 of x^2 + 23) """ return self.ramification_group(0) @@ -2175,6 +2191,14 @@ def invertible_residues(self, reduce=True): sage: len(list(K.primes_above(3)[0].invertible_residues())) 80 + TESTS: + + Check that the integrality is not lost, cf. :trac:`30801`:: + + sage: K. = NumberField(x^2 + x + 1) + sage: list(K.ideal(8).invertible_residues())[:5] + [1, a - 1, -3*a, -2*a + 3, -a - 1] + AUTHOR: John Cremona """ return self.invertible_residues_mod(subgp_gens=None, reduce=reduce) @@ -2261,7 +2285,7 @@ def invertible_residues_mod(self, subgp_gens=[], reduce=True): A, U, V = M.smith_form() V = V.inverse() - new_basis = [prod([g[j]**V[i, j] for j in range(n)]) for i in range(n)] + new_basis = [prod([g[j]**(V[i, j] % invs[j]) for j in range(n)]) for i in range(n)] if reduce: combo = lambda c: self.small_residue(prod(new_basis[i] ** c[i] @@ -2512,16 +2536,15 @@ def _pari_bid_(self, flag=1): from sage.libs.pari.all import PariError try: bid = self._bid - if flag==2: + if flag == 2: # Try to access generators, we get PariError if this fails. - bid.bid_get_gen(); + bid.bid_get_gen() except (AttributeError, PariError): k = self.number_field() bid = k.pari_nf().idealstar(self.pari_hnf(), flag) self._bid = bid return bid - def idealstar(self, flag=1): r""" Returns the finite abelian group `(O_K/I)^*`, where I is the ideal self @@ -3120,7 +3143,7 @@ def residue_class_degree(self): sage: K. = NumberField(x^5 + 2); K Number Field in a with defining polynomial x^5 + 2 sage: f = K.factor(19); f - (Fractional ideal (a^2 + a - 3)) * (Fractional ideal (-2*a^4 - a^2 + 2*a - 1)) * (Fractional ideal (a^2 + a - 1)) + (Fractional ideal (a^2 + a - 3)) * (Fractional ideal (2*a^4 + a^2 - 2*a + 1)) * (Fractional ideal (a^2 + a - 1)) sage: [i.residue_class_degree() for i, _ in f] [2, 2, 1] """ diff --git a/src/sage/rings/number_field/number_field_ideal_rel.py b/src/sage/rings/number_field/number_field_ideal_rel.py index 37c20150ad1..078682fa1fa 100644 --- a/src/sage/rings/number_field/number_field_ideal_rel.py +++ b/src/sage/rings/number_field/number_field_ideal_rel.py @@ -18,7 +18,7 @@ sage: G = [from_A(z) for z in I.gens()]; G [7, -2*b*a - 1] sage: K.fractional_ideal(G) - Fractional ideal (2*b*a + 1) + Fractional ideal ((1/2*b + 2)*a - 1/2*b + 2) sage: K.fractional_ideal(G).absolute_norm().factor() 7^2 """ diff --git a/src/sage/rings/number_field/number_field_rel.py b/src/sage/rings/number_field/number_field_rel.py index e103bd8e59a..3097a98002d 100644 --- a/src/sage/rings/number_field/number_field_rel.py +++ b/src/sage/rings/number_field/number_field_rel.py @@ -396,18 +396,18 @@ def subfields(self, degree=0, name=None): sage: K. = F.extension(Y^2 - (1 + a)*(a + b)*a*b) sage: K.subfields(2) [ - (Number Field in c0 with defining polynomial x^2 - 24*x + 72, Ring morphism: - From: Number Field in c0 with defining polynomial x^2 - 24*x + 72 + (Number Field in c0 with defining polynomial x^2 - 24*x + 96, Ring morphism: + From: Number Field in c0 with defining polynomial x^2 - 24*x + 96 To: Number Field in c with defining polynomial Y^2 + (-2*b - 3)*a - 2*b - 6 over its base field - Defn: c0 |--> -6*a + 12, None), + Defn: c0 |--> -4*b + 12, None), (Number Field in c1 with defining polynomial x^2 - 24*x + 120, Ring morphism: From: Number Field in c1 with defining polynomial x^2 - 24*x + 120 To: Number Field in c with defining polynomial Y^2 + (-2*b - 3)*a - 2*b - 6 over its base field Defn: c1 |--> 2*b*a + 12, None), - (Number Field in c2 with defining polynomial x^2 - 24*x + 96, Ring morphism: - From: Number Field in c2 with defining polynomial x^2 - 24*x + 96 + (Number Field in c2 with defining polynomial x^2 - 24*x + 72, Ring morphism: + From: Number Field in c2 with defining polynomial x^2 - 24*x + 72 To: Number Field in c with defining polynomial Y^2 + (-2*b - 3)*a - 2*b - 6 over its base field - Defn: c2 |--> -4*b + 12, None) + Defn: c2 |--> -6*a + 12, None) ] sage: K.subfields(8, 'w') [ diff --git a/src/sage/rings/number_field/order.py b/src/sage/rings/number_field/order.py index b5cdeb2a96f..be1548f7a2e 100644 --- a/src/sage/rings/number_field/order.py +++ b/src/sage/rings/number_field/order.py @@ -2180,7 +2180,7 @@ def EisensteinIntegers(names="omega"): sage: R Eisenstein Integers in Number Field in omega with defining polynomial x^2 + x + 1 with omega = -0.50000000000000000? + 0.866025403784439?*I sage: factor(3 + omega) - (omega) * (-3*omega - 2) + (-1) * (-omega - 3) sage: CC(omega) -0.500000000000000 + 0.866025403784439*I sage: omega.minpoly() diff --git a/src/sage/rings/number_field/totallyreal.pyx b/src/sage/rings/number_field/totallyreal.pyx index 4ac05950d71..0512f0559fc 100644 --- a/src/sage/rings/number_field/totallyreal.pyx +++ b/src/sage/rings/number_field/totallyreal.pyx @@ -1,4 +1,4 @@ -""" +r""" Enumeration of Primitive Totally Real Fields This module contains functions for enumerating all primitive @@ -138,8 +138,9 @@ cpdef double odlyzko_bound_totallyreal(int n): - John Voight (2007-09-03) - NOTES: - The values are calculated by Martinet [Mar1980]_. + .. NOTE:: + + The values are calculated by Martinet [Mar1980]_. """ if n <= 10: diff --git a/src/sage/rings/number_field/totallyreal_phc.py b/src/sage/rings/number_field/totallyreal_phc.py index 6dbc0b60576..59467e5af40 100644 --- a/src/sage/rings/number_field/totallyreal_phc.py +++ b/src/sage/rings/number_field/totallyreal_phc.py @@ -7,15 +7,15 @@ * Zeroth attempt. """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007 William Stein and John Voight # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** import os import sage.misc.misc @@ -36,9 +36,9 @@ def coefficients_to_power_sums(n, m, a): list of integers. - NOTES: + .. NOTE:: - Uses Newton's relations, which are classical. + This uses Newton's relations, which are classical. AUTHORS: @@ -62,6 +62,7 @@ def __lagrange_bounds_phc(n, m, a, tmpfile=None): r""" This function determines the bounds on the roots in the enumeration of totally real fields via Lagrange multipliers. + It is used internally by the main function enumerate_totallyreal_fields_prim(), which should be consulted for further information. @@ -75,10 +76,10 @@ def __lagrange_bounds_phc(n, m, a, tmpfile=None): the lower and upper bounds as real numbers. - NOTES: + .. NOTE:: - See Cohen [Coh2000]_ for the general idea and unpublished work of the - author for more detail. + See Cohen [Coh2000]_ for the general idea and unpublished work of the + author for more detail. AUTHORS: diff --git a/src/sage/rings/number_field/unit_group.py b/src/sage/rings/number_field/unit_group.py index d0508258a0d..6dd6b68cb3d 100644 --- a/src/sage/rings/number_field/unit_group.py +++ b/src/sage/rings/number_field/unit_group.py @@ -15,12 +15,12 @@ sage: UK.gens_values() # random [-1/12*a^3 + 1/6*a, 1/24*a^3 + 1/4*a^2 - 1/12*a - 1] sage: UK.gen(0).value() - -1/12*a^3 + 1/6*a + 1/12*a^3 - 1/6*a sage: UK.gen(0) u0 sage: UK.gen(0) + K.one() # coerce abstract generator into number field - -1/12*a^3 + 1/6*a + 1 + 1/12*a^3 - 1/6*a + 1 sage: [u.multiplicative_order() for u in UK.gens()] [4, +Infinity] @@ -37,18 +37,18 @@ sage: UK(-1) u0^2 sage: [UK(u) for u in (x^4-1).roots(K, multiplicities=False)] - [1, u0^2, u0^3, u0] + [1, u0^2, u0, u0^3] sage: UK.fundamental_units() # random [1/24*a^3 + 1/4*a^2 - 1/12*a - 1] sage: torsion_gen = UK.torsion_generator(); torsion_gen u0 sage: torsion_gen.value() - -1/12*a^3 + 1/6*a + 1/12*a^3 - 1/6*a sage: UK.zeta_order() 4 sage: UK.roots_of_unity() - [-1/12*a^3 + 1/6*a, -1, 1/12*a^3 - 1/6*a, 1] + [1/12*a^3 - 1/6*a, -1, -1/12*a^3 + 1/6*a, 1] Exp and log functions provide maps between units as field elements and exponent vectors with respect to the generators:: @@ -82,7 +82,7 @@ sage: SUK.rank() 4 sage: SUK.gens_values() - [-1, a^2 + 1, a^5 + a^4 - a^2 - a - 1, a + 1, -a + 1] + [-1, a^2 + 1, -a^5 - a^4 + a^2 + a + 1, a + 1, a - 1] sage: u = 9*prod(SUK.gens_values()); u -18*a^5 - 18*a^4 - 18*a^3 - 9*a^2 + 9*a + 27 sage: SUK.log(u) @@ -100,29 +100,29 @@ sage: UL.zeta_order() 24 sage: UL.roots_of_unity() - [-b*a - b, - b^2*a, - b^3, - a + 1, - -b*a, - -b^2, - b^3*a + b^3, - a, - b, + [-b*a, -b^2*a - b^2, - b^3*a, - -1, - b*a + b, - -b^2*a, -b^3, - -a - 1, - b*a, - b^2, - -b^3*a - b^3, -a, + -b*a - b, + -b^2, + b^3*a, + -a - 1, -b, + b^2*a, + b^3*a + b^3, + -1, + b*a, b^2*a + b^2, + b^3, + a, + b*a + b, + b^2, -b^3*a, + a + 1, + b, + -b^2*a, + -b^3*a - b^3, 1] A relative extension example, which worked thanks to the code review by F.W.Clarke:: @@ -199,7 +199,7 @@ class UnitGroup(AbelianGroupWithValues_class): sage: UK.gen(5) u5 sage: UK.gen(5).value() - z^7 + z + -z^7 - z An S-unit group:: @@ -216,7 +216,7 @@ class UnitGroup(AbelianGroupWithValues_class): sage: SUK.zeta_order() 26 sage: SUK.log(21*z) - (12, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1) + (25, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1) """ # This structure is not a parent in the usual sense. The # "elements" are NumberFieldElement_absolute. Instead, they should @@ -250,7 +250,7 @@ def __init__(self, number_field, proof=True, S=None): sage: UK.gens() (u0, u1) sage: UK.gens_values() - [-1, 6*a - 37] + [-1, -6*a + 37] sage: K. = QuadraticField(-3) sage: UK = K.unit_group(); UK @@ -258,7 +258,7 @@ def __init__(self, number_field, proof=True, S=None): sage: UK.gens() (u,) sage: UK.gens_values() - [1/2*a + 1/2] + [-1/2*a + 1/2] sage: K. = CyclotomicField(13) sage: UK = K.unit_group(); UK @@ -329,28 +329,25 @@ def __init__(self, number_field, proof=True, S=None): # compute the additional S-unit generators: if S: - self.__S_unit_data = pK.bnfsunit(pS) - su = [K(u, check=False) for u in self.__S_unit_data[0]] + self.__S_unit_data = pK.bnfunits(pS) else: - su = [] - self.__nsu = len(su) + self.__S_unit_data = pK.bnfunits() + # TODO: converting the factored matrix representation of bnfunits into polynomial + # form is a *big* waste of time + su_fu_tu = [pK.nfbasistoalg(pK.nffactorback(z)) for z in self.__S_unit_data[0]] + + self.__nfu = len(pK.bnf_get_fu()) # number of fundamental units + self.__nsu = len(su_fu_tu) - self.__nfu - 1 # number of S-units + self.__ntu = pK.bnf_get_tu()[0] # order of torsion self.__rank = self.__nfu + self.__nsu - # compute a torsion generator and pick the 'simplest' one: - n, z = pK[7][3] # number of roots of unity and bnf.tu as in pari documentation - n = ZZ(n) - self.__ntu = n - z = K(z, check=False) - - # If we replaced z by another torsion generator we would need - # to allow for this in the dlog function! So we do not. + # Move the torsion unit first, then fundamental units then S-units + gens = [K(u, check=False) for u in su_fu_tu] + gens = [gens[-1]] + gens[self.__nsu:-1] + gens[:self.__nsu] - # Store the actual generators (torsion first): - gens = [z] + fu + su - values = Sequence(gens, immutable=True, universe=self, check=False) # Construct the abstract group: - gens_orders = tuple([ZZ(n)]+[ZZ(0)]*(self.__rank)) - AbelianGroupWithValues_class.__init__(self, gens_orders, 'u', values, number_field) + gens_orders = tuple([ZZ(self.__ntu)]+[ZZ(0)]*(self.__rank)) + AbelianGroupWithValues_class.__init__(self, gens_orders, 'u', gens, number_field) def _element_constructor_(self, u): """ @@ -375,7 +372,7 @@ def _element_constructor_(self, u): sage: UK.gens() (u0, u1) sage: UK.gens_values() - [-1, 6*a - 37] + [-1, -6*a + 37] sage: UK.ngens() 2 sage: [UK(u) for u in UK.gens()] @@ -394,8 +391,8 @@ def _element_constructor_(self, u): except TypeError: raise ValueError("%s is not an element of %s"%(u,K)) if self.__S: - m = pK.bnfissunit(self.__S_unit_data, pari(u)).mattranspose() - if m.ncols()==0: + m = pK.bnfisunit(pari(u), self.__S_unit_data).mattranspose() + if m.ncols() == 0: raise ValueError("%s is not an S-unit"%u) else: if not u.is_integral() or u.norm().abs() != 1: @@ -405,9 +402,8 @@ def _element_constructor_(self, u): # convert column matrix to a list: m = [ZZ(m[0,i].sage()) for i in range(m.ncols())] - # NB pari puts the torsion after the fundamental units, before - # the extra S-units but we have the torsion first: - m = [m[self.__nfu]] + m[:self.__nfu] + m[self.__nfu+1:] + # NOTE: pari ordering for the units is (S-units, fundamental units, torsion unit) + m = [m[-1]] + m[self.__nsu:-1] + m[:self.__nsu] return self.element_class(self, m) @@ -527,9 +523,9 @@ def zeta(self, n=2, all=False): sage: U.zeta(2, all=True) [-1] sage: U.zeta(3) - 1/2*z - 1/2 + -1/2*z - 1/2 sage: U.zeta(3, all=True) - [1/2*z - 1/2, -1/2*z - 1/2] + [-1/2*z - 1/2, 1/2*z - 1/2] sage: U.zeta(4) Traceback (most recent call last): ... @@ -645,7 +641,7 @@ def log(self, u): sage: SUK = UnitGroup(K,S=2) sage: v = (3,1,4,1,5,9,2) sage: u = SUK.exp(v); u - -8732*z^11 + 15496*z^10 + 51840*z^9 + 68804*z^8 + 51840*z^7 + 15496*z^6 - 8732*z^5 + 34216*z^3 + 64312*z^2 + 64312*z + 34216 + 8732*z^11 - 15496*z^10 - 51840*z^9 - 68804*z^8 - 51840*z^7 - 15496*z^6 + 8732*z^5 - 34216*z^3 - 64312*z^2 - 64312*z - 34216 sage: SUK.log(u) (3, 1, 4, 1, 5, 9, 2) sage: SUK.log(u) == v @@ -692,7 +688,7 @@ def exp(self, exponents): sage: SUK = UnitGroup(K,S=2) sage: v = (3,1,4,1,5,9,2) sage: u = SUK.exp(v); u - -8732*z^11 + 15496*z^10 + 51840*z^9 + 68804*z^8 + 51840*z^7 + 15496*z^6 - 8732*z^5 + 34216*z^3 + 64312*z^2 + 64312*z + 34216 + 8732*z^11 - 15496*z^10 - 51840*z^9 - 68804*z^8 - 51840*z^7 - 15496*z^6 + 8732*z^5 - 34216*z^3 - 64312*z^2 - 64312*z - 34216 sage: SUK.log(u) (3, 1, 4, 1, 5, 9, 2) sage: SUK.log(u) == v diff --git a/src/sage/rings/padics/CA_template.pxi b/src/sage/rings/padics/CA_template.pxi index 38901797076..809b24116f5 100644 --- a/src/sage/rings/padics/CA_template.pxi +++ b/src/sage/rings/padics/CA_template.pxi @@ -449,6 +449,14 @@ cdef class CAElement(pAdicTemplateElement): 1 + 14*19^2 + 11*19^3 + 13*19^4 + O(19^5) sage: (a.log() * 19/7).exp() 1 + 14*19^2 + 11*19^3 + 13*19^4 + O(19^5) + + Check that :trac:`31875` is fixed:: + + sage: R(1)^R(0) + 1 + O(19^5) + sage: S. = ZqCA(4) + sage: S(1)^S(0) + 1 + O(2^20) """ cdef long relprec, val, rval cdef mpz_t tmp @@ -464,7 +472,7 @@ cdef class CAElement(pAdicTemplateElement): elif self.parent() is _right.parent(): ## For extension elements, we need to switch to the ## fraction field sometimes in highly ramified extensions. - exact_exp = False + exact_exp = (_right)._is_exact_zero() pright = _right else: self, _right = canonical_coercion(self, _right) diff --git a/src/sage/rings/padics/CR_template.pxi b/src/sage/rings/padics/CR_template.pxi index fc4d46cf0ff..5424be0f500 100644 --- a/src/sage/rings/padics/CR_template.pxi +++ b/src/sage/rings/padics/CR_template.pxi @@ -654,6 +654,16 @@ cdef class CRElement(pAdicTemplateElement): 1 + 14*19^2 + 11*19^3 + 13*19^4 + O(19^5) sage: (a.log() * 19/7).exp() 1 + 14*19^2 + 11*19^3 + 13*19^4 + O(19^5) + + TESTS: + + Check that :trac:`31875` is fixed:: + + sage: R(1)^R(0) + 1 + O(19^5) + sage: S. = ZqCR(4) + sage: S(1)^S(0) + 1 + O(2^20) """ cdef long base_level, exp_prec cdef mpz_t tmp @@ -668,7 +678,7 @@ cdef class CRElement(pAdicTemplateElement): elif self.parent() is _right.parent(): ## For extension elements, we need to switch to the ## fraction field sometimes in highly ramified extensions. - exact_exp = False + exact_exp = (_right)._is_exact_zero() pright = _right else: self, _right = canonical_coercion(self, _right) diff --git a/src/sage/rings/padics/FP_template.pxi b/src/sage/rings/padics/FP_template.pxi index ec8bf5ce594..c7ce3937c0d 100644 --- a/src/sage/rings/padics/FP_template.pxi +++ b/src/sage/rings/padics/FP_template.pxi @@ -600,6 +600,16 @@ cdef class FPElement(pAdicTemplateElement): 1 + 4*11^2 + 3*11^3 + 7*11^4 sage: R(11)^-1 11^-1 + + TESTS: + + Check that :trac:`31875` is fixed:: + + sage: R(1)^R(0) + 1 + sage: S. = ZqFP(4) + sage: S(1)^S(0) + 1 """ cdef long dummyL cdef mpz_t tmp @@ -614,7 +624,7 @@ cdef class FPElement(pAdicTemplateElement): elif self.parent() is _right.parent(): ## For extension elements, we need to switch to the ## fraction field sometimes in highly ramified extensions. - exact_exp = False + exact_exp = (_right)._is_exact_zero() pright = _right else: self, _right = canonical_coercion(self, _right) diff --git a/src/sage/rings/padics/factory.py b/src/sage/rings/padics/factory.py index 38546c6d262..90c54a2524a 100644 --- a/src/sage/rings/padics/factory.py +++ b/src/sage/rings/padics/factory.py @@ -1320,8 +1320,10 @@ def Qq(q, prec = None, type = 'capped-rel', modulus = None, names=None, q = tuple(q) p,k = q - if not isinstance(p, Integer): p = Integer(p) - if not isinstance(k, Integer): k = Integer(k) + if not isinstance(p, Integer): + p = Integer(p) + if not isinstance(k, Integer): + k = Integer(k) if check: if not p.is_prime() or k <=0: diff --git a/src/sage/rings/padics/generic_nodes.py b/src/sage/rings/padics/generic_nodes.py index 6a1da3b4747..4b09b5678c1 100644 --- a/src/sage/rings/padics/generic_nodes.py +++ b/src/sage/rings/padics/generic_nodes.py @@ -643,9 +643,9 @@ def convert_multiple(self, *elts): p = self.prime() # We sort elements by precision lattice - elt_by_prec = { } - elt_other = [ ] - indices = { } + elt_by_prec = {} + elt_other = [] + indices = {} for i in range(len(elts)): x = elts[i] idx = id(x) @@ -682,7 +682,8 @@ def convert_multiple(self, *elts): except PrecisionError: raise NotImplementedError("multiple conversion of a set of variables for which the module precision is not a lattice is not implemented yet") for j in range(len(L)): - x = L[j]; dx = [ ] + x = L[j] + dx = [] for i in range(j): dx.append([L[i], lattice[i,j]]) prec = lattice[j,j].valuation(p) diff --git a/src/sage/rings/padics/lattice_precision.py b/src/sage/rings/padics/lattice_precision.py index b2901ea7008..9c8b8b55a11 100644 --- a/src/sage/rings/padics/lattice_precision.py +++ b/src/sage/rings/padics/lattice_precision.py @@ -179,7 +179,7 @@ def reduce(self, prec): exp -= valdenom if prec > exp: modulo = self.p ** (prec - exp) - # probably we should use Newton iteration instead + # probably we should use Newton iteration instead # (but it is actually slower for now - Python implementation) _, inv, _ = denom.xgcd(modulo) x = (num*inv) % modulo @@ -219,7 +219,7 @@ def reduce_relative(self, prec): def normalize(self): r""" - Normalize this element, i.e. write it as ``p^v * u`` where + Normalize this element, i.e. write it as ``p^v * u`` where ``u`` is coprime to `p`. TESTS:: @@ -311,8 +311,10 @@ def __add__(self, other): p = self.p sexp = self.exponent oexp = other.exponent - if sexp is Infinity: return other - if oexp is Infinity: return self + if sexp is Infinity: + return other + if oexp is Infinity: + return self if self._valuation is None or other._valuation is None: val = None elif self._valuation < other._valuation: @@ -560,7 +562,7 @@ def value(self): def list(self, prec): r""" - Return the list of the digits of this element (written in radix + Return the list of the digits of this element (written in radix `p`) up to position ``prec``. The first zeros are omitted. @@ -593,7 +595,7 @@ def list(self, prec): return [] p = self.p x = ZZ(self.x * p**(self.exponent - val)) - l = [ ] + l = [] for _ in range(val, prec): x, digit = x.quo_rem(p) l.append(digit) @@ -747,7 +749,7 @@ def prime(self): def _index(self, ref): r""" - Return the index of the column in the precision matrix that + Return the index of the column in the precision matrix that corresponds to ``ref``. Only for internal use. @@ -877,15 +879,15 @@ def _record_collected_element(self, ref): @abstract_method def del_elements(self, threshold=None): r""" - Delete (or mark for future deletion) the columns of precision - matrix corresponding to elements that were collected by the + Delete (or mark for future deletion) the columns of precision + matrix corresponding to elements that were collected by the garbage collector. INPUT: - ``threshold`` -- an integer or ``None`` (default: ``None``): a column whose distance to the right is greater than the - threshold is not erased but marked for deletion; + threshold is not erased but marked for deletion; if ``None``, always erase (never mark for deletion). EXAMPLES:: @@ -988,7 +990,7 @@ def precision_lattice(self, elements=None): def diffused_digits(self, elements=None): r""" - Return the number of diffused digits of precision within a + Return the number of diffused digits of precision within a subset of elements. A diffused digit of precision is a known digit which is not @@ -997,7 +999,7 @@ def diffused_digits(self, elements=None): The number of diffused digits of precision quantifies the quality of the approximation of the lattice precision by a - jagged precision (that is a precision which is split over + jagged precision (that is a precision which is split over all variables). We refer to [CRV2018]_ for a detail exposition of the notion of @@ -1110,7 +1112,7 @@ def history_enable(self): r""" Enable history. - We refer to the documentation of the method :meth:`history` for + We refer to the documentation of the method :meth:`history` for a complete documentation (including examples) about history. TESTS:: @@ -1126,7 +1128,7 @@ def history_enable(self): sage: prec.history_enable() sage: print(prec.history()) Timings - --- + --- .. SEEALSO:: @@ -1140,7 +1142,7 @@ def history_disable(self): r""" Disable history. - We refer to the documentation of the method :meth:`history` for + We refer to the documentation of the method :meth:`history` for a complete documentation (including examples) about history. TESTS:: @@ -1156,7 +1158,7 @@ def history_disable(self): sage: prec.history_enable() sage: print(prec.history()) Timings - --- + --- sage: prec.history_disable() sage: print(prec.history()) @@ -1174,7 +1176,7 @@ def history_clear(self): r""" Clear history. - We refer to the documentation of the method :meth:`history` for + We refer to the documentation of the method :meth:`history` for a complete documentation (including examples) about history. TESTS:: @@ -1215,18 +1217,18 @@ def history_clear(self): def _format_history(self, time, status, timings): r""" Return a formatted output for the history. - + This is a helper function for the method :meth:`history`. - + TESTS:: - + sage: R = ZpLC(2, label='history_en') sage: prec = R.precision() sage: prec._format_history(1.23456789, ['o', 'o', 'o', 'o', 'o', 'o', '~', 'o', 'o'], true) '1.234568s oooooo~oo' sage: prec._format_history(1.23456789, ['o', 'o', 'o', 'o', 'o', 'o', '~', 'o', 'o'], false) 'oooooo~oo' - + sage: prec._format_history(12.3456789, ['o', 'o', 'o', 'o', 'o', 'o', '~', 'o', 'o'], true) ' >= 10s oooooo~oo' sage: prec._format_history(10^(-10), ['o', 'o', 'o', 'o', 'o', 'o', '~', 'o', 'o'], true) @@ -1253,12 +1255,12 @@ def history(self, compact=True, separate_reduce=False, timings=True, output_type r""" Show history. - The history records creations and deletions of elements attached + The history records creations and deletions of elements attached to this precision lattice, together with many timings. INPUT: - - ``compact`` -- a boolean (default: ``True``); if true, all + - ``compact`` -- a boolean (default: ``True``); if true, all consecutive operations of the same type appear on a single row - ``separate_reduce`` -- a boolean (default: ``False``); specify @@ -1288,7 +1290,7 @@ def history(self, compact=True, separate_reduce=False, timings=True, output_type At the beginning, the history is of course empty:: sage: print(prec.history()) - Timings + Timings --- Now we start creating and deleting elements:: @@ -1347,8 +1349,8 @@ def history(self, compact=True, separate_reduce=False, timings=True, output_type Timings for automatic reduction do not appear because they are included in the timings for deletion. - The symbol ``R`` is used to symbolize a column which is under full - Hermite reduction. Note that full Hermite reduction are never performed + The symbol ``R`` is used to symbolize a column which is under full + Hermite reduction. Note that full Hermite reduction are never performed automatically but needs to be called by hand:: sage: prec.reduce() @@ -1442,7 +1444,8 @@ def history(self, compact=True, separate_reduce=False, timings=True, output_type if separate_reduce: if status: hist.append(self._format_history(total_time, status, timings)) - if event == 'partial reduce': code = 'r' + if event == 'partial reduce': + code = 'r' else: code = 'R' status_red = status[:index] + (len(status) - index) * [code] hist.append(self._format_history(tme, status_red, timings)) @@ -1474,13 +1477,13 @@ def history(self, compact=True, separate_reduce=False, timings=True, output_type def timings(self, action=None): r""" - Return cumulated timings (grouped by actions) since the last + Return cumulated timings (grouped by actions) since the last time history has been cleared. INPUT: - ``action`` -- ``None`` (the default), ``add``, ``mark``, ``del``, - ``partial reduce`` or ``full reduce``; if not None, return the + ``partial reduce`` or ``full reduce``; if not None, return the cumulated timing corresponding to this action; otherwise, return a dictionary @@ -1584,20 +1587,20 @@ def __reduce__(self): def _index(self, ref): r""" Return the index of the element whose reference is ``ref``. - + TESTS:: - + sage: from sage.rings.padics.lattice_precision import pAdicLatticeElementWeakProxy sage: R = ZpLC(2, label="index") sage: prec = R.precision() sage: x = R(1, 10) sage: y = R(1, 5) - + sage: prec._index(pAdicLatticeElementWeakProxy(x)) 0 sage: prec._index(pAdicLatticeElementWeakProxy(y)) 1 - + sage: del x sage: prec.del_elements() sage: prec._index(pAdicLatticeElementWeakProxy(y)) @@ -1636,8 +1639,8 @@ def reduce(self, index=0, partial=False): NOTE: - The partial reduction has cost `O(m^2)` where `m` is the number of - rows that need to be reduced (that is the difference between the + The partial reduction has cost `O(m^2)` where `m` is the number of + rows that need to be reduced (that is the difference between the total number of rows and ``index``). The full Hermite reduction has cost `O(m^3)`. @@ -1686,7 +1689,8 @@ def reduce(self, index=0, partial=False): for i in range(index, j): reduced = col[i].reduce(valpivot) scalar = (col[i] - reduced) >> valpivot - if scalar.is_zero(): continue + if scalar.is_zero(): + continue col[i] = reduced col[i].normalize() for j2 in range(j+1, n): @@ -1711,18 +1715,18 @@ def _new_element(self, x, dx, bigoh, dx_mode='linear_combination', capped=False) - ``dx`` -- a dictionary representing the differential of ``x`` - - ``bigoh`` -- an integer or ``None`` (default: ``None``): the + - ``bigoh`` -- an integer or ``None`` (default: ``None``): the bigoh to be added to the precision of ``x``; if ``None``, the default cap is used. - ``dx_mode`` -- a string, either ``linear_combination`` (the default) or ``values`` - - ``capped`` -- a boolean, whether this element has been capped + - ``capped`` -- a boolean, whether this element has been capped according to the parent's cap - If ``dx_mode`` is ``linear_combination``, the dictionary ``dx`` - encodes the expression of the differential of ``x``. + If ``dx_mode`` is ``linear_combination``, the dictionary ``dx`` + encodes the expression of the differential of ``x``. For example, if ``x`` was defined as ``x = y*z`` then: .. MATH:: @@ -1838,8 +1842,10 @@ def del_elements(self, threshold=None): self._marked_for_deletion.sort(reverse=True) count = 0 for index in self._marked_for_deletion: - if threshold is not None and index < n - threshold: break - n -= 1; count += 1 + if threshold is not None and index < n - threshold: + break + n -= 1 + count += 1 tme = walltime() ref = self._elements[index] @@ -1856,7 +1862,7 @@ def del_elements(self, threshold=None): self._capped[ref], capped = capped, capped or self._capped[ref] else: capped = capped or self._capped[ref] - + d, u, v = col[i].xgcd(col[i+1]) up, vp = col[i+1]/d, col[i]/d col[i] = d @@ -1894,7 +1900,7 @@ def _lift_to_precision(self, x, prec): p^{prec} \Z_p dx \oplus \bigoplus_{y \neq x} \Q_p dy - This function may change at the same time the precision of + This function may change at the same time the precision of other elements having the same parent. .. NOTE:: @@ -1937,7 +1943,8 @@ def _lift_to_precision(self, x, prec): rows = rows_by_val[v] piv = max(rows) for i in rows: - if i == piv: continue + if i == piv: + continue # We clear the entry on the i-th row scalar = (col[i]/col[piv]).reduce(prec-v) for j in range(piv,n): @@ -2116,7 +2123,8 @@ def precision_lattice(self, elements=None): col = self._matrix[ref] row = [ x.value() for x in col ] valcol = min([ x.valuation() for x in col ]) - if valcol < val: val = valcol + if valcol < val: + val = valcol row += (n-len(row)) * [ZZ(0)] rows.append(row) from sage.matrix.constructor import matrix @@ -2153,7 +2161,7 @@ def __init__(self, p, label, prec): NOTE: - The precision module is automatically initialized at the + The precision module is automatically initialized at the creation of the parent. TESTS:: @@ -2191,7 +2199,7 @@ def internal_prec(self): internally. It is slightly greater than the actual precision and increases - a bit (at a logarithmic rate) when new elements are created + a bit (at a logarithmic rate) when new elements are created and/or computed. EXAMPLES:: @@ -2237,7 +2245,7 @@ def dimension(self): sage: prec.dimension() 2 - Of course, it may also decrease when a sufficient + Of course, it may also decrease when a sufficient number of variables are collected:: sage: del x, y, u @@ -2293,7 +2301,7 @@ def _new_element(self, x, dx, bigoh, dx_mode='linear_combination'): - ``dx`` -- a dictionary representing the differential of ``x`` - - ``bigoh`` -- an integer or ``None`` (default: ``None``): the + - ``bigoh`` -- an integer or ``None`` (default: ``None``): the bigoh to be added to the precision of ``x``; if ``None``, the default cap is used. @@ -2387,7 +2395,7 @@ def del_elements(self, threshold=None): INPUT: - ``threshold`` -- an integer or ``None`` (default: ``None``): - a non-pivot column whose distance to the right is greater than + a non-pivot column whose distance to the right is greater than the threshold is not erased but only marked for future deletion EXAMPLES:: @@ -2447,8 +2455,10 @@ def del_elements(self, threshold=None): self._marked_for_deletion.sort(reverse=True) count = 0 for index in self._marked_for_deletion: - if threshold is not None and index < n - threshold: break - n -= 1; count += 1 + if threshold is not None and index < n - threshold: + break + n -= 1 + count += 1 tme = walltime() @@ -2462,7 +2472,7 @@ def del_elements(self, threshold=None): end = n while i < n: col = self._matrix[self._elements[i]] - if len(col) > length: + if len(col) > length: end = i break v = col[-1].valuation() @@ -2478,7 +2488,8 @@ def del_elements(self, threshold=None): # No pivot was found. We re-echelonize for i in range(start, end): del self._matrix[self._elements[i]][-1] - if end == n: break + if end == n: + break # col is the column of index "end" # its size is (length + 1) d, u, v = col[length-1].xgcd(col[length]) @@ -2488,8 +2499,11 @@ def del_elements(self, threshold=None): start = end + 1 for j in range(start, n): col = self._matrix[self._elements[j]] - a1 = u*col[length-1]; a2 = v*col[length]; a = a1 + a2 - b1 = up*col[length-1]; b2 = vp*col[length]; b = b1 + b2 + a1 = u*col[length-1] + a2 = v*col[length] + a = a1 + a2 + b1 = up*col[length-1] + b2 = vp*col[length]; b = b1 + b2 if a.valuation() > min(a1.valuation(), a2.valuation()) + self._zero_cap: col[length-1] = self._approx_zero else: @@ -2525,7 +2539,7 @@ def _lift_to_precision(self, x, prec): p^{prec} \Z_p dx \oplus \bigoplus_{y \neq x} \Q_p dy - This function may change at the same time the precision of + This function may change at the same time the precision of other elements having the same parent. .. NOTE:: @@ -2569,7 +2583,8 @@ def _lift_to_precision(self, x, prec): rows = rows_by_val[v] piv = max(rows) for i in rows: - if i == piv: continue + if i == piv: + continue # We clear the entry on the i-th row scalar = (col[i]/col[piv]).reduce(prec-v) for j in range(n): @@ -2696,12 +2711,14 @@ def precision_lattice(self, elements=None): else: elements = list_of_padics(elements) n = len(self._elements) - rows = [ ]; val = 0 + rows = [ ] + val = 0 for ref in elements: col = self._matrix[ref] row = [ x.value() for x in col ] valcol = min([ x.valuation() for x in col ]) - if valcol < val: val = valcol + if valcol < val: + val = valcol row += (n-len(row)) * [ZZ(0)] rows.append(row) from sage.matrix.constructor import matrix diff --git a/src/sage/rings/padics/local_generic.py b/src/sage/rings/padics/local_generic.py index 63960d81fd6..69cc9026bb9 100644 --- a/src/sage/rings/padics/local_generic.py +++ b/src/sage/rings/padics/local_generic.py @@ -1,4 +1,4 @@ -""" +r""" Local Generic Superclass for `p`-adic and power series rings. @@ -8,7 +8,7 @@ - David Roe """ -#***************************************************************************** +# ***************************************************************************** # Copyright (C) 2007-2013 David Roe # William Stein # @@ -16,8 +16,8 @@ # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# ***************************************************************************** from copy import copy from sage.rings.ring import CommutativeRing @@ -30,8 +30,8 @@ class LocalGeneric(CommutativeRing): def __init__(self, base, prec, names, element_class, category=None): - """ - Initializes self. + r""" + Initialize ``self``. EXAMPLES:: @@ -75,7 +75,7 @@ def __init__(self, base, prec, names, element_class, category=None): Parent.__init__(self, base, names=(names,), normalize=False, category=category) def is_capped_relative(self): - """ + r""" Return whether this `p`-adic ring bounds precision in a capped relative fashion. @@ -100,7 +100,7 @@ def is_capped_relative(self): return False def is_capped_absolute(self): - """ + r""" Return whether this `p`-adic ring bounds precision in a capped absolute fashion. @@ -125,7 +125,7 @@ def is_capped_absolute(self): return False def is_fixed_mod(self): - """ + r""" Return whether this `p`-adic ring bounds precision in a fixed modulus fashion. @@ -152,7 +152,7 @@ def is_fixed_mod(self): return False def is_floating_point(self): - """ + r""" Return whether this `p`-adic ring bounds precision in a floating point fashion. @@ -177,7 +177,7 @@ def is_floating_point(self): return False def is_lattice_prec(self): - """ + r""" Return whether this `p`-adic ring bounds precision using a lattice model. @@ -206,7 +206,7 @@ def is_lattice_prec(self): return False def is_relaxed(self): - """ + r""" Return whether this `p`-adic ring bounds precision in a relaxed fashion. @@ -456,7 +456,7 @@ def get_unramified_modulus(q, res_name): functor.extras['names'] = kwds.pop('names') elif functor.extras['names'][0] == curpstr: functor.extras['names'] = (str(p),) - # labels for lattice precision + # Labels for lattice precision if 'label' in kwds: functor.extras['label'] = kwds.pop('label') elif 'label' in functor.extras and functor.type not in ['lattice-cap','lattice-float']: @@ -590,7 +590,7 @@ def residue_characteristic(self): OUTPUT: - - integer -- the characteristic of the residue field. + The characteristic of the residue field. EXAMPLES:: @@ -646,7 +646,7 @@ def ground_ring(self): OUTPUT: - - the ground ring of ``self``, i.e., itself + The ground ring of ``self``, i.e., itself. EXAMPLES:: @@ -671,7 +671,7 @@ def ground_ring_of_tower(self): OUTPUT: - - the ground ring of the tower for ``self``, i.e., itself + The ground ring of the tower for ``self``, i.e., itself. EXAMPLES:: @@ -683,8 +683,8 @@ def ground_ring_of_tower(self): def absolute_degree(self): - """ - Return the degree of this extension over the prime p-adic field/ring + r""" + Return the degree of this extension over the prime p-adic field/ring. EXAMPLES:: @@ -699,8 +699,8 @@ def absolute_degree(self): return self.absolute_e() * self.absolute_f() def relative_degree(self): - """ - Return the degree of this extension over its base field/ring + r""" + Return the degree of this extension over its base field/ring. EXAMPLES:: @@ -715,7 +715,7 @@ def relative_degree(self): return self.absolute_degree() // self.base_ring().absolute_degree() def degree(self): - """ + r""" Return the degree of this extension. Raise an error if the base ring/field is itself an extension. @@ -737,8 +737,8 @@ def degree(self): def absolute_e(self): - """ - Return the absolute ramification index of this ring/field + r""" + Return the absolute ramification index of this ring/field. EXAMPLES:: @@ -757,8 +757,8 @@ def absolute_e(self): return self.base_ring().absolute_e() def absolute_ramification_index(self): - """ - Return the absolute ramification index of this ring/field + r""" + Return the absolute ramification index of this ring/field. EXAMPLES:: @@ -773,8 +773,8 @@ def absolute_ramification_index(self): return self.absolute_e() def relative_e(self): - """ - Return the ramification index of this extension over its base ring/field + r""" + Return the ramification index of this extension over its base ring/field. EXAMPLES:: @@ -789,8 +789,8 @@ def relative_e(self): return self.absolute_e() // self.base_ring().absolute_e() def relative_ramification_index(self): - """ - Return the ramification index of this extension over its base ring/field + r""" + Return the ramification index of this extension over its base ring/field. EXAMPLES:: @@ -805,7 +805,7 @@ def relative_ramification_index(self): return self.relative_e() def e(self): - """ + r""" Return the ramification index of this extension. Raise an error if the base ring/field is itself an extension. @@ -826,7 +826,7 @@ def e(self): raise NotImplementedError("For a relative p-adic ring or field you must use relative_e or absolute_e as appropriate") def ramification_index(self): - """ + r""" Return the ramification index of this extension. Raise an error if the base ring/field is itself an extension. @@ -845,9 +845,9 @@ def ramification_index(self): def absolute_f(self): - """ + r""" Return the degree of the residue field of this ring/field - over its prime subfield + over its prime subfield. EXAMPLES:: @@ -866,9 +866,9 @@ def absolute_f(self): return self.base_ring().absolute_f() def absolute_inertia_degree(self): - """ + r""" Return the degree of the residue field of this ring/field - over its prime subfield + over its prime subfield. EXAMPLES:: @@ -883,8 +883,8 @@ def absolute_inertia_degree(self): return self.absolute_f() def relative_f(self): - """ - Return the degree of the residual extension over its base ring/field + r""" + Return the degree of the residual extension over its base ring/field. EXAMPLES:: @@ -899,8 +899,8 @@ def relative_f(self): return self.absolute_f() // self.base_ring().absolute_f() def relative_inertia_degree(self): - """ - Return the degree of the residual extension over its base ring/field + r""" + Return the degree of the residual extension over its base ring/field. EXAMPLES:: @@ -915,7 +915,7 @@ def relative_inertia_degree(self): return self.relative_f() def f(self): - """ + r""" Return the degree of the residual extension. Raise an error if the base ring/field is itself an extension. @@ -936,7 +936,7 @@ def f(self): raise NotImplementedError("For a relative p-adic ring or field you must use relative_f or absolute_f as appropriate") def inertia_degree(self): - """ + r""" Return the degree of the residual extension. Raise an error if the base ring/field is itself an extension. @@ -1000,7 +1000,7 @@ def maximal_unramified_subextension(self): # raise NotImplementedError def uniformiser(self): - """ + r""" Return a uniformiser for ``self``, ie a generator for the unique maximal ideal. EXAMPLES:: @@ -1017,7 +1017,7 @@ def uniformiser(self): return self.uniformizer() def uniformiser_pow(self, n): - """ + r""" Return the `n`th power of the uniformiser of ``self`` (as an element of ``self``). EXAMPLES:: @@ -1029,8 +1029,8 @@ def uniformiser_pow(self, n): return self.uniformizer_pow(n) def ext(self, *args, **kwds): - """ - Constructs an extension of self. See ``extension`` for more details. + r""" + Construct an extension of self. See :meth:`extension` for more details. EXAMPLES:: @@ -1120,14 +1120,14 @@ def _test_residue(self, **options): tester.assertEqual(x, z) def _matrix_flatten_precision(self, M): - """ + r""" Rescale rows and columns of ``M`` so that the minimal absolute precision of each row and column is equal to the cap. This method is useful for increasing the numerical stability. It is called by :meth:`_matrix_smith_form` - and :meth:`_matrix_determinant` + and :meth:`_matrix_determinant` Only for internal use. @@ -1151,18 +1151,21 @@ def _matrix_flatten_precision(self, M): """ parent = M.base_ring() cap = parent.precision_cap() - n = M.nrows(); m = M.ncols() + n = M.nrows() + m = M.ncols() shift_rows = n * [ ZZ(0) ] shift_cols = m * [ ZZ(0) ] for i in range(n): prec = min(M[i,j].precision_absolute() for j in range(m)) - if prec is Infinity or prec == cap: continue + if prec is Infinity or prec == cap: + continue shift_rows[i] = s = cap - prec for j in range(m): M[i,j] <<= s for j in range(m): prec = min(M[i,j].precision_absolute() for i in range(n)) - if prec is Infinity or prec == cap: continue + if prec is Infinity or prec == cap: + continue shift_cols[j] = s = cap - prec for i in range(n): M[i,j] <<= s @@ -1341,10 +1344,13 @@ def _matrix_smith_form(self, M, transformation, integral, exact): if exact: # we only care in this case allexact = allexact and Sij.precision_absolute() is infinity if v < curval: - pivi = i; pivj = j + pivi = i + pivj = j curval = v - if v == val: break - else: continue + if v == val: + break + else: + continue break val = curval @@ -1362,8 +1368,10 @@ def _matrix_smith_form(self, M, transformation, integral, exact): for i in range(i,n): for j in range(piv,m): allexact = allexact and S[i,j].precision_absolute() is infinity - if not allexact: break - else: continue + if not allexact: + break + else: + continue break if not allexact: raise PrecisionError("some elementary divisors indistinguishable from zero (try exact=False)") @@ -1549,11 +1557,11 @@ def _matrix_determinant(self, M): O(37) """ n = M.nrows() - + # For 2x2 matrices, we use the formula if n == 2: return M[0,0]*M[1,1] - M[0,1]*M[1,0] - + R = M.base_ring() track_precision = R._prec_type() in ['capped-rel','capped-abs'] @@ -1572,7 +1580,8 @@ def _matrix_determinant(self, M): for j in range(piv,n): v = S[i,j].valuation() if v < curval: - pivi = i; pivj = j + pivi = i + pivj = j curval = v if v == val: break @@ -1588,9 +1597,11 @@ def _matrix_determinant(self, M): valdet += val S.swap_rows(pivi,piv) - if pivi > piv: sign = -sign + if pivi > piv: + sign = -sign S.swap_columns(pivj,piv) - if pivj > piv: sign = -sign + if pivj > piv: + sign = -sign det *= S[piv,piv] inv = ~(S[piv,piv] >> val) @@ -1608,9 +1619,12 @@ def _matrix_determinant(self, M): for j in range(n): prec = min(prec, S[i,j].precision_absolute()) prec -= S[i,i].valuation() - if prec < relprec: relprec = prec - if prec < 0: relprec_neg += prec - if relprec_neg < 0: relprec = relprec_neg + if prec < relprec: + relprec = prec + if prec < 0: + relprec_neg += prec + if relprec_neg < 0: + relprec = relprec_neg det = (sign*det).add_bigoh(valdet+relprec) else: det = sign*det diff --git a/src/sage/rings/padics/local_generic_element.pyx b/src/sage/rings/padics/local_generic_element.pyx index 73e4125c09b..8d1dd7639c5 100644 --- a/src/sage/rings/padics/local_generic_element.pyx +++ b/src/sage/rings/padics/local_generic_element.pyx @@ -612,9 +612,13 @@ cdef class LocalGenericElement(CommutativeRingElement): - boolean -- whether ``self`` is a unit - NOTES: + .. NOTE:: - For fields all nonzero elements are units. For DVR's, only those elements of valuation 0 are. An older implementation ignored the case of fields, and returned always the negation of self.valuation()==0. This behavior is now supported with self.is_padic_unit(). + For fields all nonzero elements are units. For DVR's, only + those elements of valuation 0 are. An older implementation + ignored the case of fields, and returned always the + negation of self.valuation()==0. This behavior is now + supported with self.is_padic_unit(). EXAMPLES:: diff --git a/src/sage/rings/padics/padic_base_leaves.py b/src/sage/rings/padics/padic_base_leaves.py index cbf71588bfd..4d4f44735d3 100644 --- a/src/sage/rings/padics/padic_base_leaves.py +++ b/src/sage/rings/padics/padic_base_leaves.py @@ -1101,7 +1101,7 @@ def random_element(self, prec=None, integral=False): ######### class pAdicRingRelaxed(pAdicRelaxedGeneric, pAdicRingBaseGeneric): - """ + r""" An implementation of relaxed arithmetics over `\ZZ_p`. INPUT: @@ -1136,7 +1136,7 @@ def __init__(self, p, prec, print_mode, names): self._element_class_prefix = "pAdicRelaxedElement_" class pAdicFieldRelaxed(pAdicRelaxedGeneric, pAdicFieldBaseGeneric): - """ + r""" An implementation of relaxed arithmetics over `\QQ_p`. INPUT: diff --git a/src/sage/rings/padics/padic_generic.py b/src/sage/rings/padics/padic_generic.py index ae15159be84..6179e29f022 100644 --- a/src/sage/rings/padics/padic_generic.py +++ b/src/sage/rings/padics/padic_generic.py @@ -1,4 +1,4 @@ -""" +r""" p-Adic Generic A generic superclass for all p-adic parents. @@ -9,10 +9,9 @@ - Genya Zaytman: documentation - David Harvey: doctests - Julian Rueth (2013-03-16): test methods for basic arithmetic - """ -#***************************************************************************** +# **************************************************************************** # Copyright (C) 2007-2013 David Roe # William Stein # Julian Rueth @@ -21,8 +20,8 @@ # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # -# http://www.gnu.org/licenses/ -#***************************************************************************** +# https://www.gnu.org/licenses/ +# **************************************************************************** from sage.misc.misc import some_tuples from copy import copy @@ -44,20 +43,20 @@ class pAdicGeneric(PrincipalIdealDomain, LocalGeneric): def __init__(self, base, p, prec, print_mode, names, element_class, category=None): - """ - Initialization. + r""" + Initialize ``self``. INPUT: - - base -- Base ring. - - p -- prime - - print_mode -- dictionary of print options - - names -- how to print the uniformizer - - element_class -- the class for elements of this ring + - ``base`` -- Base ring. + - ``p`` -- prime + - ``print_mode`` -- dictionary of print options + - ``names`` -- how to print the uniformizer + - ``element_class`` -- the class for elements of this ring EXAMPLES:: - sage: R = Zp(17) #indirect doctest + sage: R = Zp(17) # indirect doctest """ if category is None: if self.is_field(): @@ -71,7 +70,7 @@ def __init__(self, base, p, prec, print_mode, names, element_class, category=Non def some_elements(self): r""" - Returns a list of elements in this ring. + Return a list of elements in this ring. This is typically used for running generic tests (see :class:`TestSuite`). @@ -91,14 +90,16 @@ def some_elements(self): return L def _modified_print_mode(self, print_mode): - """ - Returns a dictionary of print options, starting with self's + r""" + Return a dictionary of print options, starting with self's print options but modified by the options in the dictionary print_mode. INPUT: - - print_mode -- dictionary with keys in ['mode', 'pos', 'ram_name', 'unram_name', 'var_name', 'max_ram_terms', 'max_unram_terms', 'max_terse_terms', 'sep', 'alphabet'] + - ``print_mode`` -- dictionary with keys in ['mode', 'pos', 'ram_name', + 'unram_name', 'var_name', 'max_ram_terms', 'max_unram_terms', + 'max_terse_terms', 'sep', 'alphabet'] EXAMPLES:: @@ -110,14 +111,16 @@ def _modified_print_mode(self, print_mode): print_mode = {} elif isinstance(print_mode, str): print_mode = {'mode': print_mode} - for option in ['mode', 'pos', 'ram_name', 'unram_name', 'var_name', 'max_ram_terms', 'max_unram_terms', 'max_terse_terms', 'sep', 'alphabet', 'show_prec']: + for option in ['mode', 'pos', 'ram_name', 'unram_name', 'var_name', + 'max_ram_terms', 'max_unram_terms', 'max_terse_terms', + 'sep', 'alphabet', 'show_prec']: if option not in print_mode: print_mode[option] = self._printer.dict()[option] return print_mode def ngens(self): - """ - Returns the number of generators of self. + r""" + Return the number of generators of self. We conventionally define this as 1: for base rings, we take a uniformizer as the generator; for extension rings, we take a @@ -133,8 +136,8 @@ def ngens(self): return 1 def gens(self): - """ - Returns a list of generators. + r""" + Return a list of generators. EXAMPLES:: @@ -148,7 +151,7 @@ def gens(self): return [self.gen()] def __richcmp__(self, other, op): - """ + r""" Return 0 if self == other, and 1 or -1 otherwise. We consider two p-adic rings or fields to be equal if they are @@ -182,25 +185,25 @@ def __richcmp__(self, other, op): return self._printer.richcmp_modes(other._printer, op) - #def ngens(self): - # return 1 + # def ngens(self): + # return 1 - #def gen(self, n = 0): - # if n != 0: - # raise IndexError, "only one generator" - # return self(self.prime()) + # def gen(self, n = 0): + # if n != 0: + # raise IndexError, "only one generator" + # return self(self.prime()) def print_mode(self): r""" - Returns the current print mode as a string. + Return the current print mode as a string. INPUT: - self -- a p-adic field + - ``self`` -- a p-adic field OUTPUT: - string -- self's print mode + The print mode for this p-adic field, as a string. EXAMPLES:: @@ -212,15 +215,13 @@ def print_mode(self): def characteristic(self): r""" - Returns the characteristic of self, which is always 0. + Return the characteristic of self, which is always 0. INPUT: - self -- a p-adic parent + - ``self`` -- a p-adic parent - OUTPUT: - - integer -- self's characteristic, i.e., 0 + OUTPUT: self's characteristic, i.e., 0. EXAMPLES:: @@ -230,16 +231,16 @@ def characteristic(self): return Integer(0) def prime(self): - """ - Returns the prime, ie the characteristic of the residue field. + r""" + Return the prime, ie the characteristic of the residue field. INPUT: - self -- a p-adic parent + - ``self`` -- a p-adic parent OUTPUT: - integer -- the characteristic of the residue field + The characteristic of the residue field. EXAMPLES:: @@ -250,10 +251,10 @@ def prime(self): return self.prime_pow._prime() def uniformizer_pow(self, n): - """ - Returns p^n, as an element of self. + r""" + Return `p^n`, as an element of ``self``. - If n is infinity, returns 0. + If `n` is infinity, returns 0. EXAMPLES:: @@ -268,8 +269,9 @@ def uniformizer_pow(self, n): return self(self.prime_pow.pow_Integer_Integer(n)) def _unram_print(self): - """ - For printing. Will be None if the unramified subextension of self is of degree 1 over Z_p or Q_p. + r""" + For printing. Will be ``None`` if the unramified subextension + of self is of degree 1 over `\ZZ_p` or `\QQ_p`. EXAMPLES:: @@ -278,12 +280,12 @@ def _unram_print(self): return None def residue_characteristic(self): - """ + r""" Return the prime, i.e., the characteristic of the residue field. OUTPUT: - integer -- the characteristic of the residue field + The characteristic of the residue field. EXAMPLES:: @@ -294,16 +296,16 @@ def residue_characteristic(self): return self.prime() def residue_class_field(self): - """ - Returns the residue class field. + r""" + Return the residue class field. INPUT: - self -- a p-adic ring + - ``self`` -- a p-adic ring OUTPUT: - the residue field + The residue field. EXAMPLES:: @@ -316,16 +318,16 @@ def residue_class_field(self): return GF(self.prime()) def residue_field(self): - """ - Returns the residue class field. + r""" + Return the residue class field. INPUT: - self -- a p-adic ring + - ``self`` -- a p-adic ring OUTPUT: - the residue field + The residue field. EXAMPLES:: @@ -337,8 +339,8 @@ def residue_field(self): return self.residue_class_field() def residue_ring(self, n): - """ - Returns the quotient of the ring of integers by the nth power of the maximal ideal. + r""" + Return the quotient of the ring of integers by the nth power of the maximal ideal. EXAMPLES:: @@ -350,16 +352,16 @@ def residue_ring(self, n): return Zmod(self.prime()**n) def residue_system(self): - """ - Returns a list of elements representing all the residue classes. + r""" + Return a list of elements representing all the residue classes. INPUT: - self -- a p-adic ring + - ``self`` -- a p-adic ring OUTPUT: - list of elements -- a list of elements representing all the residue classes + A list of elements representing all the residue classes. EXAMPLES:: @@ -370,8 +372,9 @@ def residue_system(self): return [self(i) for i in self.residue_class_field()] def _fraction_field_key(self, print_mode=None): - """ - Changes print_mode from a dictionary to a tuple and raises a deprecation warning if it is present. + r""" + Change print_mode from a dictionary to a tuple, + raising a deprecation warning if it is present. EXAMPLES:: @@ -391,7 +394,7 @@ def _fraction_field_key(self, print_mode=None): @cached_method(key=_fraction_field_key) def fraction_field(self, print_mode=None): r""" - Returns the fraction field of this ring or field. + Return the fraction field of this ring or field. For `\ZZ_p`, this is the `p`-adic field with the same options, and for extensions, it is just the extension of the fraction @@ -407,7 +410,7 @@ def fraction_field(self, print_mode=None): OUTPUT: - - the fraction field of this ring. + The fraction field of this ring. EXAMPLES:: @@ -454,7 +457,7 @@ def fraction_field(self, print_mode=None): def integer_ring(self, print_mode=None): r""" - Returns the ring of integers of this ring or field. + Return the ring of integers of this ring or field. For `\QQ_p`, this is the `p`-adic ring with the same options, and for extensions, it is just the extension of the ring @@ -467,7 +470,7 @@ def integer_ring(self, print_mode=None): OUTPUT: - - the ring of elements of this field with nonnegative valuation. + The ring of elements of this field with nonnegative valuation. EXAMPLES:: @@ -507,7 +510,9 @@ def integer_ring(self, print_mode=None): sage: R.integer_ring({'mode':'series'}) is R True """ - #Currently does not support fields with non integral defining polynomials. This should change when the padic_general_extension framework gets worked out. + # Currently does not support fields with non integral defining + # polynomials. This should change when the padic_general_extension + # framework gets worked out. if not self.is_field() and print_mode is None: return self if print_mode is None: @@ -519,16 +524,16 @@ def integer_ring(self, print_mode=None): def teichmuller(self, x, prec = None): r""" - Returns the teichmuller representative of x. + Return the Teichmüller representative of `x`. INPUT: - - self -- a p-adic ring - - x -- something that can be cast into self + - ``self`` -- a p-adic ring + - ``x`` -- something that can be cast into ``self`` OUTPUT: - - element -- the teichmuller lift of x + The Teichmüller lift of `x`. EXAMPLES:: @@ -567,10 +572,10 @@ def teichmuller(self, x, prec = None): AUTHORS: - Initial version: David Roe - - Quadratic time version: Kiran Kedlaya (3/27/07) + - Quadratic time version: Kiran Kedlaya (2007-03-27) """ ans = self(x) if prec is None else self(x, prec) - # Since teichmuller representatives are defined at infinite precision, + # Since Teichmüller representatives are defined at infinite precision, # we can lift to precision prec, as long as the absolute precision of ans is positive. if ans.precision_absolute() <= 0: raise ValueError("Not enough precision to determine Teichmuller representative") @@ -584,15 +589,17 @@ def teichmuller(self, x, prec = None): def teichmuller_system(self): r""" - Returns a set of teichmuller representatives for the invertible elements of `\ZZ / p\ZZ`. + Return a set of Teichmüller representatives for the invertible + elements of `\ZZ / p\ZZ`. INPUT: - - self -- a p-adic ring + - ``self`` -- a p-adic ring OUTPUT: - - list of elements -- a list of teichmuller representatives for the invertible elements of `\ZZ / p\ZZ` + A list of Teichmüller representatives for the invertible elements + of `\ZZ / p\ZZ`. EXAMPLES:: @@ -606,13 +613,14 @@ def teichmuller_system(self): sage: F.teichmuller_system()[3] (2*a + 2) + (4*a + 1)*5 + 4*5^2 + (2*a + 1)*5^3 + (4*a + 1)*5^4 + (2*a + 3)*5^5 + O(5^6) - NOTES: + .. NOTE:: - Should this return 0 as well? + Should this return 0 as well? """ R = self.residue_class_field() prec = self.precision_cap() - return [self.teichmuller(self(i).lift_to_precision(prec)) for i in R if i != 0] + return [self.teichmuller(self(i).lift_to_precision(prec)) + for i in R if i != 0] # def different(self): # raise NotImplementedError @@ -636,7 +644,7 @@ def teichmuller_system(self): # raise NotImplementedError def extension(self, modulus, prec = None, names = None, print_mode = None, implementation='FLINT', **kwds): - """ + r""" Create an extension of this p-adic ring. EXAMPLES:: @@ -689,7 +697,7 @@ def extension(self, modulus, prec = None, names = None, print_mode = None, imple return ExtensionFactory(base=self, modulus=modulus, prec=prec, names=names, check = True, implementation=implementation, **print_mode) def _is_valid_homomorphism_(self, codomain, im_gens, base_map=None): - """ + r""" Check whether the given images and map on the base ring determine a valid homomorphism to the codomain. @@ -733,7 +741,7 @@ def _is_valid_homomorphism_(self, codomain, im_gens, base_map=None): return f(im_gens[0]) == 0 def _test_add(self, **options): - """ + r""" Test addition of elements of this ring. INPUT: @@ -747,7 +755,6 @@ def _test_add(self, **options): .. SEEALSO:: :class:`TestSuite` - """ tester = self._tester(**options) elements = tester.some_elements() @@ -773,7 +780,7 @@ def _test_add(self, **options): tester.assertTrue(x.is_equal_to(z-y,zprec)) def _test_sub(self, **options): - """ + r""" Test subtraction on elements of this ring. INPUT: @@ -787,7 +794,6 @@ def _test_sub(self, **options): .. SEEALSO:: :class:`TestSuite` - """ tester = self._tester(**options) @@ -837,7 +843,8 @@ def _test_invert(self, **options): y = ~x except (ZeroDivisionError, PrecisionError, ValueError): tester.assertFalse(x.is_unit()) - if not self.is_fixed_mod(): tester.assertTrue(x.is_zero()) + if not self.is_fixed_mod(): + tester.assertTrue(x.is_zero()) else: try: e = y * x @@ -851,7 +858,7 @@ def _test_invert(self, **options): tester.assertEqual(y.valuation(), -x.valuation()) def _test_mul(self, **options): - """ + r""" Test multiplication of elements of this ring. INPUT: @@ -865,7 +872,6 @@ def _test_mul(self, **options): .. SEEALSO:: :class:`TestSuite` - """ tester = self._tester(**options) @@ -881,7 +887,7 @@ def _test_mul(self, **options): tester.assertEqual(z.valuation(), x.valuation() + y.valuation()) def _test_div(self, **options): - """ + r""" Test division of elements of this ring. INPUT: @@ -895,7 +901,6 @@ def _test_div(self, **options): .. SEEALSO:: :class:`TestSuite` - """ tester = self._tester(**options) @@ -904,8 +909,10 @@ def _test_div(self, **options): try: z = x / y except (ZeroDivisionError, PrecisionError, ValueError): - if self.is_fixed_mod(): tester.assertFalse(y.is_unit()) - else: tester.assertTrue(y.is_zero()) + if self.is_fixed_mod(): + tester.assertFalse(y.is_unit()) + else: + tester.assertTrue(y.is_zero()) else: try: xx = z*y @@ -921,7 +928,7 @@ def _test_div(self, **options): tester.assertEqual(xx, x) def _test_neg(self, **options): - """ + r""" Test the negation operator on elements of this ring. INPUT: @@ -994,7 +1001,7 @@ def _test_shift(self, **options): def _test_log(self, **options): - """ + r""" Test the log operator on elements of this ring. INPUT: @@ -1011,7 +1018,8 @@ def _test_log(self, **options): """ tester = self._tester(**options) for x in tester.some_elements(): - if x.is_zero(): continue + if x.is_zero(): + continue try: l = x.log(p_branch=0) tester.assertIs(l.parent(), self) @@ -1026,14 +1034,16 @@ def _test_log(self, **options): if self.is_capped_absolute() or self.is_capped_relative(): # In the fixed modulus setting, rounding errors may occur for x, y, b in tester.some_elements(repeat=3): - if (x*y).is_zero(): continue + if (x*y).is_zero(): + continue r1 = x.log(pi_branch=b) + y.log(pi_branch=b) r2 = (x*y).log(pi_branch=b) tester.assertEqual(r1, r2) p = self.prime() for x in tester.some_elements(): - if x.is_zero(): continue + if x.is_zero(): + continue if p == 2: a = 4 * x.unit_part() else: @@ -1044,8 +1054,8 @@ def _test_log(self, **options): tester.assertEqual(1+a, c) def _test_teichmuller(self, **options): - """ - Test Teichmuller lifts. + r""" + Test Teichmüller lifts. INPUT: @@ -1133,7 +1143,10 @@ def _log_unit_part_p(self): return self(self.prime()).unit_part().log() def frobenius_endomorphism(self, n=1): - """ + r""" + Return the `n`-th power of the absolute arithmetic Frobeninus + endomorphism on this field. + INPUT: - ``n`` -- an integer (default: 1) @@ -1147,19 +1160,22 @@ def frobenius_endomorphism(self, n=1): sage: K. = Qq(3^5) sage: Frob = K.frobenius_endomorphism(); Frob - Frobenius endomorphism on 3-adic Unramified Extension ... lifting a |--> a^3 on the residue field + Frobenius endomorphism on 3-adic Unramified Extension + ... lifting a |--> a^3 on the residue field sage: Frob(a) == a.frobenius() True We can specify a power:: sage: K.frobenius_endomorphism(2) - Frobenius endomorphism on 3-adic Unramified Extension ... lifting a |--> a^(3^2) on the residue field + Frobenius endomorphism on 3-adic Unramified Extension + ... lifting a |--> a^(3^2) on the residue field The result is simplified if possible:: sage: K.frobenius_endomorphism(6) - Frobenius endomorphism on 3-adic Unramified Extension ... lifting a |--> a^3 on the residue field + Frobenius endomorphism on 3-adic Unramified Extension + ... lifting a |--> a^3 on the residue field sage: K.frobenius_endomorphism(5) Identity endomorphism of 3-adic Unramified Extension ... @@ -1172,9 +1188,11 @@ def frobenius_endomorphism(self, n=1): return FrobeniusEndomorphism_padics(self, n) def _test_elements_eq_transitive(self, **options): - """ - The operator ``==`` is not transitive for `p`-adic numbers. We disable - the check of the category framework by overriding this method. + r""" + The operator ``==`` is not transitive for `p`-adic numbers. + + We disable the check of the category framework by overriding + this method. EXAMPLES:: @@ -1223,13 +1241,12 @@ def valuation(self): :meth:`NumberField_generic.valuation() `, :meth:`Order.valuation() ` - """ from sage.rings.padics.padic_valuation import pAdicValuation return pAdicValuation(self) def _primitive_qth_root_of_unity(self, exponent): - """ + r""" Compute the ``p^exponent``-th roots of unity in this ring. INPUT: @@ -1238,13 +1255,11 @@ def _primitive_qth_root_of_unity(self, exponent): OUTPUT: - A triple ``(zeta,n,nextzeta)`` where + A triple ``(zeta, n, nextzeta)`` where - ``zeta`` is a generator of the group of ``p^exponent``-th roots of unity in this ring, and - - ``p^n`` is the order of ``zeta``. - - ``nextzeta`` is the result of ``zeta._inverse_pth_root()`` if ``n`` is positive and ``None`` otherwise @@ -1304,7 +1319,7 @@ def _primitive_qth_root_of_unity(self, exponent): return self._qth_roots_of_unity[-2][0], n-2, self._qth_roots_of_unity[-1] def primitive_root_of_unity(self, n=None, order=False): - """ + r""" Return a generator of the group of ``n``-th roots of unity in this ring. @@ -1352,7 +1367,6 @@ def primitive_root_of_unity(self, n=None, order=False): 6 sage: zeta.multiplicative_order() 6 - """ p = self.prime() k = self.residue_field() @@ -1387,7 +1401,7 @@ def primitive_root_of_unity(self, n=None, order=False): return zeta def roots_of_unity(self, n=None): - """ + r""" Return all the ``n``-th roots of unity in this ring. INPUT: @@ -1408,7 +1422,7 @@ def roots_of_unity(self, n=None): [1 + O(5^10), 4 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + O(5^10)] - In this case, the roots of unity are the Teichmuller representatives:: + In this case, the roots of unity are the Teichmüller representatives:: sage: R.teichmuller_system() [1 + O(5^10), @@ -1416,7 +1430,7 @@ def roots_of_unity(self, n=None): 3 + 3*5 + 2*5^2 + 3*5^3 + 5^4 + 2*5^6 + 5^7 + 4*5^8 + 5^9 + O(5^10), 4 + 4*5 + 4*5^2 + 4*5^3 + 4*5^4 + 4*5^5 + 4*5^6 + 4*5^7 + 4*5^8 + 4*5^9 + O(5^10)] - In general, there might be more roots of unity (it happens when the ring has non + In general, there might be more roots of unity (it happens when the ring has non trivial ``p``-th roots of unity):: sage: W. = Zq(3^2, 2) @@ -1426,7 +1440,7 @@ def roots_of_unity(self, n=None): sage: roots = R.roots_of_unity(); roots [1 + O(pi^4), a + 2*a*pi + 2*a*pi^2 + a*pi^3 + O(pi^4), - ... + ... 1 + pi + O(pi^4), a + a*pi^2 + 2*a*pi^3 + O(pi^4), ... @@ -1439,8 +1453,8 @@ def roots_of_unity(self, n=None): We check that the logarithm of each root of unity vanishes:: sage: for root in roots: - ....: if root.log() != 0: raise ValueError - + ....: if root.log() != 0: + ....: raise ValueError """ zeta, order = self.primitive_root_of_unity(n, order=True) return [ zeta**i for i in range(order) ] @@ -1455,23 +1469,23 @@ def _roots_univariate_polynomial(self, P, ring, multiplicities, algorithm, secur - ``ring`` -- a ring into which this ring coerces - - ``multiplicities`` -- a boolean (default: ``True``); + - ``multiplicities`` -- a boolean (default: ``True``); whether we have to return the multiplicities of each root or not - - ``algorithm`` -- ``"pari"``, ``"sage"`` or ``None`` (default: - ``None``); Sage provides an implementation for any extension of - `Q_p` whereas only roots of polynomials over `\QQ_p` is implemented - in Pari; the default is ``"pari"`` if ``ring`` is `\ZZ_p` or `\QQ_p`, + - ``algorithm`` -- ``"pari"``, ``"sage"`` or ``None`` (default: + ``None``); Sage provides an implementation for any extension of + `\QQ_p` whereas only roots of polynomials over `\QQ_p` is implemented + in Pari; the default is ``"pari"`` if ``ring`` is `\ZZ_p` or `\QQ_p`, ``"sage"`` otherwise. - ``secure`` -- a boolean (default: ``False``) NOTE: - When ``secure`` is ``True``, this method raises an error when - the precision on the input polynomial is not enough to determine - the number of roots in the ground field. This happens when two + When ``secure`` is ``True``, this method raises an error when + the precision on the input polynomial is not enough to determine + the number of roots in the ground field. This happens when two roots cannot be separated. A typical example is the polynomial @@ -1479,14 +1493,14 @@ def _roots_univariate_polynomial(self, P, ring, multiplicities, algorithm, secur (1 + O(p^10))*X^2 + O(p^10)*X + O(p^10) - Indeed its discriminant might be any `p`-adic integer divisible - by `p^{10}` (resp. `p^{11}` when `p=2`) and so can be as well + Indeed its discriminant might be any `p`-adic integer divisible + by `p^{10}` (resp. `p^{11}` when `p=2`) and so can be as well zero, a square and a non-square. In the first case, the polynomial has one double root; in the second case, it has two roots; in the third case, it has no root in `\QQ_p`. - When ``secure`` is ``False``, this method assumes that two + When ``secure`` is ``False``, this method assumes that two inseparable roots actually collapse. In the above example, it then answers that the given polynomial has a double root `O(p^5)`. @@ -1598,7 +1612,6 @@ def _roots_univariate_polynomial(self, P, ring, multiplicities, algorithm, secur Traceback (most recent call last): ... ArithmeticError: factorization of 0 is not defined - """ if P.is_zero(): raise ArithmeticError("factorization of 0 is not defined") @@ -1629,7 +1642,7 @@ def _roots_univariate_polynomial(self, P, ring, multiplicities, algorithm, secur class ResidueReductionMap(Morphism): - """ + r""" Reduction map from a p-adic ring or field to its residue field or ring. These maps must be created using the :meth:`_create_` method in order @@ -1646,7 +1659,7 @@ class ResidueReductionMap(Morphism): """ @staticmethod def _create_(R, k): - """ + r""" Initialization. We have to implement this as a static method in order to call ``__make_element_class__``. @@ -1685,7 +1698,7 @@ def _create_(R, k): return f def is_surjective(self): - """ + r""" The reduction map is surjective. EXAMPLES:: @@ -1696,7 +1709,7 @@ def is_surjective(self): return True def is_injective(self): - """ + r""" The reduction map is far from injective. EXAMPLES:: @@ -1707,7 +1720,7 @@ def is_injective(self): return False def _call_(self, x): - """ + r""" Evaluate this morphism. EXAMPLES:: @@ -1727,7 +1740,7 @@ def _call_(self, x): return x.residue(self._n, field=self._field, check_prec=self._field) def section(self): - """ + r""" Returns the section from the residue ring or field back to the p-adic ring or field. @@ -1741,7 +1754,7 @@ def section(self): return ResidueLiftingMap._create_(self.codomain(), self.domain()) def _repr_type(self): - """ + r""" Type of morphism, for printing. EXAMPLES:: @@ -1769,10 +1782,10 @@ def _richcmp_(self, other, op): return NotImplemented return richcmp((self.domain(), self.codomain()), (other.domain(), other.codomain()), op) -# A class for the Teichmuller lift would also be reasonable.... +# A class for the Teichmüller lift would also be reasonable.... class ResidueLiftingMap(Morphism): - """ + r""" Lifting map to a p-adic ring or field from its residue field or ring. These maps must be created using the :meth:`_create_` method in order @@ -1789,7 +1802,7 @@ class ResidueLiftingMap(Morphism): """ @staticmethod def _create_(k, R): - """ + r""" Initialization. We have to implement this as a static method in order to call ``__make_element_class__``. @@ -1817,7 +1830,7 @@ def _create_(k, R): return f def _call_(self, x): - """ + r""" Evaluate this morphism. EXAMPLES:: @@ -1847,7 +1860,7 @@ def _call_(self, x): raise NotImplementedError def _call_with_args(self, x, args=(), kwds={}): - """ + r""" Evaluate this morphism with extra arguments. EXAMPLES:: @@ -1874,7 +1887,7 @@ def _call_with_args(self, x, args=(), kwds={}): raise NotImplementedError def _repr_type(self): - """ + r""" Type of morphism, for printing. EXAMPLES:: @@ -1918,7 +1931,7 @@ def local_print_mode(obj, print_options, pos = None, ram_name = None): .. NOTE:: - For more documentation see ``localvars`` in parent_gens.pyx + For more documentation see :class:`sage.structure.parent_gens.localvars`. """ if isinstance(print_options, str): print_options = {'mode': print_options} diff --git a/src/sage/rings/padics/padic_generic_element.pyx b/src/sage/rings/padics/padic_generic_element.pyx index b1988778707..37b2393c9ce 100644 --- a/src/sage/rings/padics/padic_generic_element.pyx +++ b/src/sage/rings/padics/padic_generic_element.pyx @@ -32,6 +32,7 @@ from cysignals.memory cimport sig_malloc, sig_free cimport sage.rings.padics.local_generic_element from sage.libs.gmp.mpz cimport mpz_set_si +from sage.arith.srange import srange from sage.rings.padics.local_generic_element cimport LocalGenericElement from sage.rings.padics.precision_error import PrecisionError from sage.rings.rational cimport Rational @@ -2552,14 +2553,14 @@ cdef class pAdicGenericElement(LocalGenericElement): By default, we use the binary splitting if it is available. Otherwise we switch to the generic algorithm. - NOTES: + .. NOTE:: - What some other systems do: + What some other systems do: - - PARI: Seems to define the logarithm for units not congruent - to 1 as we do. + - PARI: Seems to define the logarithm for units not congruent + to 1 as we do. - - MAGMA: Only implements logarithm for 1-units (as of version 2.19-2) + - MAGMA: Only implements logarithm for 1-units (version 2.19-2) .. TODO:: @@ -4038,7 +4039,7 @@ cdef class pAdicGenericElement(LocalGenericElement): """ raise NotImplementedError - def _polylog_res_1(self, n): + def _polylog_res_1(self, n, p_branch = 0): """ Return `Li_n(`self`)` , the `n`th `p`-adic polylogarithm of ``self``, assuming that self is congruent to 1 mod p. @@ -4050,7 +4051,7 @@ cdef class pAdicGenericElement(LocalGenericElement): OUTPUT: - - Li_n(self) + - `Li_n(`self`)` EXAMPLES :: @@ -4061,6 +4062,13 @@ cdef class pAdicGenericElement(LocalGenericElement): Traceback (most recent call last): ... ValueError: Polylogarithm is not defined for 1. + + Only polylogarithms for `n` at least two are defined by this function :: + + sage: Qp(11)(2)._polylog_res_1(1) + Traceback (most recent call last): + ... + ValueError: Polylogarithm only implemented for n at least 2. """ from sage.rings.power_series_ring import PowerSeriesRing from sage.functions.other import ceil,floor @@ -4069,6 +4077,8 @@ cdef class pAdicGenericElement(LocalGenericElement): if self == 1: raise ValueError('Polylogarithm is not defined for 1.') + if n <= 1: + raise ValueError('Polylogarithm only implemented for n at least 2.') p = self.parent().prime() prec = self.precision_absolute() @@ -4076,17 +4086,18 @@ cdef class pAdicGenericElement(LocalGenericElement): K = self.parent().fraction_field() z = K(self) - hsl = max(prec / ((z - 1).valuation()) + 1, prec*(p == 2)) - N = floor(prec - n*(hsl - 1).log(p)) + hsl = max(prec / ((z - 1).valuation()) + 1, prec*(p == 2), 2) + N = floor(prec - n*(hsl - 1).log(p).n()) verbose(hsl, level=3) def bound(m): - return prec - m + Integer(1-2**(m-1)).valuation(p) - m*(hsl - 1).log(p) + return prec - m + Integer(1-2**(m-1)).valuation(p) - m*(hsl - 1).log(p).n() - gsl = max([_findprec(1/(p-1), 1, _polylog_c(m,p) + bound(m), p) for m in range(2,n+1)]) + gsl = max(_findprec(1/(p-1), 1, _polylog_c(m,p) + bound(m), p) for m in range(2,n+1)) + gsl = max(gsl, 2) verbose(gsl, level=3) - g = _compute_g(p, n, max([bound(m) + m*floor((gsl-1).log(p)) for m in range(2, n+1)]), gsl) + g = _compute_g(p, n, max(bound(m) + m*floor((gsl-1).log(p).n()) for m in range(2, n+1)), gsl) verbose(g, level=3) S = PowerSeriesRing(K, default_prec = ceil(hsl), names='t') t = S.gen() @@ -4101,7 +4112,7 @@ cdef class pAdicGenericElement(LocalGenericElement): verbose(G, level=3) H = (n+1)*[0] - H[2] = -sum([((-t)**i)/Integer(i)**2 for i in range(1,hsl+2)]) + H[2] = -sum([((-t)**i)/i**2 for i in srange(1,hsl+2)]) for i in range(2, n): H[i+1] = (H[i]/(1+t) + G[i]/t).integral() if (i + 1) % 2 == 1: @@ -4111,19 +4122,22 @@ cdef class pAdicGenericElement(LocalGenericElement): H[i+1] += (2**i*H[i+1](K(-2)))/(1 - 2**(i+1)) verbose(H, level=3) - return (H[n](z - 1) - ((z.log(0))**(n-1)*(1 - z).log(0))/Integer(n-1).factorial()).add_bigoh(N) + return (H[n](z - 1) - ((z.log(p_branch))**(n-1)*(1 - z).log(p_branch))/Integer(n-1).factorial()).add_bigoh(N) - def polylog(self, n): + def polylog(self, n, p_branch = 0): """ - Return `Li_n(self)` , the `n`th `p`-adic polylogarithm of this element. + Return `Li_n(self)`, the `n`th `p`-adic polylogarithm of this element. INPUT: - - ``n`` -- a non-negative integer + - ``n`` -- a non-negative integer + - ``p_branch`` -- an element in the base ring or its fraction + field; the implementation will choose the branch of the + logarithm which sends `p` to ``branch``. OUTPUT: - - `Li_n(self)` + - `Li_n(`self`)` EXAMPLES: @@ -4155,19 +4169,36 @@ cdef class pAdicGenericElement(LocalGenericElement): sage: Qp(5)(10).polylog(1) == -Qp(5)(1-10).log(0) True - The polylogarithm of 1 is not defined :: + The dilogarithm of 1 is zero :: - sage: Qp(5)(1).polylog(1) - Traceback (most recent call last): - ... - ValueError: Polylogarithm is not defined for 1. + sage: Qp(5)(1).polylog(2) + O(5^20) + The cubing relation holds for the trilogarithm at 1 :: + + sage: K = Qp(7) + sage: z = K.zeta(3) + sage: -8*K(1).polylog(3) == 9*(K(z).polylog(3) + K(z^2).polylog(3)) + True The polylogarithm of 0 is 0 :: sage: Qp(11)(0).polylog(7) 0 + Only polylogarithms for positive `n` are defined :: + + sage: Qp(11)(2).polylog(-1) + Traceback (most recent call last): + ... + ValueError: Polylogarithm only implemented for n at least 0. + + Check that :trac:`29222` is fixed :: + + sage: K = Qp(7) + sage: print(K(1 + 7^11).polylog(4)) + 6*7^14 + 3*7^15 + 7^16 + 7^17 + O(7^18) + ALGORITHM: The algorithm of Besser-de Jeu, as described in [BdJ2008]_ is used. @@ -4193,6 +4224,12 @@ cdef class pAdicGenericElement(LocalGenericElement): if self.parent().absolute_degree() != 1: raise NotImplementedError("Polylogarithms are not currently implemented for elements of extensions") # TODO implement this (possibly after the change method for padic generic elements is added). + if n == 0: + return self/(1-self) + if n == 1: + return -(1-self).log(p_branch) + if n < 0: + raise ValueError('Polylogarithm only implemented for n at least 0.') prec = self.precision_absolute() @@ -4204,7 +4241,7 @@ cdef class pAdicGenericElement(LocalGenericElement): if z.valuation() < 0: verbose("residue oo, using functional equation for reciprocal. %d %s"%(n,str(self)), level=2) - return (-1)**(n+1)*(1/z).polylog(n)-(z.log(0)**n)/K(n.factorial()) + return (-1)**(n+1)*(1/z).polylog(n)-(z.log(p_branch)**n)/K(n.factorial()) zeta = K.teichmuller(z) @@ -4213,7 +4250,7 @@ cdef class pAdicGenericElement(LocalGenericElement): if z.precision_absolute() == PlusInfinity(): return K(0) verbose("residue 0, using series. %d %s"%(n,str(self)), level=2) - M = ceil((prec/z.valuation()).log(p)) + M = ceil((prec/z.valuation()).log(p).n()) N = prec - n*M ret = K(0) for m in range(M + 1): @@ -4225,16 +4262,17 @@ cdef class pAdicGenericElement(LocalGenericElement): if zeta == 1: if z == 1: - raise ValueError("Polylogarithm is not defined for 1.") + return Integer(2)**(n-1)*K(-1).polylog(n, p_branch=p_branch)/(1-Integer(2)**(n-1)) verbose("residue 1, using _polylog_res_1. %d %s"%(n,str(self)), level=2) - return self._polylog_res_1(n) + return self._polylog_res_1(n, p_branch) # Set up precision bounds tsl = prec / (z - zeta).valuation() + 1 - N = floor(prec - n*(tsl - 1).log(p)) - gsl = max([_findprec(1/(p-1), 1, prec - m + _polylog_c(m,p) - m*(tsl - 1).log(p), p) for m in range(1,n+1)]) + N = floor(prec - n*(tsl - 1).log(p).n()) + gsl = max(_findprec(1/(p-1), 1, prec - m + _polylog_c(m,p) - m*(tsl - 1).log(p).n(), p) for m in range(1,n+1)) + gsl = max(gsl,2) - gtr = _compute_g(p, n, prec + n*(gsl - 1).log(p), gsl) + gtr = _compute_g(p, n, prec + n*(gsl - 1).log(p).n(), gsl) K = Qp(p, prec) @@ -4362,9 +4400,9 @@ def _polylog_c(n, p): EXAMPLES:: sage: sage.rings.padics.padic_generic_element._polylog_c(1, 2) - log(4/log(2))/log(2) + 2 + 4.52876637294490 """ - return p/(p-1) - (n-1)/p.log() + (n-1)*(n*(p-1)/p.log()).log(p) + (2*p*(p-1)*n/p.log()).log(p) + return p/(p-1) - (n-1)/p.log().n() + (n-1)*(n*(p-1)/p.log().n()).log(p).n() + (2*p*(p-1)*n/p.log().n()).log(p).n() def _findprec(c_1, c_2, c_3, p): """ @@ -4392,7 +4430,7 @@ def _findprec(c_1, c_2, c_3, p): from sage.functions.other import ceil k = Integer(max(ceil(c_2/c_1), 2)) while True: - if c_1*k - c_2*k.log(p) > c_3: + if c_1*k - c_2*k.log(p).n() > c_3: return k k += 1 diff --git a/src/sage/rings/padics/relaxed_template.pxi b/src/sage/rings/padics/relaxed_template.pxi index ba15d4f5d3c..9ef4ea4e704 100644 --- a/src/sage/rings/padics/relaxed_template.pxi +++ b/src/sage/rings/padics/relaxed_template.pxi @@ -2637,9 +2637,9 @@ cdef class RelaxedElement_random(RelaxedElementWithDigits): It is guaranteed that `a` and `b` are equal at any precision:: - sage: a[:30] + sage: a[:30] # random ...?343214211432220241412003314311 - sage: b[:30] + sage: b[:30] # random ...?343214211432220241412003314311 sage: a == b diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index 611a355ab11..8106cf5f7a5 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -35,122 +35,104 @@ cdef class MPolynomial(CommutativeRingElement): #################### # Some standard conversions #################### - def __int__(self): - """ + def _scalar_conversion(self, R): + r""" TESTS:: - sage: type(RR['x,y']) - - sage: type(RR['x, y'](0)) - - - sage: int(RR['x,y'](0)) # indirect doctest + sage: ZZ(RR['x,y'](0)) # indirect doctest 0 - sage: int(RR['x,y'](10)) - 10 - sage: int(ZZ['x,y'].gen(0)) + sage: ZZ(RR['x,y'](0.5)) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to an integer - """ - if self.degree() <= 0: - return int(self.constant_coefficient()) - raise TypeError(f"unable to convert non-constant polynomial {self} to an integer") - - def __float__(self): - """ - TESTS:: - - sage: float(RR['x,y'](0)) # indirect doctest - 0.0 - sage: float(ZZ['x,y'].gen(0)) + TypeError: Attempt to coerce non-integral RealNumber to Integer + sage: ZZ(RR['x,y'].gen(0)) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to a float - """ - if self.degree() <= 0: - return float(self.constant_coefficient()) - raise TypeError(f"unable to convert non-constant polynomial {self} to a float") - - def _mpfr_(self, R): - """ - TESTS:: + TypeError: unable to convert non-constant polynomial x to Integer Ring sage: RR(RR['x,y'](0)) # indirect doctest 0.000000000000000 sage: RR(ZZ['x,y'].gen(0)) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to a real number - """ - if self.degree() <= 0: - return R(self.constant_coefficient()) - raise TypeError(f"unable to convert non-constant polynomial {self} to a real number") - - def _complex_mpfr_field_(self, R): - """ - TESTS:: + TypeError: unable to convert non-constant polynomial x to Real Field with 53 bits of precision sage: CC(RR['x,y'](0)) # indirect doctest 0.000000000000000 sage: CC(ZZ['x,y'].gen(0)) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to a complex number - """ - if self.degree() <= 0: - return R(self.constant_coefficient()) - raise TypeError(f"unable to convert non-constant polynomial {self} to a complex number") + TypeError: unable to convert non-constant polynomial x to Complex Field with 53 bits of precision - def _complex_double_(self, R): - """ - TESTS:: + sage: RDF(RR['x,y'](0)) + 0.0 + sage: RDF(ZZ['x,y'].gen(0)) + Traceback (most recent call last): + ... + TypeError: unable to convert non-constant polynomial x to Real Double Field sage: CDF(RR['x,y'](0)) # indirect doctest 0.0 sage: CDF(ZZ['x,y'].gen(0)) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to a complex number - """ - if self.degree() <= 0: - return R(self.constant_coefficient()) - raise TypeError(f"unable to convert non-constant polynomial {self} to a complex number") + TypeError: unable to convert non-constant polynomial x to Complex Double Field - def _real_double_(self, R): - """ - TESTS:: + sage: a = RR['x,y'](1) + sage: RBF(a) + 1.000000000000000 + sage: RIF(a) + 1 + sage: CBF(a) + 1.000000000000000 + sage: CIF(a) + 1 - sage: RDF(RR['x,y'](0)) - 0.0 - sage: RDF(ZZ['x,y'].gen(0)) + sage: CBF(RR['x,y'](1)) # indirect doctest + 1.000000000000000 + sage: CBF(ZZ['x,y'].gen(0)) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to a real number + TypeError: unable to convert non-constant polynomial x to Complex ball field with 53 bits of precision + + sage: x = polygen(QQ) + sage: A. = NumberField(x^3 - 2) + sage: A(A['x,y'](u)) + u """ if self.degree() <= 0: return R(self.constant_coefficient()) - raise TypeError(f"unable to convert non-constant polynomial {self} to a real number") + raise TypeError(f"unable to convert non-constant polynomial {self} to {R}") + + _real_double_ = _scalar_conversion + _complex_double_ = _scalar_conversion + _mpfr_ = _scalar_conversion + _complex_mpfr_ = _scalar_conversion + _real_mpfi_ = _scalar_conversion + _complex_mpfi_ = _scalar_conversion + _arb_ = _scalar_conversion + _acb_ = _scalar_conversion + _integer_ = _scalar_conversion + _algebraic_ = _scalar_conversion + _number_field_ = _scalar_conversion - def _rational_(self): + def __int__(self): """ TESTS:: - sage: QQ(RR['x,y'](0.5)) # indirect doctest - 1/2 - sage: QQ(RR['x,y'].gen(0)) + sage: type(RR['x,y']) + + sage: type(RR['x, y'](0)) + + + sage: int(RR['x,y'](0)) # indirect doctest + 0 + sage: int(RR['x,y'](10)) + 10 + sage: int(ZZ['x,y'].gen(0)) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to a rational - """ - if self.degree() <= 0: - from sage.rings.rational import Rational - return Rational(self.constant_coefficient()) - raise TypeError(f"unable to convert non-constant polynomial {self} to a rational") - - def _integer_(self, ZZ=None): - """ - TESTS:: + TypeError: unable to convert non-constant polynomial x to sage: ZZ(RR['x,y'](0)) # indirect doctest 0 @@ -161,12 +143,36 @@ cdef class MPolynomial(CommutativeRingElement): sage: ZZ(RR['x,y'].gen(0)) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to an integer + TypeError: unable to convert non-constant polynomial x to Integer Ring """ - if self.degree() <= 0: - from sage.rings.integer import Integer - return Integer(self.constant_coefficient()) - raise TypeError(f"unable to convert non-constant polynomial {self} to an integer") + return self._scalar_conversion(int) + + def __float__(self): + """ + TESTS:: + + sage: float(RR['x,y'](0)) # indirect doctest + 0.0 + sage: float(ZZ['x,y'].gen(0)) + Traceback (most recent call last): + ... + TypeError: unable to convert non-constant polynomial x to + """ + return self._scalar_conversion(float) + + def _rational_(self): + """ + TESTS:: + + sage: QQ(RR['x,y'](0.5)) # indirect doctest + 1/2 + sage: QQ(RR['x,y'].gen(0)) + Traceback (most recent call last): + ... + TypeError: unable to convert non-constant polynomial x to Rational Field + """ + from sage.rings.rational_field import QQ + return self._scalar_conversion(QQ) def _symbolic_(self, R): """ @@ -821,6 +827,41 @@ cdef class MPolynomial(CommutativeRingElement): else: return True + def homogeneous_components(self): + """ + Return the homogeneous components of this polynomial. + + OUTPUT: + + A dictionary mapping degrees to homogeneous polynomials. + + EXAMPLES:: + + sage: R. = QQ[] + sage: (x^3 + 2*x*y^3 + 4*y^3 + y).homogeneous_components() + {1: y, 3: x^3 + 4*y^3, 4: 2*x*y^3} + sage: R.zero().homogeneous_components() + {} + + In case of weighted term orders, the polynomials are homogeneous with + respect to the weights:: + + sage: S. = PolynomialRing(ZZ, order=TermOrder('wdegrevlex', (1,2,3))) + sage: (a^6 + b^3 + b*c + a^2*c + c + a + 1).homogeneous_components() + {0: 1, 1: a, 3: c, 5: a^2*c + b*c, 6: a^6 + b^3} + """ + cdef ETuple e + from collections import defaultdict + d = defaultdict(dict) + if self._parent.term_order()._weights: + for c, m in self: + d[m.degree()][m.exponents()[0]] = c + else: + # Otherwise it is unweighted, so we use a faster implementation + for e, c in self.iterator_exp_coeff(): + d[e.unweighted_degree()][e] = c + return {k: self._parent(d[k]) for k in d} + cpdef _mod_(self, other): """ EXAMPLES:: @@ -1077,6 +1118,26 @@ cdef class MPolynomial(CommutativeRingElement): return '%s!(%s)'%(R.name(), s) + def _giac_init_(self): + r""" + Return a Giac string representation of this polynomial. + + TESTS:: + + sage: R. = GF(101)['e,i'][] + sage: f = R('e*i') * x + y^2 + sage: f._giac_init_() + '((1)*1)*sageVARy^2+((1)*sageVARe*sageVARi)*sageVARx' + sage: giac(f) + sageVARy^2+sageVARe*sageVARi*sageVARx + sage: giac(R.zero()) + 0 + """ + g = ['sageVAR' + x for x in self.parent().variable_names()] + s = '+'.join('(%s)*%s' % (c._giac_init_(), + m._repr_with_changed_varnames(g)) + for c, m in self) + return s if s else '0' def gradient(self): r""" @@ -1968,7 +2029,7 @@ cdef class MPolynomial(CommutativeRingElement): sage: p.weighted_degree(x,1,1) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to an integer + TypeError: unable to convert non-constant polynomial x to Integer Ring sage: p.weighted_degree(2/1,1,1) 6 diff --git a/src/sage/rings/polynomial/multi_polynomial_ring.py b/src/sage/rings/polynomial/multi_polynomial_ring.py index 91b350b61da..d3faae1c812 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring.py +++ b/src/sage/rings/polynomial/multi_polynomial_ring.py @@ -115,12 +115,12 @@ def __init__(self, base_ring, n, names, order): self._base = base_ring # Construct the generators v = [0] * n - one = base_ring(1); + one = base_ring(1) self._gens = [] C = self._poly_class() for i in range(n): v[i] = 1 # int's! - self._gens.append(C(self, {tuple(v):one})) + self._gens.append(C(self, {tuple(v): one})) v[i] = 0 self._gens = tuple(self._gens) self._zero_tuple = tuple(v) @@ -174,7 +174,7 @@ def __hash__(self): return hash((self.base_ring(), self.ngens(), self.variable_names(), self.term_order())) - def __call__(self, x, check=True): + def __call__(self, x=0, check=True): """ Convert ``x`` to an element of this multivariate polynomial ring, possibly non-canonically. diff --git a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx index 879a7bbb707..b617281ddc5 100644 --- a/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx +++ b/src/sage/rings/polynomial/multi_polynomial_ring_base.pyx @@ -873,7 +873,7 @@ cdef class MPolynomialRing_base(sage.rings.ring.CommutativeRing): total = binomial(n+d-1, d) #Select random monomial of degree d - random_index = ZZ.random_element(0, total-1) + random_index = ZZ.random_element(0, total) #Generate the corresponding monomial return self._to_monomial(random_index, n, d) diff --git a/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py b/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py index 760fde7c563..13d09467427 100644 --- a/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py +++ b/src/sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py @@ -1245,13 +1245,16 @@ def is_eisenstein(self, secure=False): valaddeds = self._valaddeds relprecs = self._relprecs if relprecs[0] <= compval: # not enough precision - if valaddeds[0] < relprecs[0]: return False + if valaddeds[0] < relprecs[0]: + return False raise PrecisionError("Not enough precision on the constant coefficient") else: - if valaddeds[0] != compval: return False + if valaddeds[0] != compval: + return False for i in range(1, deg): if relprecs[i] < compval: # not enough precision - if valaddeds[i] < relprecs[i]: return False + if valaddeds[i] < relprecs[i]: + return False if secure: if i == 1: raise PrecisionError("Not enough precision on the coefficient of %s" % self.variable_name()) diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index 570c148e72a..11fac767f4a 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -1347,6 +1347,11 @@ cdef class Polynomial(CommutativeAlgebraElement): Traceback (most recent call last): ... TypeError: cannot convert nonconstant polynomial + + sage: x = polygen(QQ) + sage: A. = NumberField(x^3 - 2) + sage: A(A['x'](u)) + u """ if self.degree() > 0: raise TypeError("cannot convert nonconstant polynomial") @@ -6352,6 +6357,26 @@ cdef class Polynomial(CommutativeAlgebraElement): from sage.libs.gap.libgap import libgap return self._gap_(libgap) + def _giac_init_(self): + r""" + Return a Giac string representation of this polynomial. + + TESTS:: + + sage: R. = GF(101)['e,i'][] + sage: f = R('e*i') * x + x^2 + sage: f._giac_init_() + '((1)*1)*sageVARx^2+((1)*sageVARe*sageVARi)*sageVARx' + sage: giac(f) + sageVARx^2+sageVARe*sageVARi*sageVARx + sage: giac(R.zero()) + 0 + """ + g = 'sageVAR' + self.variable_name() + s = '+'.join('(%s)*%s' % (self.monomial_coefficient(m)._giac_init_(), + m._repr(g)) for m in self.monomials()) + return s if s else '0' + ###################################################################### @coerce_binop @@ -7619,7 +7644,7 @@ cdef class Polynomial(CommutativeAlgebraElement): [(-3.5074662110434039?e451, 1)] sage: p = bigc*x + 1 sage: p.roots(ring=RR) - [(0.000000000000000, 1)] + [(-2.85106096489671e-452, 1)] sage: p.roots(ring=AA) [(-2.8510609648967059?e-452, 1)] sage: p.roots(ring=QQbar) @@ -7830,11 +7855,17 @@ cdef class Polynomial(CommutativeAlgebraElement): [(a + O(3^20), 1), (2*a + 2*a*3 + 2*a*3^2 + 2*a*3^3 + 2*a*3^4 + 2*a*3^5 + 2*a*3^6 + 2*a*3^7 + 2*a*3^8 + 2*a*3^9 + 2*a*3^10 + 2*a*3^11 + 2*a*3^12 + 2*a*3^13 + 2*a*3^14 + 2*a*3^15 + 2*a*3^16 + 2*a*3^17 + 2*a*3^18 + 2*a*3^19 + O(3^20), 1)] + + Check that :trac:`31710` is fixed:: + + sage: CBF['x'].zero().roots(multiplicities=False) + Traceback (most recent call last): + ... + ArithmeticError: taking the roots of the zero polynomial """ from sage.rings.finite_rings.finite_field_constructor import GF K = self._parent.base_ring() - # If the base ring has a method _roots_univariate_polynomial, # try to use it. An exception is raised if the method does not # handle the current parameters diff --git a/src/sage/rings/polynomial/polynomial_element_generic.py b/src/sage/rings/polynomial/polynomial_element_generic.py index 67de318829e..d9578903c64 100644 --- a/src/sage/rings/polynomial/polynomial_element_generic.py +++ b/src/sage/rings/polynomial/polynomial_element_generic.py @@ -1199,7 +1199,8 @@ def hensel_lift(self, a): b = ~dera while(True): na = a - selfa * b - if na == a: return a + if na == a: + return a a = na selfa = self(a) dera = der(a) @@ -1477,7 +1478,8 @@ def _roots(self, secure, minval, hint): continue if hint is not None and slope == minval: rootsbar = hint - if not rootsbar: continue + if not rootsbar: + continue if i < len(vertices) - 1: F = P._factor_of_degree(deg_right - deg) P = P // F @@ -1491,7 +1493,8 @@ def _roots(self, secure, minval, hint): if hint is None or slope != minval: Fbar = Pk([ F[j] >> (val - j*slope) for j in range(F.degree()+1) ]) rootsbar = [ r for (r, _) in Fbar.roots() ] - if not rootsbar: continue + if not rootsbar: + continue rbar = rootsbar.pop() shift = K(rbar).lift_to_precision() << slope # probably we should choose a better lift roots += [(r+shift, m) for (r, m) in F(x+shift)._roots(secure, slope, [r-rbar for r in rootsbar])] # recursive call diff --git a/src/sage/rings/polynomial/polynomial_fateman.py b/src/sage/rings/polynomial/polynomial_fateman.py index 557417d4470..580cee21d63 100644 --- a/src/sage/rings/polynomial/polynomial_fateman.py +++ b/src/sage/rings/polynomial/polynomial_fateman.py @@ -88,7 +88,8 @@ def _mul_fateman_mul(f,g): n_f = z_poly_f(1< = QQ[] + sage: u = x^7 + x + 1 + sage: u.galois_group_davenport_smith_test() + 1 + sage: u = x^7 - x^4 - x^3 + 3*x^2 - 1 + sage: u.galois_group_davenport_smith_test() + 2 + sage: u = x^7 - 2 + sage: u.galois_group_davenport_smith_test() + 0 + + """ + from sage.arith.misc import primes_first_n + from sage.rings.finite_rings.integer_mod_ring import IntegerModRing + if not assume_irreducible and not self.is_irreducible(): + return 0 + d = self.degree() + for p in primes_first_n(num_trials): + fp = self.change_ring(IntegerModRing(p)) + g = fp.factor()[-1][0] + d1 = g.degree() + # Here we use the fact that a transitive permutation representation with a long prime cycle + # must have image at least as big as A_n. + if (d1 <= 7 and (d,d1) in ((1,1),(2,2),(3,2),(3,3),(4,3),(5,3),(5,4),(6,5),(7,5))) or\ + (d1 > d/2 and d1 < d-2 and d1.is_prime()): + return (2 if self.disc().is_square() else 1) + return 0 diff --git a/src/sage/rings/polynomial/weil/power_sums.c b/src/sage/rings/polynomial/weil/power_sums.c index 9f7e1f9c9fe..a210a874902 100644 --- a/src/sage/rings/polynomial/weil/power_sums.c +++ b/src/sage/rings/polynomial/weil/power_sums.c @@ -52,8 +52,11 @@ int _fmpz_poly_all_real_roots(fmpz *poly, long n, fmpz *w, int force_squarefree, fmpz *d = w + 2*n+1; fmpz *t; - if (n <= 2) return(1); _fmpz_vec_set(f0, poly, n); + /* Sanitize input so that n = deg(f0). */ + while ((n > 2) && fmpz_is_zero(f0+n-1)) + n--; + if (n <= 2) return(1); if (a != NULL && b != NULL) fmpz_addmul(f0, a, b); _fmpz_poly_derivative(f1, f0, n); n--; @@ -74,6 +77,7 @@ int _fmpz_poly_all_real_roots(fmpz *poly, long n, fmpz *w, int force_squarefree, _fmpz_vec_scalar_mul_fmpz(f0, f0, n, d); _fmpz_vec_scalar_addmul_fmpz(f0, f1, n, c); + /* If f0 = 0, we win unless we are insisting on squarefree. */ if (!force_squarefree && _fmpz_vec_is_zero(f0, n)) return(1); /* If we miss any one sign change, we cannot have enough. */ diff --git a/src/sage/rings/polynomial/weil/weil_polynomials.pyx b/src/sage/rings/polynomial/weil/weil_polynomials.pyx index 419c31ab98e..3a6857f0b2b 100755 --- a/src/sage/rings/polynomial/weil/weil_polynomials.pyx +++ b/src/sage/rings/polynomial/weil/weil_polynomials.pyx @@ -540,6 +540,14 @@ class WeilPolynomials(): sage: u in WeilPolynomials(6, 11, 1, [(1,0),(1,11),(6,11)]) True + Test that :trac:`31809` is resolved:: + + sage: from sage.rings.polynomial.weil.weil_polynomials import WeilPolynomials + sage: foo = list(WeilPolynomials(12, 3, lead=(1,0,9,2,46), squarefree=False)) + sage: bar = list(WeilPolynomials(12, 3, lead=(1,0,9,2,46), squarefree=True)) + sage: bar == [f for f in foo if f.is_squarefree()] + True + """ def __init__(self, d, q, sign=1, lead=1, node_limit=None, parallel=False, squarefree=False, polring=None): r""" diff --git a/src/sage/rings/qqbar.py b/src/sage/rings/qqbar.py index 2b586491c61..2403ef43b34 100644 --- a/src/sage/rings/qqbar.py +++ b/src/sage/rings/qqbar.py @@ -573,7 +573,7 @@ from sage.rings.integer_ring import ZZ from sage.rings.rational_field import QQ from sage.rings.number_field.number_field import NumberField, GaussianField, CyclotomicField -from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_quadratic, NumberFieldElement_gaussian +from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_gaussian from sage.arith.all import factor from . import infinity from sage.categories.action import Action @@ -4669,8 +4669,8 @@ def _richcmp_(self, other, op): [-0.0221204634374361? - 1.090991904211621?*I, -0.0221204634374361? + 1.090991904211621?*I, -0.8088604911480535?*I, - 0.?e-79 - 0.7598602580415435?*I, - 0.?e-79 + 0.7598602580415435?*I, + 0.?e-215 - 0.7598602580415435?*I, + 0.?e-229 + 0.7598602580415435?*I, 0.8088604911480535?*I, 0.0221204634374361? - 1.090991904211621?*I, 0.0221204634374361? + 1.090991904211621?*I] @@ -4761,18 +4761,28 @@ def _richcmp_(self, other, op): sage: a > r True """ - # note: we can assume that self is not other here + if self is other: + return rich_to_bool(op, 0) + + # case 0: rationals sd = self._descr od = other._descr - if isinstance(sd, ANRational) and isinstance(od, ANRational): return richcmp(sd._value, od._value, op) + # case 1: real parts are clearly distinct + ri1 = self._value.real() + ri2 = other._value.real() + if not ri1.overlaps(ri2): + # NOTE: do not call richcmp here as self._value and other._value + # might have different precisions. See + # https://trac.sagemath.org/ticket/29220 + return ri1._richcmp_(ri2, op) + if op == op_EQ or op == op_NE: # some cheap and quite common tests where we can decide # equality or difference - if not (self._value.real().overlaps(other._value.real()) and - self._value.imag().overlaps(other._value.imag())): + if not self._value.imag().overlaps(other._value.imag()): return op == op_NE if isinstance(sd, ANRational) and not sd._value: return bool(other) == (op == op_NE) @@ -4783,21 +4793,6 @@ def _richcmp_(self, other, op): sd._generator is od._generator): return sd._value == od._value if op == op_EQ else sd._value != od._value - # case 0: real parts are clearly distinct - ri1 = self._value.real() - ri2 = other._value.real() - if not ri1.overlaps(ri2): - # NOTE: do not call richcmp here as self._value and other._value - # might have different precisions. See - # https://trac.sagemath.org/ticket/29220 - return ri1._richcmp_(ri2, op) - - # case 1: rationals - sd = self._descr - od = other._descr - if isinstance(sd, ANRational) and isinstance(od, ANRational): - return richcmp(sd._value, od._value, op) - # case 2: possibly equal or conjugate values # (this case happen a lot when sorting the roots of a real polynomial) ci1 = self._value.imag().abs() @@ -5130,7 +5125,7 @@ def rational_argument(self): -1/3 sage: QQbar(3+4*I).rational_argument() is None True - sage: (QQbar(2)**(1/5) * QQbar.zeta(7)**2).rational_argument() + sage: (QQbar(2)**(1/5) * QQbar.zeta(7)**2).rational_argument() # long time 2/7 sage: (QQbar.zeta(73)**5).rational_argument() 5/73 @@ -5280,18 +5275,48 @@ def _richcmp_(self, other, op): True sage: AA(7) >= AA(50/7) False + + Check for trivial equality with identical elements:: + + sage: x1 = AA(2^(1/50)) + sage: x2 = AA(2^(1/50)) + sage: y = x1 - x2 + sage: y == y + True + sage: y >= y + True + sage: y < y + False + + sage: z = x1 - x2 + sage: z == 0 + True + + sage: a = x1 - x2 + sage: b = x1 - x2 + sage: a == b + True """ + if self is other: + return rich_to_bool(op, 0) + # note: we can assume that self is not other here sd = self._descr od = other._descr + # case 0: rationals if type(sd) is ANRational and type(od) is ANRational: return richcmp(sd._value, od._value, op) + # case 1: real parts are clearly distinct + if not self._value.overlaps(other._value): + # NOTE: do not call richcmp here as self._value and other._value + # might have different precisions. See + # https://trac.sagemath.org/ticket/29220 + return self._value._richcmp_(other._value, op) + if op == op_EQ or op == op_NE: # some cheap and quite common tests where we can decide equality or difference - if not self._value.real().overlaps(other._value.real()): - return op == op_NE if type(sd) is ANRational and not sd._value: return bool(other) == (op == op_NE) elif type(od) is ANRational and not od._value: @@ -5300,28 +5325,26 @@ def _richcmp_(self, other, op): type(od) is ANExtensionElement and sd._generator is od._generator): return sd._value == od._value if op == op_EQ else sd._value != od._value - elif self.minpoly() != other.minpoly(): - return op == op_NE - - # case 0: real parts are clearly distinct - if not self._value.overlaps(other._value): - # NOTE: do not call richcmp here as self._value and other._value - # might have different precisions. See - # https://trac.sagemath.org/ticket/29220 - return self._value._richcmp_(other._value, op) - - # case 1: rationals - sd = self._descr - od = other._descr - if type(sd) is ANRational and type(od) is ANRational: - return richcmp(sd._value, od._value, op) + else: + # Only compare the minimal polynomials if they have been computed + # as otherwise it calls exactify(). + try: + if self._minimal_polynomial != other._minimal_polynomial: + return op == op_NE + except AttributeError: + pass # case 2: possibly equal values # (this case happen a lot when sorting the roots of a real polynomial) - if self.minpoly() == other.minpoly(): - c = cmp_elements_with_same_minpoly(self, other, self.minpoly()) - if c is not None: - return rich_to_bool(op, c) + # Only compare the minimal polynomials if they have been computed + # as otherwise it calls exactify(). + try: + if self._minimal_polynomial != other._minimal_polynomial: + c = cmp_elements_with_same_minpoly(self, other, self.minpoly()) + if c is not None: + return rich_to_bool(op, c) + except AttributeError: + pass if self._value.prec() < 128: self._more_precision() @@ -5609,6 +5632,30 @@ def sign(self): 1 sage: (a*b - b*a).sign() 0 + + sage: a = AA(sqrt(1/2)) + sage: b = AA(-sqrt(1/2)) + sage: (a + b).sign() + 0 + + TESTS: + + We avoid calling :meth:`exactify()` for trivial differences. The + following example will take a long time (more than 5 seconds) + when calling ``y.exactify()``:: + + sage: x1 = AA(2^(1/50)) + sage: x2 = AA(2^(1/50)) + sage: y = x1 - x2 + sage: y.sign() + 0 + + Simplify to rationals for binary operations when computing the sign:: + + sage: a = AA(2^(1/60)) + sage: b = a - (a + 1) + sage: (b + 1).sign() + 0 """ if not self._value.contains_zero(): return self._value.unique_sign() @@ -5628,7 +5675,7 @@ def sign(self): ls = sd._left.sign() rs = sd._right.sign() if sd._op is operator.mul or sd._op is operator.truediv: - return sd._left.sign() * sd._right.sign() + return ls * rs elif sd._op is operator.add: if ls == rs: return ls @@ -5641,6 +5688,9 @@ def sign(self): elif not rs: self._set_descr(sd._left._descr) return ls + elif sd._left is sd._right: + self._set_descr(ANRational(QQ.zero())) + return 0 elif type(sd) is ANUnaryExpr: if sd._op == 'abs': c = 1 if bool(sd._arg) else 0 @@ -5658,6 +5708,38 @@ def sign(self): if not self._value.contains_zero(): return self._value.unique_sign() + if type(sd) is ANBinaryExpr: + # We will now exactify both sides and do another sign comparison. + # We try to avoid making ourself exact if possible. + # It will only reach this block if the operation is addition or subtraction. + sd._left.exactify() + sd._right.exactify() + + # Rationals + if type(sd._left._descr) is ANRational and type(sd._right._descr) is ANRational: + ret = sd._op(sd._left._descr._value, sd._right._descr._value) + if ret == 0: + self._set_descr(ANRational(QQ.zero())) + return 0 + return ret.sign() + + if sd._left.minpoly() == sd._right.minpoly(): + # Negating the element does not change the minpoly + right = sd._right if sd._op is operator.sub else -sd._right + c = cmp_elements_with_same_minpoly(sd._left, right, sd._left.minpoly()) + if c == 0: + self._set_descr(ANRational(QQ.zero())) + return 0 + elif c is not None: + return c + + ret = sd._op(sd._left._value, sd._right._value) + if not ret.contains_zero(): + return ret.unique_sign() + if not ret: # Known to be exactly 0 + self._set_descr(ANRational(QQ.zero())) + return 0 + # Sigh... self.exactify() return self.sign() diff --git a/src/sage/rings/quotient_ring_element.py b/src/sage/rings/quotient_ring_element.py index 09564e4c3eb..eab1154f30c 100644 --- a/src/sage/rings/quotient_ring_element.py +++ b/src/sage/rings/quotient_ring_element.py @@ -473,11 +473,11 @@ def __int__(self): sage: int(a) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to an integer + TypeError: unable to convert non-constant polynomial x to """ return int(self.lift()) - def _integer_(self, Z=None): + def _integer_(self, Z): """ EXAMPLES:: @@ -488,13 +488,10 @@ def _integer_(self, Z=None): TESTS:: - sage: type(S(-3)._integer_()) - + sage: type(ZZ(S(-3))) + """ - try: - return self.lift()._integer_(Z) - except AttributeError: - raise NotImplementedError + return Z(self.lift()) def _rational_(self): """ @@ -510,10 +507,8 @@ def _rational_(self): sage: type(S(-2/3)._rational_()) """ - try: - return self.lift()._rational_() - except AttributeError: - raise NotImplementedError + from sage.rings.rational_field import QQ + return QQ(self.lift()) def __neg__(self): """ @@ -579,7 +574,7 @@ def __float__(self): sage: float(a) Traceback (most recent call last): ... - TypeError: unable to convert non-constant polynomial x to a float + TypeError: unable to convert non-constant polynomial x to """ return float(self.lift()) diff --git a/src/sage/rings/rational_field.py b/src/sage/rings/rational_field.py index e7959e21a01..c79d2655535 100644 --- a/src/sage/rings/rational_field.py +++ b/src/sage/rings/rational_field.py @@ -1576,6 +1576,20 @@ def _polymake_init_(self): """ return '"Rational"' + def _sympy_(self): + r""" + Return the SymPy set ``Rationals``. + + EXAMPLES:: + + sage: QQ._sympy_() + Rationals + """ + from sympy import Rationals + from sage.interfaces.sympy import sympy_init + sympy_init() + return Rationals + def _sage_input_(self, sib, coerced): r""" Produce an expression which will reproduce this value when evaluated. diff --git a/src/sage/rings/ring.pyx b/src/sage/rings/ring.pyx index 0408a5bcabe..18c7f750020 100644 --- a/src/sage/rings/ring.pyx +++ b/src/sage/rings/ring.pyx @@ -1365,29 +1365,6 @@ cdef class CommutativeRing(Ring): except (NotImplementedError,TypeError): return coercion_model.division_parent(self) - def __pow__(self, n, _): - """ - Return the free module of rank `n` over this ring. If n is a tuple of - two elements, creates a matrix space. - - EXAMPLES:: - - sage: QQ^5 - Vector space of dimension 5 over Rational Field - sage: Integers(20)^1000 - Ambient free module of rank 1000 over Ring of integers modulo 20 - - sage: QQ^(2,3) - Full MatrixSpace of 2 by 3 dense matrices over Rational Field - """ - if isinstance(n, tuple): - m, n = n - from sage.matrix.matrix_space import MatrixSpace - return MatrixSpace(self, m, n) - else: - import sage.modules.all - return sage.modules.all.FreeModule(self, n) - def is_commutative(self): """ Return ``True``, since this ring is commutative. @@ -1515,9 +1492,9 @@ cdef class CommutativeRing(Ring): name = str(poly.parent().gen(0)) for key, val in kwds.items(): if key not in ['structure', 'implementation', 'prec', 'embedding', 'latex_name', 'latex_names']: - raise TypeError("extension() got an unexpected keyword argument '%s'"%key) + raise TypeError("extension() got an unexpected keyword argument '%s'" % key) if not (val is None or isinstance(val, list) and all(c is None for c in val)): - raise NotImplementedError("ring extension with prescripted %s is not implemented"%key) + raise NotImplementedError("ring extension with prescribed %s is not implemented" % key) R = self[name] I = R.ideal(R(poly.list())) return R.quotient(I, name) diff --git a/src/sage/sandpiles/sandpile.py b/src/sage/sandpiles/sandpile.py index ad1cc562762..a03112b98b4 100644 --- a/src/sage/sandpiles/sandpile.py +++ b/src/sage/sandpiles/sandpile.py @@ -335,7 +335,7 @@ from sage.graphs.all import DiGraph, Graph from sage.graphs.digraph_generators import digraphs from sage.probability.probability_distribution import GeneralDiscreteDistribution -from sage.homology.simplicial_complex import SimplicialComplex +from sage.topology.simplicial_complex import SimplicialComplex from sage.interfaces.singular import singular from sage.matrix.constructor import matrix, identity_matrix from sage.misc.all import prod, det, tmp_filename, exists, denominator @@ -1494,12 +1494,12 @@ def group_gens(self, verbose=True): sage: s = sandpiles.Cycle(5) sage: s.group_gens() - [{1: 1, 2: 1, 3: 1, 4: 0}] + [{1: 0, 2: 1, 3: 1, 4: 1}] sage: s.group_gens()[0].order() 5 sage: s = sandpiles.Complete(5) sage: s.group_gens(False) - [[2, 2, 3, 2], [2, 3, 2, 2], [3, 2, 2, 2]] + [[2, 3, 2, 2], [2, 2, 3, 2], [2, 2, 2, 3]] sage: [i.order() for i in s.group_gens()] [5, 5, 5] sage: s.invariant_factors() @@ -2790,7 +2790,7 @@ def _set_points(self): True """ L = self._reduced_laplacian.transpose().dense_matrix() - n = self.num_verts()-1; + n = self.num_verts() - 1 D, U, V = L.smith_form() self._points = [] one = [1]*n @@ -2817,7 +2817,7 @@ def points(self): sage: S = sandpiles.Complete(4) sage: S.points() - [[1, I, -I], [I, 1, -I]] + [[-I, I, 1], [-I, 1, I]] """ return self._points @@ -5015,7 +5015,7 @@ def is_linearly_equivalent(self, D, with_firing_vector=False): sage: D.is_linearly_equivalent([0,1,1]) True sage: D.is_linearly_equivalent([0,1,1],True) - (1, 0, 0) + (0, -1, -1) sage: v = vector(D.is_linearly_equivalent([0,1,1],True)) sage: vector(D.values()) - s.laplacian()*v (0, 1, 1) @@ -6646,8 +6646,8 @@ def wilmes_algorithm(M): sage: P = matrix([[2,3,-7,-3],[5,2,-5,5],[8,2,5,4],[-5,-9,6,6]]) sage: wilmes_algorithm(P) - [ 1642 -13 -1627 -1] - [ -1 1980 -1582 -397] + [ 3279 -79 -1599 -1600] + [ -1 1539 -136 -1402] [ 0 -1 1650 -1649] [ 0 0 -1658 1658] diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py index 2aca9491afb..c82b56cdc08 100644 --- a/src/sage/schemes/elliptic_curves/ell_finite_field.py +++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py @@ -294,7 +294,7 @@ def random_element(self): sage: k = GF(next_prime(7^5)) sage: E = EllipticCurve(k,[2,4]) - sage: P = E.random_element(); P # random + sage: P = E.random_element(); P # random (16740 : 12486 : 1) sage: type(P) @@ -305,7 +305,7 @@ def random_element(self): sage: k. = GF(7^5) sage: E = EllipticCurve(k,[2,4]) - sage: P = E.random_element(); P + sage: P = E.random_element(); P # random (5*a^4 + 3*a^3 + 2*a^2 + a + 4 : 2*a^4 + 3*a^3 + 4*a^2 + a + 5 : 1) sage: type(P) @@ -316,7 +316,7 @@ def random_element(self): sage: k. = GF(2^5) sage: E = EllipticCurve(k,[a^2,a,1,a+1,1]) - sage: P = E.random_element(); P + sage: P = E.random_element();P # random (a^4 + a : a^4 + a^3 + a^2 : 1) sage: type(P) @@ -326,10 +326,9 @@ def random_element(self): Ensure that the entire point set is reachable:: sage: E = EllipticCurve(GF(11), [2,1]) - sage: len(set(E.random_element() for _ in range(100))) - 16 - sage: E.cardinality() - 16 + sage: S = set() + sage: while len(S) < E.cardinality(): + ....: S.add(E.random_element()) TESTS: @@ -755,10 +754,9 @@ def gens(self): sage: E.abelian_group() Additive abelian group isomorphic to Z/22 + Z/2 embedded in Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 2*x + 5 over Finite Field of size 41 - sage: E.abelian_group().gens() - ((30 : 13 : 1), (23 : 0 : 1)) - sage: E.gens() - ((30 : 13 : 1), (23 : 0 : 1)) + sage: ab_gens = E.abelian_group().gens() + sage: ab_gens == E.gens() + True sage: E.gens()[0].order() 22 sage: E.gens()[1].order() @@ -785,11 +783,13 @@ def gens(self): sage: len(E.gens()) 2 sage: E.cardinality() - 867361737988403547207212930746733987710588 - sage: E.gens()[0].order() - 433680868994201773603606465373366993855294 - sage: E.gens()[1].order() - 433680868994201773603606465373366993855294 + 867361737988403547206134229616487867594472 + sage: a = E.gens()[0].order(); a # random + 433680868994201773603067114808243933797236 + sage: b = E.gens()[1].order(); b # random + 30977204928157269543076222486303138128374 + sage: lcm(a,b) + 433680868994201773603067114808243933797236 """ G = self.__pari__().ellgroup(flag=1) return tuple(self.point(list(pt)) for pt in G[2]) diff --git a/src/sage/schemes/elliptic_curves/ell_generic.py b/src/sage/schemes/elliptic_curves/ell_generic.py index 7b02e8f6635..a1a48d2397f 100644 --- a/src/sage/schemes/elliptic_curves/ell_generic.py +++ b/src/sage/schemes/elliptic_curves/ell_generic.py @@ -527,7 +527,7 @@ def __call__(self, *args, **kwds): sage: E = EllipticCurve([0,0,0,-49,0]) sage: T = E.torsion_subgroup() sage: [E(t) for t in T] - [(0 : 1 : 0), (-7 : 0 : 1), (0 : 0 : 1), (7 : 0 : 1)] + [(0 : 1 : 0), (0 : 0 : 1), (-7 : 0 : 1), (7 : 0 : 1)] :: @@ -2951,8 +2951,8 @@ def pari_curve(self): Mod(-928, y^2 - 2), Mod(3456/29, y^2 - 2), Vecsmall([5]), [[y^2 - 2, [2, 0], 8, 1, [[1, -1.41421356237310; 1, 1.41421356237310], [1, -1.41421356237310; 1, 1.41421356237310], - [1, -1; 1, 1], [2, 0; 0, 4], [4, 0; 0, 2], [2, 0; 0, 1], - [2, [0, 2; 1, 0]], []], [-1.41421356237310, 1.41421356237310], + [16, -23; 16, 23], [2, 0; 0, 4], [4, 0; 0, 2], [2, 0; 0, 1], + [2, [0, 2; 1, 0]], [2]], [-1.41421356237310, 1.41421356237310], [1, y], [1, 0; 0, 1], [1, 0, 0, 2; 0, 1, 1, 0]]], [0, 0, 0, 0, 0]] PARI no longer requires that the `j`-invariant has negative `p`-adic valuation:: diff --git a/src/sage/schemes/elliptic_curves/ell_modular_symbols.py b/src/sage/schemes/elliptic_curves/ell_modular_symbols.py index a32f64ea47b..30a61e1635c 100644 --- a/src/sage/schemes/elliptic_curves/ell_modular_symbols.py +++ b/src/sage/schemes/elliptic_curves/ell_modular_symbols.py @@ -298,19 +298,27 @@ def __init__(self, E, sign, nap=1000): sage: m(0) 1/5 - If ``nap`` is too small, the normalization in eclib may be incorrect. See :trac:`31317`:: + If ``nap`` is too small, the normalization in eclib used to be + incorrect (see :trac:`31317`), but since ``eclib`` version + v20210310 the value of ``nap`` is increased automatically by + ``eclib``:: sage: from sage.schemes.elliptic_curves.ell_modular_symbols import ModularSymbolECLIB sage: E = EllipticCurve('1590g1') sage: m = ModularSymbolECLIB(E, sign=+1, nap=300) sage: [m(a/5) for a in [1..4]] - [1001/153, -1001/153, -1001/153, 1001/153] + [13/2, -13/2, -13/2, 13/2] - Those values are incorrect. The correct values are:: + These values are correct, and increasing ``nap`` has no + effect. The correct values may verified by the numerical + implementation:: sage: m = ModularSymbolECLIB(E, sign=+1, nap=400) sage: [m(a/5) for a in [1..4]] [13/2, -13/2, -13/2, 13/2] + sage: m = E.modular_symbol(implementation='num') + sage: [m(a/5) for a in [1..4]] + [13/2, -13/2, -13/2, 13/2] """ from sage.libs.eclib.newforms import ECModularSymbol diff --git a/src/sage/schemes/elliptic_curves/ell_number_field.py b/src/sage/schemes/elliptic_curves/ell_number_field.py index 29316bebeb8..ccfa33915a9 100644 --- a/src/sage/schemes/elliptic_curves/ell_number_field.py +++ b/src/sage/schemes/elliptic_curves/ell_number_field.py @@ -214,9 +214,9 @@ def simon_two_descent(self, verbose=0, lim1=2, lim3=4, limtriv=2, sage: E == loads(dumps(E)) True sage: E.simon_two_descent() - (2, 2, [(0 : 0 : 1), (1/8*a + 5/8 : -3/16*a - 7/16 : 1)]) - sage: E.simon_two_descent(lim1=3, lim3=20, limtriv=5, maxprob=7, limbigprime=10) - (2, 2, [(-1 : 0 : 1), (-1/8*a + 5/8 : -3/16*a - 9/16 : 1)]) + (2, 2, [(0 : 0 : 1)]) + sage: E.simon_two_descent(lim1=5, lim3=5, limtriv=10, maxprob=7, limbigprime=10) + (2, 2, [(-1 : 0 : 1), (-2 : -1/2*a - 1/2 : 1)]) :: @@ -238,35 +238,7 @@ def simon_two_descent(self, verbose=0, lim1=2, lim3=4, limtriv=2, K = bnfinit(y^2 + 7); a = Mod(y,K.pol); bnfellrank(K, [0, 0, 0, 1, a], [[Mod(1/2*y + 3/2, y^2 + 7), Mod(-y - 2, y^2 + 7)]]); - elliptic curve: Y^2 = x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7) - A = Mod(0, y^2 + 7) - B = Mod(1, y^2 + 7) - C = Mod(y, y^2 + 7) - - Computing L(S,2) - L(S,2) = [Mod(Mod(-1/2*y + 1/2, y^2 + 7)*x^2 + Mod(-1/2*y - 1/2, y^2 + 7)*x + Mod(-y - 1, y^2 + 7), x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7)), Mod(-x^2 + Mod(-1/2*y - 1/2, y^2 + 7)*x + 1, x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7)), Mod(-1, x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7)), Mod(x^2 + 2, x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7)), Mod(x + Mod(1/2*y + 3/2, y^2 + 7), x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7)), Mod(x + Mod(1/2*y - 3/2, y^2 + 7), x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7))] - - Computing the Selmer group - #LS2gen = 2 - LS2gen = [Mod(Mod(-1/2*y + 1/2, y^2 + 7)*x^2 + Mod(-1/2*y - 1/2, y^2 + 7)*x + Mod(-y - 1, y^2 + 7), x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7)), Mod(x^2 + Mod(1/2*y + 1/2, y^2 + 7)*x - 1, x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7))] - Search for trivial points on the curve - Trivial points on the curve = [[Mod(1/2*y + 3/2, y^2 + 7), Mod(-y - 2, y^2 + 7)], [1, 1, 0], [Mod(1/2*y + 3/2, y^2 + 7), Mod(-y - 2, y^2 + 7), 1]] - zc = Mod(Mod(-1/2*y + 1/2, y^2 + 7)*x^2 + Mod(-1/2*y - 1/2, y^2 + 7)*x + Mod(-y - 1, y^2 + 7), x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7)) - Hilbert symbol (Mod(1, y^2 + 7),Mod(-2*y + 2, y^2 + 7)) = - sol of quadratic equation = [1, 1, 0]~ - zc*z1^2 = Mod(4*x + Mod(-2*y + 6, y^2 + 7), x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7)) - quartic: (-1)*Y^2 = x^4 + (3*y - 9)*x^2 + (-8*y + 16)*x + (9/2*y - 11/2) - reduced: Y^2 = -x^4 + (-3*y + 9)*x^2 + (-8*y + 16)*x + (-9/2*y + 11/2) - not ELS at [2, [0, 1]~, 1, 1, [1, -2; 1, 0]] - zc = Mod(Mod(1, y^2 + 7)*x^2 + Mod(1/2*y + 1/2, y^2 + 7)*x + Mod(-1, y^2 + 7), x^3 + Mod(1, y^2 + 7)*x + Mod(y, y^2 + 7)) - comes from the trivial point [Mod(1/2*y + 3/2, y^2 + 7), Mod(-y - 2, y^2 + 7)] - m1 = 1 - m2 = 1 - #S(E/K)[2] = 2 - #E(K)/2E(K) = 2 - #III(E/K)[2] = 1 - rank(E/K) = 1 - listpoints = [[Mod(1/2*y + 3/2, y^2 + 7), Mod(-y - 2, y^2 + 7)]] + ... v = [1, 1, [[Mod(1/2*y + 3/2, y^2 + 7), Mod(-y - 2, y^2 + 7)]]] sage: v (1, 1, [(1/2*a + 3/2 : -a - 2 : 1)]) @@ -298,8 +270,8 @@ def simon_two_descent(self, verbose=0, lim1=2, lim3=4, limtriv=2, sage: E.simon_two_descent() # long time (4s on sage.math, 2013) (3, 3, - [(0 : 0 : 1), - (-1/2*zeta43_0^2 - 1/2*zeta43_0 + 7 : -3/2*zeta43_0^2 - 5/2*zeta43_0 + 18 : 1)...) + [(5/8*zeta43_0^2 + 17/8*zeta43_0 - 9/4 : -27/16*zeta43_0^2 - 103/16*zeta43_0 + 39/8 : 1), + (0 : 0 : 1)]) """ verbose = int(verbose) if known_points is None: @@ -2228,29 +2200,29 @@ def torsion_points(self): sage: EK = E.base_extend(K) sage: EK.torsion_points() # long time (1s on sage.math, 2014) [(0 : 1 : 0), - (16 : 60 : 1), - (5 : 5 : 1), - (5 : -6 : 1), - (16 : -61 : 1), (t : 1/11*t^3 + 6/11*t^2 + 19/11*t + 48/11 : 1), - (-3/55*t^3 - 7/55*t^2 - 2/55*t - 133/55 : 6/55*t^3 + 3/55*t^2 + 25/11*t + 156/55 : 1), - (-9/121*t^3 - 21/121*t^2 - 127/121*t - 377/121 : -7/121*t^3 + 24/121*t^2 + 197/121*t + 16/121 : 1), - (5/121*t^3 - 14/121*t^2 - 158/121*t - 453/121 : -49/121*t^3 - 129/121*t^2 - 315/121*t - 207/121 : 1), - (10/121*t^3 + 49/121*t^2 + 168/121*t + 73/121 : 32/121*t^3 + 60/121*t^2 - 261/121*t - 807/121 : 1), (1/11*t^3 - 5/11*t^2 + 19/11*t - 40/11 : -6/11*t^3 - 3/11*t^2 - 26/11*t - 321/11 : 1), - (14/121*t^3 - 15/121*t^2 + 90/121*t + 232/121 : 16/121*t^3 - 69/121*t^2 + 293/121*t - 46/121 : 1), - (3/55*t^3 + 7/55*t^2 + 2/55*t + 78/55 : 7/55*t^3 - 24/55*t^2 + 9/11*t + 17/55 : 1), - (-5/121*t^3 + 36/121*t^2 - 84/121*t + 24/121 : 34/121*t^3 - 27/121*t^2 + 305/121*t + 708/121 : 1), - (-26/121*t^3 + 20/121*t^2 - 219/121*t - 995/121 : 15/121*t^3 + 156/121*t^2 - 232/121*t + 2766/121 : 1), (1/11*t^3 - 5/11*t^2 + 19/11*t - 40/11 : 6/11*t^3 + 3/11*t^2 + 26/11*t + 310/11 : 1), - (-26/121*t^3 + 20/121*t^2 - 219/121*t - 995/121 : -15/121*t^3 - 156/121*t^2 + 232/121*t - 2887/121 : 1), - (-5/121*t^3 + 36/121*t^2 - 84/121*t + 24/121 : -34/121*t^3 + 27/121*t^2 - 305/121*t - 829/121 : 1), - (3/55*t^3 + 7/55*t^2 + 2/55*t + 78/55 : -7/55*t^3 + 24/55*t^2 - 9/11*t - 72/55 : 1), - (14/121*t^3 - 15/121*t^2 + 90/121*t + 232/121 : -16/121*t^3 + 69/121*t^2 - 293/121*t - 75/121 : 1), (t : -1/11*t^3 - 6/11*t^2 - 19/11*t - 59/11 : 1), + (16 : 60 : 1), + (-3/55*t^3 - 7/55*t^2 - 2/55*t - 133/55 : 6/55*t^3 + 3/55*t^2 + 25/11*t + 156/55 : 1), + (14/121*t^3 - 15/121*t^2 + 90/121*t + 232/121 : 16/121*t^3 - 69/121*t^2 + 293/121*t - 46/121 : 1), + (-26/121*t^3 + 20/121*t^2 - 219/121*t - 995/121 : -15/121*t^3 - 156/121*t^2 + 232/121*t - 2887/121 : 1), (10/121*t^3 + 49/121*t^2 + 168/121*t + 73/121 : -32/121*t^3 - 60/121*t^2 + 261/121*t + 686/121 : 1), + (5 : 5 : 1), + (-9/121*t^3 - 21/121*t^2 - 127/121*t - 377/121 : -7/121*t^3 + 24/121*t^2 + 197/121*t + 16/121 : 1), + (3/55*t^3 + 7/55*t^2 + 2/55*t + 78/55 : 7/55*t^3 - 24/55*t^2 + 9/11*t + 17/55 : 1), + (-5/121*t^3 + 36/121*t^2 - 84/121*t + 24/121 : -34/121*t^3 + 27/121*t^2 - 305/121*t - 829/121 : 1), (5/121*t^3 - 14/121*t^2 - 158/121*t - 453/121 : 49/121*t^3 + 129/121*t^2 + 315/121*t + 86/121 : 1), + (5 : -6 : 1), + (5/121*t^3 - 14/121*t^2 - 158/121*t - 453/121 : -49/121*t^3 - 129/121*t^2 - 315/121*t - 207/121 : 1), + (-5/121*t^3 + 36/121*t^2 - 84/121*t + 24/121 : 34/121*t^3 - 27/121*t^2 + 305/121*t + 708/121 : 1), + (3/55*t^3 + 7/55*t^2 + 2/55*t + 78/55 : -7/55*t^3 + 24/55*t^2 - 9/11*t - 72/55 : 1), (-9/121*t^3 - 21/121*t^2 - 127/121*t - 377/121 : 7/121*t^3 - 24/121*t^2 - 197/121*t - 137/121 : 1), + (16 : -61 : 1), + (10/121*t^3 + 49/121*t^2 + 168/121*t + 73/121 : 32/121*t^3 + 60/121*t^2 - 261/121*t - 807/121 : 1), + (-26/121*t^3 + 20/121*t^2 - 219/121*t - 995/121 : 15/121*t^3 + 156/121*t^2 - 232/121*t + 2766/121 : 1), + (14/121*t^3 - 15/121*t^2 + 90/121*t + 232/121 : -16/121*t^3 + 69/121*t^2 - 293/121*t - 75/121 : 1), (-3/55*t^3 - 7/55*t^2 - 2/55*t - 133/55 : -6/55*t^3 - 3/55*t^2 - 25/11*t - 211/55 : 1)] :: diff --git a/src/sage/schemes/elliptic_curves/ell_point.py b/src/sage/schemes/elliptic_curves/ell_point.py index 038d105e1a7..dc14aa5caff 100644 --- a/src/sage/schemes/elliptic_curves/ell_point.py +++ b/src/sage/schemes/elliptic_curves/ell_point.py @@ -1748,14 +1748,14 @@ def tate_pairing(self, Q, n, k, q=None): sage: Px.weil_pairing(Qx, 41)^e == num/den True - NOTES: + .. NOTE:: - This function uses Miller's algorithm, followed by a naive - exponentiation. It does not do anything fancy. In the case - that there is an issue with `Q` being on one of the lines - generated in the `r*P` calculation, `Q` is offset by a random - point `R` and P.tate_pairing(Q+R,n,k)/P.tate_pairing(R,n,k) - is returned. + This function uses Miller's algorithm, followed by a naive + exponentiation. It does not do anything fancy. In the case + that there is an issue with `Q` being on one of the lines + generated in the `r*P` calculation, `Q` is offset by a random + point `R` and P.tate_pairing(Q+R,n,k)/P.tate_pairing(R,n,k) + is returned. AUTHORS: @@ -1945,13 +1945,14 @@ def ate_pairing(self, Q, n, k, t, q=None): ... ValueError: This point (14 : 10*a : 1) is not in Ker(pi - 1) - NOTES: + .. NOTE:: - First defined in the paper of [HSV2006]_, the ate pairing can be - computationally effective in those cases when the trace of the curve - over the base field is significantly smaller than the expected - value. This implementation is simply Miller's algorithm followed by a - naive exponentiation, and makes no claims towards efficiency. + First defined in the paper of [HSV2006]_, the ate pairing + can be computationally effective in those cases when the + trace of the curve over the base field is significantly + smaller than the expected value. This implementation is + simply Miller's algorithm followed by a naive + exponentiation, and makes no claims towards efficiency. AUTHORS: diff --git a/src/sage/schemes/elliptic_curves/ell_rational_field.py b/src/sage/schemes/elliptic_curves/ell_rational_field.py index 1ce8323e1b0..bda999eeca3 100644 --- a/src/sage/schemes/elliptic_curves/ell_rational_field.py +++ b/src/sage/schemes/elliptic_curves/ell_rational_field.py @@ -779,7 +779,7 @@ def mwrank_curve(self, verbose=False): sage: E = EllipticCurve('11a1') sage: EE = E.mwrank_curve() sage: EE - y^2+ y = x^3 - x^2 - 10*x - 20 + y^2 + y = x^3 - x^2 - 10 x - 20 sage: type(EE) sage: EE.isogeny_class() @@ -1283,22 +1283,21 @@ def modular_symbol(self, sign=+1, normalize=None, implementation='eclib', nap=0) sage: [Mminus(1/i) for i in [1..11]] [0, 0, 1/2, 1/2, 0, 0, -1/2, -1/2, 0, 0, 0] - With the default 'eclib' implementation, if ``nap`` is too - small, the normalization may be computed incorrectly. See - :trac:`31317`:: + With older version of eclib, in the default 'eclib' + implementation, if ``nap`` is too small, the normalization may + be computed incorrectly (see :trac:`31317`). This was fixed + in eclib version v20210310, since now eclib increase ``nap`` + automatically. The following used to give incorrect results. + See :trac:`31443`:: sage: E = EllipticCurve('1590g1') sage: m = E.modular_symbol(nap=300) sage: [m(a/5) for a in [1..4]] - [1001/153, -1001/153, -1001/153, 1001/153] + [13/2, -13/2, -13/2, 13/2] - Those values are incorrect. The correct values may be - obtained by increasing ``nap``, as verified by the numerical + These values are correct, as verified by the numerical implementation:: - sage: m = E.modular_symbol(nap=400) - sage: [m(a/5) for a in [1..4]] - [13/2, -13/2, -13/2, 13/2] sage: m = E.modular_symbol(implementation='num') sage: [m(a/5) for a in [1..4]] [13/2, -13/2, -13/2, 13/2] @@ -2525,9 +2524,8 @@ def regulator(self, proof=None, precision=53, **kwds): assert reg.parent() is R return reg - def saturation(self, points, verbose=False, max_prime=0, odd_primes_only=False): - """ - Given a list of rational points on E, compute the saturation in + def saturation(self, points, verbose=False, max_prime=-1, min_prime=2): + """Given a list of rational points on E, compute the saturation in E(Q) of the subgroup they generate. INPUT: @@ -2538,17 +2536,24 @@ def saturation(self, points, verbose=False, max_prime=0, odd_primes_only=False): - ``verbose (bool)`` - (default: ``False``), if ``True``, give verbose output - - ``max_prime (int)`` - (default: 0), saturation is - performed for all primes up to max_prime. If max_prime==0, - perform saturation at *all* primes, i.e., compute the true - saturation. + - ``max_prime`` (int, default -1) -- If `-1` (the default), an + upper bound is computed for the primes at which the subgroup + may not be saturated, and saturation is performed for all + primes up to this bound. Otherwise, the bound used is the + minimum of ``max_prime`` and the computed bound. - - ``odd_primes_only (bool)`` - only do saturation at - odd primes + - ``min_prime (int)`` - (default: 2), only do `p`-saturation + at primes `p` greater than or equal to this. + .. note:: - OUTPUT: + To saturate at a single prime `p`, set ``max_prime`` and + ``min_prime`` both to `p`. One situation where this is + useful is after mapping saturated points from another + elliptic curve by a `p`-isogeny, since the images may not + be `p`-saturated but will be saturated at all other primes. + OUTPUT: - ``saturation (list)`` - points that form a basis for the saturation @@ -2559,12 +2564,32 @@ def saturation(self, points, verbose=False, max_prime=0, odd_primes_only=False): - ``regulator (real with default precision)`` - regulator of saturated points. + ALGORITHM: Uses Cremona's ``eclib`` package, which computes a + bound on the saturation index. To `p`-saturate, or prove + `p`-saturation, we consider the reductions of the points + modulo primes `q` of good reduction such that `E(\GF{q})` has + order divisible by `p`. + + .. note:: + + In versons of ``eclib`` up to ``v20190909``, division of + points in ``eclib`` was done using floating point methods, + without automatic handling of precision, so that + `p`-saturation sometimes failed unless + ``mwrank_set_precision()`` was called in advance with a + suitably high bit precision. Since version ``v20210310`` + of ``eclib``, division is done using exact methods based on + division polynomials, and `p`-saturation cannot fail in + this way. + + .. note:: + + The computed index of saturation may be large, in which + case saturation may take a long time. For example, the + rank 4 curve ``EllipticCurve([0,1,1,-9872,374262])`` has a + saturation index bound of 11816 and takes around 40 seconds + to prove saturation. - ALGORITHM: Uses Cremona's ``mwrank`` package. With ``max_prime=0``, - we call ``mwrank`` with successively larger prime bounds until the full - saturation is provably found. The results of saturation at the - previous primes is stored in each case, so this should be - reasonably fast. EXAMPLES:: @@ -2577,7 +2602,9 @@ def saturation(self, points, verbose=False, max_prime=0, odd_primes_only=False): TESTS: - See :trac:`10590`. This example would loop forever at default precision:: + See :trac:`10590`. With ``eclib`` versions up to + ``v20190909``, this example would loop forever at default + precision. Since version ``v20210310`` it runs fine:: sage: E = EllipticCurve([1, 0, 1, -977842, -372252745]) sage: P = E([-192128125858676194585718821667542660822323528626273/336995568430319276695106602174283479617040716649, 70208213492933395764907328787228427430477177498927549075405076353624188436/195630373799784831667835900062564586429333568841391304129067339731164107, 1]) @@ -2585,7 +2612,7 @@ def saturation(self, points, verbose=False, max_prime=0, odd_primes_only=False): 113.302910926080 sage: E.saturation([P]) ([(-192128125858676194585718821667542660822323528626273/336995568430319276695106602174283479617040716649 : 70208213492933395764907328787228427430477177498927549075405076353624188436/195630373799784831667835900062564586429333568841391304129067339731164107 : 1)], 1, 113.302910926080) - sage: (Q,), ind, reg = E.saturation([2*P]) # needs higher precision, handled by eclib + sage: (Q,), ind, reg = E.saturation([2*P]) sage: 2*Q == 2*P True sage: ind @@ -2634,36 +2661,16 @@ def saturation(self, points, verbose=False, max_prime=0, odd_primes_only=False): c = Emin.mwrank_curve() from sage.libs.eclib.all import mwrank_MordellWeil mw = mwrank_MordellWeil(c, verbose) - mw.process(v) - repeat_until_saturated = False - if max_prime == 0: - repeat_until_saturated = True - max_prime = 9973 - from sage.libs.all import mwrank_get_precision, mwrank_set_precision - prec0 = mwrank_get_precision() - prec = 100 - if prec0 = NumberField(x**6-320*x**3-320) sage: E = EllipticCurve(K,[0,0,1,0,0]) sage: isogenies_5_0(E) - [Isogeny of degree 5 from Elliptic Curve defined by y^2 + y = x^3 over Number Field in a with defining polynomial x^6 - 320*x^3 - 320 to Elliptic Curve defined by y^2 + y = x^3 + (643/8*a^5-15779/48*a^4-32939/24*a^3-71989/2*a^2+214321/6*a-112115/3)*x + (2901961/96*a^5+4045805/48*a^4+12594215/18*a^3-30029635/6*a^2+15341626/3*a-38944312/9) over Number Field in a with defining polynomial x^6 - 320*x^3 - 320, - Isogeny of degree 5 from Elliptic Curve defined by y^2 + y = x^3 over Number Field in a with defining polynomial x^6 - 320*x^3 - 320 to Elliptic Curve defined by y^2 + y = x^3 + (-1109/8*a^5-53873/48*a^4-180281/24*a^3-14491/2*a^2+35899/6*a-43745/3)*x + (-17790679/96*a^5-60439571/48*a^4-77680504/9*a^3+1286245/6*a^2-4961854/3*a-73854632/9) over Number Field in a with defining polynomial x^6 - 320*x^3 - 320] + [Isogeny of degree 5 from Elliptic Curve defined by y^2 + y = x^3 over Number Field in a with defining polynomial x^6 - 320*x^3 - 320 to Elliptic Curve defined by y^2 + y = x^3 + (241565/32*a^5-362149/48*a^4+180281/24*a^3-9693307/4*a^2+14524871/6*a-7254985/3)*x + (1660391123/192*a^5-829315373/96*a^4+77680504/9*a^3-66622345345/24*a^2+33276655441/12*a-24931615912/9) over Number Field in a with defining polynomial x^6 - 320*x^3 - 320, + Isogeny of degree 5 from Elliptic Curve defined by y^2 + y = x^3 over Number Field in a with defining polynomial x^6 - 320*x^3 - 320 to Elliptic Curve defined by y^2 + y = x^3 + (47519/32*a^5-72103/48*a^4+32939/24*a^3-1909753/4*a^2+2861549/6*a-1429675/3)*x + (-131678717/192*a^5+65520419/96*a^4-12594215/18*a^3+5280985135/24*a^2-2637787519/12*a+1976130088/9) over Number Field in a with defining polynomial x^6 - 320*x^3 - 320] """ F = E.base_field() if E.j_invariant() != 0: diff --git a/src/sage/schemes/elliptic_curves/lseries_ell.py b/src/sage/schemes/elliptic_curves/lseries_ell.py index 9aa647093f4..1fcd02f9ec2 100644 --- a/src/sage/schemes/elliptic_curves/lseries_ell.py +++ b/src/sage/schemes/elliptic_curves/lseries_ell.py @@ -871,7 +871,7 @@ def L_ratio(self): # and is a multiple of the degree of an isogeny between E # and the optimal curve. # - # NOTES: We *do* have to worry about the Manin constant, since + # NOTE: We *do* have to worry about the Manin constant, since # we are using the Neron model to compute omega, not the # newform. My theorem replaces the omega above by omega/c, # where c is the Manin constant, and the bound must be diff --git a/src/sage/schemes/elliptic_curves/period_lattice.py b/src/sage/schemes/elliptic_curves/period_lattice.py index 0f1d17f9955..fe1899f647c 100644 --- a/src/sage/schemes/elliptic_curves/period_lattice.py +++ b/src/sage/schemes/elliptic_curves/period_lattice.py @@ -1625,7 +1625,7 @@ def elliptic_logarithm(self, P, prec=None, reduce=True): sage: P,Q = T[2] sage: embs = K.embeddings(CC) sage: Lambda = E.period_lattice(embs[0]) - sage: Lambda.elliptic_logarithm(P+3*Q, 100) + sage: Lambda.elliptic_logarithm(P, 100) 4.7100131126199672766973600998 sage: R. = QQ[] sage: K. = NumberField(x^2 + x + 5) diff --git a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py index 5313d2f74dd..445b22726f4 100644 --- a/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +++ b/src/sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py @@ -1501,14 +1501,13 @@ def _Cartier_matrix_cached(self): ... ValueError: curve is not smooth """ - #Compute the finite field and prime p. - Fq=self.base_ring(); - p=Fq.characteristic() + # Compute the finite field and prime p. + Fq = self.base_ring() + p = Fq.characteristic() #checks if p == 2: - raise ValueError("p must be odd"); - + raise ValueError("p must be odd") g = self.genus() @@ -1850,8 +1849,7 @@ def a_number(self): if E != self: self._Cartier_matrix_cached.clear_cache() M,Coeffs,g, Fq, p,E= self._Cartier_matrix_cached() - a=g-rank(M); - return a; + return g - rank(M) def p_rank(self): r""" @@ -1888,9 +1886,8 @@ def p_rank(self): #the last entry in A. If it does not match, clear A and compute Hasse Witt. # However, it seems a waste of time to manually analyse the cache # -- See Trac Ticket #11115 - N,E= self._Hasse_Witt_cached() - if E!=self: + N, E = self._Hasse_Witt_cached() + if E != self: self._Hasse_Witt_cached.clear_cache() - N,E= self._Hasse_Witt_cached() - pr=rank(N); - return pr + N, E = self._Hasse_Witt_cached() + return rank(N) diff --git a/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py b/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py index f51c22ff0fb..cc4fe5f198d 100644 --- a/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py +++ b/src/sage/schemes/hyperelliptic_curves/jacobian_morphism.py @@ -147,16 +147,17 @@ def cantor_reduction_simple(a, b, f, genus): """ a2 = (f - b**2) // a a2 = a2.monic() - b2 = -b % (a2); + b2 = -b % (a2) if a2.degree() == a.degree(): # XXX - assert a2.degree() == genus+1 + assert a2.degree() == genus + 1 print("Returning ambiguous form of degree genus+1.") return (a2, b2) elif a2.degree() > genus: return cantor_reduction_simple(a2, b2, f, genus) return (a2, b2) + def cantor_reduction(a, b, f, h, genus): r""" Return the unique reduced divisor linearly equivalent to diff --git a/src/sage/schemes/hyperelliptic_curves/mestre.py b/src/sage/schemes/hyperelliptic_curves/mestre.py index 8ee60d478d1..577888dc0c4 100644 --- a/src/sage/schemes/hyperelliptic_curves/mestre.py +++ b/src/sage/schemes/hyperelliptic_curves/mestre.py @@ -77,8 +77,10 @@ def HyperellipticCurve_from_invariants(i, reduced=True, precision=None, An example over a finite field:: - sage: HyperellipticCurve_from_invariants([GF(13)(1),3,7,5]) - Hyperelliptic Curve over Finite Field of size 13 defined by y^2 = 8*x^5 + 5*x^4 + 5*x^2 + 9*x + 3 + sage: H = HyperellipticCurve_from_invariants([GF(13)(1),3,7,5]); H + Hyperelliptic Curve over Finite Field of size 13 defined by ... + sage: H.igusa_clebsch_invariants() + (4, 9, 6, 11) An example over a number field:: diff --git a/src/sage/schemes/plane_conics/con_field.py b/src/sage/schemes/plane_conics/con_field.py index 8de20e8ee71..823595850b5 100644 --- a/src/sage/schemes/plane_conics/con_field.py +++ b/src/sage/schemes/plane_conics/con_field.py @@ -331,11 +331,11 @@ def diagonalization(self, names=None): Traceback (most recent call last): ... ValueError: The conic self (= Projective Conic Curve over Finite Field of size 2 defined by x^2 + x*y + y^2 + x*z + y*z) has no symmetric matrix because the base field has characteristic 2 - + An example over a global function field: - + :: - + sage: K = FractionField(PolynomialRing(GF(7), 't')) sage: (t,) = K.gens() sage: C = Conic(K, [t/2,0, 1, 2, 0, 3]) @@ -659,11 +659,11 @@ def hom(self, x, Y=None): ValueError: The matrix x (= [ 0 0 1/2] [ 0 1 0] [ 1 0 0]) does not define a map from self (= Projective Conic Curve over Rational Field defined by -x^2 + y^2 + z^2) to Y (= Projective Conic Curve over Rational Field defined by -x^2 + y^2 + z^2) - + The identity map between two representations of the same conic: - + :: - + sage: C = Conic([1,2,3,4,5,6]) sage: D = Conic([2,4,6,8,10,12]) sage: C.hom(identity_matrix(3), D) @@ -674,9 +674,9 @@ def hom(self, x, Y=None): (x : y : z) An example not over the rational numbers: - + :: - + sage: P. = QQ[] sage: C = Conic([1,0,0,t,0,1/t]) sage: D = Conic([1/t^2, 0, -2/t^2, t, 0, (t + 1)/t^2]) @@ -818,6 +818,11 @@ def parametrization(self, point=None, morphism=True): Return a parametrization `f` of ``self`` together with the inverse of `f`. + .. warning:: + + The second map is currently broken and neither the inverse nor + well-defined. + If ``point`` is specified, then that point is used for the parametrization. Otherwise, use ``self.rational_point()`` to find a point. @@ -831,19 +836,21 @@ def parametrization(self, point=None, morphism=True): An example over a finite field :: sage: c = Conic(GF(2), [1,1,1,1,1,0]) - sage: c.parametrization() + sage: f, g = c.parametrization(); f, g (Scheme morphism: From: Projective Space of dimension 1 over Finite Field of size 2 To: Projective Conic Curve over Finite Field of size 2 defined by x^2 + x*y + y^2 + x*z + y*z - Defn: Defined on coordinates by sending (x : y) to - (x*y + y^2 : x^2 + x*y : x^2 + x*y + y^2), + Defn: Defined on coordinates by sending (x : y) to ..., Scheme morphism: From: Projective Conic Curve over Finite Field of size 2 defined by x^2 + x*y + y^2 + x*z + y*z To: Projective Space of dimension 1 over Finite Field of size 2 - Defn: Defined on coordinates by sending (x : y : z) to - (y : x)) + Defn: Defined on coordinates by sending (x : y : z) to ...) + sage: set(f(p) for p in f.domain()) + {(0 : 0 : 1), (0 : 1 : 1), (1 : 0 : 1)} + sage: (g*f).is_one() # known bug (see :trac:`31892`) + True An example with ``morphism = False`` :: diff --git a/src/sage/schemes/projective/projective_rational_point.py b/src/sage/schemes/projective/projective_rational_point.py index a75958eae6a..6196eaa1021 100644 --- a/src/sage/schemes/projective/projective_rational_point.py +++ b/src/sage/schemes/projective/projective_rational_point.py @@ -343,7 +343,7 @@ def sieve(X, bound): sage: from sage.schemes.projective.projective_rational_point import sieve sage: P.=ProjectiveSpace(QQ,3) sage: Y=P.subscheme([x^2-3^2*y^2+z*q,x+z+4*q]) - sage: sorted(sieve(Y, 12)) + sage: sorted(sieve(Y, 12)) # long time [(-4 : -4/3 : 0 : 1), (-4 : 4/3 : 0 : 1), (-1 : -1/3 : 1 : 0), (-1 : 1/3 : 1 : 0)] @@ -351,7 +351,7 @@ def sieve(X, bound): sage: from sage.schemes.projective.projective_rational_point import sieve sage: E = EllipticCurve('37a') - sage: sorted(sieve(E, 14)) + sage: sorted(sieve(E, 14)) # long time [(-1 : -1 : 1), (-1 : 0 : 1), (0 : -1 : 1), (0 : 0 : 1), (0 : 1 : 0), (1/4 : -5/8 : 1), (1/4 : -3/8 : 1), (1 : -1 : 1), (1 : 0 : 1), @@ -539,12 +539,11 @@ def lift_all_points(): primes_list = good_primes(B.ceil()) modulo_points = points_modulo_primes(X, primes_list) - len_modulo_points = [len(_) for _ in modulo_points] + len_modulo_points = [len(pt) for pt in modulo_points] len_primes = len(primes_list) prod_primes = prod(primes_list) # stores final result - rat_points = set() for i in range(N + 1): w = [0 for _ in range(N + 1)] @@ -554,4 +553,3 @@ def lift_all_points(): rat_points = lift_all_points() return sorted(rat_points) - diff --git a/src/sage/schemes/toric/all.py b/src/sage/schemes/toric/all.py index 00e93fa50f1..9d7c83954a7 100644 --- a/src/sage/schemes/toric/all.py +++ b/src/sage/schemes/toric/all.py @@ -1,10 +1,7 @@ -# code exports - -from .fano_variety import CPRFanoToricVariety -from .ideal import ToricIdeal -from .library import toric_varieties -from .variety import AffineToricVariety, ToricVariety - - from sage.misc.lazy_import import lazy_import + lazy_import('sage.schemes.toric.weierstrass', 'WeierstrassForm') +lazy_import('sage.schemes.toric.variety', ['AffineToricVariety', 'ToricVariety']) +lazy_import('sage.schemes.toric.library', 'toric_varieties') +lazy_import('sage.schemes.toric.fano_variety', 'CPRFanoToricVariety') +lazy_import('sage.schemes.toric.ideal', 'ToricIdeal') diff --git a/src/sage/schemes/toric/chow_group.py b/src/sage/schemes/toric/chow_group.py index 89e82467b05..84d32fe373e 100644 --- a/src/sage/schemes/toric/chow_group.py +++ b/src/sage/schemes/toric/chow_group.py @@ -62,7 +62,7 @@ 7 sage: a = sum( A.gen(i) * (i+1) for i in range(A.ngens()) ) # an element of A sage: a # long time (2s on sage.math, 2011) - ( 3 | 1 mod 7 | 0 mod 2, 1 mod 2, 4, 5, 6, 7, 8 | 9 ) + ( 9 | 1 mod 7 | 1 mod 2, 0 mod 2, 4, 5, 6, 7, 8 | 3 ) The Chow group elements are printed as ``( a0 | a1 mod 7 | a2 mod 2, a3 mod 2, a4, a5, a6, a7, a8 | a9 )``, which denotes the element of @@ -93,13 +93,13 @@ sage: cone = X.fan(dim=2)[3]; cone 2-d cone of Rational polyhedral fan in 3-d lattice N sage: A_cone = A(cone); A_cone - ( 0 | 1 mod 7 | 0 mod 2, 0 mod 2, 0, 0, 0, 0, 0 | 0 ) + ( 0 | 6 mod 7 | 0 mod 2, 0 mod 2, 0, 0, 0, 0, 0 | 0 ) sage: A_cone.degree() 1 sage: 2 * A_cone - ( 0 | 2 mod 7 | 0 mod 2, 0 mod 2, 0, 0, 0, 0, 0 | 0 ) + ( 0 | 5 mod 7 | 0 mod 2, 0 mod 2, 0, 0, 0, 0, 0 | 0 ) sage: A_cone + A.gen(0) - ( 0 | 1 mod 7 | 0 mod 2, 1 mod 2, 0, 0, 0, 0, 0 | 0 ) + ( 0 | 6 mod 7 | 1 mod 2, 0 mod 2, 0, 0, 0, 0, 0 | 0 ) Chow cycles can be of mixed degrees:: @@ -151,7 +151,7 @@ class ChowCycle(FGP_Element): sage: P2 = toric_varieties.P2() sage: A = P2.Chow_group() sage: A.gens() - (( 1 | 0 | 0 ), ( 0 | 1 | 0 ), ( 0 | 0 | 1 )) + (( 0 | 0 | 1 ), ( 0 | 1 | 0 ), ( 1 | 0 | 0 )) sage: cone = P2.fan(1)[0] sage: A(cone) ( 0 | 1 | 0 ) @@ -199,7 +199,7 @@ def _repr_(self): sage: A.degree() (Z, Z, Z) sage: A.an_element()._repr_() - '( 1 | 0 | 0 )' + '( 0 | 0 | 1 )' A more complicated example with torsion:: @@ -208,7 +208,7 @@ def _repr_(self): sage: A.degree() (Z, 0, C2 x Z^5, Z) sage: sum( A.gen(i) * (i+1) for i in range(A.ngens()) ) - ( 2 || 1 mod 2, 3, 4, 5, 6, 7 | 8 ) + ( 8 || 1 mod 2, 3, 4, 5, 6, 7 | 2 ) """ A = self.parent() s = '(' @@ -245,7 +245,7 @@ def degree(self): sage: P2 = toric_varieties.P2() sage: A = P2.Chow_group() sage: [ a.degree() for a in A.gens() ] - [0, 1, 2] + [2, 1, 0] """ if '_dim' in self.__dict__: return self._dim @@ -279,9 +279,9 @@ def project_to_degree(self, degree): sage: A = toric_varieties.P2().Chow_group() sage: cycle = 10*A.gen(0) + 11*A.gen(1) + 12*A.gen(2) sage: cycle - ( 10 | 11 | 12 ) + ( 12 | 11 | 10 ) sage: cycle.project_to_degree(2) - ( 0 | 0 | 12 ) + ( 0 | 0 | 10 ) """ ambient_dim = self.parent()._variety.dimension() v = list(self.lift()) @@ -307,7 +307,7 @@ def count_points(self): sage: P2 = toric_varieties.P2() sage: A = P2.Chow_group() - sage: a = 5*A.gen(0) + 7*A.gen(1); a + sage: a = 5*A.gen(2) + 7*A.gen(1); a ( 5 | 7 | 0 ) sage: a.count_points() 5 @@ -373,7 +373,7 @@ def intersection_with_divisor(self, divisor): V(y) sage: A = dP6.Chow_group() sage: A(cone) - ( 0 | 0, 0, 0, 1 | 0 ) + ( 0 | 0, 0, 1, 0 | 0 ) sage: intersection = A(cone).intersection_with_divisor(D); intersection ( -1 | 0, 0, 0, 0 | 0 ) sage: intersection.count_points() @@ -405,8 +405,8 @@ def intersection_with_divisor(self, divisor): ( 0 | 0, 0, 0, 0 | 0 ), ( 0 | 0, 0, 0, 0 | 0 ), ( 0 | 0, 0, 0, 0 | 0 )] sage: [ r.intersection_with_divisor(D).lift() for r in dP6.Chow_group().relation_gens() ] - [(0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0), - (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + [(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + (0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), @@ -504,13 +504,13 @@ def cohomology_class(self): sage: HH = WP4.cohomology_ring() sage: cone3d = Cone([(0,0,1,0), (0,0,0,1), (-9,-6,-1,-1)]) sage: A(cone3d) - ( 0 | 1 | 0 | 0 | 0 ) + ( 0 | -1 | 0 | 0 | 0 ) sage: HH(cone3d) [3*z4^3] sage: D = -WP4.K() # the anticanonical divisor sage: A(D) - ( 0 | 0 | 0 | 18 | 0 ) + ( 0 | 0 | 0 | -18 | 0 ) sage: HH(D) [18*z4] @@ -605,7 +605,7 @@ class ChowGroup_class(FGP_Module_class, WithEqualityById): sage: A = ChowGroup_class(P2,ZZ,True); A Chow group of 2-d CPR-Fano toric variety covered by 3 affine patches sage: A.an_element() - ( 1 | 0 | 0 ) + ( 0 | 0 | 1 ) """ Element = ChowCycle @@ -627,7 +627,7 @@ def __init__(self, toric_variety, base_ring, check): sage: A_ZZ = P2.Chow_group() sage: 2 * A_ZZ.an_element() * 3 - ( 6 | 0 | 0 ) + ( 0 | 0 | 6 ) sage: 1/2 * A_ZZ.an_element() * 1/3 Traceback (most recent call last): ... @@ -705,9 +705,9 @@ def _element_constructor_(self, x, check=True): sage: A = dP6.Chow_group() sage: cone = dP6.fan(dim=1)[4] sage: A(cone) - ( 0 | 0, 1, 0, 0 | 0 ) + ( 0 | 1, 1, 0, -1 | 0 ) sage: A(Cone(cone)) # isomorphic but not identical to a cone of the fan! - ( 0 | 0, 1, 0, 0 | 0 ) + ( 0 | 1, 1, 0, -1 | 0 ) sage: A( dP6.K() ) ( 0 | -1, -2, -2, -1 | 0 ) """ @@ -1003,7 +1003,7 @@ def gens(self, degree=None): sage: A = toric_varieties.P2().Chow_group() sage: A.gens() - (( 1 | 0 | 0 ), ( 0 | 1 | 0 ), ( 0 | 0 | 1 )) + (( 0 | 0 | 1 ), ( 0 | 1 | 0 ), ( 1 | 0 | 0 )) sage: A.gens(degree=1) (( 0 | 1 | 0 ),) """ diff --git a/src/sage/schemes/toric/divisor.py b/src/sage/schemes/toric/divisor.py index e03b724ae6f..576845ac1e8 100644 --- a/src/sage/schemes/toric/divisor.py +++ b/src/sage/schemes/toric/divisor.py @@ -113,7 +113,7 @@ in Basis lattice of The toric rational divisor class group of a 2-d CPR-Fano toric variety covered by 6 affine patches sage: Kc.ray(1).lift() - V(y) + V(v) + V(x) + V(w) Given a divisor `D`, we have an associated line bundle (or a reflexive sheaf, if `D` is not Cartier) `\mathcal{O}(D)`. Its sections are:: @@ -170,7 +170,7 @@ from sage.geometry.cone import is_Cone from sage.geometry.polyhedron.constructor import Polyhedron from sage.geometry.toric_lattice_element import is_ToricLatticeElement -from sage.homology.simplicial_complex import SimplicialComplex +from sage.topology.simplicial_complex import SimplicialComplex from sage.matrix.constructor import matrix from sage.misc.all import cached_method, flatten, latex, prod from sage.modules.all import vector @@ -1011,10 +1011,10 @@ def move_away_from(self, cone): sage: Cartier 2*V(z0) + 2*V(z1) + V(z2) + V(z3) + V(z4) sage: Cartier.move_away_from(line_cone) - -V(z2) - V(z3) + V(z4) + 3*V(z2) + 3*V(z3) - V(z4) sage: QQ_Weil = X.divisor([1,0,1,1,0]) sage: QQ_Weil.move_away_from(line_cone) - V(z2) + 2*V(z2) + V(z3) - 1/2*V(z4) """ m = self.m(cone) X = self.parent().scheme() @@ -1112,9 +1112,9 @@ def Chow_cycle(self, ring=ZZ): EXAMPLES:: sage: dP6 = toric_varieties.dP6() - sage: cone = dP6.fan(1)[0] + sage: cone = dP6.fan(1)[5] sage: D = dP6.divisor(cone); D - V(x) + V(w) sage: D.Chow_cycle() ( 0 | -1, 0, 1, 1 | 0 ) sage: dP6.Chow_group()(cone) @@ -1552,7 +1552,7 @@ def _sheaf_complex(self, m): OUTPUT: - - :class:`simplicial complex `. + - :class:`simplicial complex `. EXAMPLES:: @@ -1959,11 +1959,11 @@ def __init__(self, toric_variety): [1 1 0 0 0] [0 2 1 1 1] sage: Cl._lift_matrix - [1 0] - [0 0] - [0 0] - [0 1] - [0 0] + [ 0 0] + [ 1 0] + [ 0 0] + [-2 1] + [ 0 0] sage: Cl._lift_matrix.base_ring() Integer Ring """ diff --git a/src/sage/schemes/toric/divisor_class.pyx b/src/sage/schemes/toric/divisor_class.pyx index f0e6eeca7ef..52874594a51 100644 --- a/src/sage/schemes/toric/divisor_class.pyx +++ b/src/sage/schemes/toric/divisor_class.pyx @@ -39,9 +39,9 @@ The only special method is :meth:`~ToricRationalDivisorClass.lift` to get a divisor representing a divisor class:: sage: D.lift() - V(x) - 2*V(u) + 3*V(y) - 4*V(v) + -3*V(x) - 9*V(u) + 7*V(z) + 3*V(w) sage: E.lift() - 1/2*V(x) - 2/3*V(u) + 3/4*V(y) - 4/5*V(v) + -3/10*V(x) - 133/60*V(u) + 31/20*V(z) + 3/4*V(w) """ @@ -279,7 +279,7 @@ cdef class ToricRationalDivisorClass(Vector_rational_dense): sage: D.divisor_class() Divisor class [29, 6, 8, 10, 0] sage: Dequiv = D.divisor_class().lift(); Dequiv - 6*V(z1) - 17*V(z2) - 22*V(z3) - 7*V(z4) + 25*V(z6) + 32*V(z7) + 15*V(z1) - 11*V(z2) - 9*V(z5) + 19*V(z6) + 10*V(z7) sage: Dequiv == D False sage: Dequiv.divisor_class() == D.divisor_class() diff --git a/src/sage/schemes/toric/homset.py b/src/sage/schemes/toric/homset.py index 4bff92bcb01..f13482bae0c 100644 --- a/src/sage/schemes/toric/homset.py +++ b/src/sage/schemes/toric/homset.py @@ -641,7 +641,7 @@ def __iter__(self): sage: P2. = toric_varieties.P2(base_ring=GF(5)) sage: cubic = P2.subscheme([x^3 + y^3 + z^3]) sage: list(cubic.point_set()) - [[0 : 1 : 4], [1 : 0 : 4], [1 : 4 : 0], [1 : 2 : 1], [1 : 1 : 2], [1 : 3 : 3]] + [[0 : 1 : 4], [1 : 0 : 4], [1 : 4 : 0], [1 : 1 : 2], [1 : 2 : 1], [1 : 3 : 3]] sage: cubic.point_set().cardinality() 6 """ @@ -661,7 +661,7 @@ def cardinality(self): sage: P2. = toric_varieties.P2(base_ring=GF(5)) sage: cubic = P2.subscheme([x^3 + y^3 + z^3]) sage: list(cubic.point_set()) - [[0 : 1 : 4], [1 : 0 : 4], [1 : 4 : 0], [1 : 2 : 1], [1 : 1 : 2], [1 : 3 : 3]] + [[0 : 1 : 4], [1 : 0 : 4], [1 : 4 : 0], [1 : 1 : 2], [1 : 2 : 1], [1 : 3 : 3]] sage: cubic.point_set().cardinality() 6 """ diff --git a/src/sage/schemes/toric/morphism.py b/src/sage/schemes/toric/morphism.py index 94be447f9ce..0d8ed6f20d0 100644 --- a/src/sage/schemes/toric/morphism.py +++ b/src/sage/schemes/toric/morphism.py @@ -1692,7 +1692,7 @@ class SchemeMorphism_fan_fiber_component_toric_variety(SchemeMorphism): From: 2-d toric variety covered by 4 affine patches To: 4-d toric variety covered by 23 affine patches Defn: Defined on coordinates by sending [z0 : z1 : z2 : z3] to - [1 : 1 : 1 : 1 : z3 : 0 : 1 : z2 : 1 : 1 : 1 : z1 : z0 : 1 : 1] + [1 : 1 : 1 : 1 : z2 : 0 : 1 : z3 : 1 : 1 : 1 : z1 : z0 : 1 : 1] sage: type(fiber_component.embedding_morphism()) """ @@ -1775,7 +1775,7 @@ def as_polynomial_map(self): From: 2-d toric variety covered by 4 affine patches To: 4-d toric variety covered by 23 affine patches Defn: Defined on coordinates by sending [z0 : z1 : z2 : z3] to - [1 : 1 : 1 : 1 : z3 : 0 : 1 : z2 : 1 : 1 : 1 : z1 : z0 : 1 : 1] + [1 : 1 : 1 : 1 : z2 : 0 : 1 : z3 : 1 : 1 : 1 : z1 : z0 : 1 : 1] sage: primitive_cone = Cone([(-1, 2, -1, 0)]) sage: f = fibration.fiber_component(primitive_cone).embedding_morphism() @@ -1949,11 +1949,11 @@ def _image_ray_multiplicity(self, fiber_ray): sage: f = fc.embedding_morphism() sage: for r in fc.fan().rays(): ....: print("{} {}".format(r, f._image_ray_multiplicity(r))) - N(-1, 2) (11, 1) + N(-1, -1) (9, 2) N(0, 1) (5, 1) - N(1, -3) (9, 2) + N(1, 0) (11, 1) sage: f._ray_index_map - {N(-3, 4): 10, N(-1, 2): 11, N(0, 1): 5, N(1, 0): 4, N(2, -6): 9} + {N(-2, -2): 9, N(-1, 2): 4, N(0, 1): 5, N(1, 0): 11, N(3, -2): 10} """ try: image_ray_index = self._ray_index_map[fiber_ray] @@ -1997,7 +1997,7 @@ def pullback_divisor(self, divisor): V(z0) + V(z1) + 3*V(z2) + 4*V(z3) sage: fc = f.fiber_component(Cone([(1,1,0)])) sage: fc.embedding_morphism().pullback_divisor(D) - 2*V(z0) + 3*V(z1) + 4*V(z0) + V(z1) + 4*V(z2) sage: fc = f.fiber_component(Cone([(1,0,0)])) sage: fc.embedding_morphism().pullback_divisor(D) -V(z0) - 3*V(z1) - 3*V(z2) diff --git a/src/sage/schemes/toric/points.py b/src/sage/schemes/toric/points.py index 31e7769ede7..2ea3067e8ab 100644 --- a/src/sage/schemes/toric/points.py +++ b/src/sage/schemes/toric/points.py @@ -986,7 +986,7 @@ def __iter__(self): sage: point_set = X.point_set() sage: ffe = point_set._enumerator() sage: list(ffe) # indirect doctest - [(1, 4, 3), (1, 1, 6), (1, 2, 5)] + [(1, 1, 6), (1, 2, 5), (1, 4, 3)] """ for cone, nonzero_coordinates, cokernel in self.ambient.cone_points_iter(): R = PolynomialRing(self.ambient.ring, cokernel.ngens(), 't') diff --git a/src/sage/schemes/toric/sheaf/klyachko.py b/src/sage/schemes/toric/sheaf/klyachko.py index 35cc2d678f6..8927bdde20a 100644 --- a/src/sage/schemes/toric/sheaf/klyachko.py +++ b/src/sage/schemes/toric/sheaf/klyachko.py @@ -946,8 +946,8 @@ def random_deformation(self, epsilon=None): sage: V = P1.sheaves.line_bundle(H) + P1.sheaves.line_bundle(-H) sage: V.cohomology(dim=True, weight=(0,)) (1, 0) - sage: Vtilde = V.random_deformation() - sage: Vtilde.cohomology(dim=True, weight=(0,)) + sage: Vtilde = V.random_deformation() # not tested, known bug + sage: Vtilde.cohomology(dim=True, weight=(0,)) # not tested, known bug (1, 0) """ filt = self._filt.random_deformation(epsilon) diff --git a/src/sage/schemes/toric/variety.py b/src/sage/schemes/toric/variety.py index 1bcfd27f828..7c4d929923f 100644 --- a/src/sage/schemes/toric/variety.py +++ b/src/sage/schemes/toric/variety.py @@ -1498,7 +1498,7 @@ def Kaehler_cone(self): in Basis lattice of The toric rational divisor class group of a 2-d CPR-Fano toric variety covered by 4 affine patches sage: [ divisor_class.lift() for divisor_class in Kc.rays() ] - [V(x), V(s)] + [V(y), V(t)] sage: Kc.lattice() Basis lattice of The toric rational divisor class group of a 2-d CPR-Fano toric variety covered by 4 affine patches @@ -1654,7 +1654,7 @@ def Chow_group(self, base_ring=ZZ): sage: A = toric_varieties.P2().Chow_group(); A Chow group of 2-d CPR-Fano toric variety covered by 3 affine patches sage: A.gens() - (( 1 | 0 | 0 ), ( 0 | 1 | 0 ), ( 0 | 0 | 1 )) + (( 0 | 0 | 1 ), ( 0 | 1 | 0 ), ( 1 | 0 | 0 )) """ from sage.schemes.toric.chow_group import ChowGroup return ChowGroup(self,base_ring) @@ -2513,7 +2513,7 @@ def toric_divisor_group(self, base_ring=ZZ): Multivariate Polynomial Ring in x, u, y, v, z, w over Rational Field """ from sage.schemes.toric.divisor import ToricDivisorGroup - return ToricDivisorGroup(self, base_ring); + return ToricDivisorGroup(self, base_ring) def _semigroup_ring(self, cone=None, names=None): r""" diff --git a/src/sage/sets/family.py b/src/sage/sets/family.py index d391cbaa49f..7ad6dbf17e0 100644 --- a/src/sage/sets/family.py +++ b/src/sage/sets/family.py @@ -581,6 +581,25 @@ def __hash__(self): return hash(frozenset(self.keys() + [repr(v) for v in self.values()])) + def __bool__(self): + r""" + Return if ``self`` is empty or not. + + EXAMPLES:: + + sage: from sage.sets.family import TrivialFamily + sage: f = Family(["c", "a", "b"], lambda x: x+x) + sage: bool(f) + True + sage: g = Family({}) + sage: bool(g) + False + sage: h = Family([], lambda x: x+x) + sage: bool(h) + False + """ + return bool(self._dictionary) + def keys(self): """ Returns the index set of this family @@ -909,6 +928,25 @@ def __init__(self, set, function, name=None): self.function = function self.function_name = name + def __bool__(self): + r""" + Return if ``self`` is empty or not. + + EXAMPLES:: + + sage: from sage.sets.family import LazyFamily + sage: f = LazyFamily([3,4,7], lambda i: 2*i) + sage: bool(f) + True + sage: g = LazyFamily([], lambda i: 2*i) + sage: bool(g) + False + sage: h = Family(ZZ, lambda x: x+x) + sage: bool(h) + True + """ + return bool(self.set) + @cached_method def __hash__(self): """ @@ -1165,6 +1203,22 @@ def __init__(self, enumeration): Parent.__init__(self, category = FiniteEnumeratedSets()) self._enumeration = tuple(enumeration) + def __bool__(self): + r""" + Return if ``self`` is empty or not. + + EXAMPLES:: + + sage: from sage.sets.family import TrivialFamily + sage: f = TrivialFamily((3,4,7)) + sage: bool(f) + True + sage: g = Family([]) + sage: bool(g) + False + """ + return bool(self._enumeration) + def __eq__(self, other): """ TESTS:: diff --git a/src/sage/sets/integer_range.py b/src/sage/sets/integer_range.py index 95e0c5d79fc..62138a9ecfb 100644 --- a/src/sage/sets/integer_range.py +++ b/src/sage/sets/integer_range.py @@ -222,9 +222,12 @@ def __classcall_private__(cls, begin, end=None, step=Integer(1), middle_point=No ... TypeError: end must be Integer or Infinity, not <... 'sage.rings.real_mpfr.RealLiteral'> """ - if isinstance(begin, int): begin = Integer(begin) - if isinstance(end, int): end = Integer(end) - if isinstance(step,int): step = Integer(step) + if isinstance(begin, int): + begin = Integer(begin) + if isinstance(end, int): + end = Integer(end) + if isinstance(step, int): + step = Integer(step) if end is None: end = begin diff --git a/src/sage/sets/non_negative_integers.py b/src/sage/sets/non_negative_integers.py index 9d3579a5f46..9b01ad6f3d4 100644 --- a/src/sage/sets/non_negative_integers.py +++ b/src/sage/sets/non_negative_integers.py @@ -221,3 +221,18 @@ def unrank(self, rnk): 100 """ return self.from_integer(rnk) + + def _sympy_(self): + r""" + Return the SymPy set ``Naturals0``. + + EXAMPLES:: + + sage: NN = NonNegativeIntegers() + sage: NN._sympy_() + Naturals0 + """ + from sympy import Naturals0 + from sage.interfaces.sympy import sympy_init + sympy_init() + return Naturals0 diff --git a/src/sage/sets/positive_integers.py b/src/sage/sets/positive_integers.py index c800d201e95..7ed7d9657e9 100644 --- a/src/sage/sets/positive_integers.py +++ b/src/sage/sets/positive_integers.py @@ -75,3 +75,17 @@ def an_element(self): 42 """ return Integer(42) + + def _sympy_(self): + r""" + Return the SymPy set ``Naturals``. + + EXAMPLES:: + + sage: PositiveIntegers()._sympy_() + Naturals + """ + from sympy import Naturals + from sage.interfaces.sympy import sympy_init + sympy_init() + return Naturals diff --git a/src/sage/sets/real_set.py b/src/sage/sets/real_set.py index a4621569133..a9b0ce85ece 100644 --- a/src/sage/sets/real_set.py +++ b/src/sage/sets/real_set.py @@ -10,7 +10,7 @@ sage: RealSet(0,1) (0, 1) sage: RealSet((0,1), [2,3]) - (0, 1) + [2, 3] + (0, 1) ∪ [2, 3] sage: RealSet(-oo, oo) (-oo, +oo) @@ -42,7 +42,7 @@ Relations containing symbols and numeric values or constants:: sage: RealSet(x != 0) - (-oo, 0) + (0, +oo) + (-oo, 0) ∪ (0, +oo) sage: RealSet(x == pi) {pi} sage: RealSet(x < 1/2) @@ -88,11 +88,12 @@ class RealSet. from sage.structure.richcmp import richcmp, richcmp_method from sage.structure.parent import Parent from sage.structure.unique_representation import UniqueRepresentation -from sage.categories.sets_cat import Sets +from sage.categories.topological_spaces import TopologicalSpaces from sage.rings.all import ZZ from sage.rings.real_lazy import LazyFieldElement, RLF from sage.rings.infinity import infinity, minus_infinity +from sage.misc.superseded import deprecated_function_alias @richcmp_method class InternalRealInterval(UniqueRepresentation, Parent): @@ -382,6 +383,31 @@ def _repr_(self): s += ']' if self._upper_closed else ')' return s + def _latex_(self): + """ + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: RealSet.open_closed(1/2, pi)._latex_() + '(\\frac{1}{2}, \\pi]' + sage: (RealSet.point(sqrt(2)))._latex_() + '\\{\\sqrt{2}\\}' + """ + from sage.misc.latex import latex + if self.is_point(): + # Converting to str avoids the extra whitespace + # that LatexExpr add on concenation. We do not need + # the whitespace because we are wrapping it in + # non-letter characters. + return r'\{' + str(latex(self.lower())) + r'\}' + s = '[' if self._lower_closed else '(' + s += str(latex(self.lower())) + s += ', ' + s += str(latex(self.upper())) + s += ']' if self._upper_closed else ')' + return s + def _sympy_condition_(self, variable): """ Convert to a sympy conditional expression. @@ -415,6 +441,30 @@ def _sympy_condition_(self, variable): upper_condition = true return lower_condition & upper_condition + def _sympy_(self): + r""" + Return the SymPy set corresponding to ``self``. + + EXAMPLES:: + + sage: RealSet.open_closed(0, 1)[0]._sympy_() + Interval.Lopen(0, 1) + sage: RealSet.point(0)[0]._sympy_() + FiniteSet(0) + sage: RealSet.open(0,1)[0]._sympy_() + Interval.open(0, 1) + sage: RealSet.open(-oo,1)[0]._sympy_() + Interval.open(-oo, 1) + sage: RealSet.open(0, oo)[0]._sympy_() + Interval.open(0, oo) + """ + from sympy import Interval + from sage.interfaces.sympy import sympy_init + sympy_init() + return Interval(self.lower(), self.upper(), + left_open=not self._lower_closed, + right_open=not self._upper_closed) + def closure(self): """ Return the closure @@ -455,6 +505,23 @@ def interior(self): """ return InternalRealInterval(self._lower, False, self._upper, False) + def boundary_points(self): + """ + Generate the boundary points of ``self`` + + EXAMPLES:: + + sage: list(RealSet.open_closed(-oo, 1)[0].boundary_points()) + [1] + sage: list(RealSet.open(1, 2)[0].boundary_points()) + [1, 2] + + """ + if self._lower != minus_infinity: + yield self._lower + if self._upper != infinity: + yield self._upper + def is_connected(self, other): """ Test whether two intervals are connected @@ -742,12 +809,12 @@ def __classcall__(cls, *args): EXAMPLES:: sage: R = RealSet(RealSet.open_closed(0,1), RealSet.closed_open(2,3)); R - (0, 1] + [2, 3) + (0, 1] ∪ [2, 3) :: sage: RealSet(x != 0) - (-oo, 0) + (0, +oo) + (-oo, 0) ∪ (0, +oo) sage: RealSet(x == pi) {pi} sage: RealSet(x < 1/2) @@ -857,7 +924,25 @@ def rel_to_interval(op, val): else: raise ValueError(str(arg) + ' does not determine real interval') else: - raise ValueError(str(arg) + ' does not determine real interval') + from sage.manifolds.differentiable.examples.real_line import OpenInterval + from sage.manifolds.subsets.closure import ManifoldSubsetClosure + if isinstance(arg, OpenInterval): + lower, upper = RealSet._prep(arg.lower_bound(), arg.upper_bound()) + intervals.append(InternalRealInterval(lower, False, upper, False)) + elif (isinstance(arg, ManifoldSubsetClosure) + and isinstance(arg._subset, OpenInterval)): + interval = arg._subset + lower, upper = RealSet._prep(interval.lower_bound(), + interval.upper_bound()) + ambient = interval.manifold() + ambient_lower, ambient_upper = RealSet._prep(ambient.lower_bound(), + ambient.upper_bound()) + lower_closed = ambient_lower < lower + upper_closed = upper < ambient_upper + intervals.append(InternalRealInterval(lower, lower_closed, + upper, upper_closed)) + else: + raise ValueError(str(arg) + ' does not determine real interval') intervals = RealSet.normalize(intervals) return UniqueRepresentation.__classcall__(cls, *intervals) @@ -882,11 +967,63 @@ def __init__(self, *intervals): sage: RealSet(i) # interval (0, 1) sage: RealSet(i, (3,4)) # tuple of two numbers = open set - (0, 1) + (3, 4) + (0, 1) ∪ (3, 4) sage: RealSet(i, [3,4]) # list of two numbers = closed set - (0, 1) + [3, 4] + (0, 1) ∪ [3, 4] + + Initialization from manifold objects:: + + sage: R = RealLine(); R + Real number line R + sage: RealSet(R) + (-oo, +oo) + sage: I02 = OpenInterval(0, 2); I + I + sage: RealSet(I02) + (0, 2) + sage: I01_of_R = OpenInterval(0, 1, ambient_interval=R); I01_of_R + Real interval (0, 1) + sage: RealSet(I01_of_R) + (0, 1) + sage: RealSet(I01_of_R.closure()) + [0, 1] + sage: I01_of_I02 = OpenInterval(0, 1, ambient_interval=I02); I01_of_I02 + Real interval (0, 1) + sage: RealSet(I01_of_I02) + (0, 1) + sage: RealSet(I01_of_I02.closure()) + (0, 1] + + Real sets belong to a subcategory of topological spaces:: + + sage: RealSet().category() + Join of Category of finite sets and Category of subobjects of sets and Category of connected topological spaces + sage: RealSet.point(1).category() + Join of Category of finite sets and Category of subobjects of sets and Category of connected topological spaces + sage: RealSet([1, 2]).category() + Join of Category of infinite sets and Category of compact topological spaces and Category of subobjects of sets and Category of connected topological spaces + sage: RealSet((1, 2), (3, 4)).category() + Join of Category of infinite sets and Category of subobjects of sets and Category of topological spaces + """ - Parent.__init__(self, category = Sets()) + category = TopologicalSpaces() + if len(intervals) <= 1: + category = category.Connected() + if all(i.is_point() for i in intervals): + category = category.Subobjects().Finite() + else: + # Have at least one non-degenerate interval + category = category.Infinite() + inf = intervals[0].lower() + sup = intervals[-1].upper() + if not (len(intervals) == 1 and inf is minus_infinity and sup is infinity): + category = category.Subobjects() # subobject of real line + if inf is not minus_infinity and sup is not infinity: + # Bounded + if all(i.lower_closed() and i.upper_closed() + for i in intervals): + category = category.Compact() + Parent.__init__(self, category=category) self._intervals = intervals def __richcmp__(self, other, op): @@ -986,6 +1123,17 @@ def is_empty(self): """ return len(self._intervals) == 0 + def is_universe(self): + """ + Return whether the set is the ambient space (the real line). + + EXAMPLES:: + + sage: RealSet().ambient().is_universe() + True + """ + return self == self.ambient() + def get_interval(self, i): """ Return the ``i``-th connected component. @@ -1017,6 +1165,72 @@ def get_interval(self, i): __getitem__ = get_interval + def __bool__(self): + """ + A set is considered True unless it is empty, in which case it is + considered to be False. + + EXAMPLES:: + + sage: bool(RealSet(0, 1)) + True + sage: bool(RealSet()) + False + """ + return not self.is_empty() + + # ParentMethods of Subobjects + + def ambient(self): + """ + Return the ambient space (the real line). + + EXAMPLES:: + + sage: s = RealSet(RealSet.open_closed(0,1), RealSet.closed_open(2,3)) + sage: s.ambient() + (-oo, +oo) + """ + return RealSet(minus_infinity, infinity) + + def lift(self, x): + """ + Lift ``x`` to the ambient space for ``self``. + + This version of the method just returns ``x``. + + EXAMPLES:: + + sage: s = RealSet(0, 2); s + (0, 2) + sage: s.lift(1) + 1 + """ + return x + + def retract(self, x): + """ + Retract ``x`` to ``self``. + + It raises an error if ``x`` does not lie in the set ``self``. + + EXAMPLES:: + + sage: s = RealSet(0, 2); s + (0, 2) + sage: s.retract(1) + 1 + sage: s.retract(2) + Traceback (most recent call last): + ... + ValueError: 2 is not an element of (0, 2) + """ + if x not in self: + raise ValueError(f'{x} is not an element of {self}') + return x + + # + @staticmethod def normalize(intervals): """ @@ -1047,9 +1261,9 @@ def normalize(intervals): sage: RealSet((0, 1), [1, 2], (2, 3)) (0, 3) sage: RealSet((0, 1), (1, 2), (2, 3)) - (0, 1) + (1, 2) + (2, 3) + (0, 1) ∪ (1, 2) ∪ (2, 3) sage: RealSet([0, 1], [2, 3]) - [0, 1] + [2, 3] + [0, 1] ∪ [2, 3] sage: RealSet((0, 2), (1, 3)) (0, 3) sage: RealSet(0,0) @@ -1077,7 +1291,7 @@ def normalize(intervals): def _repr_(self): """ - Return a string representation + Return a string representation of ``self``. OUTPUT: @@ -1091,10 +1305,24 @@ def _repr_(self): if self.n_components() == 0: return '{}' else: - # Switch to u'\u222A' (cup sign) with Python 3 - return ' + '.join(map(repr, self._intervals)) - # return u' ∪ '.join(map(repr, self._intervals)) # py3 only + return ' ∪ '.join(map(repr, self._intervals)) + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: latex(RealSet(0, 1)) + (0, 1) + sage: latex((RealSet(0, 1).union(RealSet.unbounded_above_closed(2)))) + (0, 1) \cup [2, +\infty) + """ + from sage.misc.latex import latex + if self.n_components() == 0: + return r'\emptyset' + else: + return r' \cup '.join(latex(i) for i in self._intervals) def _sympy_condition_(self, variable): """ @@ -1411,26 +1639,26 @@ def intersection(self, *other): EXAMPLES:: sage: s1 = RealSet(0,2) + RealSet.unbounded_above_closed(10); s1 - (0, 2) + [10, +oo) + (0, 2) ∪ [10, +oo) sage: s2 = RealSet(1,3) + RealSet.unbounded_below_closed(-10); s2 - (-oo, -10] + (1, 3) + (-oo, -10] ∪ (1, 3) sage: s1.intersection(s2) (1, 2) sage: s1 & s2 # syntactic sugar (1, 2) sage: s1 = RealSet((0, 1), (2, 3)); s1 - (0, 1) + (2, 3) + (0, 1) ∪ (2, 3) sage: s2 = RealSet([0, 1], [2, 3]); s2 - [0, 1] + [2, 3] + [0, 1] ∪ [2, 3] sage: s3 = RealSet([1, 2]); s3 [1, 2] sage: s1.intersection(s2) - (0, 1) + (2, 3) + (0, 1) ∪ (2, 3) sage: s1.intersection(s3) {} sage: s2.intersection(s3) - {1} + {2} + {1} ∪ {2} """ other = RealSet(*other) # TODO: this can be done in linear time since the intervals are already sorted @@ -1453,12 +1681,12 @@ def inf(self): EXAMPLES:: sage: s1 = RealSet(0,2) + RealSet.unbounded_above_closed(10); s1 - (0, 2) + [10, +oo) + (0, 2) ∪ [10, +oo) sage: s1.inf() 0 sage: s2 = RealSet(1,3) + RealSet.unbounded_below_closed(-10); s2 - (-oo, -10] + (1, 3) + (-oo, -10] ∪ (1, 3) sage: s2.inf() -Infinity """ @@ -1477,12 +1705,12 @@ def sup(self): EXAMPLES:: sage: s1 = RealSet(0,2) + RealSet.unbounded_above_closed(10); s1 - (0, 2) + [10, +oo) + (0, 2) ∪ [10, +oo) sage: s1.sup() +Infinity sage: s2 = RealSet(1,3) + RealSet.unbounded_below_closed(-10); s2 - (-oo, -10] + (1, 3) + (-oo, -10] ∪ (1, 3) sage: s2.sup() 3 """ @@ -1501,17 +1729,17 @@ def complement(self): EXAMPLES:: sage: RealSet(0,1).complement() - (-oo, 0] + [1, +oo) + (-oo, 0] ∪ [1, +oo) sage: s1 = RealSet(0,2) + RealSet.unbounded_above_closed(10); s1 - (0, 2) + [10, +oo) + (0, 2) ∪ [10, +oo) sage: s1.complement() - (-oo, 0] + [2, 10) + (-oo, 0] ∪ [2, 10) sage: s2 = RealSet(1,3) + RealSet.unbounded_below_closed(-10); s2 - (-oo, -10] + (1, 3) + (-oo, -10] ∪ (1, 3) sage: s2.complement() - (-10, 1] + [3, +oo) + (-10, 1] ∪ [3, +oo) """ n = self.n_components() if n == 0: @@ -1549,19 +1777,19 @@ def difference(self, *other): EXAMPLES:: sage: s1 = RealSet(0,2) + RealSet.unbounded_above_closed(10); s1 - (0, 2) + [10, +oo) + (0, 2) ∪ [10, +oo) sage: s2 = RealSet(1,3) + RealSet.unbounded_below_closed(-10); s2 - (-oo, -10] + (1, 3) + (-oo, -10] ∪ (1, 3) sage: s1.difference(s2) - (0, 1] + [10, +oo) + (0, 1] ∪ [10, +oo) sage: s1 - s2 # syntactic sugar - (0, 1] + [10, +oo) + (0, 1] ∪ [10, +oo) sage: s2.difference(s1) - (-oo, -10] + [2, 3) + (-oo, -10] ∪ [2, 3) sage: s2 - s1 # syntactic sugar - (-oo, -10] + [2, 3) + (-oo, -10] ∪ [2, 3) sage: s1.difference(1,11) - (0, 1] + [11, +oo) + (0, 1] ∪ [11, +oo) """ other = RealSet(*other) return self.intersection(other.complement()) @@ -1583,7 +1811,7 @@ def contains(self, x): EXAMPLES:: sage: s = RealSet(0,2) + RealSet.unbounded_above_closed(10); s - (0, 2) + [10, +oo) + (0, 2) ∪ [10, +oo) sage: s.contains(1) True sage: s.contains(0) @@ -1599,13 +1827,13 @@ def contains(self, x): __contains__ = contains - def is_included_in(self, *other): + def is_subset(self, *other): r""" - Tests interval inclusion + Return whether ``self`` is a subset of ``other``. INPUT: - - ``*args`` -- a :class:`RealSet` or something that defines + - ``*other`` -- a :class:`RealSet` or something that defines one. OUTPUT: @@ -1617,13 +1845,15 @@ def is_included_in(self, *other): sage: I = RealSet((1,2)) sage: J = RealSet((1,3)) sage: K = RealSet((2,3)) - sage: I.is_included_in(J) + sage: I.is_subset(J) True - sage: J.is_included_in(K) + sage: J.is_subset(K) False """ return RealSet(*other).intersection(self) == self + is_included_in = deprecated_function_alias(31927, is_subset) + def an_element(self): """ Return a point of the set @@ -1662,7 +1892,92 @@ def an_element(self): return i.upper() return (i.lower() + i.upper())/ZZ(2) - def is_disjoint_from(self, *other): + def is_open(self): + """ + Return whether ``self`` is an open set. + + EXAMPLES:: + + sage: RealSet().is_open() + True + sage: RealSet.point(1).is_open() + False + sage: RealSet((1, 2)).is_open() + True + sage: RealSet([1, 2], (3, 4)).is_open() + False + + """ + return all(not i.lower_closed() + and not i.upper_closed() + for i in self._intervals) + + def is_closed(self): + """ + Return whether ``self`` is a closed set. + + EXAMPLES:: + + sage: RealSet().is_closed() + True + sage: RealSet.point(1).is_closed() + True + sage: RealSet([1, 2]).is_closed() + True + sage: RealSet([1, 2], (3, 4)).is_closed() + False + """ + return all((i.lower_closed() or i.lower() is minus_infinity) + and (i.upper_closed() or i.upper() is infinity) + for i in self._intervals) + + def closure(self): + """ + Return the topological closure of ``self``. + + EXAMPLES:: + + sage: RealSet(-oo, oo).closure() + (-oo, +oo) + sage: RealSet((1, 2), (2, 3)).closure() + [1, 3] + """ + return RealSet(*[i.closure() for i in self._intervals]) + + def interior(self): + """ + Return the topological interior of ``self``. + + EXAMPLES:: + + sage: RealSet(-oo, oo).interior() + (-oo, +oo) + sage: RealSet.point(2).interior() + {} + sage: RealSet([1, 2], (3, 4)).interior() + (1, 2) ∪ (3, 4) + """ + return RealSet(*[i.interior() for i in self._intervals]) + + def boundary(self): + """ + Return the topological boundary of ``self``. + + EXAMPLES:: + + sage: RealSet(-oo, oo).boundary() + {} + sage: RealSet.point(2).boundary() + {2} + sage: RealSet([1, 2], (3, 4)).boundary() + {1} ∪ {2} ∪ {3} ∪ {4} + sage: RealSet((1, 2), (2, 3)).boundary() + {1} ∪ {2} ∪ {3} + + """ + return RealSet(*[RealSet.point(x) for i in self._intervals for x in i.boundary_points()]) + + def is_disjoint(self, *other): """ Test whether the two sets are disjoint @@ -1677,17 +1992,19 @@ def is_disjoint_from(self, *other): EXAMPLES:: sage: s1 = RealSet((0, 1), (2, 3)); s1 - (0, 1) + (2, 3) + (0, 1) ∪ (2, 3) sage: s2 = RealSet([1, 2]); s2 [1, 2] - sage: s1.is_disjoint_from(s2) + sage: s1.is_disjoint(s2) True - sage: s1.is_disjoint_from([1, 2]) + sage: s1.is_disjoint([1, 2]) True """ other = RealSet(*other) return self.intersection(other).is_empty() + is_disjoint_from = deprecated_function_alias(31927, is_disjoint) + @staticmethod def are_pairwise_disjoint(*real_set_collection): """ @@ -1719,7 +2036,7 @@ def are_pairwise_disjoint(*real_set_collection): for j in range(i): si = sets[i] sj = sets[j] - if not si.is_disjoint_from(sj): + if not si.is_disjoint(sj): return False return True @@ -1782,15 +2099,15 @@ def __mul__(self, right): EXAMPLES:: sage: A = RealSet([0, 1/2], (2, infinity)); A - [0, 1/2] + (2, +oo) + [0, 1/2] ∪ (2, +oo) sage: 2 * A - [0, 1] + (4, +oo) + [0, 1] ∪ (4, +oo) sage: A * 100 - [0, 50] + (200, +oo) + [0, 50] ∪ (200, +oo) sage: 1.5 * A - [0.000000000000000, 0.750000000000000] + (3.00000000000000, +oo) + [0.000000000000000, 0.750000000000000] ∪ (3.00000000000000, +oo) sage: (-2) * A - (-oo, -4) + [-1, 0] + (-oo, -4) ∪ [-1, 0] """ if not isinstance(right, RealSet): return RealSet(*[e * right for e in self]) @@ -1806,8 +2123,40 @@ def __rmul__(self, other): TESTS:: sage: A = RealSet([0, 1/2], RealSet.unbounded_above_closed(2)); A - [0, 1/2] + [2, +oo) + [0, 1/2] ∪ [2, +oo) sage: pi * A - [0, 1/2*pi] + [2*pi, +oo) + [0, 1/2*pi] ∪ [2*pi, +oo) """ return self * other + + def _sympy_(self): + r""" + Return the SymPy set corresponding to ``self``. + + EXAMPLES:: + + sage: RealSet()._sympy_() + EmptySet + sage: RealSet.point(5)._sympy_() + FiniteSet(5) + sage: (RealSet.point(1).union(RealSet.point(2)))._sympy_() + FiniteSet(1, 2) + sage: (RealSet(1, 2).union(RealSet.closed(3, 4)))._sympy_() + Union(Interval.open(1, 2), Interval(3, 4)) + sage: RealSet(-oo, oo)._sympy_() + Reals + + Infinities are not elements:: + + sage: import sympy + sage: RealSet(-oo, oo)._sympy_().contains(sympy.oo) + False + """ + from sympy import Reals, Union + from sage.interfaces.sympy import sympy_init + sympy_init() + if self.is_universe(): + return Reals + else: + return Union(*[interval._sympy_() + for interval in self._intervals]) diff --git a/src/sage/structure/factorization.py b/src/sage/structure/factorization.py index 1d32db08422..7636f1a9ba7 100644 --- a/src/sage/structure/factorization.py +++ b/src/sage/structure/factorization.py @@ -133,17 +133,17 @@ sage: K. = NumberField(x^2 + 3); K Number Field in a with defining polynomial x^2 + 3 sage: f = K.factor(15); f - (Fractional ideal (-a))^2 * (Fractional ideal (5)) + (Fractional ideal (1/2*a + 3/2))^2 * (Fractional ideal (5)) sage: f.universe() Monoid of ideals of Number Field in a with defining polynomial x^2 + 3 sage: f.unit() Fractional ideal (1) sage: g=K.factor(9); g - (Fractional ideal (-a))^4 + (Fractional ideal (1/2*a + 3/2))^4 sage: f.lcm(g) - (Fractional ideal (-a))^4 * (Fractional ideal (5)) + (Fractional ideal (1/2*a + 3/2))^4 * (Fractional ideal (5)) sage: f.gcd(g) - (Fractional ideal (-a))^2 + (Fractional ideal (1/2*a + 3/2))^2 sage: f.is_integral() True diff --git a/src/sage/structure/list_clone_timings.py b/src/sage/structure/list_clone_timings.py index 73f5da69f24..efe5d20efc7 100644 --- a/src/sage/structure/list_clone_timings.py +++ b/src/sage/structure/list_clone_timings.py @@ -128,7 +128,8 @@ def add1_internal(bla): """ blo = bla.__copy__() lst = blo._get_list() - for i in range(len(blo)): lst[i] += 1 + for i in range(len(blo)): + lst[i] += 1 blo.set_immutable() blo.check() return blo @@ -142,7 +143,8 @@ def add1_immutable(bla): [2, 5, 6] """ lbla = bla[:] - for i in range(len(lbla)): lbla[i] += 1 + for i in range(len(lbla)): + lbla[i] += 1 return bla.__class__(bla.parent(), lbla) def add1_mutable(bla): @@ -154,7 +156,8 @@ def add1_mutable(bla): [2, 5, 6] """ blo = bla.__copy__() - for i in range(len(blo)): blo[i] += 1 + for i in range(len(blo)): + blo[i] += 1 blo.set_immutable() blo.check() return blo @@ -168,5 +171,6 @@ def add1_with(bla): [2, 5, 6] """ with bla.clone() as blo: - for i in range(len(blo)): blo[i] += 1 + for i in range(len(blo)): + blo[i] += 1 return blo diff --git a/src/sage/structure/parent.pyx b/src/sage/structure/parent.pyx index a5a16bacdd4..5a46c73c26a 100644 --- a/src/sage/structure/parent.pyx +++ b/src/sage/structure/parent.pyx @@ -992,6 +992,64 @@ cdef class Parent(sage.structure.category_object.CategoryObject): return _mul_(self, switch_sides=True) return _mul_(x) + def __pow__(self, x, mod): + """ + Power function. + + The default implementation of ``__pow__`` on parent redirects to the + super class (in case of multiple inheritance) or to the category. This + redirection is necessary when the parent is a Cython class (aka + extension class) because in that case the parent class does not inherit + from the ``ParentMethods`` of the category. + + Concrete implementations of parents can freely overwrite this default + method. + + TESTS:: + + sage: ZZ^3 + Ambient free module of rank 3 over the principal ideal domain + Integer Ring + sage: QQ^3 + Vector space of dimension 3 over Rational Field + sage: QQ[x]^3 + Ambient free module of rank 3 over the principal ideal domain + Univariate Polynomial Ring in x over Rational Field + sage: IntegerModRing(6)^3 + Ambient free module of rank 3 over Ring of integers modulo 6 + + sage: 3^ZZ + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for ^: 'Integer Ring' and '' + sage: Partitions(3)^3 + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for ** or pow(): 'Partitions_n_with_category' and 'int' + + Check multiple inheritance:: + + sage: class A: + ....: def __pow__(self, n): + ....: return 'Apow' + sage: class MyParent(A, Parent): + ....: pass + sage: MyParent()^2 + 'Apow' + """ + if mod is not None or not isinstance(self, Parent): + return NotImplemented + try: + # get __pow__ from super class + meth = super(Parent, ( self)).__pow__ + except AttributeError: + # get __pow__ from category in case the parent is a Cython class + try: + meth = ( self).getattr_from_category('__pow__') + except AttributeError: + return NotImplemented + return meth(x) + ############################################################################# # Containment testing ############################################################################# diff --git a/src/sage/symbolic/constants.py b/src/sage/symbolic/constants.py index a223991c08d..a24bbcebc38 100644 --- a/src/sage/symbolic/constants.py +++ b/src/sage/symbolic/constants.py @@ -955,7 +955,8 @@ def __init__(self, name='euler_gamma'): """ conversions = dict(kash='EulerGamma(R)', maple='gamma', mathematica='EulerGamma', pari='Euler', - maxima='%gamma', pynac='Euler', giac='euler_gamma') + maxima='%gamma', pynac='Euler', giac='euler_gamma', + fricas='-digamma(1)') Constant.__init__(self, name, conversions=conversions, latex=r'\gamma', domain='positive') diff --git a/src/sage/symbolic/expression.pyx b/src/sage/symbolic/expression.pyx index f81c4ff9717..0e8dc117bc0 100644 --- a/src/sage/symbolic/expression.pyx +++ b/src/sage/symbolic/expression.pyx @@ -767,6 +767,29 @@ cdef class Expression(CommutativeRingElement): """ return new_Expression_from_GEx(self._parent, self._gobj) + def __enter__(self): + """ + Method used by temporary variables with Python `with` to + automatically clean up after themselves. + """ + return self + + def __exit__(self, *args): + """ + Method used by temporary variables with Python `with` to + automatically clean up after themselves. + + TESTS:: + + sage: symbols_copy = SR.symbols.copy() + sage: with SR.temp_var() as t: pass + sage: symbols_copy == SR.symbols + True + + """ + SR.cleanup_var(self) + return False + def _repr_(self): r""" Return string representation of this symbolic expression. @@ -2849,28 +2872,42 @@ cdef class Expression(CommutativeRingElement): def is_square(self): """ - Return ``True`` if ``self`` is a perfect square. + Return ``True`` if ``self`` is the square of another symbolic expression. + + This is ``True`` for all constant, non-relational expressions + (containing no variables or comparison), and not implemented + otherwise. EXAMPLES:: - sage: f(n,m) = n*2 + m - sage: f(2,1).is_square() - False - sage: f(3,3).is_square() + sage: SR(4).is_square() + True + sage: SR(5).is_square() True - sage: f(n,m).is_square() + sage: pi.is_square() + True + sage: x.is_square() Traceback (most recent call last): ... - NotImplementedError: is_square() not implemented for non numeric elements of Symbolic Ring - sage: SR(42).is_square() - False - sage: SR(4).is_square() - True + NotImplementedError: is_square() not implemented for non-constant + or relational elements of Symbolic Ring + sage: r = SR(4) == SR(5) + sage: r.is_square() + Traceback (most recent call last): + ... + NotImplementedError: is_square() not implemented for non-constant + or relational elements of Symbolic Ring + """ + if self.is_constant() and not self.is_relational(): + # The square root of any "number" is in SR... just call + # sqrt() on it. + return True + try: obj = self.pyobject() except TypeError as e: - raise NotImplementedError("is_square() not implemented for non numeric elements of Symbolic Ring") + raise NotImplementedError("is_square() not implemented for non-constant or relational elements of Symbolic Ring") return obj.is_square() @@ -10013,8 +10050,8 @@ cdef class Expression(CommutativeRingElement): sage: a = RR.random_element() sage: b = RR.random_element() sage: f = SR(a + b*I) - sage: bool(f.rectform() == a + b*I) - True + sage: abs(f.rectform() - (a + b*I)) # abs tol 1e-16 + 0.0 If we decompose a complex number into its real and imaginary parts, they should correspond to the real and imaginary terms @@ -10023,9 +10060,8 @@ cdef class Expression(CommutativeRingElement): sage: z = CC.random_element() sage: a = z.real_part() sage: b = z.imag_part() - sage: bool(SR(z).rectform() == a + b*I) - True - + sage: abs(SR(z).rectform() - (a + b*I)) # abs tol 1e-16 + 0.0 """ return self.maxima_methods().rectform() @@ -10227,10 +10263,10 @@ cdef class Expression(CommutativeRingElement): 1/3*x*hypergeometric((), (2, 3), x) + hypergeometric((), (1, 2), x) sage: (2*hypergeometric((), (), x)).simplify_hypergeometric() 2*e^x - sage: (nest(lambda y: hypergeometric([y], [1], x), 3, 1) + sage: (nest(lambda y: hypergeometric([y], [1], x), 3, 1) # not tested, unstable ....: .simplify_hypergeometric()) laguerre(-laguerre(-e^x, x), x) - sage: (nest(lambda y: hypergeometric([y], [1], x), 3, 1) + sage: (nest(lambda y: hypergeometric([y], [1], x), 3, 1) # not tested, unstable ....: .simplify_hypergeometric(algorithm='sage')) hypergeometric((hypergeometric((e^x,), (1,), x),), (1,), x) sage: hypergeometric_M(1, 3, x).simplify_hypergeometric() @@ -12885,25 +12921,25 @@ cdef class Expression(CommutativeRingElement): if not self.has(Y): raise ValueError("Expression {} contains no {} terms".format(self, Y)) - x = SR.symbol() - yy = SR.symbol() - y = SymbolicFunction('y', 1)(x) - f = SymbolicFunction('f', 2)(x, yy) - Fx = f.diff(x) - Fy = f.diff(yy) - G = -(Fx/Fy) - G = G.subs({yy: y}) - di = {y.diff(x): -self.diff(X)/self.diff(Y)} - R = G - S = G.diff(x, n - 1) - for i in range(n + 1): - di[y.diff(x, i + 1).subs({x: x})] = R - S = S.subs(di) - R = G.diff(x, i) - for j in range(n + 1 - i): - di[f.diff(x, i, yy, j).subs({x: x, yy: y})] = self.diff(X, i, Y, j) - S = S.subs(di) - return S + with SR.temp_var() as x: + with SR.temp_var() as yy: + y = SymbolicFunction('y', 1)(x) + f = SymbolicFunction('f', 2)(x, yy) + Fx = f.diff(x) + Fy = f.diff(yy) + G = -(Fx/Fy) + G = G.subs({yy: y}) + di = {y.diff(x): -self.diff(X)/self.diff(Y)} + R = G + S = G.diff(x, n - 1) + for i in range(n + 1): + di[y.diff(x, i + 1).subs({x: x})] = R + S = S.subs(di) + R = G.diff(x, i) + for j in range(n + 1 - i): + di[f.diff(x, i, yy, j).subs({x: x, yy: y})] = self.diff(X, i, Y, j) + S = S.subs(di) + return S def solve_diophantine(f, *args, **kwds): """ diff --git a/src/sage/symbolic/expression_conversions.py b/src/sage/symbolic/expression_conversions.py index 9cf695317fc..d6681cb8366 100644 --- a/src/sage/symbolic/expression_conversions.py +++ b/src/sage/symbolic/expression_conversions.py @@ -18,11 +18,9 @@ import operator as _operator from sage.rings.rational_field import QQ from sage.symbolic.ring import SR -from sage.symbolic.constants import I from sage.functions.all import exp from sage.symbolic.operators import arithmetic_operators, relation_operators, FDerivativeOperator, add_vararg, mul_vararg -from sage.rings.number_field.number_field import GaussianField -from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_quadratic +from sage.rings.number_field.number_field_element_quadratic import NumberFieldElement_gaussian from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField from functools import reduce @@ -425,9 +423,14 @@ def symbol(self, ex): sage: ii = InterfaceInit(gp) sage: ii.symbol(x) 'x' + sage: g = InterfaceInit(giac) + sage: g.symbol(x) + 'sageVARx' """ if self.interface.name()=='maxima': return '_SAGE_VAR_'+repr(SR(ex)) + elif self.interface.name() == 'giac': + return 'sageVAR' + repr(SR(ex)) else: return repr(SR(ex)) @@ -448,8 +451,7 @@ def pyobject(self, ex, obj): 'Pi' """ if (self.interface.name() in ['pari','gp'] and - isinstance(obj, NumberFieldElement_quadratic) and - obj.parent() is GaussianField()): + isinstance(obj, NumberFieldElement_gaussian)): return repr(obj) try: return getattr(obj, self.name_init)() @@ -509,7 +511,7 @@ def derivative(self, ex, operator): sage: t D[0](f)(x*y) sage: m.derivative(t, t.operator()) - "at(diff('f(_SAGE_VAR_t0), _SAGE_VAR_t0, 1), [_SAGE_VAR_t0 = (_SAGE_VAR_x)*(_SAGE_VAR_y)])" + "at(diff('f(_SAGE_VAR__symbol0), _SAGE_VAR__symbol0, 1), [_SAGE_VAR__symbol0 = (_SAGE_VAR_x)*(_SAGE_VAR_y)])" TESTS: @@ -533,7 +535,7 @@ def derivative(self, ex, operator): sage: a = df.subs(x=exp(x)); a D[0](f)(e^x) sage: b = maxima(a); b - %at('diff('f(_SAGE_VAR_t0),_SAGE_VAR_t0,1),_SAGE_VAR_t0=%e^_SAGE_VAR_x) + %at('diff('f(_SAGE_VAR__symbol0),_SAGE_VAR__symbol0,1),_SAGE_VAR__symbol0=%e^_SAGE_VAR_x) sage: bool(b.sage() == a) True @@ -542,7 +544,7 @@ def derivative(self, ex, operator): sage: a = df.subs(x=4); a D[0](f)(4) sage: b = maxima(a); b - %at('diff('f(_SAGE_VAR_t0),_SAGE_VAR_t0,1),_SAGE_VAR_t0=4) + %at('diff('f(_SAGE_VAR__symbol0),_SAGE_VAR__symbol0,1),_SAGE_VAR__symbol0=4) sage: bool(b.sage() == a) True @@ -562,7 +564,7 @@ def derivative(self, ex, operator): sage: a = f_x.subs(x=4); a D[0](f)(4, y) sage: b = maxima(a); b - %at('diff('f(_SAGE_VAR_t0,_SAGE_VAR_y),_SAGE_VAR_t0,1),_SAGE_VAR_t0=4) + %at('diff('f(_SAGE_VAR__symbol0,_SAGE_VAR_y),_SAGE_VAR__symbol0,1),_SAGE_VAR__symbol0=4) sage: bool(b.sage() == a) True @@ -571,7 +573,7 @@ def derivative(self, ex, operator): sage: a = f_x.subs(x=4).subs(y=8); a D[0](f)(4, 8) sage: b = maxima(a); b - %at('diff('f(_SAGE_VAR_t0,8),_SAGE_VAR_t0,1),_SAGE_VAR_t0=4) + %at('diff('f(_SAGE_VAR__symbol0,8),_SAGE_VAR__symbol0,1),_SAGE_VAR__symbol0=4) sage: bool(b.sage() == a) True @@ -594,9 +596,12 @@ def derivative(self, ex, operator): # An evaluated derivative of the form f'(1) is not a # symbolic variable, yet we would like to treat it like # one. So, we replace the argument `1` with a temporary - # variable e.g. `t0` and then evaluate the derivative - # f'(t0) symbolically at t0=1. See trac #12796. - temp_args = [SR.var("t%s"%i) for i in range(len(args))] + # variable e.g. `_symbol0` and then evaluate the + # derivative f'(_symbol0) symbolically at _symbol0=1. See + # trac #12796. Note that we cannot use SR.temp_var here + # since two conversions of the same expression have to be + # equal. + temp_args = [SR.symbol("_symbol%s"%i) for i in range(len(args))] f = operator.function()(*temp_args) params = operator.parameter_set() params = ["%s, %s"%(temp_args[i]._maxima_init_(), params.count(i)) for i in set(params)] @@ -945,6 +950,90 @@ def __init__(self): import sage.interfaces.fricas super(FriCASConverter, self).__init__(sage.interfaces.fricas.fricas) + def pyobject(self, ex, obj): + """ + Return a string which, when evaluated by FriCAS, returns the + object as an expression. + + We explicitly add the coercion to the FriCAS domains + `Expression Integer` and `Expression Complex Integer` to make + sure that elements of the symbolic ring are translated to + these. In particular, this is needed for integration, see + :trac:`28641` and :trac:`28647`. + + EXAMPLES:: + + sage: 2._fricas_().domainOf() # optional - fricas + PositiveInteger() + + sage: (-1/2)._fricas_().domainOf() # optional - fricas + Fraction(Integer()) + + sage: SR(2)._fricas_().domainOf() # optional - fricas + Expression(Integer()) + + sage: (sqrt(2))._fricas_().domainOf() # optional - fricas + Expression(Integer()) + + sage: pi._fricas_().domainOf() # optional - fricas + Pi() + + sage: asin(pi)._fricas_() # optional - fricas + asin(%pi) + + sage: I._fricas_().domainOf() # optional - fricas + Complex(Integer()) + + sage: SR(I)._fricas_().domainOf() # optional - fricas + Expression(Complex(Integer())) + + sage: ex = (I+sqrt(2)+2) + sage: ex._fricas_().domainOf() # optional - fricas + Expression(Complex(Integer())) + + sage: ex._fricas_()^2 # optional - fricas + +-+ + (4 + 2 %i)\|2 + 5 + 4 %i + + sage: (ex^2)._fricas_() # optional - fricas + +-+ + (4 + 2 %i)\|2 + 5 + 4 %i + + """ + try: + result = getattr(obj, self.name_init)() + if isinstance(obj, NumberFieldElement_gaussian): + return "((%s)::EXPR COMPLEX INT)" % result + except AttributeError: + result = repr(obj) + return "((%s)::EXPR INT)" % result + + def symbol(self, ex): + """ + Convert the argument, which is a symbol, to FriCAS. + + In this case, we do not return an `Expression Integer`, + because FriCAS frequently requires elements of domain + `Symbol` or `Variable` as arguments, for example to + `integrate`. Moreover, FriCAS is able to do the conversion + itself, whenever the argument should be interpreted as a + symbolic expression. + + EXAMPLES:: + + sage: x._fricas_().domainOf() # optional - fricas + Variable(x) + + sage: (x^2)._fricas_().domainOf() # optional - fricas + Expression(Integer()) + + sage: (2*x)._fricas_().integrate(x) # optional - fricas + 2 + x + + """ + return repr(ex) + def derivative(self, ex, operator): """ Convert the derivative of ``self`` in FriCAS. @@ -1002,9 +1091,12 @@ def derivative(self, ex, operator): # An evaluated derivative of the form f'(1) is not a # symbolic variable, yet we would like to treat it like # one. So, we replace the argument `1` with a temporary - # variable e.g. `t0` and then evaluate the derivative - # f'(t0) symbolically at t0=1. See trac #12796. - temp_args = [SR.var("t%s" % i) for i in range(len(args))] + # variable e.g. `_symbol0` and then evaluate the + # derivative f'(_symbol0) symbolically at _symbol0=1. See + # trac #12796. Note that we cannot use SR.temp_var here + # since two conversions of the same expression have to be + # equal. + temp_args = [SR.symbol("_symbol%s" % i) for i in range(len(args))] f = operator.function()(*temp_args) vars = ",".join(temp_args[i]._fricas_init_() for i in params_set) subs = ",".join("%s = %s" % (t._fricas_init_(), a._fricas_init_()) @@ -2095,6 +2187,7 @@ def __init__(self, ex): sage: s = ExpressionTreeWalker(ex) sage: bool(s() == ex) True + sage: set_random_seed(0) # random_expr is unstable sage: foo = random_expr(20, nvars=2) sage: s = ExpressionTreeWalker(foo) sage: bool(s() == foo) diff --git a/src/sage/symbolic/function_factory.py b/src/sage/symbolic/function_factory.py index 68ac5e0c3b0..57534da2320 100644 --- a/src/sage/symbolic/function_factory.py +++ b/src/sage/symbolic/function_factory.py @@ -63,7 +63,7 @@ def _maxima_init_(self): sage: f._maxima_init_() "'f" """ - return "'%s"%self.name() + return "'%s" % self.name() def _fricas_init_(self): """ @@ -78,7 +78,7 @@ def _fricas_init_(self): sage: f._fricas_init_() 'operator("f")' """ - return 'operator("%s")'%self.name() + return 'operator("%s")' % self.name() def _sympy_(self): from sympy import Function @@ -104,7 +104,7 @@ def __reduce__(self): if func: if not callable(func): raise ValueError(func_name + "_func" + " parameter must be callable") - setattr(NewSymbolicFunction, '_%s_'%func_name, func) + setattr(NewSymbolicFunction, '_%s_' % func_name, func) return NewSymbolicFunction() @@ -148,14 +148,12 @@ def unpickle_function(name, nargs, latex_name, conversions, evalf_params_first, return function_factory(*args) -def function(s, *args, **kwds): +def function(s, **kwds): r""" Create a formal symbolic function with the name *s*. INPUT: - - ``args`` - arguments to the function, if specified returns the new - function evaluated at the given arguments (deprecated as of :trac:`17447`) - ``nargs=0`` - number of arguments the function accepts, defaults to variable number of arguments, or 0 - ``latex_name`` - name used when printing in latex mode @@ -201,7 +199,7 @@ def function(s, *args, **kwds): sage: foo(x, y) + foo(y, z)^2 foo(y, z)^2 + foo(x, y) - In Sage 4.0, you need to use :meth:`substitute_function` to + You need to use :meth:`substitute_function` to replace all occurrences of a function with another:: sage: g.substitute_function(cr, cos) @@ -210,7 +208,7 @@ def function(s, *args, **kwds): sage: g.substitute_function(cr, (sin(x) + cos(x)).function(x)) b*(cos(a) - sin(a)) - In Sage 4.0, basic arithmetic with unevaluated functions is no + Basic arithmetic with unevaluated functions is no longer supported:: sage: x = var('x') @@ -339,7 +337,7 @@ def function(s, *args, **kwds): if not isinstance(s, str): raise TypeError("expect string as first argument") - # create the function + # create the function or functions if ',' in s: names = s.split(',') elif ' ' in s: @@ -348,18 +346,11 @@ def function(s, *args, **kwds): names = [s] names = [sn.strip() for sn in names if sn.strip()] - funcs = [function_factory(name, **kwds) for name in names] + funcs = tuple(function_factory(name, **kwds) for name in names) - if len(args) > 0: - from sage.misc.superseded import deprecation - deprecation(17447, "Calling function('f',x) is deprecated. Use function('f')(x) instead.") - res = [f(*args) for f in funcs] - else: - res = funcs - - if len(res) == 1: - return res[0] - return tuple(res) + if len(funcs) == 1: + return funcs[0] + return funcs def deprecated_custom_evalf_wrapper(func): diff --git a/src/sage/symbolic/integration/external.py b/src/sage/symbolic/integration/external.py index 76604397d57..5af3490f23b 100644 --- a/src/sage/symbolic/integration/external.py +++ b/src/sage/symbolic/integration/external.py @@ -156,6 +156,7 @@ def request_wolfram_alpha(input, verbose=False): 'error', 'host', 'id', + 'inputstring', 'numpods', 'parsetimedout', 'parsetiming', @@ -374,7 +375,7 @@ def fricas_integrator(expression, v, a=None, b=None, noPole=True): sage: fricas_integrator(cos(x), x) # optional - fricas sin(x) sage: fricas_integrator(1/(x^2-2), x, 0, 1) # optional - fricas - 1/4*sqrt(2)*(log(3*sqrt(2) - 4) - log(sqrt(2))) + -1/8*sqrt(2)*(log(2) - log(-24*sqrt(2) + 34)) sage: fricas_integrator(1/(x^2+6), x, -oo, oo) # optional - fricas 1/6*sqrt(6)*pi @@ -387,33 +388,46 @@ def fricas_integrator(expression, v, a=None, b=None, noPole=True): Check that in case of failure one gets unevaluated integral:: - sage: integral(cos(ln(cos(x))), x, 0, pi/8, algorithm='fricas') # optional - fricas + sage: integral(cos(ln(cos(x))), x, 0, pi/8, algorithm='fricas') # optional - fricas integrate(cos(log(cos(x))), x, 0, 1/8*pi) - sage: integral(cos(ln(cos(x))), x, algorithm='fricas') # optional - fricas + + sage: integral(cos(ln(cos(x))), x, algorithm='fricas') # optional - fricas integral(cos(log(cos(x))), x) + + Check that :trac:`28641` is fixed:: + + sage: integrate(sqrt(2)*x^2 + 2*x, x, algorithm="fricas") # optional - fricas + 1/3*sqrt(2)*x^3 + x^2 + + sage: integrate(sqrt(2), x, algorithm="fricas") # optional - fricas + sqrt(2)*x + + sage: integrate(1, x, algorithm="fricas") # optional - fricas + x """ if not isinstance(expression, Expression): expression = SR(expression) from sage.interfaces.fricas import fricas - ex = fricas(expression) + e_fricas = fricas(expression) + v_fricas = fricas(v) if a is None: - result = ex.integrate(v) + result = e_fricas.integrate(v_fricas) else: - seg = fricas.equation(v, fricas.segment(a, b)) + seg = fricas.equation(v_fricas, fricas.segment(a, b)) if noPole: - result = ex.integrate(seg, '"noPole"') + result = e_fricas.integrate(seg, '"noPole"') else: - result = ex.integrate(seg) + result = e_fricas.integrate(seg) result = result.sage() if result == "failed": - return expression.integrate(v, a, b, hold=True) + result = expression.integrate(v, a, b, hold=True) - if result == "potentialPole": + elif result == "potentialPole": raise ValueError("The integrand has a potential pole" " in the integration interval") @@ -421,7 +435,7 @@ def fricas_integrator(expression, v, a=None, b=None, noPole=True): def giac_integrator(expression, v, a=None, b=None): - """ + r""" Integration using Giac EXAMPLES:: @@ -436,6 +450,15 @@ def giac_integrator(expression, v, a=None, b=None): sage: giac_integrator(e^(-x^2)*log(x), x) integrate(e^(-x^2)*log(x), x) + + Check that :trac:`30133` is fixed:: + + sage: ee = SR.var('e') + sage: giac_integrator(ee^x, x) + e^x/log(e) + sage: y = SR.var('π') + sage: giac_integrator(cos(y), y) + sin(π) """ ex = expression._giac_() if a is None: diff --git a/src/sage/symbolic/integration/integral.py b/src/sage/symbolic/integration/integral.py index 46b1a11a82a..220f1aebef3 100644 --- a/src/sage/symbolic/integration/integral.py +++ b/src/sage/symbolic/integration/integral.py @@ -901,8 +901,11 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None, hold=False): 4 sage: f = sqrt(x + 1/x^2) - sage: integrate(f, x) - 1/3*(2*sqrt(x^3 + 1) - log(sqrt(x^3 + 1) + 1) + log(abs(sqrt(x^3 + 1) - 1)))*sgn(x) + sage: actual = integrate(f, x) + sage: expected = (1/3*(2*sqrt(x^3 + 1) - log(sqrt(x^3 + 1) + 1) + ....: + log(abs(sqrt(x^3 + 1) - 1)))*sgn(x)) + sage: bool(actual == expected) + True sage: g = abs(sin(x)*cos(x)) sage: g.integrate(x, 0, 2*pi) @@ -912,7 +915,7 @@ def integrate(expression, v=None, a=None, b=None, algorithm=None, hold=False): 2*sqrt(x*sgn(x))/sgn(x) sage: integrate(sgn(x) - sgn(1-x), x) - x*(sgn(x) - sgn(-x + 1)) + sgn(-x + 1) + abs(x - 1) + abs(x) sage: integrate(1 / (1 + abs(x-5)), x, -5, 6) log(11) + log(2) diff --git a/src/sage/symbolic/operators.py b/src/sage/symbolic/operators.py index 368d6f68f89..2fa839149dc 100644 --- a/src/sage/symbolic/operators.py +++ b/src/sage/symbolic/operators.py @@ -114,7 +114,7 @@ def __call__(self, *args): # temporary variable e.g. `t0` and then evaluate the # derivative f'(t0) symbolically at t0=1. See trac # #12796. - temp_args=[SR.var("t%s"%i) for i in range(len(args))] + temp_args=SR.temp_var(n=len(args)) vars=[temp_args[i] for i in self._parameter_set] return self._f(*temp_args).diff(*vars).function(*temp_args)(*args) vars = [args[i] for i in self._parameter_set] diff --git a/src/sage/symbolic/random_tests.py b/src/sage/symbolic/random_tests.py index 81ac9310f40..e4b9a1fa619 100644 --- a/src/sage/symbolic/random_tests.py +++ b/src/sage/symbolic/random_tests.py @@ -20,7 +20,7 @@ from sage.symbolic.constants import (pi, e, golden_ratio, log2, euler_gamma, catalan, khinchin, twinprime, mertens) from sage.functions.hypergeometric import hypergeometric -from sage.functions.other import cases +from sage.functions.other import (cases, element_of) from sage.symbolic.comparison import mixed_order ################################################################### @@ -48,13 +48,13 @@ def _mk_full_functions(): Note that this doctest will produce different output whenever a symbolic function is added or removed. """ + excluded = [hypergeometric, cases, element_of] items = sorted(symbol_table['functions'].items()) return [(1.0, f, f.number_of_arguments()) for (name, f) in items if hasattr(f, 'number_of_arguments') and f.number_of_arguments() > 0 and - f != hypergeometric and - f != cases] + f not in excluded] # For creating simple expressions @@ -141,16 +141,19 @@ def choose_from_prob_list(lst): sage: from sage.symbolic.random_tests import * sage: v = [(0.1, False), (0.9, True)] - sage: choose_from_prob_list(v) + sage: choose_from_prob_list(v) # random (0.900000000000000, True) sage: true_count = 0 - sage: for _ in range(10000): - ....: if choose_from_prob_list(v)[1]: - ....: true_count += 1 - sage: true_count - 9033 - sage: true_count - (10000 * 9/10) - 33 + sage: total_count = 0 + sage: def more_samples(): + ....: global true_count, total_count + ....: for _ in range(10000): + ....: total_count += 1.0 + ....: if choose_from_prob_list(v)[1]: + ....: true_count += 1.0 + sage: more_samples() + sage: while abs(true_count/total_count - 0.9) > 0.01: + ....: more_samples() """ r = random() for i in range(len(lst)-1): @@ -168,22 +171,39 @@ def random_integer_vector(n, length): That gives values uniformly at random, but might be slow; this routine is not uniform, but should always be fast. - (This routine is uniform if *length* is 1 or 2; for longer vectors, + (This routine is uniform if ``length`` is 1 or 2; for longer vectors, we prefer approximately balanced vectors, where all the values are around `n/{length}`.) EXAMPLES:: sage: from sage.symbolic.random_tests import * - sage: random_integer_vector(100, 2) + sage: a = random_integer_vector(100, 2); a # random [11, 89] - sage: random_integer_vector(100, 2) - [51, 49] - sage: random_integer_vector(100, 2) - [4, 96] - sage: random_integer_vector(10000, 20) - [332, 529, 185, 738, 82, 964, 596, 892, 732, 134, - 834, 765, 398, 608, 358, 300, 652, 249, 586, 66] + sage: len(a) + 2 + sage: sum(a) + 100 + + sage: b = random_integer_vector(10000, 20) + sage: len(b) + 20 + sage: sum(b) + 10000 + + The routine is uniform if ``length`` is 2:: + + sage: true_count = 0 + sage: total_count = 0 + sage: def more_samples(): + ....: global true_count, total_count + ....: for _ in range(1000): + ....: total_count += 1.0 + ....: if a == random_integer_vector(100, 2): + ....: true_count += 1.0 + sage: more_samples() + sage: while abs(true_count/total_count - 0.01) > 0.01: + ....: more_samples() """ if length == 0: return [] @@ -206,15 +226,26 @@ def random_expr_helper(n_nodes, internal, leaves, verbose): EXAMPLES:: sage: from sage.symbolic.random_tests import * - sage: random_expr_helper(9, [(0.5, operator.add, 2), + sage: a = random_expr_helper(9, [(0.5, operator.add, 2), ....: (0.5, operator.neg, 1)], [(0.5, 1), (0.5, x)], True) - About to apply to [1, x] - About to apply to [x, x + 1] - About to apply to [1] - About to apply to [-1] - About to apply to [1] - About to apply to [2*x + 1, -1] - 2*x + About to apply to [31] About to apply sgn to [v1] About to apply to [1/31, sgn(v1)] diff --git a/src/sage/symbolic/relation.py b/src/sage/symbolic/relation.py index 093086d2c53..d04af7b9079 100644 --- a/src/sage/symbolic/relation.py +++ b/src/sage/symbolic/relation.py @@ -1621,10 +1621,12 @@ def _solve_mod_prime_power(eqns, p, m, vars): pairs = cartesian_product_iterator([shifts, ans]) possibles = (tuple(vector(t)+vector(shift)*(mrunning//p)) for shift, t in pairs) ans = list(t for t in possibles if all(e(*t) == 0 for e in eqns_mod)) - if not ans: return ans + if not ans: + return ans return ans + def solve_ineq_univar(ineq): """ Function solves rational inequality in one variable. diff --git a/src/sage/symbolic/ring.pyx b/src/sage/symbolic/ring.pyx index 692266db00c..eb4c7c89911 100644 --- a/src/sage/symbolic/ring.pyx +++ b/src/sage/symbolic/ring.pyx @@ -254,6 +254,17 @@ cdef class SymbolicRing(CommutativeRing): sage: SR(complex(2,-3)) (2-3j) + Any proper subset of the complex numbers:: + + sage: SR(NN) + Non negative integer semiring + sage: SR(ZZ) + Integer Ring + sage: SR(Set([1/2, 2/3, 3/4])) + {3/4, 2/3, 1/2} + sage: SR(RealSet(0, 1)) + (0, 1) + TESTS:: sage: SR._coerce_(int(5)) @@ -364,6 +375,7 @@ cdef class SymbolicRing(CommutativeRing): from sage.rings.infinity import (infinity, minus_infinity, unsigned_infinity) from sage.structure.factorization import Factorization + from sage.categories.sets_cat import Sets if isinstance(x, RealNumber): if x.is_NaN(): @@ -392,6 +404,14 @@ cdef class SymbolicRing(CommutativeRing): elif isinstance(x, Factorization): from sage.misc.all import prod return prod([SR(p)**e for p,e in x], SR(x.unit())) + elif x in Sets(): + from sage.rings.all import NN, ZZ, QQ, AA + from sage.sets.real_set import RealSet + if (x.is_finite() or x in (NN, ZZ, QQ, AA) + or isinstance(x, RealSet)): + exp = x + else: + raise TypeError(f"unable to convert {x!r} to a symbolic expression") else: raise TypeError(f"unable to convert {x!r} to a symbolic expression") @@ -730,6 +750,7 @@ cdef class SymbolicRing(CommutativeRing): if name is None: # Check if we need a temporary anonymous new symbol symb = ginac_new_symbol() + name = symb.get_name().decode('ascii') if domain is not None: symb.set_domain(sage_domain_to_ginac_domain(domain)) else: @@ -741,14 +762,102 @@ cdef class SymbolicRing(CommutativeRing): ginac_domain = domain_complex symb = ginac_symbol(str_to_bytes(name), str_to_bytes(latex_name), ginac_domain) - self.symbols[name] = e e._gobj = GEx(symb) + self.symbols[name] = e if domain is not None: send_sage_domain_to_maxima(e, domain) return e + def temp_var(self, n=None, domain=None): + """ + Return one or multiple new unique symbolic variables as an element + of the symbolic ring. Use this instead of SR.var() if there is a + possibility of name clashes occuring. Call SR.cleanup_var() once + the variables are no longer needed or use a `with SR.temp_var() + as ...` construct. + + INPUT: + + - ``n`` -- (optional) positive integer; number of symbolic variables + + - ``domain`` -- (optional) specify the domain of the variable(s); + + EXAMPLES: + + Simple definition of a functional derivative:: + + sage: def functional_derivative(expr,f,x): + ....: with SR.temp_var() as a: + ....: return expr.subs({f(x):a}).diff(a).subs({a:f(x)}) + sage: f = function('f') + sage: a = var('a') + sage: functional_derivative(f(a)^2+a,f,a) + 2*f(a) + + Contrast this to a similar implementation using SR.var(), + which gives a wrong result in our example:: + + sage: def functional_derivative(expr,f,x): + ....: a = SR.var('a') + ....: return expr.subs({f(x):a}).diff(a).subs({a:f(x)}) + sage: f = function('f') + sage: a = var('a') + sage: functional_derivative(f(a)^2+a,f,a) + 2*f(a) + 1 + + TESTS: + + sage: x = SR.temp_var() + sage: y = SR.temp_var() + sage: bool(x == x) + True + sage: bool(x == y) + False + sage: bool(x.parent()(x._maxima_()) == x) + True + + """ + if (n == None): + return self.symbol(None, domain=domain) + return TemporaryVariables([self.temp_var(domain=domain) for i in range(n)]) + + def cleanup_var(self, symbol): + """ + Cleans up a variable, removing assumptions about the + variable and allowing for it to be garbage collected + + INPUT: + + - ``symbol`` -- a variable or a list of variables + + TESTS: + + sage: from sage.symbolic.assumptions import assumptions + sage: symbols_copy = SR.symbols.copy() + sage: assumptions_copy = assumptions().copy() + sage: x = SR.temp_var(domain='real') + sage: SR.cleanup_var(x) + sage: symbols_copy == SR.symbols + True + sage: assumptions_copy == assumptions() + True + """ + from sage.symbolic.assumptions import assumptions + if isinstance(symbol,list) or isinstance(symbol,tuple): + for s in symbol: + self.cleanup_var(s) + else: + try: + name = self._repr_element_(symbol) + del self.symbols[name] + except KeyError: + pass + for asm in assumptions(): + if asm.has(symbol): + asm.forget() + def var(self, name, latex_name=None, n=None, domain=None): """ Return a symbolic variable as an element of the symbolic ring. @@ -888,6 +997,15 @@ cdef class SymbolicRing(CommutativeRing): for s in names_list: if not isidentifier(s): raise ValueError(f'The name "{s}" is not a valid Python identifier.') + # warn on bad symbol names, but only once + # symbol... names are temporary variables created with + # SR.temp_var + # _symbol... names are used in the conversion of + # derivatives of symbolic functions to maxima and other + # external libraries + if self.symbols.get(s) is None and ((s.startswith('symbol') and s[6:].isdigit()) or (s.startswith('_symbol') and s[7:].isdigit())): + import warnings + warnings.warn(f'The name "{name}" may clash with names used internally in sagemath. It is recommended to choose a different name for your variable.') formatted_latex_name = None if latex_name is not None and n is None: @@ -1424,3 +1542,23 @@ def isidentifier(x): if x in KEYWORDS: return False return x.isidentifier() + +class TemporaryVariables(tuple): + """ + Instances of this class can be used with Python `with` to + automatically clean up after themselves. + """ + def __enter__(self): + return self + + def __exit__(self, *args): + """ + TESTS:: + + sage: symbols_copy = SR.symbols.copy() + sage: with SR.temp_var(n=2) as temp_vars: pass + sage: symbols_copy == SR.symbols + True + """ + SR.cleanup_var(self) + return False diff --git a/src/sage/symbolic/units.py b/src/sage/symbolic/units.py index eff5394e98d..86db0b94ce4 100644 --- a/src/sage/symbolic/units.py +++ b/src/sage/symbolic/units.py @@ -509,11 +509,13 @@ def evalunitdict(): # Format the table for easier use. # for k, v in unitdict.items(): - for a in v: unit_to_type[a] = k + for a in v: + unit_to_type[a] = k for w in unitdict: for j in unitdict[w]: - if isinstance(unitdict[w][j], tuple): unitdict[w][j] = unitdict[w][j][0] + if isinstance(unitdict[w][j], tuple): + unitdict[w][j] = unitdict[w][j][0] value_to_unit[w] = {b: a for a, b in unitdict[w].items()} diff --git a/src/sage/tensor/modules/free_module_morphism.py b/src/sage/tensor/modules/free_module_morphism.py index 558e00c9ff8..3451b2c0554 100644 --- a/src/sage/tensor/modules/free_module_morphism.py +++ b/src/sage/tensor/modules/free_module_morphism.py @@ -488,8 +488,6 @@ def __bool__(self): matrix_rep = next(iter(self._matrices.values())) return not matrix_rep.is_zero() - __nonzero__ = __bool__ - def _add_(self, other): r""" Homomorphism addition. diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index 570fabd251d..ef6a73e76a8 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -376,8 +376,6 @@ def __bool__(self): self._is_zero = True return False - __nonzero__ = __bool__ - ##### End of required methods for ModuleElement (beside arithmetic) ##### def _repr_(self): @@ -1506,7 +1504,8 @@ def del_other_comp(self, basis=None): [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring] """ - if basis is None: basis = self._fmodule._def_basis + if basis is None: + basis = self._fmodule._def_basis if basis not in self._components: raise ValueError("the components w.r.t. the {}".format(basis) + " have not been defined") diff --git a/src/sage/tests/books/computational-mathematics-with-sagemath/linalg_doctest.py b/src/sage/tests/books/computational-mathematics-with-sagemath/linalg_doctest.py index d67a33e7c4a..e3c78e66202 100644 --- a/src/sage/tests/books/computational-mathematics-with-sagemath/linalg_doctest.py +++ b/src/sage/tests/books/computational-mathematics-with-sagemath/linalg_doctest.py @@ -233,11 +233,11 @@ ....: [-1,-1,-1,-2,-2,-2,1,1,-1,2,2,2,2,2,-1,2,2,2,2,2]) sage: S,U,V = A.smith_form(); S,U,V ( - [ 0 -2 -1 -5 0] - [1 0 0 0 0] [ 1 0 0 0] [ 1 0 1 -1 -1] - [0 1 0 0 0] [ 0 0 1 0] [ 0 0 0 0 1] - [0 0 3 0 0] [-2 1 0 0] [-1 2 0 5 0] - [0 0 0 6 0], [ 0 0 -2 -1], [ 0 -1 0 -2 0] + [ 3 1 2 -1 0] + [1 0 0 0 0] [ 0 0 1 0] [ 0 0 0 0 1] + [0 1 0 0 0] [ 0 1 0 0] [ 1 1 1 1 -1] + [0 0 3 0 0] [ 1 -2 -4 1] [-3 -2 -3 -1 0] + [0 0 0 6 0], [ 0 0 4 -1], [ 1 0 0 -2 0] ) Sage example in ./linalg.tex, line 1674:: diff --git a/src/sage/tests/books/judson-abstract-algebra/galois-sage.py b/src/sage/tests/books/judson-abstract-algebra/galois-sage.py index ac15dad7a9d..6996d34012d 100644 --- a/src/sage/tests/books/judson-abstract-algebra/galois-sage.py +++ b/src/sage/tests/books/judson-abstract-algebra/galois-sage.py @@ -414,18 +414,18 @@ To: Number Field in c with defining polynomial x^8 + 28*x^4 + 2500 Defn: c4 |--> 2*c^2, None), - (Number Field in c5 with defining polynomial x^4 + 648, - Ring morphism: - From: Number Field in c5 with defining polynomial x^4 + 648 - To: Number Field in c with defining polynomial x^8 + 28*x^4 + 2500 - Defn: c5 |--> 1/80*c^5 + 79/40*c, - None), - (Number Field in c6 with defining polynomial x^4 + 8, + (Number Field in c5 with defining polynomial x^4 + 8, Ring morphism: - From: Number Field in c6 with defining polynomial x^4 + 8 + From: Number Field in c5 with defining polynomial x^4 + 8 To: Number Field in c with defining polynomial x^8 + 28*x^4 + 2500 - Defn: c6 |--> -1/80*c^5 + 1/40*c, + Defn: c5 |--> -1/80*c^5 + 1/40*c, None), + (Number Field in c6 with defining polynomial x^4 + 648, + Ring morphism: + From: Number Field in c6 with defining polynomial x^4 + 648 + To: Number Field in c with defining polynomial x^8 + 28*x^4 + 2500 + Defn: c6 |--> 1/80*c^5 + 79/40*c, + None), (Number Field in c7 with defining polynomial x^4 - 512, Ring morphism: From: Number Field in c7 with defining polynomial x^4 - 512 diff --git a/src/sage/tests/gap_packages.py b/src/sage/tests/gap_packages.py index cf9a68cec25..2e4518ca226 100644 --- a/src/sage/tests/gap_packages.py +++ b/src/sage/tests/gap_packages.py @@ -6,7 +6,6 @@ sage: from sage.tests.gap_packages import all_installed_packages, test_packages sage: pkgs = all_installed_packages(ignore_dot_gap=True) sage: test_packages(pkgs, only_failures=True) # optional - gap_packages - ... Status Package GAP Output +--------+---------+------------+ @@ -55,36 +54,6 @@ def test_packages(packages, only_failures=False): +---------+------------+------------+ Alnuth true GAPDoc true - HAPcryst true - Hap true - QPA true - aclib true - atlasrep true - autpgrp true - cohomolo true - corelg true - crime true - cryst true - crystcat true - ctbllib true - design true - factint true - gbnp true - grape true - guava true - happrime true - hecke true - laguna true - liealgdb true - liepring true - liering true - loops true - mapclass true - polycyclic true - polymaking true - quagroup true - repsn true - sla true sonata true tomlib true toric true diff --git a/src/sage/tests/lazy_imports.py b/src/sage/tests/lazy_imports.py new file mode 100644 index 00000000000..83bbdfe7b50 --- /dev/null +++ b/src/sage/tests/lazy_imports.py @@ -0,0 +1,26 @@ +r""" +TESTS: + +Check that all non deprecated lazy imports resolve correctly. We avoid libgiac +on purpose because it does print stuff, see :trac:`31655`.:: + + sage: from sage.misc.lazy_import import LazyImport + sage: G = globals() + sage: for name, obj in sorted(G.items()): + ....: if name == 'libgiac': + ....: continue + ....: if type(obj) is LazyImport and obj._get_deprecation_ticket() == 0: + ....: try: + ....: _ = obj._get_object() + ....: except Exception as e: + ....: print('{} does not resolve: {}'.format(name, e)) + +Check that all deprecated lazy imports resolve correctly:: + + sage: import warnings + sage: for name, obj in sorted(G.items()): + ....: if type(obj) is LazyImport and obj._get_deprecation_ticket() != 0: + ....: with warnings.catch_warnings(record=True) as w: + ....: _ = obj._get_object() + ....: assert w[0].category == DeprecationWarning +""" diff --git a/src/sage/tests/parigp.py b/src/sage/tests/parigp.py index c118b6eb374..4692b613de4 100644 --- a/src/sage/tests/parigp.py +++ b/src/sage/tests/parigp.py @@ -6,9 +6,7 @@ self-test :pari:`rnfkummer` but was modified such that the answer is canonical:: - sage: pari('addprimes([31438243, 238576291, 18775387483, 24217212463267, 1427657500359111961, 135564809928627323997297867381959])') - [31438243, 238576291, 18775387483, 24217212463267, 1427657500359111961, 135564809928627323997297867381959] - sage: pari('K = bnfinit(y^4-52*y^2+26,1); pol = rnfkummer(bnrinit(K,3,1),Mat(5)); L = rnfinit(K, pol); polredabs(polredbest(L.polabs))') # long time + sage: pari('K = bnfinit(y^4-52*y^2+26,1); pol = rnfkummer(bnrinit(K,3,1),Mat(5)); L = rnfinit(K, [pol, 10^6]); polredabs(polredbest(L.polabs))') # long time x^20 - 112*x^18 + 5108*x^16 - 123460*x^14 + 1724337*x^12 - 14266996*x^10 + 69192270*x^8 - 188583712*x^6 + 260329852*x^4 - 141461008*x^2 + 19860776 Check that :trac:`10195` (PARI bug 1153) has been fixed:: diff --git a/src/sage/topology/__init__.py b/src/sage/topology/__init__.py new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/src/sage/topology/__init__.py @@ -0,0 +1 @@ + diff --git a/src/sage/topology/all.py b/src/sage/topology/all.py new file mode 100644 index 00000000000..c4451e79f3c --- /dev/null +++ b/src/sage/topology/all.py @@ -0,0 +1,16 @@ +from .simplicial_complex import SimplicialComplex, Simplex + +from .simplicial_complex_morphism import SimplicialComplexMorphism + +from .delta_complex import DeltaComplex, delta_complexes + +from .cubical_complex import CubicalComplex, cubical_complexes + +from sage.misc.lazy_import import lazy_import +lazy_import('sage.topology', 'simplicial_complex_catalog', 'simplicial_complexes') +lazy_import('sage.topology', 'simplicial_set_catalog', 'simplicial_sets') + + +# # For taking care of old pickles +# from sage.misc.persist import register_unpickle_override +# register_unpickle_override('sage.topology.simplicial_complex_examples', 'SimplicialSurface', SimplicialComplex) diff --git a/src/sage/topology/cell_complex.py b/src/sage/topology/cell_complex.py new file mode 100644 index 00000000000..3a518cf585e --- /dev/null +++ b/src/sage/topology/cell_complex.py @@ -0,0 +1,1230 @@ +# -*- coding: utf-8 -*- +r""" +Generic cell complexes + +AUTHORS: + +- John H. Palmieri (2009-08) + +This module defines a class of abstract finite cell complexes. This +is meant as a base class from which other classes (like +:class:`~sage.homology.simplicial_complex.SimplicialComplex`, +:class:`~sage.homology.cubical_complex.CubicalComplex`, and +:class:`~sage.homology.delta_complex.DeltaComplex`) should derive. As +such, most of its properties are not implemented. It is meant for use +by developers producing new classes, not casual users. + +.. NOTE:: + + Keywords for :meth:`~GenericCellComplex.chain_complex`, + :meth:`~GenericCellComplex.homology`, etc.: any keywords given to + the :meth:`~GenericCellComplex.homology` method get passed on to + the :meth:`~GenericCellComplex.chain_complex` method and also to + the constructor for chain complexes in + :class:`sage.homology.chain_complex.ChainComplex_class `, + as well as its associated + :meth:`~sage.homology.chain_complex.ChainComplex_class.homology` method. + This means that those keywords should have consistent meaning in + all of those situations. It also means that it is easy to + implement new keywords: for example, if you implement a new + keyword for the + :meth:`sage.homology.chain_complex.ChainComplex_class.homology` method, + then it will be automatically accessible through the + :meth:`~GenericCellComplex.homology` method for cell complexes -- + just make sure it gets documented. +""" + +######################################################################## +# Copyright (C) 2009 John H. Palmieri +# +# 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 sage.structure.sage_object import SageObject +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.misc.abstract_method import abstract_method +from sage.homology.chains import Chains, Cochains + + +class GenericCellComplex(SageObject): + r""" + Class of abstract cell complexes. + + This is meant to be used by developers to produce new classes, not + by casual users. Classes which derive from this are + :class:`~sage.homology.simplicial_complex.SimplicialComplex`, + :class:`~sage.homology.delta_complex.DeltaComplex`, and + :class:`~sage.homology.cubical_complex.CubicalComplex`. + + Most of the methods here are not implemented, but probably should + be implemented in a derived class. Most of the other methods call + a non-implemented one; their docstrings contain examples from + derived classes in which the various methods have been defined. + For example, :meth:`homology` calls :meth:`chain_complex`; the + class :class:`~sage.homology.delta_complex.DeltaComplex` + implements + :meth:`~sage.homology.delta_complex.DeltaComplex.chain_complex`, + and so the :meth:`homology` method here is illustrated with + examples involving `\Delta`-complexes. + + EXAMPLES: + + It's hard to give informative examples of the base class, since + essentially nothing is implemented. :: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex() + """ + def __eq__(self,right): + """ + Comparisons of cell complexes are not implemented. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex(); B = GenericCellComplex() + sage: A == B # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError + """ + raise NotImplementedError + + def __ne__(self,right): + """ + Comparisons of cell complexes are not implemented. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex(); B = GenericCellComplex() + sage: A != B # indirect doctest + Traceback (most recent call last): + ... + NotImplementedError + """ + raise NotImplementedError + + ############################################################ + # self.cells() and related methods + ############################################################ + + @abstract_method + def cells(self, subcomplex=None): + """ + The cells of this cell complex, in the form of a dictionary: + the keys are integers, representing dimension, and the value + associated to an integer `d` is the set of `d`-cells. If the + optional argument ``subcomplex`` is present, then return only + the cells which are *not* in the subcomplex. + + :param subcomplex: a subcomplex of this cell complex. Return + the cells which are not in this subcomplex. + :type subcomplex: optional, default None + + This is not implemented in general; it should be implemented + in any derived class. When implementing, see the warning in + the :meth:`dimension` method. + + This method is used by various other methods, such as + :meth:`n_cells` and :meth:`f_vector`. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex() + sage: A.cells() + Traceback (most recent call last): + ... + NotImplementedError: + """ + + def dimension(self): + """ + The dimension of this cell complex: the maximum + dimension of its cells. + + .. WARNING:: + + If the :meth:`cells` method calls :meth:`dimension`, + then you'll get an infinite loop. So either don't use + :meth:`dimension` or override :meth:`dimension`. + + EXAMPLES:: + + sage: simplicial_complexes.RandomComplex(d=5, n=8).dimension() + 5 + sage: delta_complexes.Sphere(3).dimension() + 3 + sage: T = cubical_complexes.Torus() + sage: T.product(T).dimension() + 4 + """ + try: + return max([x.dimension() for x in self._facets]) + except AttributeError: + if len(self.cells()) == 0: + # The empty cell complex has dimension -1. + return -1 + return max(self.cells()) + + def n_cells(self, n, subcomplex=None): + """ + List of cells of dimension ``n`` of this cell complex. + If the optional argument ``subcomplex`` is present, then + return the ``n``-dimensional cells which are *not* in the + subcomplex. + + :param n: the dimension + :type n: non-negative integer + :param subcomplex: a subcomplex of this cell complex. Return + the cells which are not in this subcomplex. + :type subcomplex: optional, default ``None`` + + .. NOTE:: + + The resulting list need not be sorted. If you want a sorted + list of `n`-cells, use :meth:`_n_cells_sorted`. + + EXAMPLES:: + + sage: delta_complexes.Torus().n_cells(1) + [(0, 0), (0, 0), (0, 0)] + sage: cubical_complexes.Cube(1).n_cells(0) + [[1,1], [0,0]] + """ + if n in self.cells(subcomplex): + return list(self.cells(subcomplex)[n]) + else: + # don't barf if someone asks for n_cells in a dimension where there are none + return [] + + def _n_cells_sorted(self, n, subcomplex=None): + """ + Sorted list of cells of dimension ``n`` of this cell complex. + If the optional argument ``subcomplex`` is present, then + return the ``n``-dimensional cells which are *not* in the + subcomplex. + + :param n: the dimension + :type n: non-negative integer + :param subcomplex: a subcomplex of this cell complex. Return + the cells which are not in this subcomplex. + :type subcomplex: optional, default ``None`` + + EXAMPLES:: + + sage: S = Set(range(1,5)) + sage: Z = SimplicialComplex(S.subsets()) + sage: Z + Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)} + sage: Z._n_cells_sorted(2) + [(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)] + sage: K = SimplicialComplex([[1,2,3], [2,3,4]]) + sage: Z._n_cells_sorted(2, subcomplex=K) + [(1, 2, 4), (1, 3, 4)] + sage: S = SimplicialComplex([[complex(i), complex(1)]]) + sage: S._n_cells_sorted(0) + [((1+0j),), (1j,)] + """ + n_cells = self.n_cells(n, subcomplex) + try: + return sorted(n_cells) + except TypeError: + return sorted(n_cells, key=str) + + def f_vector(self): + """ + The `f`-vector of this cell complex: a list whose `n^{th}` + item is the number of `(n-1)`-cells. Note that, like all + lists in Sage, this is indexed starting at 0: the 0th element + in this list is the number of `(-1)`-cells (which is 1: the + empty cell is the only `(-1)`-cell). + + EXAMPLES:: + + sage: simplicial_complexes.KleinBottle().f_vector() + [1, 8, 24, 16] + sage: delta_complexes.KleinBottle().f_vector() + [1, 1, 3, 2] + sage: cubical_complexes.KleinBottle().f_vector() + [1, 42, 84, 42] + """ + return [self._f_dict()[n] for n in range(-1, self.dimension()+1)] + + def _f_dict(self): + """ + The `f`-vector of this cell complex as a dictionary: the + item associated to an integer `n` is the number of the + `n`-cells. + + EXAMPLES:: + + sage: simplicial_complexes.KleinBottle()._f_dict()[1] + 24 + sage: delta_complexes.KleinBottle()._f_dict()[1] + 3 + """ + answer = {} + answer[-1] = 1 + for n in range(self.dimension() + 1): + answer[n] = len(self.cells()[n]) + return answer + + def euler_characteristic(self): + r""" + The Euler characteristic of this cell complex: the + alternating sum over `n \geq 0` of the number of + `n`-cells. + + EXAMPLES:: + + sage: simplicial_complexes.Simplex(5).euler_characteristic() + 1 + sage: delta_complexes.Sphere(6).euler_characteristic() + 2 + sage: cubical_complexes.KleinBottle().euler_characteristic() + 0 + """ + return sum([(-1)**n * self.f_vector()[n+1] for n in range(self.dimension() + 1)]) + + ############################################################ + # end of methods using self.cells() + ############################################################ + + @abstract_method + def product(self, right, rename_vertices=True): + """ + The (Cartesian) product of this cell complex with another one. + + Products are not implemented for general cell complexes. They + may be implemented in some derived classes (like simplicial + complexes). + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex(); B = GenericCellComplex() + sage: A.product(B) + Traceback (most recent call last): + ... + NotImplementedError: + """ + + @abstract_method + def disjoint_union(self, right): + """ + The disjoint union of this cell complex with another one. + + :param right: the other cell complex (the right-hand factor) + + Disjoint unions are not implemented for general cell complexes. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex(); B = GenericCellComplex() + sage: A.disjoint_union(B) + Traceback (most recent call last): + ... + NotImplementedError: + """ + + @abstract_method + def wedge(self, right): + """ + The wedge (one-point union) of this cell complex with + another one. + + :param right: the other cell complex (the right-hand factor) + + Wedges are not implemented for general cell complexes. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex(); B = GenericCellComplex() + sage: A.wedge(B) + Traceback (most recent call last): + ... + NotImplementedError: + """ + + ############################################################ + # self.join() and related methods + ############################################################ + + @abstract_method + def join(self, right): + """ + The join of this cell complex with another one. + + :param right: the other cell complex (the right-hand factor) + + Joins are not implemented for general cell complexes. They + may be implemented in some derived classes (like simplicial + complexes). + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex(); B = GenericCellComplex() + sage: A.join(B) + Traceback (most recent call last): + ... + NotImplementedError: + """ + + # for some classes, you may want * to mean join: + ### + # __mul__ = join + + # the cone on X is the join of X with a point. See + # simplicial_complex.py for one implementation. + ### + # def cone(self): + # return self.join(POINT) + + # the suspension of X is the join of X with the 0-sphere (two + # points). See simplicial_complex.py for one implementation. + ### + # def suspension(self, n=1): + # """ + # The suspension of this cell complex. + # + # INPUT: + # + # - ``n`` - positive integer (optional, default 1): suspend this + # many times. + # """ + # raise NotImplementedError + + ############################################################ + # end of methods using self.join() + ############################################################ + + ############################################################ + # chain complexes, homology + ############################################################ + + @abstract_method + def chain_complex(self, subcomplex=None, augmented=False, + verbose=False, check=True, dimensions=None, + base_ring=ZZ, cochain=False): + """ + This is not implemented for general cell complexes. + + Some keywords to possibly implement in a derived class: + + - ``subcomplex`` -- a subcomplex: compute the relative chain complex + - ``augmented`` -- a bool: whether to return the augmented complex + - ``verbose`` -- a bool: whether to print informational messages as + the chain complex is being computed + - ``check`` -- a bool: whether to check that the each + composite of two consecutive differentials is zero + - ``dimensions`` -- if ``None``, compute the chain complex in all + dimensions. If a list or tuple of integers, compute the + chain complex in those dimensions, setting the chain groups + in all other dimensions to zero. + + Definitely implement the following: + + - ``base_ring`` -- commutative ring (optional, default ZZ) + - ``cochain`` -- a bool: whether to return the cochain complex + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex() + sage: A.chain_complex() + Traceback (most recent call last): + ... + NotImplementedError: + """ + + def homology(self, dim=None, base_ring=ZZ, subcomplex=None, + generators=False, cohomology=False, algorithm='pari', + verbose=False, reduced=True, **kwds): + r""" + The (reduced) homology of this cell complex. + + :param dim: If None, then return the homology in every + dimension. If ``dim`` is an integer or list, return the + homology in the given dimensions. (Actually, if ``dim`` is + a list, return the homology in the range from ``min(dim)`` + to ``max(dim)``.) + :type dim: integer or list of integers or None; optional, + default None + :param base_ring: commutative ring, must be ZZ or a field. + :type base_ring: optional, default ZZ + :param subcomplex: a subcomplex of this simplicial complex. + Compute homology relative to this subcomplex. + :type subcomplex: optional, default empty + :param generators: If ``True``, return generators for the homology + groups along with the groups. NOTE: Since :trac:`6100`, the result + may not be what you expect when not using CHomP since its return + is in terms of the chain complex. + :type generators: boolean; optional, default False + :param cohomology: If True, compute cohomology rather than homology. + :type cohomology: boolean; optional, default False + :param algorithm: The options are 'auto', 'dhsw', 'pari' or 'no_chomp'. + See below for a description of what they mean. + :type algorithm: string; optional, default 'pari' + :param verbose: If True, print some messages as the homology is + computed. + :type verbose: boolean; optional, default False + :param reduced: If ``True``, return the reduced homology. + :type reduced: boolean; optional, default ``True`` + + ALGORITHM: + + If ``algorithm`` is set to 'auto', then use + CHomP if available. (CHomP is available at the web page + http://chomp.rutgers.edu/. It is also an optional package + for Sage.) + + CHomP computes homology, not cohomology, and only works over + the integers or finite prime fields. Therefore if any of + these conditions fails, or if CHomP is not present, or if + ``algorithm`` is set to 'no_chomp', go to plan B: if this complex + has a ``_homology`` method -- each simplicial complex has + this, for example -- then call that. Such a method implements + specialized algorithms for the particular type of cell + complex. + + Otherwise, move on to plan C: compute the chain complex of + this complex and compute its homology groups. To do this: over a + field, just compute ranks and nullities, thus obtaining + dimensions of the homology groups as vector spaces. Over the + integers, compute Smith normal form of the boundary matrices + defining the chain complex according to the value of + ``algorithm``. If ``algorithm`` is 'auto' or 'no_chomp', then + for each relatively small matrix, use the standard Sage + method, which calls the Pari package. For any large matrix, + reduce it using the Dumas, Heckenbach, Saunders, and Welker + elimination algorithm: see + :func:`sage.homology.matrix_utils.dhsw_snf` for details. + + Finally, ``algorithm`` may also be 'pari' or 'dhsw', which + forces the named algorithm to be used regardless of the size + of the matrices and regardless of whether CHomP is available. + + As of this writing, ``'pari'`` is the fastest standard option. + The optional CHomP package may be better still. + + EXAMPLES:: + + sage: P = delta_complexes.RealProjectivePlane() + sage: P.homology() + {0: 0, 1: C2, 2: 0} + sage: P.homology(reduced=False) + {0: Z, 1: C2, 2: 0} + sage: P.homology(base_ring=GF(2)) + {0: Vector space of dimension 0 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: S7 = delta_complexes.Sphere(7) + sage: S7.homology(7) + Z + sage: cubical_complexes.KleinBottle().homology(1, base_ring=GF(2)) + Vector space of dimension 2 over Finite Field of size 2 + + Sage can compute generators of homology groups:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: S2.homology(dim=2, generators=True, base_ring=GF(2), algorithm='no_chomp') + [(Vector space of dimension 1 over Finite Field of size 2, (0, 1, 2) + (0, 1, 3) + (0, 2, 3) + (1, 2, 3))] + + Note: the answer may be formatted differently if the optional + package CHomP is installed. + + When generators are computed, Sage returns a pair for each + dimension: the group and the list of generators. For + simplicial complexes, each generator is represented as a + linear combination of simplices, as above, and for cubical + complexes, each generator is a linear combination of cubes:: + + sage: S2_cub = cubical_complexes.Sphere(2) + sage: S2_cub.homology(dim=2, generators=True, algorithm='no_chomp') + [(Z, + [0,0] x [0,1] x [0,1] - [0,1] x [0,0] x [0,1] + [0,1] x [0,1] x [0,0] - [0,1] x [0,1] x [1,1] + [0,1] x [1,1] x [0,1] - [1,1] x [0,1] x [0,1])] + + Similarly for simpicial sets:: + + sage: S = simplicial_sets.Sphere(2) + sage: S.homology(generators=True) + {0: [], 1: 0, 2: [(Z, sigma_2)]} + """ + from sage.interfaces.chomp import have_chomp, homcubes, homsimpl + from sage.topology.cubical_complex import CubicalComplex + from sage.topology.simplicial_complex import SimplicialComplex + from sage.modules.all import VectorSpace + from sage.homology.homology_group import HomologyGroup + + if dim is not None: + if isinstance(dim, (list, tuple, range)): + low = min(dim) - 1 + high = max(dim) + 2 + else: + low = dim - 1 + high = dim + 2 + dims = range(low, high) + else: + dims = None + + # try to use CHomP if computing homology (not cohomology) and + # working over Z or F_p, p a prime. + if (algorithm == 'auto' and cohomology is False + and (base_ring == ZZ or (base_ring.is_prime_field() + and base_ring != QQ))): + # homcubes, homsimpl seems fastest if all of homology is computed. + H = None + if isinstance(self, CubicalComplex): + if have_chomp('homcubes'): + H = homcubes(self, subcomplex, base_ring=base_ring, + verbose=verbose, generators=generators) + elif isinstance(self, SimplicialComplex): + if have_chomp('homsimpl'): + H = homsimpl(self, subcomplex, base_ring=base_ring, + verbose=verbose, generators=generators) + + # now pick off the requested dimensions + if H: + answer = {} + if not dims: + dims = range(self.dimension() + 1) + for d in dims: + answer[d] = H.get(d, HomologyGroup(0, base_ring)) + if dim is not None: + if not isinstance(dim, (list, tuple, range)): + answer = answer.get(dim, HomologyGroup(0, base_ring)) + return answer + + # Derived classes can implement specialized algorithms using a + # _homology_ method. See SimplicialComplex for one example. + # Those may allow for other arguments, so we pass **kwds. + if hasattr(self, '_homology_'): + return self._homology_(dim, subcomplex=subcomplex, + cohomology=cohomology, base_ring=base_ring, + verbose=verbose, algorithm=algorithm, + reduced=reduced, generators=generators, + **kwds) + + C = self.chain_complex(cochain=cohomology, augmented=reduced, + dimensions=dims, subcomplex=subcomplex, + base_ring=base_ring, verbose=verbose) + answer = C.homology(base_ring=base_ring, generators=generators, + verbose=verbose, algorithm=algorithm) + + if generators: + # Try to convert chain complex information to topological + # chain information. + for i in answer: + H_with_gens = answer[i] + if H_with_gens: + chains = self.n_chains(i, base_ring=base_ring) + new_H = [] + for (H, gen) in H_with_gens: + v = gen.vector(i) + new_gen = chains.zero() + for (coeff, chain) in zip(v, chains.gens()): + new_gen += coeff * chain + new_H.append((H, new_gen)) + answer[i] = new_H + + if dim is None: + dim = range(self.dimension() + 1) + zero = HomologyGroup(0, base_ring) + if isinstance(dim, (list, tuple, range)): + return dict([d, answer.get(d, zero)] for d in dim) + return answer.get(dim, zero) + + def cohomology(self, dim=None, base_ring=ZZ, subcomplex=None, + generators=False, algorithm='pari', + verbose=False, reduced=True): + r""" + The reduced cohomology of this cell complex. + + The arguments are the same as for the :meth:`homology` method, + except that :meth:`homology` accepts a ``cohomology`` key + word, while this function does not: ``cohomology`` is + automatically true here. Indeed, this function just calls + :meth:`homology` with ``cohomology`` set to ``True``. + + :param dim: + :param base_ring: + :param subcomplex: + :param algorithm: + :param verbose: + :param reduced: + + EXAMPLES:: + + sage: circle = SimplicialComplex([[0,1], [1,2], [0, 2]]) + sage: circle.cohomology(0) + 0 + sage: circle.cohomology(1) + Z + sage: P2 = SimplicialComplex([[0,1,2], [0,2,3], [0,1,5], [0,4,5], [0,3,4], [1,2,4], [1,3,4], [1,3,5], [2,3,5], [2,4,5]]) # projective plane + sage: P2.cohomology(2) + C2 + sage: P2.cohomology(2, base_ring=GF(2)) + Vector space of dimension 1 over Finite Field of size 2 + sage: P2.cohomology(2, base_ring=GF(3)) + Vector space of dimension 0 over Finite Field of size 3 + + sage: cubical_complexes.KleinBottle().cohomology(2) + C2 + + Relative cohomology:: + + sage: T = SimplicialComplex([[0,1]]) + sage: U = SimplicialComplex([[0], [1]]) + sage: T.cohomology(1, subcomplex=U) + Z + + A `\Delta`-complex example:: + + sage: s5 = delta_complexes.Sphere(5) + sage: s5.cohomology(base_ring=GF(7))[5] + Vector space of dimension 1 over Finite Field of size 7 + """ + return self.homology(dim=dim, cohomology=True, base_ring=base_ring, + subcomplex=subcomplex, generators=generators, + algorithm=algorithm, verbose=verbose, + reduced=reduced) + + def betti(self, dim=None, subcomplex=None): + r""" + The Betti numbers of this simplicial complex as a dictionary + (or a single Betti number, if only one dimension is given): + the ith Betti number is the rank of the ith homology group. + + :param dim: If ``None``, then return every Betti number, as + a dictionary with keys the non-negative integers. If + ``dim`` is an integer or list, return the Betti number for + each given dimension. (Actually, if ``dim`` is a list, + return the Betti numbers, as a dictionary, in the range + from ``min(dim)`` to ``max(dim)``. If ``dim`` is a number, + return the Betti number in that dimension.) + :type dim: integer or list of integers or ``None``; optional, + default ``None`` + :param subcomplex: a subcomplex of this cell complex. Compute + the Betti numbers of the homology relative to this subcomplex. + :type subcomplex: optional, default ``None`` + + EXAMPLES: + + Build the two-sphere as a three-fold join of a + two-point space with itself:: + + sage: S = SimplicialComplex([[0], [1]]) + sage: (S*S*S).betti() + {0: 1, 1: 0, 2: 1} + sage: (S*S*S).betti([1,2]) + {1: 0, 2: 1} + sage: (S*S*S).betti(2) + 1 + + Or build the two-sphere as a `\Delta`-complex:: + + sage: S2 = delta_complexes.Sphere(2) + sage: S2.betti([1,2]) + {1: 0, 2: 1} + + Or as a cubical complex:: + + sage: S2c = cubical_complexes.Sphere(2) + sage: S2c.betti(2) + 1 + """ + dict = {} + H = self.homology(dim, base_ring=QQ, subcomplex=subcomplex) + try: + for n in H.keys(): + dict[n] = H[n].dimension() + if n == 0: + dict[n] += 1 + return dict + except AttributeError: + return H.dimension() + + def is_acyclic(self, base_ring=ZZ): + """ + True if the reduced homology with coefficients in ``base_ring`` of + this cell complex is zero. + + INPUT: + + - ``base_ring`` -- optional, default ``ZZ``. Compute homology + with coefficients in this ring. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: RP2.is_acyclic() + False + sage: RP2.is_acyclic(QQ) + True + + This first computes the Euler characteristic: if it is not 1, + the complex cannot be acyclic. So this should return ``False`` + reasonably quickly on complexes with Euler characteristic not + equal to 1:: + + sage: K = cubical_complexes.KleinBottle() + sage: C = cubical_complexes.Cube(2) + sage: P = K.product(C) + sage: P + Cubical complex with 168 vertices and 1512 cubes + sage: P.euler_characteristic() + 0 + sage: P.is_acyclic() + False + """ + if self.euler_characteristic() != 1: + return False + H = self.homology(base_ring=base_ring) + if base_ring == ZZ: + return all(len(x.invariants()) == 0 for x in H.values()) + else: + # base_ring is a field. + return all(x.dimension() == 0 for x in H.values()) + + def n_chains(self, n, base_ring=ZZ, cochains=False): + r""" + Return the free module of chains in degree ``n`` over ``base_ring``. + + INPUT: + + - ``n`` -- integer + - ``base_ring`` -- ring (optional, default `\ZZ`) + - ``cochains`` -- boolean (optional, default ``False``); if + ``True``, return cochains instead + + The only difference between chains and cochains is + notation. In a simplicial complex, for example, a simplex + ``(0,1,2)`` is written as "(0,1,2)" in the group of chains but + as "\chi_(0,1,2)" in the group of cochains. + + EXAMPLES:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: S2.n_chains(1, QQ) + Free module generated by {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)} over Rational Field + sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=False).basis()) + [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] + sage: list(simplicial_complexes.Sphere(2).n_chains(1, QQ, cochains=True).basis()) + [\chi_(0, 1), \chi_(0, 2), \chi_(0, 3), \chi_(1, 2), \chi_(1, 3), \chi_(2, 3)] + """ + n_cells = tuple(self._n_cells_sorted(n)) + if cochains: + return Cochains(self, n, n_cells, base_ring) + else: + return Chains(self, n, n_cells, base_ring) + + def algebraic_topological_model(self, base_ring=QQ): + r""" + Algebraic topological model for this cell complex with + coefficients in ``base_ring``. + + The term "algebraic topological model" is defined by Pilarczyk + and Réal [PR2015]_. + + This is not implemented for generic cell complexes. For any + classes deriving from this one, when this method is + implemented, it should essentially just call either + :func:`~sage.homology.algebraic_topological_model.algebraic_topological_model` + or + :func:`~sage.homology.algebraic_topological_model.algebraic_topological_model_delta_complex`. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex() + sage: A.algebraic_topological_model(QQ) + Traceback (most recent call last): + ... + NotImplementedError + """ + raise NotImplementedError + + def homology_with_basis(self, base_ring=QQ, cohomology=False): + r""" + Return the unreduced homology of this complex with + coefficients in ``base_ring`` with a chosen basis. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + + INPUT: + + - ``base_ring`` -- coefficient ring (optional, default + ``QQ``); must be a field + - ``cohomology`` -- boolean (optional, default ``False``); if + ``True``, return cohomology instead of homology + + Homology basis elements are named 'h_{dim,i}' where i ranges + between 0 and `r-1`, if `r` is the rank of the homology + group. Cohomology basis elements are denoted `h^{dim,i}` + instead. + + .. SEEALSO:: + + If ``cohomology`` is ``True``, this returns the cohomology + as a graded module. For the ring structure, use + :meth:`cohomology_ring`. + + EXAMPLES:: + + sage: K = simplicial_complexes.KleinBottle() + sage: H = K.homology_with_basis(QQ); H + Homology module of Minimal triangulation of the Klein bottle + over Rational Field + sage: sorted(H.basis(), key=str) + [h_{0,0}, h_{1,0}] + sage: H = K.homology_with_basis(GF(2)); H + Homology module of Minimal triangulation of the Klein bottle + over Finite Field of size 2 + sage: sorted(H.basis(), key=str) + [h_{0,0}, h_{1,0}, h_{1,1}, h_{2,0}] + + The homology is constructed as a graded object, so for + example, you can ask for the basis in a single degree:: + + sage: H.basis(1) + Finite family {(1, 0): h_{1,0}, (1, 1): h_{1,1}} + sage: S3 = delta_complexes.Sphere(3) + sage: H = S3.homology_with_basis(QQ, cohomology=True) + sage: list(H.basis(3)) + [h^{3,0}] + """ + from sage.homology.homology_vector_space_with_basis import HomologyVectorSpaceWithBasis + return HomologyVectorSpaceWithBasis(base_ring, self, cohomology) + + def cohomology_ring(self, base_ring=QQ): + r""" + Return the unreduced cohomology with coefficients in + ``base_ring`` with a chosen basis. + + This is implemented for simplicial, cubical, and + `\Delta`-complexes, not for arbitrary generic cell complexes. + The resulting elements are suitable for computing cup + products. For simplicial complexes, they should be suitable + for computing cohomology operations; so far, only mod 2 + cohomology operations have been implemented. + + INPUT: + + - ``base_ring`` -- coefficient ring (optional, default + ``QQ``); must be a field + + The basis elements in dimension ``dim`` are named 'h^{dim,i}' + where `i` ranges between 0 and `r-1`, if `r` is the rank of + the cohomology group. + + .. NOTE:: + + For all but the smallest complexes, this is likely to be + slower than :meth:`cohomology` (with field coefficients), + possibly by several orders of magnitude. This and its + companion :meth:`homology_with_basis` carry extra + information which allows computation of cup products, for + example, but because of speed issues, you may only wish to + use these if you need that extra information. + + EXAMPLES:: + + sage: K = simplicial_complexes.KleinBottle() + sage: H = K.cohomology_ring(QQ); H + Cohomology ring of Minimal triangulation of the Klein bottle + over Rational Field + sage: sorted(H.basis(), key=str) + [h^{0,0}, h^{1,0}] + sage: H = K.cohomology_ring(GF(2)); H + Cohomology ring of Minimal triangulation of the Klein bottle + over Finite Field of size 2 + sage: sorted(H.basis(), key=str) + [h^{0,0}, h^{1,0}, h^{1,1}, h^{2,0}] + + sage: X = delta_complexes.SurfaceOfGenus(2) + sage: H = X.cohomology_ring(QQ); H + Cohomology ring of Delta complex with 3 vertices and 29 simplices + over Rational Field + sage: sorted(H.basis(1), key=str) + [h^{1,0}, h^{1,1}, h^{1,2}, h^{1,3}] + + sage: H = simplicial_complexes.Torus().cohomology_ring(QQ); H + Cohomology ring of Minimal triangulation of the torus + over Rational Field + sage: x = H.basis()[1,0]; x + h^{1,0} + sage: y = H.basis()[1,1]; y + h^{1,1} + + You can compute cup products of cohomology classes:: + + sage: x.cup_product(y) + -h^{2,0} + sage: x * y # alternate notation + -h^{2,0} + sage: y.cup_product(x) + h^{2,0} + sage: x.cup_product(x) + 0 + + Cohomology operations:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: K = RP2.suspension() + sage: K.set_immutable() + sage: y = K.cohomology_ring(GF(2)).basis()[2,0]; y + h^{2,0} + sage: y.Sq(1) + h^{3,0} + + To compute the cohomology ring, the complex must be + "immutable". This is only relevant for simplicial complexes, + and most simplicial complexes are immutable, but certain + constructions make them mutable. The suspension is one + example, and this is the reason for calling + ``K.set_immutable()`` above. Another example:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: T = S1.product(S1) + sage: T.is_immutable() + False + sage: T.cohomology_ring() + Traceback (most recent call last): + ... + ValueError: This simplicial complex must be immutable. Call set_immutable(). + sage: T.set_immutable() + sage: T.cohomology_ring() + Cohomology ring of Simplicial complex with 9 vertices and + 18 facets over Rational Field + """ + from sage.homology.homology_vector_space_with_basis import CohomologyRing + return CohomologyRing(base_ring, self) + + @abstract_method + def alexander_whitney(self, cell, dim_left): + r""" + The decomposition of ``cell`` in this complex into left and right + factors, suitable for computing cup products. This should + provide a cellular approximation for the diagonal map `K \to K + \times K`. + + This method is not implemented for generic cell complexes, but + must be implemented for any derived class to make cup products + work in ``self.cohomology_ring()``. + + INPUT: + + - ``cell`` -- a cell in this complex + - ``dim_left`` -- the dimension of the left-hand factors in + the decomposition + + OUTPUT: a list containing triples ``(c, left, right)``. + ``left`` and ``right`` should be cells in this complex, and + ``c`` an integer. In the cellular approximation of the + diagonal map, the chain represented by ``cell`` should get + sent to the sum of terms `c (left \otimes right)` in the + tensor product `C(K) \otimes C(K)` of the chain complex for + this complex with itself. + + This gets used in the method + :meth:`~sage.homology.homology_vector_space_with_basis.CohomologyRing.product_on_basis` + for the class of cohomology rings. + + For simplicial and cubical complexes, the decomposition can be + done at the level of individual cells: see + :meth:`~sage.homology.simplicial_complex.Simplex.alexander_whitney` + and + :meth:`~sage.homology.cubical_complex.Cube.alexander_whitney`. Then + the method for simplicial complexes just calls the method for + individual simplices, and similarly for cubical complexes. For + `\Delta`-complexes and simplicial sets, the method is instead + defined at the level of the cell complex. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex() + sage: A.alexander_whitney(None, 2) + Traceback (most recent call last): + ... + NotImplementedError: + """ + + ############################################################ + # end of chain complexes, homology + ############################################################ + + def face_poset(self): + r""" + The face poset of this cell complex, the poset of + nonempty cells, ordered by inclusion. + + This uses the :meth:`cells` method, and also assumes that for + each cell ``f``, all of ``f.faces()``, ``tuple(f)``, and + ``f.dimension()`` make sense. (If this is not the case in + some derived class, as happens with `\Delta`-complexes, then + override this method.) + + EXAMPLES:: + + sage: P = SimplicialComplex([[0, 1], [1,2], [2,3]]).face_poset(); P + Finite poset containing 7 elements + sage: sorted(P.list()) + [(0,), (0, 1), (1,), (1, 2), (2,), (2, 3), (3,)] + + sage: S2 = cubical_complexes.Sphere(2) + sage: S2.face_poset() + Finite poset containing 26 elements + """ + from sage.combinat.posets.posets import Poset + from sage.misc.flatten import flatten + covers = {} + # The code for posets seems to work better if each cell is + # converted to a tuple. + all_cells = flatten([list(f) for f in self.cells().values()]) + + for C in all_cells: + if C.dimension() >= 0: + covers[tuple(C)] = [] + for C in all_cells: + for face in C.faces(): + if face.dimension() >= 0: + covers[tuple(face)].append(tuple(C)) + return Poset(covers) + + def graph(self): + """ + The 1-skeleton of this cell complex, as a graph. + + This is not implemented for general cell complexes. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex() + sage: A.graph() + Traceback (most recent call last): + ... + NotImplementedError + """ + raise NotImplementedError + + def is_connected(self): + """ + True if this cell complex is connected. + + EXAMPLES:: + + sage: V = SimplicialComplex([[0,1,2],[3]]) + sage: V + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(3,), (0, 1, 2)} + sage: V.is_connected() + False + sage: X = SimplicialComplex([[0,1,2]]) + sage: X.is_connected() + True + sage: U = simplicial_complexes.ChessboardComplex(3,3) + sage: U.is_connected() + True + sage: W = simplicial_complexes.Sphere(3) + sage: W.is_connected() + True + sage: S = SimplicialComplex([[0,1],[2,3]]) + sage: S.is_connected() + False + + sage: cubical_complexes.Sphere(0).is_connected() + False + sage: cubical_complexes.Sphere(2).is_connected() + True + """ + return self.graph().is_connected() + + @abstract_method + def n_skeleton(self, n): + """ + The `n`-skeleton of this cell complex: the cell + complex obtained by discarding all of the simplices in + dimensions larger than `n`. + + :param n: non-negative integer + + This is not implemented for general cell complexes. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: A = GenericCellComplex() + sage: A.n_skeleton(3) + Traceback (most recent call last): + ... + NotImplementedError: + """ + + def _string_constants(self): + """ + Tuple containing the name of the type of complex, and the + singular and plural of the name of the cells from which it is + built. This is used in constructing the string representation. + + :return: tuple of strings + + This returns ``('Cell', 'cell', 'cells')``, as in "Cell + complex", "1 cell", and "24 cells", but in other classes it + could be overridden, as for example with ``('Cubical', 'cube', + 'cubes')`` or ``('Delta', 'simplex', 'simplices')``. If for a + derived class, the basic form of the print representation is + acceptable, you can just modify these strings. + + EXAMPLES:: + + sage: from sage.topology.cell_complex import GenericCellComplex + sage: GenericCellComplex()._string_constants() + ('Cell', 'cell', 'cells') + sage: delta_complexes.Sphere(0)._string_constants() + ('Delta', 'simplex', 'simplices') + sage: cubical_complexes.Sphere(0)._string_constants() + ('Cubical', 'cube', 'cubes') + """ + return ('Cell', 'cell', 'cells') + + def _repr_(self): + """ + Print representation. + + :return: string + + EXAMPLES:: + + sage: delta_complexes.Sphere(7) # indirect doctest + Delta complex with 8 vertices and 257 simplices + sage: delta_complexes.Torus()._repr_() + 'Delta complex with 1 vertex and 7 simplices' + """ + vertices = len(self.n_cells(0)) + Name, cell_name, cells_name = self._string_constants() + if vertices != 1: + vertex_string = "with %s vertices" % vertices + else: + vertex_string = "with 1 vertex" + cells = 0 + for dim in self.cells(): + cells += len(self.cells()[dim]) + if cells != 1: + cells_string = " and %s %s" % (cells, cells_name) + else: + cells_string = " and 1 %s" % cell_name + return Name + " complex " + vertex_string + cells_string + + diff --git a/src/sage/topology/cubical_complex.py b/src/sage/topology/cubical_complex.py new file mode 100644 index 00000000000..2a0721bdd5e --- /dev/null +++ b/src/sage/topology/cubical_complex.py @@ -0,0 +1,1929 @@ +# -*- coding: utf-8 -*- +r""" +Finite cubical complexes + +AUTHORS: + +- John H. Palmieri (2009-08) + +This module implements the basic structure of finite cubical +complexes. For full mathematical details, see Kaczynski, Mischaikow, +and Mrozek [KMM2004]_, for example. + +Cubical complexes are topological spaces built from gluing together +cubes of various dimensions; the collection of cubes must be closed +under taking faces, just as with a simplicial complex. In this +context, a "cube" means a product of intervals of length 1 or length 0 +(degenerate intervals), with integer endpoints, and its faces are +obtained by using the nondegenerate intervals: if `C` is a cube -- a +product of degenerate and nondegenerate intervals -- and if `[i,i+1]` +is the `k`-th nondegenerate factor, then `C` has two faces indexed by +`k`: the cubes obtained by replacing `[i, i+1]` with `[i, i]` or +`[i+1, i+1]`. + +So to construct a space homeomorphic to a circle as a cubical complex, +we could take for example the four line segments in the plane from +`(0,2)` to `(0,3)` to `(1,3)` to `(1,2)` to `(0,2)`. In Sage, this is +done with the following command:: + + sage: S1 = CubicalComplex([([0,0], [2,3]), ([0,1], [3,3]), ([0,1], [2,2]), ([1,1], [2,3])]); S1 + Cubical complex with 4 vertices and 8 cubes + +The argument to ``CubicalComplex`` is a list of the maximal "cubes" in +the complex. Each "cube" can be an instance of the class ``Cube`` or +a list (or tuple) of "intervals", and an "interval" is a pair of +integers, of one of the two forms `[i, i]` or `[i, i+1]`. So the +cubical complex ``S1`` above has four maximal cubes:: + + sage: len(S1.maximal_cells()) + 4 + sage: sorted(S1.maximal_cells()) + [[0,0] x [2,3], [0,1] x [2,2], [0,1] x [3,3], [1,1] x [2,3]] + +The first of these, for instance, is the product of the degenerate +interval `[0,0]` with the unit interval `[2,3]`: this is the line +segment in the plane from `(0,2)` to `(0,3)`. We could form a +topologically equivalent space by inserting some degenerate simplices:: + + sage: S1.homology() + {0: 0, 1: Z} + sage: X = CubicalComplex([([0,0], [2,3], [2]), ([0,1], [3,3], [2]), ([0,1], [2,2], [2]), ([1,1], [2,3], [2])]) + sage: X.homology() + {0: 0, 1: Z} + +Topologically, the cubical complex ``X`` consists of four edges of a +square in `\RR^3`: the same unit square as ``S1``, but embedded in +`\RR^3` with `z`-coordinate equal to 2. Thus ``X`` is homeomorphic to +``S1`` (in fact, they're "cubically equivalent"), and this is +reflected in the fact that they have isomorphic homology groups. + +.. note:: + + This class derives from + :class:`~sage.homology.cell_complex.GenericCellComplex`, and so + inherits its methods. Some of those methods are not listed here; + see the :mod:`Generic Cell Complex ` + page instead. +""" + +from copy import copy +from .cell_complex import GenericCellComplex +from sage.structure.sage_object import SageObject +from sage.rings.integer import Integer +from sage.sets.set import Set +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.matrix.constructor import matrix +from sage.homology.chain_complex import ChainComplex +from sage.graphs.graph import Graph +from sage.misc.cachefunc import cached_method +from functools import total_ordering + + +@total_ordering +class Cube(SageObject): + r""" + Define a cube for use in constructing a cubical complex. + + "Elementary cubes" are products of intervals with integer + endpoints, each of which is either a unit interval or a degenerate + (length 0) interval; for example, + + .. MATH:: + + [0,1] \times [3,4] \times [2,2] \times [1,2] + + is a 3-dimensional cube (since one of the intervals is degenerate) + embedded in `\RR^4`. + + :param data: list or tuple of terms of the form ``(i,i+1)`` or + ``(i,i)`` or ``(i,)`` -- the last two are degenerate intervals. + :return: an elementary cube + + Each cube is stored in a standard form: a tuple of tuples, with a + nondegenerate interval ``[j,j]`` represented by ``(j,j)``, not + ``(j,)``. (This is so that for any interval ``I``, ``I[1]`` will + produce a value, not an ``IndexError``.) + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]); C + [1,2] x [5,5] x [6,7] x [-1,0] + sage: C.dimension() # number of nondegenerate intervals + 3 + sage: C.nondegenerate_intervals() # indices of these intervals + [0, 2, 3] + sage: C.face(1, upper=False) + [1,2] x [5,5] x [6,6] x [-1,0] + sage: C.face(1, upper=True) + [1,2] x [5,5] x [7,7] x [-1,0] + sage: Cube(()).dimension() # empty cube has dimension -1 + -1 + """ + def __init__(self, data): + """ + Define a cube for use in constructing a cubical complex. + + See ``Cube`` for more information. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]); C # indirect doctest + [1,2] x [5,5] x [6,7] x [-1,0] + sage: C == loads(dumps(C)) + True + """ + if isinstance(data, Cube): + data = tuple(data) + new_data = [] + nondegenerate = [] + i = 0 + for x in data: + if len(x) == 2: + try: + Integer(x[0]) + except TypeError: + raise ValueError("The interval %s is not of the correct form" % x) + if x[0] + 1 == x[1]: + nondegenerate.append(i) + elif x[0] != x[1]: + raise ValueError("The interval %s is not of the correct form" % x) + new_data.append(tuple(x)) + elif len(x) == 1: + y = tuple(x) + new_data.append(y+y) + elif len(x) != 1: + raise ValueError("The interval %s is not of the correct form" % x) + i += 1 + self.__tuple = tuple(new_data) + self.__nondegenerate = nondegenerate + + def tuple(self): + """ + The tuple attached to this cube. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) + sage: C.tuple() + ((1, 2), (5, 5), (6, 7), (-1, 0)) + """ + return self.__tuple + + def is_face(self, other): + """ + Return True iff this cube is a face of other. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C1 = Cube([[1,2], [5,], [6,7], [-1, 0]]) + sage: C2 = Cube([[1,2], [5,], [6,], [-1, 0]]) + sage: C1.is_face(C2) + False + sage: C1.is_face(C1) + True + sage: C2.is_face(C1) + True + """ + def is_subinterval(i1, i2): + return ((i1[0] == i2[0] and i1[1] == i2[1]) or + (i1[0] == i2[0] and i1[1] == i2[0]) or + (i1[0] == i2[1] and i1[1] == i2[1])) + + t = self.tuple() + u = other.tuple() + if len(t) == len(u): + # these must be equal for self to be a face of other + return all(is_subinterval(ti, ui) for ti, ui in zip(t, u)) + else: + return False + + def _translate(self, vec): + """ + Translate ``self`` by ``vec``. + + :param vec: anything which can be converted to a tuple of integers + :return: the translation of ``self`` by ``vec`` + :rtype: Cube + + If ``vec`` is shorter than the list of intervals forming the + cube, pad with zeroes, and similarly if the cube's defining + tuple is too short. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) + sage: C._translate((-12,)) + [-11,-10] x [5,5] x [6,7] x [-1,0] + sage: C._translate((0, 0, 0, 0, 0, 5)) + [1,2] x [5,5] x [6,7] x [-1,0] x [0,0] x [5,5] + """ + t = self.__tuple + embed = max(len(t), len(vec)) + t = t + ((0,0),) * (embed-len(t)) + vec = tuple(vec) + (0,) * (embed-len(vec)) + new = [] + for (a, b) in zip(t, vec): + new.append([a[0] + b, a[1] + b]) + return Cube(new) + + def __getitem__(self, n): + """ + Return the nth interval in this cube. + + :param n: an integer + :return: tuple representing the `n`-th interval in the cube. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) + sage: C[2] + (6, 7) + sage: C[1] + (5, 5) + """ + return self.__tuple[n] + + def __iter__(self): + """ + Iterator for the intervals of this cube. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) + sage: [x[0] for x in C] + [1, 5, 6, -1] + """ + return iter(self.__tuple) + + def __add__(self, other): + """ + Cube obtained by concatenating the underlying tuples of the + two arguments. + + :param other: another cube + :return: the product of ``self`` and ``other``, as a Cube + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [3,]]) + sage: D = Cube([[4], [0,1]]) + sage: C.product(D) + [1,2] x [3,3] x [4,4] x [0,1] + + You can also use ``__add__`` or ``+`` or ``__mul__`` or ``*``:: + + sage: D * C + [4,4] x [0,1] x [1,2] x [3,3] + sage: D + C * C + [4,4] x [0,1] x [1,2] x [3,3] x [1,2] x [3,3] + """ + return Cube(self.__tuple + other.__tuple) + + # the __add__ operation actually produces the product of the two cubes + __mul__ = __add__ + product = __add__ + + def nondegenerate_intervals(self): + """ + The list of indices of nondegenerate intervals of this cube. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) + sage: C.nondegenerate_intervals() + [0, 2, 3] + sage: C = Cube([[1,], [5,], [6,], [-1,]]) + sage: C.nondegenerate_intervals() + [] + """ + return self.__nondegenerate + + def dimension(self): + """ + The dimension of this cube: the number of its nondegenerate intervals. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]) + sage: C.dimension() + 3 + sage: C = Cube([[1,], [5,], [6,], [-1,]]) + sage: C.dimension() + 0 + sage: Cube([]).dimension() # empty cube has dimension -1 + -1 + """ + if len(self.__tuple) == 0: # empty cube + return -1 + return len(self.nondegenerate_intervals()) + + def face(self, n, upper=True): + """ + The nth primary face of this cube. + + :param n: an integer between 0 and one less than the dimension + of this cube + :param upper: if True, return the "upper" nth primary face; + otherwise, return the "lower" nth primary face. + :type upper: boolean; optional, default=True + :return: the cube obtained by replacing the nth non-degenerate + interval with either its upper or lower endpoint. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [5,], [6,7], [-1, 0]]); C + [1,2] x [5,5] x [6,7] x [-1,0] + sage: C.face(0) + [2,2] x [5,5] x [6,7] x [-1,0] + sage: C.face(0, upper=False) + [1,1] x [5,5] x [6,7] x [-1,0] + sage: C.face(1) + [1,2] x [5,5] x [7,7] x [-1,0] + sage: C.face(2, upper=False) + [1,2] x [5,5] x [6,7] x [-1,-1] + sage: C.face(3) + Traceback (most recent call last): + ... + ValueError: Can only compute the nth face if 0 <= n < dim. + """ + if n < 0 or n >= self.dimension(): + raise ValueError("Can only compute the nth face if 0 <= n < dim.") + idx = self.nondegenerate_intervals()[n] + t = self.__tuple + if upper: + new = t[idx][1] + else: + new = t[idx][0] + return Cube(t[0:idx] + ((new,new),) + t[idx+1:]) + + def faces(self): + """ + The list of faces (of codimension 1) of this cube. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [3,4]]) + sage: C.faces() + [[2,2] x [3,4], [1,2] x [4,4], [1,1] x [3,4], [1,2] x [3,3]] + """ + upper = [self.face(i,True) for i in range(self.dimension())] + lower = [self.face(i,False) for i in range(self.dimension())] + return upper + lower + + def faces_as_pairs(self): + """ + The list of faces (of codimension 1) of this cube, as pairs + (upper, lower). + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [3,4]]) + sage: C.faces_as_pairs() + [([2,2] x [3,4], [1,1] x [3,4]), ([1,2] x [4,4], [1,2] x [3,3])] + """ + upper = [self.face(i, True) for i in range(self.dimension())] + lower = [self.face(i, False) for i in range(self.dimension())] + return list(zip(upper, lower)) + + def _compare_for_gluing(self, other): + r""" + Given two cubes ``self`` and ``other``, describe how to + transform them so that they become equal. + + :param other: a cube of the same dimension as ``self`` + :return: a triple ``(insert_self, insert_other, translate)``. + ``insert_self`` is a tuple with entries ``(index, (list of + degenerate intervals))``. ``insert_other`` is similar. + ``translate`` is a tuple of integers, suitable as a second + argument for the ``_translate`` method. + + To do this, ``self`` and ``other`` must have the same + dimension; degenerate intervals from ``other`` are added to + ``self``, and vice versa. Intervals in ``other`` are + translated so that they coincide with the intervals in + ``self``. The output is a triple, as noted above: in the + tuple ``insert_self``, an entry like ``(3, (3, 4, 0))`` means + that in position 3 in ``self``, insert the degenerate + intervals ``[3,3]``, ``[4,4]``, and ``[0,0]``. The same goes + for ``insert_other``. After applying the translations to the + cube ``other``, call ``_translate`` with argument the tuple + ``translate``. + + This is used in forming connected sums of cubical complexes: + the two complexes are modified, via this method, so that they + have a cube which matches up, then those matching cubes are + removed. + + In the example below, this method is called with arguments + ``C1`` and ``C2``, where + + .. MATH:: + + C1 = [0,1] \times [3] \times [4] \times [6,7] \\ + C2 = [2] \times [7,8] \times [9] \times [1,2] \times [0] \times [5] + + To C1, we need to add [2] in position 0 and [0] and [5] in + position 5. To C2, we need to add [4] in position 3. Once + this has been done, we need to translate the new C2 by the + vector ``(0, -7, -6, 0, 5, 0, 0)``. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C1 = Cube([[0,1], [3], [4], [6,7]]) + sage: C2 = Cube([[2], [7,8], [9], [1,2], [0], [5]]) + sage: C1._compare_for_gluing(C2) + ([(0, ((2, 2),)), (5, ((0, 0), (5, 5)))], [(3, ((4, 4),))], [0, -7, -6, 0, 5, 0, 0]) + + sage: C1 = Cube([[1,1], [0,1]]) + sage: C2 = Cube([[2,3], [4,4], [5,5]]) + sage: C1._compare_for_gluing(C2) + ([(2, ((4, 4), (5, 5)))], [(0, ((1, 1),))], [0, -2, 0, 0]) + """ + d = self.dimension() + if d != other.dimension(): + raise ValueError("Cubes must be of the same dimension.") + insert_self = [] + insert_other = [] + translate = [] + self_tuple = self.tuple() + other_tuple = other.tuple() + nondegen = (list(zip(self.nondegenerate_intervals(), + other.nondegenerate_intervals())) + + [(len(self_tuple), len(other_tuple))]) + old = (-1, -1) + self_added = 0 + other_added = 0 + + for current in nondegen: + # number of positions between nondegenerate intervals: + self_diff = current[0] - old[0] + other_diff = current[1] - old[1] + diff = self_diff - other_diff + + if diff < 0: + insert_self.append((old[0] + self_diff + self_added, + other.tuple()[current[1]+diff:current[1]])) + common_terms = self_diff + diff = -diff + self_added += diff + elif diff > 0: + insert_other.append((old[1] + other_diff + other_added, + self.tuple()[current[0]-diff:current[0]])) + common_terms = other_diff + other_added += diff + else: + common_terms = other_diff + + if old[0] > -1: + translate.extend([self_tuple[old[0]+idx][0] - + other_tuple[old[1]+idx][0] for idx in + range(common_terms)]) + translate.extend(diff*[0]) + old = current + + return (insert_self, insert_other, translate) + + def _triangulation_(self): + r""" + Triangulate this cube by "pulling vertices," as described by + Hetyei. Return a list of simplices which triangulate + ``self``. + + ALGORITHM: + + If the cube is given by + + .. MATH:: + + C = [i_1, j_1] \times [i_2, j_2] \times ... \times [i_k, j_k] + + let `v_1` be the "upper" corner of `C`: `v` is the point + `(j_1, ..., j_k)`. Choose a coordinate `n` where the interval + `[i_n, j_n]` is non-degenerate and form `v_2` by replacing + `j_n` by `i_n`; repeat to define `v_3`, etc. The last vertex + so defined will be `(i_1, ..., i_k)`. These vertices define a + simplex, as do the vertices obtained by making different + choices at each stage. Return the list of such simplices; + thus if `C` is `n`-dimensional, then it is subdivided into + `n!` simplices. + + REFERENCES: + + - G. Hetyei, "On the Stanley ring of a cubical complex", + Discrete Comput. Geom. 14 (1995), 305-330. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C = Cube([[1,2], [3,4]]); C + [1,2] x [3,4] + sage: C._triangulation_() + [((1, 3), (1, 4), (2, 4)), ((1, 3), (2, 3), (2, 4))] + sage: C = Cube([[1,2], [3,4], [8,9]]) + sage: len(C._triangulation_()) + 6 + """ + from .simplicial_complex import Simplex + if self.dimension() < 0: # the empty cube + return [Simplex(())] # the empty simplex + v = tuple([max(j) for j in self.tuple()]) + if self.dimension() == 0: # just v + return [Simplex((v,))] + simplices = [] + for i in range(self.dimension()): + for S in self.face(i, upper=False)._triangulation_(): + simplices.append(S.join(Simplex((v,)), rename_vertices=False)) + return simplices + + def alexander_whitney(self, dim): + r""" + Subdivide this cube into pairs of cubes. + + This provides a cubical approximation for the diagonal map + `K \to K \times K`. + + INPUT: + + - ``dim`` -- integer between 0 and one more than the + dimension of this cube + + OUTPUT: + + - a list containing triples ``(coeff, left, right)`` + + This uses the algorithm described by Pilarczyk and Réal [PR2015]_ + on p. 267; the formula is originally due to Serre. Calling + this method ``alexander_whitney`` is an abuse of notation, + since the actual Alexander-Whitney map goes from `C(K \times + L) \to C(K) \otimes C(L)`, where `C(-)` denotes the associated + chain complex, but this subdivision of cubes is at the heart + of it. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C1 = Cube([[0,1], [3,4]]) + sage: C1.alexander_whitney(0) + [(1, [0,0] x [3,3], [0,1] x [3,4])] + sage: C1.alexander_whitney(1) + [(1, [0,1] x [3,3], [1,1] x [3,4]), (-1, [0,0] x [3,4], [0,1] x [4,4])] + sage: C1.alexander_whitney(2) + [(1, [0,1] x [3,4], [1,1] x [4,4])] + """ + from sage.sets.set import Set + N = Set(self.nondegenerate_intervals()) + result = [] + for J in N.subsets(dim): + Jprime = N.difference(J) + nu = 0 + for i in J: + for j in Jprime: + if j= C2 + True + sage: C1 > C2 + False + sage: C3 <= C1 + True + sage: C1 > C3 + True + """ + return tuple(self) < tuple(other) + + def __hash__(self): + """ + Hash value for this cube. This computes the hash value of the + underlying tuple, since this is what's important when testing + equality. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C1 = Cube([[1,1], [2,3], [4,5]]) + sage: hash(C1) == hash(((1,1),(2,3),(4,5))) + True + """ + return hash(self.__tuple) + + def _repr_(self): + """ + Print representation of a cube. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C1 = Cube([[1,1], [2,3], [4,5]]) + sage: C1 + [1,1] x [2,3] x [4,5] + sage: C1._repr_() + '[1,1] x [2,3] x [4,5]' + """ + s = ["[%s,%s]"%(str(x), str(y)) for (x,y) in self.__tuple] + return " x ".join(s) + + def _latex_(self): + r""" + LaTeX representation of a cube.. + + EXAMPLES:: + + sage: from sage.topology.cubical_complex import Cube + sage: C1 = Cube([[1,1], [2,3], [4,5]]) + sage: latex(C1) + [1,1] \times [2,3] \times [4,5] + sage: C1._latex_() + '[1,1] \\times [2,3] \\times [4,5]' + """ + return self._repr_().replace('x', r'\times') + + +class CubicalComplex(GenericCellComplex): + r""" + Define a cubical complex. + + :param maximal_faces: set of maximal faces + :param maximality_check: see below + :type maximality_check: boolean; optional, default True + :return: a cubical complex + + ``maximal_faces`` should be a list or tuple or set (or anything + which may be converted to a set) of "cubes": instances of the + class :class:`Cube`, or lists or tuples suitable for conversion to + cubes. These cubes are the maximal cubes in the complex. + + In addition, ``maximal_faces`` may be a cubical complex, in which + case that complex is returned. Also, ``maximal_faces`` may + instead be any object which has a ``_cubical_`` method (e.g., a + simplicial complex); then that method is used to convert the + object to a cubical complex. + + If ``maximality_check`` is True, check that each maximal face is, + in fact, maximal. In this case, when producing the internal + representation of the cubical complex, omit those that are not. + It is highly recommended that this be True; various methods for + this class may fail if faces which are claimed to be maximal are + in fact not. + + EXAMPLES: + + The empty complex, consisting of one cube, the empty cube:: + + sage: CubicalComplex() + Cubical complex with 0 vertices and 1 cube + + A "circle" (four edges connecting the vertices (0,2), (0,3), + (1,2), and (1,3)):: + + sage: S1 = CubicalComplex([([0,0], [2,3]), ([0,1], [3,3]), ([0,1], [2,2]), ([1,1], [2,3])]) + sage: S1 + Cubical complex with 4 vertices and 8 cubes + sage: S1.homology() + {0: 0, 1: Z} + + A set of five points and its product with ``S1``:: + + sage: pts = CubicalComplex([([0],), ([3],), ([6],), ([-12],), ([5],)]) + sage: pts + Cubical complex with 5 vertices and 5 cubes + sage: pts.homology() + {0: Z x Z x Z x Z} + sage: X = S1.product(pts); X + Cubical complex with 20 vertices and 40 cubes + sage: X.homology() + {0: Z x Z x Z x Z, 1: Z^5} + + Converting a simplicial complex to a cubical complex:: + + sage: S2 = simplicial_complexes.Sphere(2) + sage: C2 = CubicalComplex(S2) + sage: all(C2.homology(n) == S2.homology(n) for n in range(3)) + True + + You can get the set of maximal cells or a dictionary of all cells:: + + sage: X.maximal_cells() # random: order may depend on the version of Python + {[0,0] x [2,3] x [-12,-12], [0,1] x [3,3] x [5,5], [0,1] x [2,2] x [3,3], [0,1] x [2,2] x [0,0], [0,1] x [3,3] x [6,6], [1,1] x [2,3] x [0,0], [0,1] x [2,2] x [-12,-12], [0,0] x [2,3] x [6,6], [1,1] x [2,3] x [-12,-12], [1,1] x [2,3] x [5,5], [0,1] x [2,2] x [5,5], [0,1] x [3,3] x [3,3], [1,1] x [2,3] x [3,3], [0,0] x [2,3] x [5,5], [0,1] x [3,3] x [0,0], [1,1] x [2,3] x [6,6], [0,1] x [2,2] x [6,6], [0,0] x [2,3] x [0,0], [0,0] x [2,3] x [3,3], [0,1] x [3,3] x [-12,-12]} + sage: sorted(X.maximal_cells()) + [[0,0] x [2,3] x [-12,-12], + [0,0] x [2,3] x [0,0], + [0,0] x [2,3] x [3,3], + [0,0] x [2,3] x [5,5], + [0,0] x [2,3] x [6,6], + [0,1] x [2,2] x [-12,-12], + [0,1] x [2,2] x [0,0], + [0,1] x [2,2] x [3,3], + [0,1] x [2,2] x [5,5], + [0,1] x [2,2] x [6,6], + [0,1] x [3,3] x [-12,-12], + [0,1] x [3,3] x [0,0], + [0,1] x [3,3] x [3,3], + [0,1] x [3,3] x [5,5], + [0,1] x [3,3] x [6,6], + [1,1] x [2,3] x [-12,-12], + [1,1] x [2,3] x [0,0], + [1,1] x [2,3] x [3,3], + [1,1] x [2,3] x [5,5], + [1,1] x [2,3] x [6,6]] + sage: S1.cells() + {-1: set(), + 0: {[0,0] x [2,2], [0,0] x [3,3], [1,1] x [2,2], [1,1] x [3,3]}, + 1: {[0,0] x [2,3], [0,1] x [2,2], [0,1] x [3,3], [1,1] x [2,3]}} + + Chain complexes, homology, and cohomology:: + + sage: T = S1.product(S1); T + Cubical complex with 16 vertices and 64 cubes + sage: T.chain_complex() + Chain complex with at most 3 nonzero terms over Integer Ring + sage: T.homology(base_ring=QQ) + {0: Vector space of dimension 0 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + sage: RP2 = cubical_complexes.RealProjectivePlane() + sage: RP2.cohomology(dim=[1, 2], base_ring=GF(2)) + {1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + + Joins are not implemented:: + + sage: S1.join(S1) + Traceback (most recent call last): + ... + NotImplementedError: Joins are not implemented for cubical complexes. + + Therefore, neither are cones or suspensions. + """ + def __init__(self, maximal_faces=None, maximality_check=True): + r""" + Define a cubical complex. See ``CubicalComplex`` for more + documentation. + + EXAMPLES:: + + sage: X = CubicalComplex([([0,0], [2,3]), ([0,1], [3,3]), ([0,1], [2,2]), ([1,1], [2,3])]); X + Cubical complex with 4 vertices and 8 cubes + sage: X == loads(dumps(X)) + True + """ + if maximal_faces is None: + maximal_faces = [] + C = None + if isinstance(maximal_faces, CubicalComplex): + C = maximal_faces + try: + C = maximal_faces._cubical_() + except AttributeError: + pass + if C is not None: + self._facets = copy(C._facets) + self._cells = copy(C._cells) + self._complex = copy(C._complex) + return + + good_faces = [] + maximal_cubes = [Cube(f) for f in maximal_faces] + for face in maximal_cubes: + # check whether each given face is actually maximal + face_is_maximal = True + if maximality_check: + faces_to_be_removed = [] + for other in good_faces: + if other.is_face(face): + faces_to_be_removed.append(other) + elif face_is_maximal: + face_is_maximal = not face.is_face(other) + for x in faces_to_be_removed: + good_faces.remove(x) + if face_is_maximal: + good_faces += [face] + # if no maximal faces, add the empty face as a facet + if len(maximal_cubes) == 0: + good_faces.append(Cube(())) + # self._facets: tuple of facets + self._facets = tuple(good_faces) + # self._cells: dictionary of dictionaries of faces. The main + # dictionary is keyed by subcomplexes, and each value is a + # dictionary keyed by dimension. This should be empty until + # needed -- that is, until the faces method is called + self._cells = {} + # self._complex: dictionary indexed by dimension d, base_ring, + # etc.: differential from dim d to dim d-1 in the associated + # chain complex. thus to get the differential in the cochain + # complex from dim d-1 to dim d, take the transpose of this + # one. + self._complex = {} + + def maximal_cells(self): + """ + The set of maximal cells (with respect to inclusion) of this + cubical complex. + + :return: Set of maximal cells + + This just returns the set of cubes used in defining the + cubical complex, so if the complex was defined with no + maximality checking, none is done here, either. + + EXAMPLES:: + + sage: interval = cubical_complexes.Cube(1) + sage: interval + Cubical complex with 2 vertices and 3 cubes + sage: interval.maximal_cells() + {[0,1]} + sage: interval.product(interval).maximal_cells() + {[0,1] x [0,1]} + """ + return Set(self._facets) + + def __eq__(self, other): + r""" + Return True if the set of maximal cells is the same for + ``self`` and ``other``. + + :param other: another cubical complex + :return: True if the set of maximal cells is the same for ``self`` and ``other`` + :rtype: bool + + EXAMPLES:: + + sage: I1 = cubical_complexes.Cube(1) + sage: I2 = cubical_complexes.Cube(1) + sage: I1.product(I2) == I2.product(I1) + True + sage: I1.product(I2.product(I2)) == I2.product(I1.product(I1)) + True + sage: S1 = cubical_complexes.Sphere(1) + sage: I1.product(S1) == S1.product(I1) + False + """ + return self.maximal_cells() == other.maximal_cells() + + def __ne__(self, other): + r""" + Return True if ``self`` and ``other`` are not equal. + + :param other: another cubical complex + :return: True if the complexes are not equal + :rtype: bool + + EXAMPLES:: + + sage: I1 = cubical_complexes.Cube(1) + sage: I2 = cubical_complexes.Cube(1) + sage: I1.product(I2) != I2.product(I1) + False + sage: I1.product(I2.product(I2)) != I2.product(I1.product(I1)) + False + sage: S1 = cubical_complexes.Sphere(1) + sage: I1.product(S1) != S1.product(I1) + True + """ + return not self.__eq__(other) + + def __hash__(self): + r""" + TESTS:: + + sage: I1 = cubical_complexes.Cube(1) + sage: I2 = cubical_complexes.Cube(1) + sage: hash(I1) == hash(I2) + True + sage: hash(I1.product(I1)) == hash(I2.product(I1)) + True + """ + return hash(frozenset(self._facets)) + + def is_subcomplex(self, other): + r""" + Return True if ``self`` is a subcomplex of ``other``. + + :param other: a cubical complex + + Each maximal cube of ``self`` must be a face of a maximal cube + of ``other`` for this to be True. + + EXAMPLES:: + + sage: S1 = cubical_complexes.Sphere(1) + sage: C0 = cubical_complexes.Cube(0) + sage: C1 = cubical_complexes.Cube(1) + sage: cyl = S1.product(C1) + sage: end = S1.product(C0) + sage: end.is_subcomplex(cyl) + True + sage: cyl.is_subcomplex(end) + False + + The embedding of the cubical complex is important here:: + + sage: C2 = cubical_complexes.Cube(2) + sage: C1.is_subcomplex(C2) + False + sage: C1.product(C0).is_subcomplex(C2) + True + + ``C1`` is not a subcomplex of ``C2`` because it's not embedded + in `\RR^2`. On the other hand, ``C1 x C0`` is a face of + ``C2``. Look at their maximal cells:: + + sage: C1.maximal_cells() + {[0,1]} + sage: C2.maximal_cells() + {[0,1] x [0,1]} + sage: C1.product(C0).maximal_cells() + {[0,1] x [0,0]} + """ + other_facets = other.maximal_cells() + return all(any(cube.is_face(other_cube) + for other_cube in other_facets) + for cube in self.maximal_cells()) + + def cells(self, subcomplex=None): + """ + The cells of this cubical complex, in the form of a dictionary: + the keys are integers, representing dimension, and the value + associated to an integer d is the list of d-cells. + + If the optional argument ``subcomplex`` is present, then + return only the faces which are *not* in the subcomplex. + + :param subcomplex: a subcomplex of this cubical complex + :type subcomplex: a cubical complex; optional, default None + :return: cells of this complex not contained in ``subcomplex`` + :rtype: dictionary + + EXAMPLES:: + + sage: S2 = cubical_complexes.Sphere(2) + sage: sorted(S2.cells()[2]) + [[0,0] x [0,1] x [0,1], + [0,1] x [0,0] x [0,1], + [0,1] x [0,1] x [0,0], + [0,1] x [0,1] x [1,1], + [0,1] x [1,1] x [0,1], + [1,1] x [0,1] x [0,1]] + """ + if subcomplex not in self._cells: + if subcomplex is not None and subcomplex.dimension() > -1: + if not subcomplex.is_subcomplex(self): + raise ValueError("The 'subcomplex' is not actually a subcomplex.") + # Cells is the dictionary of cells in self but not in + # subcomplex, indexed by dimension + Cells = {} + # sub_facets is the dictionary of facets in the subcomplex + sub_facets = {} + dimension = max([cube.dimension() for cube in self._facets]) + # initialize the lists: add each maximal cube to Cells and sub_facets + for i in range(-1,dimension+1): + Cells[i] = set([]) + sub_facets[i] = set([]) + for f in self._facets: + Cells[f.dimension()].add(f) + if subcomplex is not None: + for g in subcomplex._facets: + dim = g.dimension() + Cells[dim].discard(g) + sub_facets[dim].add(g) + # bad_faces is the set of faces in the subcomplex in the + # current dimension + bad_faces = sub_facets[dimension] + for dim in range(dimension, -1, -1): + # bad_bdries = boundaries of bad_faces: things to be + # discarded in dim-1 + bad_bdries = sub_facets[dim-1] + for f in bad_faces: + bad_bdries.update(f.faces()) + for f in Cells[dim]: + Cells[dim-1].update(set(f.faces()).difference(bad_bdries)) + bad_faces = bad_bdries + self._cells[subcomplex] = Cells + return self._cells[subcomplex] + + def n_cubes(self, n, subcomplex=None): + """ + The set of cubes of dimension n of this cubical complex. + If the optional argument ``subcomplex`` is present, then + return the ``n``-dimensional cubes which are *not* in the + subcomplex. + + :param n: dimension + :type n: integer + :param subcomplex: a subcomplex of this cubical complex + :type subcomplex: a cubical complex; optional, default None + :return: cells in dimension ``n`` + :rtype: set + + EXAMPLES:: + + sage: C = cubical_complexes.Cube(3) + sage: C.n_cubes(3) + {[0,1] x [0,1] x [0,1]} + sage: sorted(C.n_cubes(2)) + [[0,0] x [0,1] x [0,1], + [0,1] x [0,0] x [0,1], + [0,1] x [0,1] x [0,0], + [0,1] x [0,1] x [1,1], + [0,1] x [1,1] x [0,1], + [1,1] x [0,1] x [0,1]] + """ + return set(self.n_cells(n, subcomplex)) + + def chain_complex(self, subcomplex=None, augmented=False, + verbose=False, check=False, dimensions=None, + base_ring=ZZ, cochain=False): + r""" + The chain complex associated to this cubical complex. + + :param dimensions: if None, compute the chain complex in all + dimensions. If a list or tuple of integers, compute the + chain complex in those dimensions, setting the chain groups + in all other dimensions to zero. NOT IMPLEMENTED YET: this + function always returns the entire chain complex + :param base_ring: commutative ring + :type base_ring: optional, default ZZ + :param subcomplex: a subcomplex of this cubical complex. + Compute the chain complex relative to this subcomplex. + :type subcomplex: optional, default empty + :param augmented: If True, return the augmented chain complex + (that is, include a class in dimension `-1` corresponding + to the empty cell). This is ignored if ``dimensions`` is + specified. + :type augmented: boolean; optional, default False + :param cochain: If True, return the cochain complex (that is, + the dual of the chain complex). + :type cochain: boolean; optional, default False + :param verbose: If True, print some messages as the chain + complex is computed. + :type verbose: boolean; optional, default False + :param check: If True, make sure that the chain complex + is actually a chain complex: the differentials are + composable and their product is zero. + :type check: boolean; optional, default False + + .. note:: + + If subcomplex is nonempty, then the argument ``augmented`` + has no effect: the chain complex relative to a nonempty + subcomplex is zero in dimension `-1`. + + EXAMPLES:: + + sage: S2 = cubical_complexes.Sphere(2) + sage: S2.chain_complex() + Chain complex with at most 3 nonzero terms over Integer Ring + sage: Prod = S2.product(S2); Prod + Cubical complex with 64 vertices and 676 cubes + sage: Prod.chain_complex() + Chain complex with at most 5 nonzero terms over Integer Ring + sage: Prod.chain_complex(base_ring=QQ) + Chain complex with at most 5 nonzero terms over Rational Field + sage: C1 = cubical_complexes.Cube(1) + sage: S0 = cubical_complexes.Sphere(0) + sage: C1.chain_complex(subcomplex=S0) + Chain complex with at most 1 nonzero terms over Integer Ring + sage: C1.homology(subcomplex=S0) + {0: 0, 1: Z} + """ + # initialize subcomplex + if subcomplex is None: + subcomplex = CubicalComplex() + else: + # subcomplex is not empty, so don't augment the chain complex + augmented = False + differentials = {} + if augmented: + empty_cell = 1 # number of (-1)-dimensional cubes + else: + empty_cell = 0 + vertices = self._n_cells_sorted(0, subcomplex=subcomplex) + n = len(vertices) + mat = matrix(base_ring, empty_cell, n, n*empty_cell*[1]) + if cochain: + differentials[-1] = mat.transpose() + else: + differentials[0] = mat + current = vertices + # now loop from 1 to dimension of the complex + for dim in range(1,self.dimension()+1): + if verbose: + print(" starting dimension %s" % dim) + if (dim, subcomplex) in self._complex: + if cochain: + differentials[dim-1] = self._complex[(dim, subcomplex)].transpose().change_ring(base_ring) + mat = differentials[dim-1] + else: + differentials[dim] = self._complex[(dim, subcomplex)].change_ring(base_ring) + mat = differentials[dim] + if verbose: + print(" boundary matrix (cached): it's %s by %s." % (mat.nrows(), mat.ncols())) + else: + # 'current' is the list of cells in dimension n + # + # 'old' is a dictionary, with keys the cells in the + # previous dimension, values the integers 0, 1, 2, + # ... (the index of the face). finding an entry in a + # dictionary seems to be faster than finding the index + # of an entry in a list. + old = dict(zip(current, range(len(current)))) + current = self._n_cells_sorted(dim, subcomplex=subcomplex) + # construct matrix. it is easiest to construct it as + # a sparse matrix, specifying which entries are + # nonzero via a dictionary. + matrix_data = {} + col = 0 + if len(old) and len(current): + for cube in current: + faces = cube.faces_as_pairs() + sign = 1 + for (upper, lower) in faces: + try: + matrix_data[(old[upper], col)] = sign + sign *= -1 + matrix_data[(old[lower], col)] = sign + except KeyError: + pass + col += 1 + mat = matrix(ZZ, len(old), len(current), matrix_data) + self._complex[(dim, subcomplex)] = mat + if cochain: + differentials[dim-1] = mat.transpose().change_ring(base_ring) + else: + differentials[dim] = mat.change_ring(base_ring) + if verbose: + print(" boundary matrix computed: it's %s by %s." % (mat.nrows(), mat.ncols())) + # finally, return the chain complex + if cochain: + return ChainComplex(data=differentials, base_ring=base_ring, + degree=1, check=check) + else: + return ChainComplex(data=differentials, base_ring=base_ring, + degree=-1, check=check) + + def alexander_whitney(self, cube, dim_left): + r""" + Subdivide ``cube`` in this cubical complex into pairs of cubes. + + See :meth:`Cube.alexander_whitney` for more details. This + method just calls that one. + + INPUT: + + - ``cube`` -- a cube in this cubical complex + - ``dim`` -- integer between 0 and one more than the + dimension of this cube + + OUTPUT: a list containing triples ``(coeff, left, right)`` + + EXAMPLES:: + + sage: C = cubical_complexes.Cube(3) + sage: c = list(C.n_cubes(3))[0]; c + [0,1] x [0,1] x [0,1] + sage: C.alexander_whitney(c, 1) + [(1, [0,1] x [0,0] x [0,0], [1,1] x [0,1] x [0,1]), + (-1, [0,0] x [0,1] x [0,0], [0,1] x [1,1] x [0,1]), + (1, [0,0] x [0,0] x [0,1], [0,1] x [0,1] x [1,1])] + """ + return cube.alexander_whitney(dim_left) + + def n_skeleton(self, n): + r""" + The n-skeleton of this cubical complex. + + :param n: dimension + :type n: non-negative integer + :return: cubical complex + + EXAMPLES:: + + sage: S2 = cubical_complexes.Sphere(2) + sage: C3 = cubical_complexes.Cube(3) + sage: S2 == C3.n_skeleton(2) + True + """ + if n >= self.dimension(): + return self + else: + data = [] + for d in range(n+1): + data.extend(list(self.cells()[d])) + return CubicalComplex(data) + + def graph(self): + """ + The 1-skeleton of this cubical complex, as a graph. + + EXAMPLES:: + + sage: cubical_complexes.Sphere(2).graph() + Graph on 8 vertices + """ + data = {} + vertex_dict = {} + i = 0 + for vertex in self.n_cells(0): + vertex_dict[vertex] = i + data[i] = [] + i += 1 + for edge in self.n_cells(1): + start = edge.face(0, False) + end = edge.face(0, True) + data[vertex_dict[start]].append(vertex_dict[end]) + return Graph(data) + + def is_pure(self): + """ + True iff this cubical complex is pure: that is, + all of its maximal faces have the same dimension. + + .. warning:: + + This may give the wrong answer if the cubical complex + was constructed with ``maximality_check`` set to False. + + EXAMPLES:: + + sage: S4 = cubical_complexes.Sphere(4) + sage: S4.is_pure() + True + sage: C = CubicalComplex([([0,0], [3,3]), ([1,2], [4,5])]) + sage: C.is_pure() + False + """ + dims = [face.dimension() for face in self._facets] + return max(dims) == min(dims) + + def join(self, other): + r""" + The join of this cubical complex with another one. + + NOT IMPLEMENTED. + + :param other: another cubical complex + + EXAMPLES:: + + sage: C1 = cubical_complexes.Cube(1) + sage: C1.join(C1) + Traceback (most recent call last): + ... + NotImplementedError: Joins are not implemented for cubical complexes. + """ + raise NotImplementedError("Joins are not implemented for cubical complexes.") + + # Use * to mean 'join': + # __mul__ = join + + def cone(self): + r""" + The cone on this cubical complex. + + NOT IMPLEMENTED + + The cone is the complex formed by taking the join of the + original complex with a one-point complex (that is, a + 0-dimensional cube). Since joins are not implemented for + cubical complexes, neither are cones. + + EXAMPLES:: + + sage: C1 = cubical_complexes.Cube(1) + sage: C1.cone() + Traceback (most recent call last): + ... + NotImplementedError: Cones are not implemented for cubical complexes. + """ + #return self.join(cubical_complexes.Cube(0)) + raise NotImplementedError("Cones are not implemented for cubical complexes.") + + def suspension(self, n=1): + r""" + The suspension of this cubical complex. + + NOT IMPLEMENTED + + :param n: suspend this many times + :type n: positive integer; optional, default 1 + + The suspension is the complex formed by taking the join of the + original complex with a two-point complex (the 0-sphere). + Since joins are not implemented for cubical complexes, neither + are suspensions. + + EXAMPLES:: + + sage: C1 = cubical_complexes.Cube(1) + sage: C1.suspension() + Traceback (most recent call last): + ... + NotImplementedError: Suspensions are not implemented for cubical complexes. + """ +# if n<0: +# raise ValueError, "n must be non-negative." +# if n==0: +# return self +# if n==1: +# return self.join(cubical_complexes.Sphere(0)) +# return self.suspension().suspension(int(n-1)) + raise NotImplementedError("Suspensions are not implemented for cubical complexes.") + + def product(self, other): + r""" + The product of this cubical complex with another one. + + :param other: another cubical complex + + EXAMPLES:: + + sage: RP2 = cubical_complexes.RealProjectivePlane() + sage: S1 = cubical_complexes.Sphere(1) + sage: RP2.product(S1).homology()[1] # long time: 5 seconds + Z x C2 + """ + facets = [] + for f in self._facets: + for g in other._facets: + facets.append(f.product(g)) + return CubicalComplex(facets) + + def disjoint_union(self, other): + """ + The disjoint union of this cubical complex with another one. + + :param right: the other cubical complex (the right-hand factor) + + Algorithm: first embed both complexes in d-dimensional + Euclidean space. Then embed in (1+d)-dimensional space, + calling the new axis `x`, and putting the first complex at + `x=0`, the second at `x=1`. + + EXAMPLES:: + + sage: S1 = cubical_complexes.Sphere(1) + sage: S2 = cubical_complexes.Sphere(2) + sage: S1.disjoint_union(S2).homology() + {0: Z, 1: Z, 2: Z} + """ + embedded_left = len(tuple(self.maximal_cells()[0])) + embedded_right = len(tuple(other.maximal_cells()[0])) + zero = [0] * max(embedded_left, embedded_right) + facets = [] + for f in self.maximal_cells(): + facets.append(Cube([[0,0]]).product(f._translate(zero))) + for f in other.maximal_cells(): + facets.append(Cube([[1,1]]).product(f._translate(zero))) + return CubicalComplex(facets) + + def wedge(self, other): + """ + The wedge (one-point union) of this cubical complex with + another one. + + :param right: the other cubical complex (the right-hand factor) + + Algorithm: if ``self`` is embedded in `d` dimensions and + ``other`` in `n` dimensions, embed them in `d+n` dimensions: + ``self`` using the first `d` coordinates, ``other`` using the + last `n`, translating them so that they have the origin as a + common vertex. + + .. note:: + + This operation is not well-defined if ``self`` or + ``other`` is not path-connected. + + EXAMPLES:: + + sage: S1 = cubical_complexes.Sphere(1) + sage: S2 = cubical_complexes.Sphere(2) + sage: S1.wedge(S2).homology() + {0: 0, 1: Z, 2: Z} + """ + embedded_left = len(tuple(self.maximal_cells()[0])) + embedded_right = len(tuple(other.maximal_cells()[0])) + translate_left = [-a[0] for a in self.maximal_cells()[0]] + [0] * embedded_right + translate_right = [-a[0] for a in other.maximal_cells()[0]] + point_right = Cube([[0,0]] * embedded_left) + + facets = [] + for f in self.maximal_cells(): + facets.append(f._translate(translate_left)) + for f in other.maximal_cells(): + facets.append(point_right.product(f._translate(translate_right))) + return CubicalComplex(facets) + + def connected_sum(self, other): + """ + Return the connected sum of self with other. + + :param other: another cubical complex + :return: the connected sum ``self # other`` + + .. warning:: + + This does not check that self and other are manifolds, only + that their facets all have the same dimension. Since a + (more or less) random facet is chosen from each complex and + then glued together, this method may return random + results if applied to non-manifolds, depending on which + facet is chosen. + + EXAMPLES:: + + sage: T = cubical_complexes.Torus() + sage: S2 = cubical_complexes.Sphere(2) + sage: T.connected_sum(S2).cohomology() == T.cohomology() + True + sage: RP2 = cubical_complexes.RealProjectivePlane() + sage: T.connected_sum(RP2).homology(1) + Z x Z x C2 + sage: RP2.connected_sum(RP2).connected_sum(RP2).homology(1) + Z x Z x C2 + """ + # connected_sum: first check whether the complexes are pure + # and of the same dimension. Then insert degenerate intervals + # and translate them so that they have a common cube C. Add one + # more dimension, embedding the first complex as (..., 0) and + # the second as (..., 1). Keep all of the other facets, but remove + # C x 0 and C x 1, putting in its place (its boundary) x (0,1). + if not (self.is_pure() and other.is_pure() and + self.dimension() == other.dimension()): + raise ValueError("Complexes are not pure of the same dimension.") + + self_facets = list(self.maximal_cells()) + other_facets = list(other.maximal_cells()) + + C1 = self_facets.pop() + C2 = other_facets.pop() + (insert_self, insert_other, translate) = C1._compare_for_gluing(C2) + + CL = list(C1.tuple()) + for (idx, L) in insert_self: + CL[idx:idx] = L + removed = Cube(CL) + + # start assembling the facets in the connected sum: first, the + # cylinder on the removed face. + new_facets = [] + cylinder = removed.product(Cube([[0,1]])) + # don't want to include the ends of the cylinder, so don't + # include the last pair of faces. therefore, choose faces up + # to removed.dimension(), not cylinder.dimension(). + for n in range(removed.dimension()): + new_facets.append(cylinder.face(n, upper=False)) + new_facets.append(cylinder.face(n, upper=True)) + + for cube in self_facets: + CL = list(cube.tuple()) + for (idx, L) in insert_self: + CL[idx:idx] = L + CL.append((0,0)) + new_facets.append(Cube(CL)) + for cube in other_facets: + CL = list(cube.tuple()) + for (idx, L) in insert_other: + CL[idx:idx] = L + CL.append((1,1)) + new_facets.append(Cube(CL)._translate(translate)) + return CubicalComplex(new_facets) + + def _translate(self, vec): + """ + Translate ``self`` by ``vec``. + + :param vec: anything which can be converted to a tuple of integers + :return: the translation of ``self`` by ``vec`` + :rtype: cubical complex + + If ``vec`` is shorter than the list of intervals forming the + complex, pad with zeroes, and similarly if the complexes + defining tuples are too short. + + EXAMPLES:: + + sage: C1 = cubical_complexes.Cube(1) + sage: C1.maximal_cells() + {[0,1]} + sage: C1._translate([2,6]).maximal_cells() + {[2,3] x [6,6]} + """ + return CubicalComplex([f._translate(vec) for f in self.maximal_cells()]) + + # This is cached for speed reasons: it can be very slow to run + # this function. + @cached_method + def algebraic_topological_model(self, base_ring=None): + r""" + Algebraic topological model for this cubical complex with + coefficients in ``base_ring``. + + The term "algebraic topological model" is defined by Pilarczyk + and Réal [PR2015]_. + + INPUT: + + - ``base_ring`` - coefficient ring (optional, default + ``QQ``). Must be a field. + + Denote by `C` the chain complex associated to this cubical + complex. The algebraic topological model is a chain complex + `M` with zero differential, with the same homology as `C`, + along with chain maps `\pi: C \to M` and `\iota: M \to C` + satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic + to `1_C`. The chain homotopy `\phi` must satisfy + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Such a chain homotopy is called a *chain contraction*. + + OUTPUT: a pair consisting of + + - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and + `\iota` + - the chain complex `M` + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: RP2 = cubical_complexes.RealProjectivePlane() + sage: phi, M = RP2.algebraic_topological_model(GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = cubical_complexes.Torus() + sage: phi, M = T.algebraic_topological_model(QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + """ + from sage.homology.algebraic_topological_model import algebraic_topological_model + if base_ring is None: + base_ring = QQ + return algebraic_topological_model(self, base_ring) + + def _chomp_repr_(self): + r""" + String representation of self suitable for use by the CHomP + program. This lists each maximal cube on its own line. + + EXAMPLES:: + + sage: C = cubical_complexes.Cube(0).product(cubical_complexes.Cube(2)) + sage: C.maximal_cells() + {[0,0] x [0,1] x [0,1]} + sage: C._chomp_repr_() + '[0,0] x [0,1] x [0,1]\n' + """ + s = "" + for c in self.maximal_cells(): + s += str(c) + s += "\n" + return s + + def _simplicial_(self): + r""" + Simplicial complex constructed from self. + + ALGORITHM: + + This is constructed as described by Hetyei: choose a total + ordering of the vertices of the cubical complex. Then for + each maximal face + + .. MATH:: + + C = [i_1, j_1] \times [i_2, j_2] \times ... \times [i_k, j_k] + + let `v_1` be the "upper" corner of `C`: `v` is the point + `(j_1, ..., j_k)`. Choose a coordinate `n` where the interval + `[i_n, j_n]` is non-degenerate and form `v_2` by replacing + `j_n` by `i_n`; repeat to define `v_3`, etc. The last vertex + so defined will be `(i_1, ..., i_k)`. These vertices define a + simplex, and do the vertices obtained by making different + choices at each stage. Thus each `n`-cube is subdivided into + `n!` simplices. + + REFERENCES: + + - G. Hetyei, "On the Stanley ring of a cubical complex", + Discrete Comput. Geom. 14 (1995), 305-330. + + EXAMPLES:: + + sage: T = cubical_complexes.Torus(); T + Cubical complex with 16 vertices and 64 cubes + sage: len(T.maximal_cells()) + 16 + + When this is triangulated, each maximal 2-dimensional cube + gets turned into a pair of triangles. Since there were 16 + maximal cubes, this results in 32 facets in the simplicial + complex:: + + sage: Ts = T._simplicial_(); Ts + Simplicial complex with 16 vertices and 32 facets + sage: T.homology() == Ts.homology() + True + + Each `n`-dimensional cube produces `n!` `n`-simplices:: + + sage: S4 = cubical_complexes.Sphere(4) + sage: len(S4.maximal_cells()) + 10 + sage: SimplicialComplex(S4) # calls S4._simplicial_() + Simplicial complex with 32 vertices and 240 facets + """ + from .simplicial_complex import SimplicialComplex + simplices = [] + for C in self.maximal_cells(): + simplices.extend(C._triangulation_()) + return SimplicialComplex(simplices) + + def _string_constants(self): + """ + Tuple containing the name of the type of complex, and the + singular and plural of the name of the cells from which it is + built. This is used in constructing the string representation. + + EXAMPLES:: + + sage: S3 = cubical_complexes.Sphere(3) + sage: S3._string_constants() + ('Cubical', 'cube', 'cubes') + sage: S3._repr_() # indirect doctest + 'Cubical complex with 16 vertices and 80 cubes' + """ + return ('Cubical', 'cube', 'cubes') + + +class CubicalComplexExamples(): + r""" + Some examples of cubical complexes. + + Here are the available examples; you can also type + "cubical_complexes." and hit TAB to get a list:: + + Sphere + Torus + RealProjectivePlane + KleinBottle + SurfaceOfGenus + Cube + + EXAMPLES:: + + sage: cubical_complexes.Torus() # indirect doctest + Cubical complex with 16 vertices and 64 cubes + sage: cubical_complexes.Cube(7) + Cubical complex with 128 vertices and 2187 cubes + sage: cubical_complexes.Sphere(7) + Cubical complex with 256 vertices and 6560 cubes + """ + + def Sphere(self,n): + r""" + A cubical complex representation of the `n`-dimensional sphere, + formed by taking the boundary of an `(n+1)`-dimensional cube. + + :param n: the dimension of the sphere + :type n: non-negative integer + + EXAMPLES:: + + sage: cubical_complexes.Sphere(7) + Cubical complex with 256 vertices and 6560 cubes + """ + return CubicalComplex(Cube([[0,1]]*(n+1)).faces()) + + def Torus(self): + r""" + A cubical complex representation of the torus, obtained by + taking the product of the circle with itself. + + EXAMPLES:: + + sage: cubical_complexes.Torus() + Cubical complex with 16 vertices and 64 cubes + """ + S1 = cubical_complexes.Sphere(1) + return S1.product(S1) + + def RealProjectivePlane(self): + r""" + A cubical complex representation of the real projective plane. + This is taken from the examples from CHomP, the Computational + Homology Project: http://chomp.rutgers.edu/. + + EXAMPLES:: + + sage: cubical_complexes.RealProjectivePlane() + Cubical complex with 21 vertices and 81 cubes + """ + return CubicalComplex([ + ([0, 1], [0], [0], [0, 1], [0]), + ([0, 1], [0], [0], [0], [0, 1]), + ([0], [0, 1], [0, 1], [0], [0]), + ([0], [0, 1], [0], [0, 1], [0]), + ([0], [0], [0, 1], [0], [0, 1]), + ([0, 1], [0, 1], [1], [0], [0]), + ([0, 1], [1], [0, 1], [0], [0]), + ([1], [0, 1], [0, 1], [0], [0]), + ([0, 1], [0, 1], [0], [0], [1]), + ([0, 1], [1], [0], [0], [0, 1]), + ([1], [0, 1], [0], [0], [0, 1]), + ([0, 1], [0], [0, 1], [1], [0]), + ([0, 1], [0], [1], [0, 1], [0]), + ([1], [0], [0, 1], [0, 1], [0]), + ([0], [0, 1], [0], [0, 1], [1]), + ([0], [0, 1], [0], [1], [0, 1]), + ([0], [1], [0], [0, 1], [0, 1]), + ([0], [0], [0, 1], [0, 1], [1]), + ([0], [0], [0, 1], [1], [0, 1]), + ([0], [0], [1], [0, 1], [0, 1])]) + + def KleinBottle(self): + r""" + A cubical complex representation of the Klein bottle, formed + by taking the connected sum of the real projective plane with + itself. + + EXAMPLES:: + + sage: cubical_complexes.KleinBottle() + Cubical complex with 42 vertices and 168 cubes + """ + RP2 = cubical_complexes.RealProjectivePlane() + return RP2.connected_sum(RP2) + + def SurfaceOfGenus(self, g, orientable=True): + """ + A surface of genus g as a cubical complex. + + :param g: the genus + :type g: non-negative integer + :param orientable: whether the surface should be orientable + :type orientable: bool, optional, default True + + In the orientable case, return a sphere if `g` is zero, and + otherwise return a `g`-fold connected sum of a torus with + itself. + + In the non-orientable case, raise an error if `g` is zero. If + `g` is positive, return a `g`-fold connected sum of a + real projective plane with itself. + + EXAMPLES:: + + sage: cubical_complexes.SurfaceOfGenus(2) + Cubical complex with 32 vertices and 134 cubes + sage: cubical_complexes.SurfaceOfGenus(1, orientable=False) + Cubical complex with 21 vertices and 81 cubes + """ + try: + g = Integer(g) + except TypeError: + raise ValueError("genus must be a non-negative integer") + if g < 0: + raise ValueError("genus must be a non-negative integer") + if g == 0: + if not orientable: + raise ValueError("no non-orientable surface of genus zero") + else: + return cubical_complexes.Sphere(2) + if orientable: + T = cubical_complexes.Torus() + else: + T = cubical_complexes.RealProjectivePlane() + S = T + for i in range(g-1): + S = S.connected_sum(T) + return S + + def Cube(self, n): + r""" + A cubical complex representation of an `n`-dimensional cube. + + :param n: the dimension + :type n: non-negative integer + + EXAMPLES:: + + sage: cubical_complexes.Cube(0) + Cubical complex with 1 vertex and 1 cube + sage: cubical_complexes.Cube(3) + Cubical complex with 8 vertices and 27 cubes + """ + if n == 0: + return CubicalComplex([Cube([[0]])]) + else: + return CubicalComplex([Cube([[0,1]]*n)]) + +cubical_complexes = CubicalComplexExamples() diff --git a/src/sage/topology/delta_complex.py b/src/sage/topology/delta_complex.py new file mode 100644 index 00000000000..9e4b08262cc --- /dev/null +++ b/src/sage/topology/delta_complex.py @@ -0,0 +1,1777 @@ +# -*- coding: utf-8 -*- +r""" +Finite Delta-complexes + +AUTHORS: + +- John H. Palmieri (2009-08) + +This module implements the basic structure of finite +`\Delta`-complexes. For full mathematical details, see Hatcher [Hat2002]_, +especially Section 2.1 and the Appendix on "Simplicial CW Structures". +As Hatcher points out, `\Delta`-complexes were first introduced by Eilenberg +and Zilber [EZ1950]_, although they called them "semi-simplicial complexes". + +A `\Delta`-complex is a generalization of a :mod:`simplicial complex +`; a `\Delta`-complex `X` consists +of sets `X_n` for each non-negative integer `n`, the elements of which +are called *n-simplices*, along with *face maps* between these sets of +simplices: for each `n` and for all `0 \leq i \leq n`, there are +functions `d_i` from `X_n` to `X_{n-1}`, with `d_i(s)` equal to the +`i`-th face of `s` for each simplex `s \in X_n`. These maps must +satisfy the *simplicial identity* + + .. MATH:: + + d_i d_j = d_{j-1} d_i \text{ for all } i` + page instead. +""" + +from copy import copy +from sage.topology.cell_complex import GenericCellComplex +from sage.homology.chains import Chains, Cochains +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.rings.integer import Integer +from sage.matrix.constructor import matrix +from .simplicial_complex import Simplex, lattice_paths, SimplicialComplex +from sage.homology.chain_complex import ChainComplex +from sage.graphs.graph import Graph +from sage.arith.all import binomial +from sage.misc.cachefunc import cached_method + + +class DeltaComplex(GenericCellComplex): + r""" + Define a `\Delta`-complex. + + :param data: see below for a description of the options + :param check_validity: If True, check that the simplicial identities hold. + :type check_validity: boolean; optional, default True + :return: a `\Delta`-complex + + Use ``data`` to define a `\Delta`-complex. It may be in any of + three forms: + + - ``data`` may be a dictionary indexed by simplices. The value + associated to a d-simplex `S` can be any of: + + - a list or tuple of (d-1)-simplices, where the ith entry is the + ith face of S, given as a simplex, + + - another d-simplex `T`, in which case the ith face of `S` is + declared to be the same as the ith face of `T`: `S` and `T` + are glued along their entire boundary, + + - None or True or False or anything other than the previous two + options, in which case the faces are just the ordinary faces + of `S`. + + For example, consider the following:: + + sage: n = 5 + sage: S5 = DeltaComplex({Simplex(n):True, Simplex(range(1,n+2)): Simplex(n)}) + sage: S5 + Delta complex with 6 vertices and 65 simplices + + The first entry in dictionary forming the argument to + ``DeltaComplex`` says that there is an `n`-dimensional simplex + with its ordinary boundary. The second entry says that there is + another simplex whose boundary is glued to that of the first + one. The resulting `\Delta`-complex is, of course, homeomorphic + to an `n`-sphere, or actually a 5-sphere, since we defined `n` + to be 5. (Note that the second simplex here can be any + `n`-dimensional simplex, as long as it is distinct from + ``Simplex(n)``.) + + Let's compute its homology, and also compare it to the simplicial version:: + + sage: S5.homology() + {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: Z} + sage: S5.f_vector() # number of simplices in each dimension + [1, 6, 15, 20, 15, 6, 2] + sage: simplicial_complexes.Sphere(5).f_vector() + [1, 7, 21, 35, 35, 21, 7] + + Both contain a single (-1)-simplex, the empty simplex; other + than that, the `\Delta`-complex version contains fewer simplices + than the simplicial one in each dimension. + + To construct a torus, use:: + + sage: torus_dict = {Simplex([0,1,2]): True, + ....: Simplex([3,4,5]): (Simplex([0,1]), Simplex([0,2]), Simplex([1,2])), + ....: Simplex([0,1]): (Simplex(0), Simplex(0)), + ....: Simplex([0,2]): (Simplex(0), Simplex(0)), + ....: Simplex([1,2]): (Simplex(0), Simplex(0)), + ....: Simplex(0): ()} + sage: T = DeltaComplex(torus_dict); T + Delta complex with 1 vertex and 7 simplices + sage: T.cohomology(base_ring=QQ) + {0: Vector space of dimension 0 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + + This `\Delta`-complex consists of two triangles (given by + ``Simplex([0,1,2])`` and ``Simplex([3,4,5])``); the boundary of + the first is just its usual boundary: the 0th face is obtained + by omitting the lowest numbered vertex, etc., and so the + boundary consists of the edges ``[1,2]``, ``[0,2]``, and + ``[0,1]``, in that order. The boundary of the second is, on the + one hand, computed the same way: the nth face is obtained by + omitting the nth vertex. On the other hand, the boundary is + explicitly declared to be edges ``[0,1]``, ``[0,2]``, and + ``[1,2]``, in that order. This glues the second triangle to the + first in the prescribed way. The three edges each start and end + at the single vertex, ``Simplex(0)``. + + .. image:: ../../media/torus_labelled.png + + - ``data`` may be nested lists or tuples. The nth entry in the + list is a list of the n-simplices in the complex, and each + n-simplex is encoded as a list, the ith entry of which is its + ith face. Each face is represented by an integer, giving its + index in the list of (n-1)-faces. For example, consider this:: + + sage: P = DeltaComplex( [ [(), ()], [(1,0), (1,0), (0,0)], + ....: [(1,0,2), (0, 1, 2)] ]) + + The 0th entry in the list is ``[(), ()]``: there are two + 0-simplices, and their boundaries are empty. + + The 1st entry in the list is ``[(1,0), (1,0), (0,0)]``: there + are three 1-simplices. Two of them have boundary ``(1,0)``, + which means that their 0th face is vertex 1 (in the list of + vertices), and their 1st face is vertex 0. The other edge has + boundary ``(0,0)``, so it starts and ends at vertex 0. + + The 2nd entry in the list is ``[(1,0,2), (0,1,2)]``: there are + two 2-simplices. The first 2-simplex has boundary ``(1,0,2)``, + meaning that its 0th face is edge 1 (in the list above), its 1st + face is edge 0, and its 2nd face is edge 2; similarly for the + 2nd 2-simplex. + + If one draws two triangles and identifies them according to this + description, the result is the real projective plane. + + .. image:: ../../media/rp2.png + + :: + + sage: P.homology(1) + C2 + sage: P.cohomology(2) + C2 + + Closely related to this form for ``data`` is ``X.cells()`` + for a `\Delta`-complex ``X``: this is a dictionary, indexed by + dimension ``d``, whose ``d``-th entry is a list of the + ``d``-simplices, as a list:: + + sage: P.cells() + {-1: ((),), + 0: ((), ()), + 1: ((1, 0), (1, 0), (0, 0)), + 2: ((1, 0, 2), (0, 1, 2))} + + - ``data`` may be a dictionary indexed by integers. For each + integer `n`, the entry with key `n` is the list of + `n`-simplices: this is the same format as is output by the + :meth:`cells` method. :: + + sage: P = DeltaComplex( [ [(), ()], [(1,0), (1,0), (0,0)], + ....: [(1,0,2), (0, 1, 2)] ]) + sage: cells_dict = P.cells() + sage: cells_dict + {-1: ((),), + 0: ((), ()), + 1: ((1, 0), (1, 0), (0, 0)), + 2: ((1, 0, 2), (0, 1, 2))} + sage: DeltaComplex(cells_dict) + Delta complex with 2 vertices and 8 simplices + sage: P == DeltaComplex(cells_dict) + True + + Since `\Delta`-complexes are generalizations of simplicial + complexes, any simplicial complex may be viewed as a + `\Delta`-complex:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: RP2_delta = RP2.delta_complex() + sage: RP2.f_vector() + [1, 6, 15, 10] + sage: RP2_delta.f_vector() + [1, 6, 15, 10] + + Finally, `\Delta`-complex constructions for several familiar + spaces are available as follows:: + + sage: delta_complexes.Sphere(4) # the 4-sphere + Delta complex with 5 vertices and 33 simplices + sage: delta_complexes.KleinBottle() + Delta complex with 1 vertex and 7 simplices + sage: delta_complexes.RealProjectivePlane() + Delta complex with 2 vertices and 8 simplices + + Type ``delta_complexes.`` and then hit the TAB key to get the + full list. + """ + def __init__(self, data=None, check_validity=True): + r""" + Define a `\Delta`-complex. See :class:`DeltaComplex` for more + documentation. + + EXAMPLES:: + + sage: X = DeltaComplex({Simplex(3):True, Simplex(range(1,5)): Simplex(3), Simplex(range(2,6)): Simplex(3)}); X # indirect doctest + Delta complex with 4 vertices and 18 simplices + sage: X.homology() + {0: 0, 1: 0, 2: 0, 3: Z x Z} + sage: X == loads(dumps(X)) + True + """ + def store_bdry(simplex, faces): + r""" + Given a simplex of dimension d and a list of boundaries + (as other simplices), this stores each boundary face in + new_data[d-1] if necessary, records the index of each + boundary face in bdry_list, represents the simplex as + bdry_list in new_data[d], and returns bdry_list. + + If the simplex is in the dictionary old_delayed, then it + is already stored, temporarily, in new_data[d], so replace + its temporary version with bdry_list. + """ + bdry_list = [] + d = simplex.dimension() + if d > 0: + for f in faces: + if f in new_data[d-1]: + bdry_list.append(new_data[d-1].index(f)) + else: + bdry_list.append(len(new_data[d-1])) + new_delayed[f] = len(new_data[d-1]) + new_data[d-1].append(f) + bdry_list = tuple(bdry_list) + else: + bdry_list = () + if simplex in old_delayed: + idx = old_delayed[simplex] + new_data[d][idx] = bdry_list + else: + new_data[d].append(bdry_list) + return bdry_list + + new_data = {-1: ((),)} # add the empty cell + if data is None: + pass + else: + if isinstance(data, (list, tuple)): + dim = 0 + for s in data: + new_data[dim] = s + dim += 1 + elif isinstance(data, dict): + if all(isinstance(a, (int, Integer)) for a in data): + # a dictionary indexed by integers + new_data = data + if -1 not in new_data: + new_data[-1] = ((),) # add the empty cell + else: + # else a dictionary indexed by simplices + dimension = max([f.dimension() for f in data]) + old_data_by_dim = {} + for dim in range(0, dimension+1): + old_data_by_dim[dim] = [] + new_data[dim] = [] + for x in data: + if not isinstance(x, Simplex): + raise TypeError("Each key in the data dictionary must be a simplex.") + old_data_by_dim[x.dimension()].append(x) + old_delayed = {} + for dim in range(dimension, -1, -1): + new_delayed = {} + current = {} + for x in old_data_by_dim[dim]: + if x in data: + bdry = data[x] + else: + bdry = True + if isinstance(bdry, Simplex): + # case 1 + # value is a simplex, so x is glued to the old + # one along its boundary. So the boundary of + # x is the boundary of the old simplex. + if bdry in current: + # if the old simplex is there, copy its boundary + if x in old_delayed: + idx = old_delayed[x] + new_data[dim][idx] = current[bdry] + else: + new_data[dim].append(current[bdry]) + elif bdry in data: + # the old simplex has not yet been added to + # new_data, but is in the data dictionary. So + # add it. + current[bdry] = store_bdry(bdry, bdry.faces()) + new_data[dim].append(current[bdry]) + else: + raise ValueError("In the data dictionary, there is a value which is a simplex not already in the dictionary. This is not allowed.") + elif isinstance(bdry, (list, tuple)): + # case 2 + # boundary is a list or tuple + current[x] = store_bdry(x, bdry) + else: + # case 3 + # no valid boundary specified, so the default + # boundary of x should be used + if x not in current: + # x hasn't already been added, in case 1 + current[x] = store_bdry(x, x.faces()) + old_delayed = new_delayed + if dim > 0: + old_data_by_dim[dim-1].extend(old_delayed.keys()) + else: + raise ValueError("data is not a list, tuple, or dictionary") + for n in new_data: + new_data[n] = tuple(new_data[n]) + # at this point, new_data is a dictionary indexed by + # dimension, with new_data[d] a list of "simplices" in + # dimension d + if check_validity: + dim = max(new_data) + for d in range(dim, 1, -1): + for s in new_data[d]: # s is a d-simplex + faces = new_data[d-1] + for j in range(d+1): + if not all(faces[s[j]][i] == faces[s[i]][j-1] for i in range(j)): + msg = "Simplicial identity d_i d_j = d_{j-1} d_i fails" + msg += " for j=%s, in dimension %s"%(j,d) + raise ValueError(msg) + # self._cells_dict: dictionary indexed by dimension d: for + # each d, have list or tuple of simplices, and for each + # simplex, have list or tuple with its boundary (as the index + # of an element in the list of (d-1)-simplices). + self._cells_dict = new_data + # self._is_subcomplex_of: if self is a subcomplex of another + # Delta complex, record that other complex here, along with + # data relating the cells in self to the cells in the + # containing complex: for each dimension, a list of indices + # specifying, for each cell in self, which cell it corresponds + # to in the containing complex. + self._is_subcomplex_of = None + # self._complex: dictionary indexed by dimension d, base_ring, + # etc.: differential from dim d to dim d-1 in the associated + # chain complex. thus to get the differential in the cochain + # complex from dim d-1 to dim d, take the transpose of this + # one. + # self._complex = {} + + def subcomplex(self, data): + r""" + Create a subcomplex. + + :param data: a dictionary indexed by dimension or a list (or + tuple); in either case, data[n] should be the list (or tuple + or set) of the indices of the simplices to be included in + the subcomplex. + + This automatically includes all faces of the simplices in + ``data``, so you only have to specify the simplices which are + maximal with respect to inclusion. + + EXAMPLES:: + + sage: X = delta_complexes.Torus() + sage: A = X.subcomplex({2: [0]}) # one of the triangles of X + sage: X.homology(subcomplex=A) + {0: 0, 1: 0, 2: Z} + + In the following, ``line`` is a line segment and ``ends`` is + the complex consisting of its two endpoints, so the relative + homology of the two is isomorphic to the homology of a circle:: + + sage: line = delta_complexes.Simplex(1) # an edge + sage: line.cells() + {-1: ((),), 0: ((), ()), 1: ((0, 1),)} + sage: ends = line.subcomplex({0: (0, 1)}) + sage: ends.cells() + {-1: ((),), 0: ((), ())} + sage: line.homology(subcomplex=ends) + {0: 0, 1: Z} + """ + if isinstance(data, (list, tuple)): + data = dict(zip(range(len(data)), data)) + + # new_dict: dictionary for constructing the subcomplex + new_dict = {} + # new_data: dictionary of all cells in the subcomplex: store + # this with the subcomplex to make it fast to list the cells + # in self which are not in the subcomplex. + new_data = {} + # max_dim: maximum dimension of cells being added + max_dim = max(data.keys()) + # cells_to_add: in each dimension, add these cells to + # new_dict. start with the cells given in new_data and add + # faces of cells one dimension higher. + cells_to_add = data[max_dim] + cells = self.cells() + for d in range(max_dim, -1, -1): + # cells_to_add is the set of indices of d-cells in self to + # add to new_dict. + cells_to_add = sorted(cells_to_add) + # we add only these cells, so we need to translate their + # indices from, for example, (0, 1, 4, 5) to (0, 1, 2, 3). + # That is, when they appear as boundaries of (d+1)-cells, + # we need to translate their indices in each (d+1)-cell. + # Here is the key for that translation: + translate = dict(zip(cells_to_add, range(len(cells_to_add)))) + new_dict[d] = [] + d_cells = cells_to_add + new_data[d] = cells_to_add + try: + cells_to_add = set(new_data[d-1]) # begin to populate the (d-1)-cells + except KeyError: + cells_to_add = set([]) + for x in d_cells: + if d+1 in new_dict: + old = new_dict[d+1] + new_dict[d+1] = [] + for f in old: + new_dict[d+1].append(tuple([translate[n] for n in f])) + new_dict[d].append(cells[d][x]) + cells_to_add.update(cells[d][x]) + new_cells = [new_dict[n] for n in range(0, max_dim+1)] + sub = DeltaComplex(new_cells) + sub._is_subcomplex_of = {self: new_data} + return sub + + def __hash__(self): + r""" + TESTS:: + + sage: hash(delta_complexes.Sphere(2)) == hash(delta_complexes.Sphere(2)) + True + sage: hash(delta_complexes.Sphere(4)) == hash(delta_complexes.Sphere(4)) + True + """ + return hash(frozenset(self._cells_dict.items())) + + def __eq__(self, right): + r""" + Two `\Delta`-complexes are equal, according to this, if they have + the same ``_cells_dict``. + + EXAMPLES:: + + sage: S4 = delta_complexes.Sphere(4) + sage: S2 = delta_complexes.Sphere(2) + sage: S4 == S2 + False + sage: newS2 = DeltaComplex({Simplex(2):True, Simplex([8,12,17]): Simplex(2)}) + sage: newS2 == S2 + True + """ + return self._cells_dict == right._cells_dict + + def __ne__(self, other): + r""" + Return ``True`` if ``self`` and ``other`` are not equal. + + EXAMPLES:: + + sage: S4 = delta_complexes.Sphere(4) + sage: S2 = delta_complexes.Sphere(2) + sage: S4 != S2 + True + sage: newS2 = DeltaComplex({Simplex(2):True, Simplex([8,12,17]): Simplex(2)}) + sage: newS2 != S2 + False + """ + return not self.__eq__(other) + + def cells(self, subcomplex=None): + r""" + The cells of this `\Delta`-complex. + + :param subcomplex: a subcomplex of this complex + :type subcomplex: optional, default None + + The cells of this `\Delta`-complex, in the form of a dictionary: + the keys are integers, representing dimension, and the value + associated to an integer d is the list of d-cells. Each + d-cell is further represented by a list, the ith entry of + which gives the index of its ith face in the list of + (d-1)-cells. + + If the optional argument ``subcomplex`` is present, then + "return only the faces which are *not* in the subcomplex". To + preserve the indexing, which is necessary to compute the + relative chain complex, this actually replaces the faces in + ``subcomplex`` with ``None``. + + EXAMPLES:: + + sage: S2 = delta_complexes.Sphere(2) + sage: S2.cells() + {-1: ((),), + 0: ((), (), ()), + 1: ((0, 1), (0, 2), (1, 2)), + 2: ((0, 1, 2), (0, 1, 2))} + sage: A = S2.subcomplex({1: [0,2]}) # one edge + sage: S2.cells(subcomplex=A) + {-1: (None,), + 0: (None, None, None), + 1: (None, (0, 2), None), + 2: ((0, 1, 2), (0, 1, 2))} + """ + cells = self._cells_dict.copy() + if subcomplex is None: + return cells + if subcomplex._is_subcomplex_of is None or self not in subcomplex._is_subcomplex_of: + if subcomplex == self: + for d in range(-1, max(cells.keys())+1): + l = len(cells[d]) + cells[d] = [None]*l # get rid of all cells + return cells + else: + raise ValueError("This is not a subcomplex of self.") + else: + subcomplex_cells = subcomplex._is_subcomplex_of[self] + for d in range(0, max(subcomplex_cells.keys())+1): + L = list(cells[d]) + for c in subcomplex_cells[d]: + L[c] = None + cells[d] = tuple(L) + cells[-1] = (None,) + return cells + + def chain_complex(self, subcomplex=None, augmented=False, + verbose=False, check=False, dimensions=None, + base_ring=ZZ, cochain=False): + r""" + The chain complex associated to this `\Delta`-complex. + + :param dimensions: if None, compute the chain complex in all + dimensions. If a list or tuple of integers, compute the + chain complex in those dimensions, setting the chain groups + in all other dimensions to zero. NOT IMPLEMENTED YET: this + function always returns the entire chain complex + :param base_ring: commutative ring + :type base_ring: optional, default ZZ + :param subcomplex: a subcomplex of this simplicial complex. + Compute the chain complex relative to this subcomplex. + :type subcomplex: optional, default empty + :param augmented: If True, return the augmented chain complex + (that is, include a class in dimension `-1` corresponding + to the empty cell). This is ignored if ``dimensions`` is + specified or if ``subcomplex`` is nonempty. + :type augmented: boolean; optional, default False + :param cochain: If True, return the cochain complex (that is, + the dual of the chain complex). + :type cochain: boolean; optional, default False + :param verbose: If True, print some messages as the chain + complex is computed. + :type verbose: boolean; optional, default False + :param check: If True, make sure that the chain complex + is actually a chain complex: the differentials are + composable and their product is zero. + :type check: boolean; optional, default False + + .. note:: + + If subcomplex is nonempty, then the argument ``augmented`` + has no effect: the chain complex relative to a nonempty + subcomplex is zero in dimension `-1`. + + EXAMPLES:: + + sage: circle = delta_complexes.Sphere(1) + sage: circle.chain_complex() + Chain complex with at most 2 nonzero terms over Integer Ring + sage: circle.chain_complex()._latex_() + '\\Bold{Z}^{1} \\xrightarrow{d_{1}} \\Bold{Z}^{1}' + sage: circle.chain_complex(base_ring=QQ, augmented=True) + Chain complex with at most 3 nonzero terms over Rational Field + sage: circle.homology(dim=1) + Z + sage: circle.cohomology(dim=1) + Z + sage: T = delta_complexes.Torus() + sage: T.chain_complex(subcomplex=T) + Trivial chain complex over Integer Ring + sage: T.homology(subcomplex=T, algorithm='no_chomp') + {0: 0, 1: 0, 2: 0} + sage: A = T.subcomplex({2: [1]}) # one of the two triangles forming T + sage: T.chain_complex(subcomplex=A) + Chain complex with at most 1 nonzero terms over Integer Ring + sage: T.homology(subcomplex=A) + {0: 0, 1: 0, 2: Z} + """ + if subcomplex is not None: + # relative chain complex, so don't augment the chain complex + augmented = False + + differentials = {} + if augmented: + empty_simplex = 1 # number of (-1)-dimensional simplices + else: + empty_simplex = 0 + vertices = self.n_cells(0, subcomplex=subcomplex) + old = vertices + old_real = [x for x in old if x is not None] # get rid of faces not in subcomplex + n = len(old_real) + differentials[0] = matrix(base_ring, empty_simplex, n, n*empty_simplex*[1]) + # current is list of simplices in dimension dim + # current_real is list of simplices in dimension dim, with None filtered out + # old is list of simplices in dimension dim-1 + # old_real is list of simplices in dimension dim-1, with None filtered out + for dim in range(1,self.dimension()+1): + current = list(self.n_cells(dim, subcomplex=subcomplex)) + current_real = [x for x in current if x is not None] + i = 0 + i_real = 0 + translate = {} + for s in old: + if s is not None: + translate[i] = i_real + i_real += 1 + i += 1 + mat_dict = {} + col = 0 + for s in current_real: + sign = 1 + for row in s: + if old[row] is not None: + actual_row = translate[row] + if (actual_row,col) in mat_dict: + mat_dict[(actual_row,col)] += sign + else: + mat_dict[(actual_row,col)] = sign + sign *= -1 + col += 1 + differentials[dim] = matrix(base_ring, len(old_real), len(current_real), mat_dict) + old = current + old_real = current_real + if cochain: + cochain_diffs = {} + for dim in differentials: + cochain_diffs[dim-1] = differentials[dim].transpose() + return ChainComplex(data=cochain_diffs, degree=1, + base_ring=base_ring, check=check) + else: + return ChainComplex(data=differentials, degree=-1, + base_ring=base_ring, check=check) + + def alexander_whitney(self, cell, dim_left): + r""" + Subdivide ``cell`` in this `\Delta`-complex into a pair of + simplices. + + For an abstract simplex with vertices `v_0`, `v_1`, ..., + `v_n`, then subdivide it into simplices `(v_0, v_1, ..., + v_{dim_left})` and `(v_{dim_left}, v_{dim_left + 1}, ..., + v_n)`. In a `\Delta`-complex, instead take iterated faces: + take top faces to get the left factor, take bottom faces to + get the right factor. + + INPUT: + + - ``cell`` -- a simplex in this complex, given as a pair + ``(idx, tuple)``, where ``idx`` is its index in the list of + cells in the given dimension, and ``tuple`` is the tuple of + its faces + + - ``dim_left`` -- integer between 0 and one more than the + dimension of this simplex + + OUTPUT: a list containing just the triple ``(1, left, + right)``, where ``left`` and ``right`` are the two cells + described above, each given as pairs ``(idx, tuple)``. + + EXAMPLES:: + + sage: X = delta_complexes.Torus() + sage: X.n_cells(2) + [(1, 2, 0), (0, 2, 1)] + sage: X.alexander_whitney((0, (1, 2, 0)), 1) + [(1, (0, (0, 0)), (1, (0, 0)))] + sage: X.alexander_whitney((0, (1, 2, 0)), 0) + [(1, (0, ()), (0, (1, 2, 0)))] + sage: X.alexander_whitney((1, (0, 2, 1)), 2) + [(1, (1, (0, 2, 1)), (0, ()))] + """ + dim = len(cell[1]) - 1 + left_cell = cell[1] + idx_l = cell[0] + for i in range(dim, dim_left, -1): + idx_l = left_cell[i] + left_cell = self.n_cells(i-1)[idx_l] + right_cell = cell[1] + idx_r = cell[0] + for i in range(dim, dim - dim_left, -1): + idx_r = right_cell[0] + right_cell = self.n_cells(i-1)[idx_r] + return [(ZZ.one(), (idx_l, left_cell), (idx_r, right_cell))] + + def n_skeleton(self, n): + r""" + The n-skeleton of this `\Delta`-complex. + + :param n: dimension + :type n: non-negative integer + + EXAMPLES:: + + sage: S3 = delta_complexes.Sphere(3) + sage: S3.n_skeleton(1) # 1-skeleton of a tetrahedron + Delta complex with 4 vertices and 11 simplices + sage: S3.n_skeleton(1).dimension() + 1 + sage: S3.n_skeleton(1).homology() + {0: 0, 1: Z x Z x Z} + """ + if n >= self.dimension(): + return self + else: + data = [] + for d in range(n+1): + data.append(self._cells_dict[d]) + return DeltaComplex(data) + + def graph(self): + r""" + The 1-skeleton of this `\Delta`-complex as a graph. + + EXAMPLES:: + + sage: T = delta_complexes.Torus() + sage: T.graph() + Looped multi-graph on 1 vertex + sage: S = delta_complexes.Sphere(2) + sage: S.graph() + Graph on 3 vertices + sage: delta_complexes.Simplex(4).graph() == graphs.CompleteGraph(5) + True + """ + data = {} + for vertex in range(len(self.n_cells(0))): + data[vertex] = [] + for edge in self.n_cells(1): + data[edge[0]].append(edge[1]) + return Graph(data) + + def join(self, other): + r""" + The join of this `\Delta`-complex with another one. + + :param other: another `\Delta`-complex (the right-hand + factor) + :return: the join ``self * other`` + + The join of two `\Delta`-complexes `S` and `T` is the + `\Delta`-complex `S*T` with simplices of the form `[v_0, ..., + v_k, w_0, ..., w_n]` for all simplices `[v_0, ..., v_k]` in + `S` and `[w_0, ..., w_n]` in `T`. The faces are computed + accordingly: the ith face of such a simplex is either `(d_i S) + * T` if `i \leq k`, or `S * (d_{i-k-1} T)` if `i > k`. + + EXAMPLES:: + + sage: T = delta_complexes.Torus() + sage: S0 = delta_complexes.Sphere(0) + sage: T.join(S0) # the suspension of T + Delta complex with 3 vertices and 21 simplices + + Compare to simplicial complexes:: + + sage: K = delta_complexes.KleinBottle() + sage: T_simp = simplicial_complexes.Torus() + sage: K_simp = simplicial_complexes.KleinBottle() + sage: T.join(K).homology()[3] == T_simp.join(K_simp).homology()[3] # long time (3 seconds) + True + + The notation '*' may be used, as well:: + + sage: S1 = delta_complexes.Sphere(1) + sage: X = S1 * S1 # X is a 3-sphere + sage: X.homology() + {0: 0, 1: 0, 2: 0, 3: Z} + """ + data = [] + # vertices of the join: the union of the vertices. put the + # vertices of self first, then the vertices of right. + data.append(self.n_cells(0) + other.n_cells(0)) + bdries = {} + for l_idx in range(len(self.n_cells(0))): + bdries[(0,l_idx,-1,0)] = l_idx + for r_idx in range(len(other.n_cells(0))): + bdries[(-1,0,0,r_idx)] = len(self.n_cells(0)) + r_idx + # dimension of the join: + maxdim = self.dimension() + other.dimension() + 1 + # now for the d-cells, d>0: + for d in range(1,maxdim+1): + d_cells = [] + positions = {} + new_idx = 0 + for k in range(-1,d+1): + n = d-1-k + # d=n+k. need a k-cell from self and an n-cell from other + if k == -1: + left = [()] + else: + left = self.n_cells(k) + l_idx = 0 + if n == -1: + right = [()] + else: + right = other.n_cells(n) + for l in left: + r_idx = 0 + for r in right: + # store index of the new simplex in positions + positions[(k, l_idx, n, r_idx)] = new_idx + # form boundary of l*r and store it in d_cells + bdry = [] + # first faces come from left-hand factor + if k == 0: + bdry.append(bdries[(-1, 0, n, r_idx)]) + else: + for i in range(k+1): + bdry.append(bdries[(k-1, l[i], n, r_idx)]) + # remaining faces come from right-hand factor + if n == 0: + bdry.append(bdries[(k, l_idx, -1, 0)]) + else: + for i in range(n+1): + bdry.append(bdries[(k, l_idx, n-1, r[i])]) + d_cells.append(tuple(bdry)) + r_idx += 1 + new_idx += 1 + l_idx += 1 + data.append(d_cells) + bdries = positions + return DeltaComplex(data) + + # Use * to mean 'join': + __mul__ = join + + def cone(self): + r""" + The cone on this `\Delta`-complex. + + The cone is the complex formed by adding a new vertex `C` and + simplices of the form `[C, v_0, ..., v_k]` for every simplex + `[v_0, ..., v_k]` in the original complex. That is, the cone + is the join of the original complex with a one-point complex. + + EXAMPLES:: + + sage: K = delta_complexes.KleinBottle() + sage: K.cone() + Delta complex with 2 vertices and 14 simplices + sage: K.cone().homology() + {0: 0, 1: 0, 2: 0, 3: 0} + """ + return self.join(delta_complexes.Simplex(0)) + + def suspension(self, n=1): + r""" + The suspension of this `\Delta`-complex. + + :param n: suspend this many times. + :type n: positive integer; optional, default 1 + + The suspension is the complex formed by adding two new + vertices `S_0` and `S_1` and simplices of the form `[S_0, v_0, + ..., v_k]` and `[S_1, v_0, ..., v_k]` for every simplex `[v_0, + ..., v_k]` in the original complex. That is, the suspension + is the join of the original complex with a two-point complex + (the 0-sphere). + + EXAMPLES:: + + sage: S = delta_complexes.Sphere(0) + sage: S3 = S.suspension(3) # the 3-sphere + sage: S3.homology() + {0: 0, 1: 0, 2: 0, 3: Z} + """ + if n<0: + raise ValueError("n must be non-negative.") + if n==0: + return self + if n==1: + return self.join(delta_complexes.Sphere(0)) + return self.suspension().suspension(int(n-1)) + + def product(self, other): + r""" + The product of this `\Delta`-complex with another one. + + :param other: another `\Delta`-complex (the right-hand + factor) + :return: the product ``self x other`` + + .. warning:: + + If ``X`` and ``Y`` are `\Delta`-complexes, then ``X*Y`` + returns their join, not their product. + + EXAMPLES:: + + sage: K = delta_complexes.KleinBottle() + sage: X = K.product(K) + sage: X.homology(1) + Z x Z x C2 x C2 + sage: X.homology(2) + Z x C2 x C2 x C2 + sage: X.homology(3) + C2 + sage: X.homology(4) + 0 + sage: X.homology(base_ring=GF(2)) + {0: Vector space of dimension 0 over Finite Field of size 2, + 1: Vector space of dimension 4 over Finite Field of size 2, + 2: Vector space of dimension 6 over Finite Field of size 2, + 3: Vector space of dimension 4 over Finite Field of size 2, + 4: Vector space of dimension 1 over Finite Field of size 2} + sage: S1 = delta_complexes.Sphere(1) + sage: K.product(S1).homology() == S1.product(K).homology() + True + sage: S1.product(S1) == delta_complexes.Torus() + True + """ + data = [] + bdries = {} + # vertices: the vertices in the product are of the form (v,w) + # for v a vertex in self, w a vertex in other + vertices = [] + l_idx = 0 + for v in self.n_cells(0): + r_idx = 0 + for w in other.n_cells(0): + # one vertex for each pair (v,w) + # store its indices in bdries; store its boundary in vertices + bdries[(0,l_idx,0,r_idx, ((0,0),))] = len(vertices) + vertices.append(()) # add new vertex (simplex with empty bdry) + r_idx += 1 + l_idx += 1 + data.append(tuple(vertices)) + # dim of the product: + maxdim = self.dimension() + other.dimension() + # d-cells, d>0: these are obtained by taking products of cells + # of dimensions k and n, where n+k >= d and n <= d, k <= d. + simplices = [] + new = {} + for d in range(1, maxdim+1): + for k in range(d+1): + for n in range(d-k,d+1): + k_idx = 0 + for k_cell in self.n_cells(k): + n_idx = 0 + for n_cell in other.n_cells(n): + # find d-dimensional faces in product of + # k_cell and n_cell. to avoid repetition, + # only look for faces which use all + # vertices of each factor: the 'path' + # corresponding to each d-cell must hit + # every row and every column in the + # lattice. (See the 'product' method for + # Simplex, as well as the function + # 'lattice_paths', in + # simplicial_complex.py.) + for path in lattice_paths(list(range(k + 1)), + list(range(n + 1)), + length=d+1): + path = tuple(path) + new[(k, k_idx, n, n_idx, path)] = len(simplices) + bdry_list = [] + for i in range(d+1): + face_path = path[:i] + path[i+1:] + if ((i0 and path[i][0] == path[i-1][0])): + # this k-simplex + k_face_idx = k_idx + k_face_dim = k + else: + # face of this k-simplex + k_face_idx = k_cell[path[i][0]] + k_face_dim = k-1 + tail = [] + for j in range(i,d): + tail.append((face_path[j][0]-1, + face_path[j][1])) + face_path = face_path[:i] + tuple(tail) + if ((i0 and path[i][1] == path[i-1][1])): + # this n-simplex + n_face_idx = n_idx + n_face_dim = n + else: + # face of this n-simplex + n_face_idx = n_cell[path[i][1]] + n_face_dim = n-1 + tail = [] + for j in range(i,d): + tail.append((face_path[j][0], + face_path[j][1]-1)) + face_path = face_path[:i] + tuple(tail) + bdry_list.append(bdries[(k_face_dim, k_face_idx, + n_face_dim, n_face_idx, + face_path)]) + simplices.append(tuple(bdry_list)) + n_idx += 1 + k_idx += 1 + # add d-simplices to data, store d-simplices in bdries, + # reset simplices + data.append(tuple(simplices)) + bdries = new + new = {} + simplices = [] + return DeltaComplex(data) + + def disjoint_union(self, right): + r""" + The disjoint union of this `\Delta`-complex with another one. + + :param right: the other `\Delta`-complex (the right-hand factor) + + EXAMPLES:: + + sage: S1 = delta_complexes.Sphere(1) + sage: S2 = delta_complexes.Sphere(2) + sage: S1.disjoint_union(S2).homology() + {0: Z, 1: Z, 2: Z} + """ + dim = max(self.dimension(), right.dimension()) + data = {} + # in dimension n, append simplices of self with simplices of + # right, but translate each entry of each right simplex: add + # len(self.n_cells(n-1)) to it + for n in range(dim, 0, -1): + data[n] = list(self.n_cells(n)) + translate = len(self.n_cells(n-1)) + for f in right.n_cells(n): + data[n].append(tuple([a+translate for a in f])) + data[0] = self.n_cells(0) + right.n_cells(0) + return DeltaComplex(data) + + def wedge(self, right): + r""" + The wedge (one-point union) of this `\Delta`-complex with + another one. + + :param right: the other `\Delta`-complex (the right-hand factor) + + .. note:: + + This operation is not well-defined if ``self`` or + ``other`` is not path-connected. + + EXAMPLES:: + + sage: S1 = delta_complexes.Sphere(1) + sage: S2 = delta_complexes.Sphere(2) + sage: S1.wedge(S2).homology() + {0: 0, 1: Z, 2: Z} + """ + data = self.disjoint_union(right).cells() + left_verts = len(self.n_cells(0)) + translate = {} + for i in range(left_verts): + translate[i] = i + translate[left_verts] = 0 + for i in range(left_verts + 1, left_verts + len(right.n_cells(0))): + translate[i] = i-1 + data[0] = data[0][:-1] + edges = [] + for e in data[1]: + edges.append([translate[a] for a in e]) + data[1] = edges + return DeltaComplex(data) + + def connected_sum(self, other): + r""" + Return the connected sum of self with other. + + :param other: another `\Delta`-complex + :return: the connected sum ``self # other`` + + .. warning:: + + This does not check that self and other are manifolds. It + doesn't even check that their facets all have the same + dimension. It just chooses top-dimensional simplices from + each complex, checks that they have the same dimension, + removes them, and glues the remaining pieces together. + Since a (more or less) random facet is chosen from each + complex, this method may return random results if applied + to non-manifolds, depending on which facet is chosen. + + ALGORITHM: + + Pick a top-dimensional simplex from each complex. Check to + see if there are any identifications on either simplex, using + the :meth:`_is_glued` method. If there are no + identifications, remove the simplices and glue the remaining + parts of complexes along their boundary. If there are + identifications on a simplex, subdivide it repeatedly (using + :meth:`elementary_subdivision`) until some piece has no + identifications. + + EXAMPLES:: + + sage: T = delta_complexes.Torus() + sage: S2 = delta_complexes.Sphere(2) + sage: T.connected_sum(S2).cohomology() == T.cohomology() + True + sage: RP2 = delta_complexes.RealProjectivePlane() + sage: T.connected_sum(RP2).homology(1) + Z x Z x C2 + sage: T.connected_sum(RP2).homology(2) + 0 + sage: RP2.connected_sum(RP2).connected_sum(RP2).homology(1) + Z x Z x C2 + """ + if not self.dimension() == other.dimension(): + raise ValueError("Complexes are not of the same dimension.") + dim = self.dimension() + # Look at the last simplex in the list of top-dimensional + # simplices for each complex. If there are identifications on + # either of these simplices, subdivide until there are no more + # identifications. + Left = self + while Left._is_glued(): + Left = Left.elementary_subdivision() + Right = other + while Right._is_glued(): + Right = Right.elementary_subdivision() + # remove last top-dimensional face from each one and glue. + data = {} + for n in Left.cells(): + data[n] = list(Left.cells()[n]) + right_cells = Right.cells() + data[dim] = data[dim][:-1] + left_simplex = Left.n_cells(dim)[-1] + right_simplex = Right.n_cells(dim)[-1] + # renaming: dictionary for translating all simplices of Right + renaming = dict(zip(right_simplex, left_simplex)) + # process_now: cells to be reindexed and added to data + process_now = right_cells[dim][:-1] + for n in range(dim, 0, -1): + # glued: dictionary of just the simplices being glued + glued = copy(renaming) + # process_later: cells one dim lower to be added to data + process_later = [] + old_idx = 0 + new_idx = len(data[n-1]) + # build 'renaming' + for s in right_cells[n-1]: + if old_idx not in renaming: + process_later.append(s) + renaming[old_idx] = new_idx + new_idx += 1 + old_idx += 1 + # reindex all simplices to be processed and add them to data + for s in process_now: + data[n].append(tuple([renaming[i] for i in s])) + # set up for next loop, one dimension down + renaming = {} + process_now = process_later + for f in glued: + renaming.update(dict(zip(right_cells[n-1][f], data[n-1][glued[f]]))) + # deal with vertices separately. we just need to add enough + # vertices: all the vertices from Right, minus the number + # being glued, which should be dim+1, the number of vertices + # in the simplex of dimension dim being glued. + for i in range(len(right_cells[0]) - dim - 1): + data[0].append(()) + return DeltaComplex(data) + + def elementary_subdivision(self, idx=-1): + r""" + Perform an "elementary subdivision" on a top-dimensional + simplex in this `\Delta`-complex. If the optional argument + ``idx`` is present, it specifies the index (in the list of + top-dimensional simplices) of the simplex to subdivide. If + not present, subdivide the last entry in this list. + + :param idx: index specifying which simplex to subdivide + :type idx: integer; optional, default -1 + :return: `\Delta`-complex with one simplex subdivided. + + *Elementary subdivision* of a simplex means replacing that + simplex with the cone on its boundary. That is, given a + `\Delta`-complex containing a `d`-simplex `S` with vertices + `v_0`, ..., `v_d`, form a new `\Delta`-complex by + + - removing `S` + - adding a vertex `w` (thought of as being in the interior of `S`) + - adding all simplices with vertices `v_{i_0}`, ..., + `v_{i_k}`, `w`, preserving any identifications present + along the boundary of `S` + + The algorithm for achieving this uses + :meth:`_epi_from_standard_simplex` to keep track of simplices + (with multiplicity) and what their faces are: this method + defines a surjection `\pi` from the standard `d`-simplex to + `S`. So first remove `S` and add a new vertex `w`, say at the + end of the old list of vertices. Then for each vertex `v` in + the standard `d`-simplex, add an edge from `\pi(v)` to `w`; + for each edge `(v_0, v_1)` in the standard `d`-simplex, add a + triangle `(\pi(v_0), \pi(v_1), w)`, etc. + + Note that given an `n`-simplex `(v_0, v_1, ..., v_n)` in the + standard `d`-simplex, the faces of the new `(n+1)`-simplex are + given by removing vertices, one at a time, from `(\pi(v_0), + ..., \pi(v_n), w)`. These are either the image of the old + `n`-simplex (if `w` is removed) or the various new + `n`-simplices added in the previous dimension. So keep track + of what's added in dimension `n` for use in computing the + faces in dimension `n+1`. + + In contrast with barycentric subdivision, note that only the + interior of `S` has been changed; this allows for subdivision + of a single top-dimensional simplex without subdividing every + simplex in the complex. + + The term "elementary subdivision" is taken from p. 112 in John + M. Lee's book [Lee2011]_. + + EXAMPLES:: + + sage: T = delta_complexes.Torus() + sage: T.n_cells(2) + [(1, 2, 0), (0, 2, 1)] + sage: T.elementary_subdivision(0) # subdivide first triangle + Delta complex with 2 vertices and 13 simplices + sage: X = T.elementary_subdivision(); X # subdivide last triangle + Delta complex with 2 vertices and 13 simplices + sage: X.elementary_subdivision() + Delta complex with 3 vertices and 19 simplices + sage: X.homology() == T.homology() + True + """ + pi = self._epi_from_standard_simplex(idx=idx) + cells_dict = {} + old_cells = self.cells() + for n in old_cells: + cells_dict[n] = list(old_cells[n]) + dim = self.dimension() + # cells of standard simplex of dimension dim + std_cells = SimplicialComplex([Simplex(dim)]).delta_complex(sort_simplices=True).cells() + # adjust zero-cells so they're distinct + std_cells[0] = tuple([[n] for n in range(dim + 1)]) + # remove the cell being subdivided + cells_dict[dim].pop(idx) + # add the new vertex "w" + cells_dict[0].append(()) + # added_cells: dict indexed by (n-1)-cells, with value the + # corresponding new n-cell. + added_cells = {(): len(cells_dict[0])-1} + for n in range(0, dim): + new_cells = {} + # for each n-cell in the standard simplex, add an + # (n+1)-cell to the subdivided complex. + try: + simplices = sorted(pi[n]) + except TypeError: + simplices = pi[n] + for simplex in simplices: + # compute the faces of the new (n+1)-cell. + cell = [] + for i in simplex: + if n > 0: + bdry = tuple(std_cells[n-1][i]) + else: + bdry = () + cell.append(added_cells[bdry]) + # last face is the image of the old simplex) + cell.append(pi[n][simplex]) + cell = tuple(cell) + cells_dict[n+1].append(cell) + new_cells[simplex] = len(cells_dict[n+1])-1 + added_cells = new_cells + return DeltaComplex(cells_dict) + + def _epi_from_standard_simplex(self, idx=-1, dim=None): + r""" + Construct an epimorphism from a standard simplex to a + top-dimensional simplex in this `\Delta`-complex. + + If the optional argument ``dim`` is not ``None``, then + construct the map to a simplex with this dimension. If the + optional argument ``idx`` is present, it specifies which + simplex to use by giving its index in the list of simplices of + the appropriate dimension; if not present, use the last + simplex in this list. + + This is used by :meth:`elementary_subdivision`. + + :param idx: index specifying which simplex to examine + :type idx: integer; optional, default -1 + :return: boolean, True if the boundary of the simplex has any + identifications + :param dim: dimension of simplex to consider + :type dim: integer; optional, default = dim of complex + + Suppose that the dimension is `d`. The map is given by a + dictionary indexed by dimension: in dimension `i`, its value + is a dictionary specifying, for each `i`-simplex in the + domain, the corresponding `i`-simplex in the codomain. The + vertices are specified as their indices in the lists of + simplices in each complex; the same goes for all of the + simplices in the codomain. The simplices of dimension 1 or + higher in the domain are listed explicitly (in the form of + entries from the output of :meth:`cells`). + + In this function, the "standard simplex" is defined to be + ``simplicial_complexes.Simplex(d).delta_complex(sort_simplices=True)``. + + EXAMPLES: + + The `\Delta`-complex model for a torus has two triangles and + three edges, but only one vertex. So a surjection from the + standard 2-simplex to either of the triangles is a bijection + in dimension 1, but in dimension 0, sends all three vertices + to the same place:: + + sage: T = delta_complexes.Torus() + sage: sorted(T._epi_from_standard_simplex()[1].items()) + [((1, 0), 1), ((2, 0), 2), ((2, 1), 0)] + sage: sorted(T._epi_from_standard_simplex()[0].items()) + [((0,), 0), ((1,), 0), ((2,), 0)] + """ + if dim is None: + dim = self.dimension() + # the output is easier to read if the entries are non-negative. + if idx == -1: + idx = len(self.n_cells(dim)) - 1 + simplex = SimplicialComplex([Simplex(dim)]).delta_complex(sort_simplices=True) + simplex_cells = simplex.cells() + self_cells = self.cells() + if dim > 0: + map = {dim: {tuple(simplex_cells[dim][0]): idx}} + else: + map = {dim: {(0,): idx}} + faces_dict = map[dim] + for n in range(dim, 0, -1): + n_cells = faces_dict + faces_dict = {} + for cell in n_cells: + if n > 1: + faces = [tuple(simplex_cells[n-1][cell[j]]) for j in range(0,n+1)] + one_cell = dict(zip(faces, self_cells[n][n_cells[cell]])) + else: + temp = dict(zip(cell, self_cells[n][n_cells[cell]])) + one_cell = {} + for j in temp: + one_cell[(j,)] = temp[j] + for j in one_cell: + if j not in faces_dict: + faces_dict[j] = one_cell[j] + map[n-1] = faces_dict + return map + + def _is_glued(self, idx=-1, dim=None): + r""" + ``True`` if there is any gluing along the boundary of a + top-dimensional simplex in this `\Delta`-complex. + + If the optional argument ``idx`` is present, it specifies + which simplex to consider by giving its index in the list of + top-dimensional simplices; if not present, look at the last + simplex in this list. If the optional argument ``dim`` is + present, it specifies the dimension of the simplex to + consider; if not present, look at a top-dimensional simplex. + + This is used by :meth:`connected_sum`. + + :param idx: index specifying which simplex to examine + :type idx: integer; optional, default -1 + :return: boolean, True if the boundary of the simplex has any + identifications + :param dim: dimension of simplex to consider + :type dim: integer; optional, default = dim of complex + + EXAMPLES:: + + sage: T = delta_complexes.Torus() + sage: T._is_glued() + True + sage: S = delta_complexes.Simplex(3) + sage: S._is_glued() + False + """ + if dim is None: + dim = self.dimension() + + simplex = self.n_cells(dim)[idx] + i = self.dimension() - 1 + i_faces = set(simplex) + # if there are enough i_faces, then no gluing is evident so far + not_glued = (len(i_faces) == binomial(dim+1, i+1)) + while not_glued and i > 0: + # count the (i-1) cells and compare to (n+1) choose i. + old_faces = i_faces + i_faces = set([]) + all_cells = self.n_cells(i) + for face in old_faces: + i_faces.update(all_cells[face]) + not_glued = (len(i_faces) == binomial(dim+1, i)) + i = i-1 + return not not_glued + + def face_poset(self): + r""" + The face poset of this `\Delta`-complex, the poset of + nonempty cells, ordered by inclusion. + + EXAMPLES:: + + sage: T = delta_complexes.Torus() + sage: T.face_poset() + Finite poset containing 6 elements + """ + from sage.combinat.posets.posets import Poset + # given the structure of self.cells(), it's easier to compute + # the dual poset, then reverse it at the end. + dim = self.dimension() + covers = {} + # store each n-simplex as a pair (n, idx). + for n in range(dim, 0, -1): + idx = 0 + for s in self.n_cells(n): + covers[(n, idx)] = list(set([(n-1, i) for i in s])) + idx += 1 + # deal with vertices separately: they have no covers (in the + # dual poset). + idx = 0 + for s in self.n_cells(0): + covers[(0, idx)] = [] + idx += 1 + return Poset(Poset(covers).hasse_diagram().reverse()) + + # implement using the definition? the simplices are obtained by + # taking chains of inclusions of simplices, etc. have to work out + # the faces and identifications. + def barycentric_subdivision(self): + r""" + Not implemented. + + EXAMPLES:: + + sage: K = delta_complexes.KleinBottle() + sage: K.barycentric_subdivision() + Traceback (most recent call last): + ... + NotImplementedError: Barycentric subdivisions are not implemented for Delta complexes. + """ + raise NotImplementedError("Barycentric subdivisions are not implemented for Delta complexes.") + + def n_chains(self, n, base_ring=None, cochains=False): + r""" + Return the free module of chains in degree ``n`` over ``base_ring``. + + INPUT: + + - ``n`` -- integer + - ``base_ring`` -- ring (optional, default `\ZZ`) + - ``cochains`` -- boolean (optional, default ``False``); if + ``True``, return cochains instead + + Since the list of `n`-cells for a `\Delta`-complex may have + some ambiguity -- for example, the list of edges may look like + ``[(0, 0), (0, 0), (0, 0)]`` if each edge starts and ends at + vertex 0 -- we record the indices of the cells along with + their tuples. So the basis of chains in such a case would look + like ``[(0, (0, 0)), (1, (0, 0)), (2, (0, 0))]``. + + The only difference between chains and cochains is notation: + the dual cochain to the chain basis element ``b`` is written + as ``\chi_b``. + + EXAMPLES:: + + sage: T = delta_complexes.Torus() + sage: T.n_chains(1, QQ) + Free module generated by {(0, (0, 0)), (1, (0, 0)), (2, (0, 0))} over Rational Field + sage: list(T.n_chains(1, QQ, cochains=False).basis()) + [(0, (0, 0)), (1, (0, 0)), (2, (0, 0))] + sage: list(T.n_chains(1, QQ, cochains=True).basis()) + [\chi_(0, (0, 0)), \chi_(1, (0, 0)), \chi_(2, (0, 0))] + """ + n_cells = tuple(enumerate(self.n_cells(n))) + if cochains: + return Cochains(self, n, n_cells, base_ring) + else: + return Chains(self, n, n_cells, base_ring) + + # the second barycentric subdivision is a simplicial complex. implement this somehow? +# def simplicial_complex(self): +# X = self.barycentric_subdivision().barycentric_subdivision() +# find facets of X and return SimplicialComplex(facets) + + # This is cached for speed reasons: it can be very slow to run + # this function. + @cached_method + def algebraic_topological_model(self, base_ring=None): + r""" + Algebraic topological model for this `\Delta`-complex with + coefficients in ``base_ring``. + + The term "algebraic topological model" is defined by Pilarczyk + and Réal [PR2015]_. + + INPUT: + + - ``base_ring`` - coefficient ring (optional, default + ``QQ``). Must be a field. + + Denote by `C` the chain complex associated to this + `\Delta`-complex. The algebraic topological model is a chain complex + `M` with zero differential, with the same homology as `C`, + along with chain maps `\pi: C \to M` and `\iota: M \to C` + satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic + to `1_C`. The chain homotopy `\phi` must satisfy + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Such a chain homotopy is called a *chain contraction*. + + OUTPUT: a pair consisting of + + - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and + `\iota` + - the chain complex `M` + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: RP2 = delta_complexes.RealProjectivePlane() + sage: phi, M = RP2.algebraic_topological_model(GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = delta_complexes.Torus() + sage: phi, M = T.algebraic_topological_model(QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + """ + from sage.homology.algebraic_topological_model import algebraic_topological_model_delta_complex + if base_ring is None: + base_ring = QQ + return algebraic_topological_model_delta_complex(self, base_ring) + + def _string_constants(self): + r""" + Tuple containing the name of the type of complex, and the + singular and plural of the name of the cells from which it is + built. This is used in constructing the string representation. + + EXAMPLES:: + + sage: T = delta_complexes.Torus() + sage: T._string_constants() + ('Delta', 'simplex', 'simplices') + """ + return ('Delta', 'simplex', 'simplices') + + +class DeltaComplexExamples(): + r""" + Some examples of `\Delta`-complexes. + + Here are the available examples; you can also type + ``delta_complexes.`` and hit TAB to get a list:: + + Sphere + Torus + RealProjectivePlane + KleinBottle + Simplex + SurfaceOfGenus + + EXAMPLES:: + + sage: S = delta_complexes.Sphere(6) # the 6-sphere + sage: S.dimension() + 6 + sage: S.cohomology(6) + Z + sage: delta_complexes.Torus() == delta_complexes.Sphere(3) + False + """ + + def Sphere(self,n): + r""" + A `\Delta`-complex representation of the `n`-dimensional sphere, + formed by gluing two `n`-simplices along their boundary, + except in dimension 1, in which case it is a single 1-simplex + starting and ending at the same vertex. + + :param n: dimension of the sphere + + EXAMPLES:: + + sage: delta_complexes.Sphere(4).cohomology(4, base_ring=GF(3)) + Vector space of dimension 1 over Finite Field of size 3 + """ + if n == 1: + return DeltaComplex([ [()], [(0, 0)] ]) + else: + return DeltaComplex({Simplex(n): True, Simplex(range(1,n+2)): Simplex(n)}) + + def Torus(self): + r""" + A `\Delta`-complex representation of the torus, consisting of one + vertex, three edges, and two triangles. + + .. image:: ../../media/torus.png + + EXAMPLES:: + + sage: delta_complexes.Torus().homology(1) + Z x Z + """ + return DeltaComplex(( ((),), ((0,0), (0,0), (0,0)), ((1,2,0), (0, 2, 1)))) + + def RealProjectivePlane(self): + r""" + A `\Delta`-complex representation of the real projective plane, + consisting of two vertices, three edges, and two triangles. + + .. image:: ../../media/rp2.png + + EXAMPLES:: + + sage: P = delta_complexes.RealProjectivePlane() + sage: P.cohomology(1) + 0 + sage: P.cohomology(2) + C2 + sage: P.cohomology(dim=1, base_ring=GF(2)) + Vector space of dimension 1 over Finite Field of size 2 + sage: P.cohomology(dim=2, base_ring=GF(2)) + Vector space of dimension 1 over Finite Field of size 2 + """ + return DeltaComplex(( ((), ()), ((1,0), (1,0), (0,0)), ((1,0,2), (0,1,2)))) + + def KleinBottle(self): + r""" + A `\Delta`-complex representation of the Klein bottle, consisting + of one vertex, three edges, and two triangles. + + .. image:: ../../media/klein.png + + EXAMPLES:: + + sage: delta_complexes.KleinBottle() + Delta complex with 1 vertex and 7 simplices + """ + return DeltaComplex(( ((),), ((0,0), (0,0), (0,0)), ((1,2,0), (0, 1, 2)))) + + def Simplex(self, n): + r""" + A `\Delta`-complex representation of an `n`-simplex, + consisting of a single `n`-simplex and its faces. (This is + the same as the simplicial complex representation available by + using ``simplicial_complexes.Simplex(n)``.) + + EXAMPLES:: + + sage: delta_complexes.Simplex(3) + Delta complex with 4 vertices and 16 simplices + """ + return DeltaComplex({Simplex(n): True}) + + def SurfaceOfGenus(self, g, orientable=True): + r""" + A surface of genus g as a `\Delta`-complex. + + :param g: the genus + :type g: non-negative integer + :param orientable: whether the surface should be orientable + :type orientable: bool, optional, default ``True`` + + In the orientable case, return a sphere if `g` is zero, and + otherwise return a `g`-fold connected sum of a torus with + itself. + + In the non-orientable case, raise an error if `g` is zero. If + `g` is positive, return a `g`-fold connected sum of a + real projective plane with itself. + + EXAMPLES:: + + sage: delta_complexes.SurfaceOfGenus(1, orientable=False) + Delta complex with 2 vertices and 8 simplices + sage: delta_complexes.SurfaceOfGenus(3, orientable=False).homology(1) + Z x Z x C2 + sage: delta_complexes.SurfaceOfGenus(3, orientable=False).homology(2) + 0 + + Compare to simplicial complexes:: + + sage: delta_g4 = delta_complexes.SurfaceOfGenus(4) + sage: delta_g4.f_vector() + [1, 3, 27, 18] + sage: simpl_g4 = simplicial_complexes.SurfaceOfGenus(4) + sage: simpl_g4.f_vector() + [1, 19, 75, 50] + sage: delta_g4.homology() == simpl_g4.homology() + True + """ + try: + g = Integer(g) + except TypeError: + raise ValueError("genus must be a non-negative integer") + if g < 0: + raise ValueError("genus must be a non-negative integer") + if g == 0: + if not orientable: + raise ValueError("no non-orientable surface of genus zero") + else: + return delta_complexes.Sphere(2) + if orientable: + X = delta_complexes.Torus() + else: + X = delta_complexes.RealProjectivePlane() + S = X + for i in range(g-1): + S = S.connected_sum(X) + return S + +delta_complexes = DeltaComplexExamples() diff --git a/src/sage/topology/simplicial_complex.py b/src/sage/topology/simplicial_complex.py new file mode 100644 index 00000000000..b607728e9ba --- /dev/null +++ b/src/sage/topology/simplicial_complex.py @@ -0,0 +1,4787 @@ +# -*- coding: utf-8 -*- +r""" +Finite simplicial complexes + +AUTHORS: + +- John H. Palmieri (2009-04) + +- D. Benjamin Antieau (2009-06): added is_connected, generated_subcomplex, + remove_facet, and is_flag_complex methods; + cached the output of the graph() method. + +- Travis Scrimshaw (2012-08-17): Made :class:`SimplicialComplex` have an + immutable option, and added ``__hash__()`` function which checks to make + sure it is immutable. Made :meth:`SimplicialComplex.remove_face()` into a + mutator. Deprecated the ``vertex_set`` parameter. + +- Christian Stump (2011-06): implementation of is_cohen_macaulay + +- Travis Scrimshaw (2013-02-16): Allowed :class:`SimplicialComplex` to make + mutable copies. + +- Simon King (2014-05-02): Let simplicial complexes be objects of the + category of simplicial complexes. + +- Jeremy Martin (2016-06-02): added cone_vertices, decone, is_balanced, + is_partitionable, intersection methods + +This module implements the basic structure of finite simplicial +complexes. Given a set `V` of "vertices", a simplicial complex on `V` +is a collection `K` of subsets of `V` satisfying the condition that if +`S` is one of the subsets in `K`, then so is every subset of `S`. The +subsets `S` are called the 'simplices' of `K`. + +.. NOTE:: + + In Sage, the elements of the vertex set are determined + automatically: `V` is defined to be the union of the sets in + `K`. So in Sage's implementation of simplicial complexes, every + vertex is included in some face. + +A simplicial complex `K` can be viewed as a purely combinatorial +object, as described above, but it also gives rise to a topological +space `|K|` (its *geometric realization*) as follows: first, the +points of `V` should be in general position in euclidean space. Next, +if `\{v\}` is in `K`, then the vertex `v` is in `|K|`. If `\{v, w\}` +is in `K`, then the line segment from `v` to `w` is in `|K|`. If `\{u, +v, w\}` is in `K`, then the triangle with vertices `u`, `v`, and `w` +is in `|K|`. In general, `|K|` is the union of the convex hulls of +simplices of `K`. Frequently, one abuses notation and uses `K` to +denote both the simplicial complex and the associated topological +space. + +.. image:: ../../media/simplices.png + +For any simplicial complex `K` and any commutative ring `R` there is +an associated chain complex, with differential of degree `-1`. The +`n^{th}` term is the free `R`-module with basis given by the +`n`-simplices of `K`. The differential is determined by its value on +any simplex: on the `n`-simplex with vertices `(v_0, v_1, ..., v_n)`, +the differential is the alternating sum with `i^{th}` summand `(-1)^i` +multiplied by the `(n-1)`-simplex obtained by omitting vertex `v_i`. + +In the implementation here, the vertex set must be finite. To define a +simplicial complex, specify its *facets*: the maximal subsets (with +respect to inclusion) of the vertex set belonging to `K`. Each facet +can be specified as a list, a tuple, or a set. + +.. NOTE:: + + This class derives from + :class:`~sage.topology.cell_complex.GenericCellComplex`, and so + inherits its methods. Some of those methods are not listed here; + see the :mod:`Generic Cell Complex ` + page instead. + +EXAMPLES:: + + sage: SimplicialComplex([[1], [3, 7]]) + Simplicial complex with vertex set (1, 3, 7) and facets {(1,), (3, 7)} + sage: SimplicialComplex() # the empty simplicial complex + Simplicial complex with vertex set () and facets {()} + sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [3,0]]) + sage: X + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 3), (1, 2), (2, 3)} + +Sage can perform a number of operations on simplicial complexes, such +as the join and the product, and it can also compute homology:: + + sage: S = SimplicialComplex([[0,1], [1,2], [0,2]]) # circle + sage: T = S.product(S) # torus + sage: T + Simplicial complex with 9 vertices and 18 facets + sage: T.homology() # this computes reduced homology + {0: 0, 1: Z x Z, 2: Z} + sage: T.euler_characteristic() + 0 + +Sage knows about some basic combinatorial data associated to a +simplicial complex:: + + sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [0,3]]) + sage: X.f_vector() + [1, 4, 4] + sage: X.face_poset() + Finite poset containing 8 elements + sage: x0, x1, x2, x3 = X.stanley_reisner_ring().gens() + sage: x0*x2 == x1*x3 == 0 + True + sage: X.is_pure() + True + +Mutability (see :trac:`12587`):: + + sage: S = SimplicialComplex([[1,4], [2,4]]) + sage: S.add_face([1,3]) + sage: S.remove_face([1,3]); S + Simplicial complex with vertex set (1, 2, 3, 4) and facets {(3,), (1, 4), (2, 4)} + sage: hash(S) + Traceback (most recent call last): + ... + ValueError: This simplicial complex must be immutable. Call set_immutable(). + sage: S = SimplicialComplex([[1,4], [2,4]]) + sage: S.set_immutable() + sage: S.add_face([1,3]) + Traceback (most recent call last): + ... + ValueError: This simplicial complex is not mutable + sage: S.remove_face([1,3]) + Traceback (most recent call last): + ... + ValueError: This simplicial complex is not mutable + sage: hash(S) == hash(S) + True + + sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) + sage: hash(S2) == hash(S) + True + +We can also make mutable copies of an immutable simplicial complex +(see :trac:`14142`):: + + sage: S = SimplicialComplex([[1,4], [2,4]]) + sage: S.set_immutable() + sage: T = copy(S) + sage: T.is_mutable() + True + sage: S == T + True +""" +from operator import index as PyNumber_Index + +# possible future directions for SimplicialComplex: +# +# make compatible with GAP (see http://linalg.org/gap.html) +# compare to and make compatible with polymake +# see Macaulay: http://www.math.uiuc.edu/Macaulay2/doc/Macaulay2-1.1/share/doc/Macaulay2/SimplicialComplexes/html/___Simplicial__Complex.html; compare performance and make compatible +# should + have any meaning? +# cohomology: compute cup products (and Massey products?) + +from copy import copy +from sage.misc.lazy_import import lazy_import +from sage.misc.cachefunc import cached_method +from .cell_complex import GenericCellComplex +from sage.structure.sage_object import SageObject +from sage.structure.parent import Parent +from sage.rings.integer import Integer +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.sets.set import Set +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.structure.category_object import normalize_names +from sage.misc.latex import latex +from sage.misc.misc import union +from sage.matrix.constructor import matrix +from sage.homology.chain_complex import ChainComplex +from sage.graphs.graph import Graph +from functools import reduce, total_ordering +from itertools import combinations +lazy_import('sage.categories.simplicial_complexes', 'SimplicialComplexes') + + +def lattice_paths(t1, t2, length=None): + r""" + Given lists (or tuples or ...) ``t1`` and ``t2``, think of them as + labelings for vertices: ``t1`` labeling points on the x-axis, + ``t2`` labeling points on the y-axis, both increasing. Return the + list of rectilinear paths along the grid defined by these points + in the plane, starting from ``(t1[0], t2[0])``, ending at + ``(t1[last], t2[last])``, and at each grid point, going either + right or up. See the examples. + + :param t1: labeling for vertices + :param t2: labeling for vertices + :param length: if not ``None``, then an integer, the length of the desired + path. + :type length: integer or ``None``; optional, default ``None`` + :type t1: list, other iterable + :type t2: list, other iterable + :return: list of lists of vertices making up the paths as described above + :rtype: list of lists + + This is used when triangulating the product of simplices. The + optional argument ``length`` is used for `\Delta`-complexes, to + specify all simplices in a product: in the triangulation of a + product of two simplices, there is a `d`-simplex for every path of + length `d+1` in the lattice. The path must start at the bottom + left and end at the upper right, and it must use at least one + point in each row and in each column, so if ``length`` is too + small, there will be no paths. + + EXAMPLES:: + + sage: from sage.topology.simplicial_complex import lattice_paths + sage: lattice_paths([0,1,2], [0,1,2]) + [[(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)], + [(0, 0), (0, 1), (1, 1), (1, 2), (2, 2)], + [(0, 0), (1, 0), (1, 1), (1, 2), (2, 2)], + [(0, 0), (0, 1), (1, 1), (2, 1), (2, 2)], + [(0, 0), (1, 0), (1, 1), (2, 1), (2, 2)], + [(0, 0), (1, 0), (2, 0), (2, 1), (2, 2)]] + sage: lattice_paths(('a', 'b', 'c'), (0, 3, 5)) + [[('a', 0), ('a', 3), ('a', 5), ('b', 5), ('c', 5)], + [('a', 0), ('a', 3), ('b', 3), ('b', 5), ('c', 5)], + [('a', 0), ('b', 0), ('b', 3), ('b', 5), ('c', 5)], + [('a', 0), ('a', 3), ('b', 3), ('c', 3), ('c', 5)], + [('a', 0), ('b', 0), ('b', 3), ('c', 3), ('c', 5)], + [('a', 0), ('b', 0), ('c', 0), ('c', 3), ('c', 5)]] + sage: lattice_paths(range(3), range(3), length=2) + [] + sage: lattice_paths(range(3), range(3), length=3) + [[(0, 0), (1, 1), (2, 2)]] + sage: lattice_paths(range(3), range(3), length=4) + [[(0, 0), (1, 1), (1, 2), (2, 2)], + [(0, 0), (0, 1), (1, 2), (2, 2)], + [(0, 0), (1, 1), (2, 1), (2, 2)], + [(0, 0), (1, 0), (2, 1), (2, 2)], + [(0, 0), (0, 1), (1, 1), (2, 2)], + [(0, 0), (1, 0), (1, 1), (2, 2)]] + """ + # Convert t1, t2 to tuples, in case they are (for example) Python 3 ranges. + t1 = tuple(t1) + t2 = tuple(t2) + if length is None: + # 0 x n (or k x 0) rectangle: + if len(t1) == 0 or len(t2) == 0: + return [[]] + # 1 x n (or k x 1) rectangle: + elif len(t1) == 1: + return [[(t1[0], w) for w in t2]] + elif len(t2) == 1: + return [[(v, t2[0]) for v in t1]] + else: + # recursive: paths in rectangle with either one fewer row + # or column, plus the upper right corner + return ([path + [(t1[-1], t2[-1])] for path + in lattice_paths(t1[:-1], t2)] + + [path + [(t1[-1], t2[-1])] for path + in lattice_paths(t1, t2[:-1])]) + else: + if length > len(t1) + len(t2) - 1: + return [] + # as above, except make sure that lengths are correct. if + # not, return an empty list. + # + # 0 x n (or k x 0) rectangle: + elif len(t1) == 0 or len(t2) == 0: + if length == 0: + return [[]] + else: + return [] + # 1 x n (or k x 1) rectangle: + elif len(t1) == 1: + if length == len(t2): + return [[(t1[0], w) for w in t2]] + else: + return [] + elif len(t2) == 1: + if length == len(t1): + return [[(v, t2[0]) for v in t1]] + else: + return [] + else: + # recursive: paths of length one fewer in rectangle with + # either one fewer row, one fewer column, or one fewer of + # each, and then plus the upper right corner + return ([path + [(t1[-1], t2[-1])] for path + in lattice_paths(t1[:-1], t2, length=length-1)] + + [path + [(t1[-1], t2[-1])] for path + in lattice_paths(t1, t2[:-1], length=length-1)] + + [path + [(t1[-1], t2[-1])] for path + in lattice_paths(t1[:-1], t2[:-1], length=length-1)]) + +def rename_vertex(n, keep, left=True): + """ + Rename a vertex: the vertices from the list ``keep`` get + relabeled 0, 1, 2, ..., in order. Any other vertex (e.g. 4) gets + renamed to by prepending an 'L' or an 'R' (thus to either 'L4' or + 'R4'), depending on whether the argument left is ``True`` or ``False``. + + :param n: a 'vertex': either an integer or a string + :param keep: a list of three vertices + :param left: if ``True``, rename for use in left factor + :type left: boolean; optional, default ``True`` + + This is used by the :meth:`~SimplicialComplex.connected_sum` method for + simplicial complexes. + + EXAMPLES:: + + sage: from sage.topology.simplicial_complex import rename_vertex + sage: rename_vertex(6, [5, 6, 7]) + 1 + sage: rename_vertex(3, [5, 6, 7, 8, 9]) + 'L3' + sage: rename_vertex(3, [5, 6, 7], left=False) + 'R3' + """ + lookup = {i:v for v,i in enumerate(keep)} + try: + return lookup[n] + except KeyError: + if left: + return "L" + str(n) + else: + return "R" + str(n) + +@total_ordering +class Simplex(SageObject): + """ + Define a simplex. + + Topologically, a simplex is the convex hull of a collection of + vertices in general position. Combinatorially, it is defined just + by specifying a set of vertices. It is represented in Sage by the + tuple of the vertices. + + :param X: set of vertices + :type X: integer, list, other iterable + :return: simplex with those vertices + + ``X`` may be a non-negative integer `n`, in which case the + simplicial complex will have `n+1` vertices `(0, 1, ..., n)`, or + it may be anything which may be converted to a tuple, in which + case the vertices will be that tuple. In the second case, each + vertex must be hashable, so it should be a number, a string, or a + tuple, for instance, but not a list. + + .. WARNING:: + + The vertices should be distinct, and no error checking is done + to make sure this is the case. + + EXAMPLES:: + + sage: Simplex(4) + (0, 1, 2, 3, 4) + sage: Simplex([3, 4, 1]) + (3, 4, 1) + sage: X = Simplex((3, 'a', 'vertex')); X + (3, 'a', 'vertex') + sage: X == loads(dumps(X)) + True + + Vertices may be tuples but not lists:: + + sage: Simplex([(1,2), (3,4)]) + ((1, 2), (3, 4)) + sage: Simplex([[1,2], [3,4]]) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'list' + """ + + def __init__(self, X): + """ + Define a simplex. See :class:`Simplex` for full documentation. + + EXAMPLES:: + + sage: Simplex(2) + (0, 1, 2) + sage: Simplex(('a', 'b', 'c')) + ('a', 'b', 'c') + sage: Simplex(-1) + () + sage: Simplex(-3) + Traceback (most recent call last): + ... + ValueError: the n-simplex is only defined if n > -2 + """ + try: + N = int(X) + 1 + if N < 0: + raise ValueError('the n-simplex is only defined if n > -2') + self.__tuple = tuple(range(N)) + except TypeError: + self.__tuple = tuple(X) + self.__set = frozenset(self.__tuple) + + def tuple(self): + """ + The tuple attached to this simplex. + + EXAMPLES:: + + sage: Simplex(3).tuple() + (0, 1, 2, 3) + + Although simplices are printed as if they were tuples, they + are not the same type:: + + sage: type(Simplex(3).tuple()) + <... 'tuple'> + sage: type(Simplex(3)) + + """ + return self.__tuple + + def set(self): + """ + The frozenset attached to this simplex. + + EXAMPLES:: + + sage: Simplex(3).set() + frozenset({0, 1, 2, 3}) + """ + return self.__set + + def is_face(self, other): + """ + Return ``True`` iff this simplex is a face of other. + + EXAMPLES:: + + sage: Simplex(3).is_face(Simplex(5)) + True + sage: Simplex(5).is_face(Simplex(2)) + False + sage: Simplex(['a', 'b', 'c']).is_face(Simplex(8)) + False + """ + return self.__set.issubset(other.__set) + + def __contains__(self, x): + """ + Return ``True`` iff ``x`` is a vertex of this simplex. + + EXAMPLES:: + + sage: 3 in Simplex(5) + True + sage: 3 in Simplex(2) + False + """ + return x in self.__set + + def __getitem__(self, n): + """ + Return the `n`-th vertex in this simplex. + + EXAMPLES:: + + sage: Simplex(5)[2] + 2 + sage: Simplex(['a', 'b', 'c'])[1] + 'b' + """ + return self.__tuple[n] + + def __iter__(self): + """ + Iterator for the vertices of this simplex. + + EXAMPLES:: + + sage: [v**2 for v in Simplex(3)] + [0, 1, 4, 9] + """ + return iter(self.__tuple) + + def __add__(self, other): + """ + Simplex obtained by concatenating the underlying tuples of the + two arguments. + + :param other: another simplex + + EXAMPLES:: + + sage: Simplex((1,2,3)) + Simplex((5,6)) + (1, 2, 3, 5, 6) + """ + return Simplex(self.__tuple + other.__tuple) + + def face(self, n): + """ + The `n`-th face of this simplex. + + :param n: an integer between 0 and the dimension of this simplex + :type n: integer + :return: the simplex obtained by removing the `n`-th vertex from this + simplex + + EXAMPLES:: + + sage: S = Simplex(4) + sage: S.face(0) + (1, 2, 3, 4) + sage: S.face(3) + (0, 1, 2, 4) + """ + if n >= 0 and n <= self.dimension(): + return Simplex(self.__tuple[:n] + self.__tuple[n+1:]) + else: + raise IndexError("{} does not have an nth face for n={}".format(self, n)) + + def faces(self): + """ + The list of faces (of codimension 1) of this simplex. + + EXAMPLES:: + + sage: S = Simplex(4) + sage: S.faces() + [(1, 2, 3, 4), (0, 2, 3, 4), (0, 1, 3, 4), (0, 1, 2, 4), (0, 1, 2, 3)] + sage: len(Simplex(10).faces()) + 11 + """ + return [self.face(i) for i in range(self.dimension() + 1)] + + def dimension(self): + """ + The dimension of this simplex. + + The dimension of a simplex is the number of vertices minus 1. + + EXAMPLES:: + + sage: Simplex(5).dimension() == 5 + True + sage: Simplex(5).face(1).dimension() + 4 + """ + return len(self.__tuple) - 1 + + def is_empty(self): + """ + Return ``True`` iff this simplex is the empty simplex. + + EXAMPLES:: + + sage: [Simplex(n).is_empty() for n in range(-1,4)] + [True, False, False, False, False] + """ + return self.dimension() < 0 + + def join(self, right, rename_vertices=True): + """ + The join of this simplex with another one. + + The join of two simplices `[v_0, ..., v_k]` and `[w_0, ..., + w_n]` is the simplex `[v_0, ..., v_k, w_0, ..., w_n]`. + + :param right: the other simplex (the right-hand factor) + + :param rename_vertices: If this is ``True``, the vertices in the + join will be renamed by this formula: vertex "v" in the + left-hand factor --> vertex "Lv" in the join, vertex "w" + in the right-hand factor --> vertex "Rw" in the join. If + this is false, this tries to construct the join without + renaming the vertices; this may cause problems if the two + factors have any vertices with names in common. + + :type rename_vertices: boolean; optional, default ``True`` + + EXAMPLES:: + + sage: Simplex(2).join(Simplex(3)) + ('L0', 'L1', 'L2', 'R0', 'R1', 'R2', 'R3') + sage: Simplex(['a', 'b']).join(Simplex(['x', 'y', 'z'])) + ('La', 'Lb', 'Rx', 'Ry', 'Rz') + sage: Simplex(['a', 'b']).join(Simplex(['x', 'y', 'z']), rename_vertices=False) + ('a', 'b', 'x', 'y', 'z') + """ + if rename_vertices: + vertex_set = (["L" + str(v) for v in self] + + ["R" + str(w) for w in right]) + else: + vertex_set = self.__tuple + right.__tuple + return Simplex(vertex_set) + + def product(self, other, rename_vertices=True): + r""" + The product of this simplex with another one, as a list of simplices. + + :param other: the other simplex + + :param rename_vertices: If this is ``False``, then the vertices in + the product are the set of ordered pairs `(v,w)` where `v` + is a vertex in the left-hand factor (``self``) and `w` is + a vertex in the right-hand factor (``other``). If this is + ``True``, then the vertices are renamed as "LvRw" (e.g., the + vertex (1,2) would become "L1R2"). This is useful if you + want to define the Stanley-Reisner ring of the complex: + vertex names like (0,1) are not suitable for that, while + vertex names like "L0R1" are. + + :type rename_vertices: boolean; optional, default ``True`` + + Algorithm: see Hatcher, p. 277-278 [Hat2002]_ (who in turn refers to + Eilenberg-Steenrod, p. 68): given ``S = Simplex(m)`` and + ``T = Simplex(n)``, then `S \times T` can be + triangulated as follows: for each path `f` from `(0,0)` to + `(m,n)` along the integer grid in the plane, going up or right + at each lattice point, associate an `(m+n)`-simplex with + vertices `v_0`, `v_1`, ..., where `v_k` is the `k^{th}` vertex + in the path `f`. + + Note that there are `m+n` choose `n` such paths. Note also + that each vertex in the product is a pair of vertices `(v,w)` + where `v` is a vertex in the left-hand factor and `w` + is a vertex in the right-hand factor. + + .. NOTE:: + + This produces a list of simplices -- not a :class:`Simplex`, not + a :class:`SimplicialComplex`. + + EXAMPLES:: + + sage: len(Simplex(2).product(Simplex(2))) + 6 + sage: Simplex(1).product(Simplex(1)) + [('L0R0', 'L0R1', 'L1R1'), ('L0R0', 'L1R0', 'L1R1')] + sage: Simplex(1).product(Simplex(1), rename_vertices=False) + [((0, 0), (0, 1), (1, 1)), ((0, 0), (1, 0), (1, 1))] + """ + if not rename_vertices: + return [Simplex(x) for x in lattice_paths(self.tuple(), other.tuple())] + + answer = [] + for x in lattice_paths(self.tuple(), other.tuple()): + new = tuple(["L" + str(v) + "R" + str(w) for (v, w) in x]) + answer.append(Simplex(new)) + return answer + + def alexander_whitney(self, dim): + r""" + Subdivide this simplex into a pair of simplices. + + If this simplex has vertices `v_0`, `v_1`, ..., `v_n`, then + subdivide it into simplices `(v_0, v_1, ..., v_{dim})` and + `(v_{dim}, v_{dim + 1}, ..., v_n)`. + + INPUT: + + - ``dim`` -- integer between 0 and one more than the + dimension of this simplex + + OUTPUT: + + - a list containing just the triple ``(1, left, right)``, + where ``left`` and ``right`` are the two simplices described + above. + + This method allows one to construct a coproduct from the + `p+q`-chains to the tensor product of the `p`-chains and the + `q`-chains. The number 1 (a Sage integer) is the coefficient + of ``left tensor right`` in this coproduct. (The corresponding + formula is more complicated for the cubes that make up a + cubical complex, and the output format is intended to be + consistent for both cubes and simplices.) + + Calling this method ``alexander_whitney`` is an abuse of + notation, since the actual Alexander-Whitney map goes from + `C(X \times Y) \to C(X) \otimes C(Y)`, where `C(-)` denotes + the chain complex of singular chains, but this subdivision of + simplices is at the heart of it. + + EXAMPLES:: + + sage: s = Simplex((0,1,3,4)) + sage: s.alexander_whitney(0) + [(1, (0,), (0, 1, 3, 4))] + sage: s.alexander_whitney(2) + [(1, (0, 1, 3), (3, 4))] + """ + return [(ZZ.one(), Simplex(self.tuple()[:dim+1]), + Simplex(self.tuple()[dim:]))] + + def __eq__(self, other): + """ + Return ``True`` iff this simplex is the same as ``other``: that + is, if the vertices of the two are the same, even with a + different ordering + + :param other: the other simplex + + EXAMPLES:: + + sage: Simplex([0,1,2]) == Simplex([0,2,1]) + True + sage: Simplex([0,1,2]) == Simplex(['a','b','c']) + False + sage: Simplex([1]) < Simplex([2]) + True + sage: Simplex([1]) > Simplex([2]) + False + """ + if not isinstance(other, Simplex): + return False + return set(self) == set(other) + + def __ne__(self, other): + """ + Return ``True`` iff this simplex is not equal to ``other``. + + :param other: the other simplex + + EXAMPLES:: + + sage: Simplex([0,1,2]) != Simplex([0,2,1]) + False + sage: Simplex([0,1,2]) != Simplex(['a','b','c']) + True + """ + return not self == other + + def __lt__(self, other): + """ + Return ``True`` iff the sorted tuple for this simplex is less than + that for ``other``. + + :param other: the other simplex + + EXAMPLES:: + + sage: Simplex([1]) < Simplex([2]) + True + sage: Simplex([2,3]) < Simplex([1]) + False + sage: Simplex([0,1,2]) < Simplex([0,2,1]) + False + + Test ``@total_ordering`` by testing other comparisons:: + + sage: Simplex([0,1,2]) <= Simplex([0,2,1]) + True + sage: Simplex([1]) <= Simplex([2]) + True + sage: Simplex([2]) <= Simplex([1]) + False + sage: Simplex([0,1,2]) > Simplex([0,2,1]) + False + sage: Simplex([1]) > Simplex([2]) + False + sage: Simplex([2]) > Simplex([1]) + True + sage: Simplex([0,1,2]) > Simplex([0,2,1]) + False + sage: Simplex([0,1,2]) >= Simplex([0,2,1]) + True + sage: Simplex([1]) >= Simplex([2]) + False + sage: Simplex([2]) >= Simplex([1]) + True + """ + if not isinstance(other, Simplex): + return False + try: + return sorted(self) < sorted(other) + except TypeError: + return sorted(map(str, self)) < sorted(map(str, other)) + + def __hash__(self): + """ + Hash value for this simplex. This computes the hash value of + the Python frozenset of the underlying tuple, since this is + what's important when testing equality. + + EXAMPLES:: + + sage: Simplex([1,2,0]).__hash__() == Simplex(2).__hash__() + True + sage: Simplex([1,2,0,1,1,2]).__hash__() == Simplex(2).__hash__() + True + """ + return hash(self.__set) + + def _repr_(self): + """ + Print representation. + + EXAMPLES:: + + sage: S = Simplex(5) + sage: S._repr_() + '(0, 1, 2, 3, 4, 5)' + """ + return repr(self.__tuple) + + def _latex_(self): + r""" + LaTeX representation. + + EXAMPLES:: + + sage: Simplex(3)._latex_() + \left(0, + 1, + 2, + 3\right) + """ + return latex(self.__tuple) + +class SimplicialComplex(Parent, GenericCellComplex): + r""" + Define a simplicial complex. + + :param maximal_faces: set of maximal faces + :param from_characteristic_function: see below + :param maximality_check: see below + :type maximality_check: boolean; optional, default ``True`` + :param sort_facets: see below + :type sort_facets: dict + :param name_check: see below + :type name_check: boolean; optional, default ``False`` + :param is_mutable: Set to ``False`` to make this immutable + :type is_mutable: boolean; optional, default ``True`` + :param category: the category of the simplicial complex + :type category: category; optional, default finite simplicial complexes + :return: a simplicial complex + + ``maximal_faces`` should be a list or tuple or set (indeed, + anything which may be converted to a set) whose elements are lists + (or tuples, etc.) of vertices. Maximal faces are also known as + 'facets'. ``maximal_faces`` can also be a list containing a single + non-negative integer `n`, in which case this constructs the + simplicial complex with a single `n`-simplex as the only facet. + + Alternatively, the maximal faces can be defined from a monotone boolean + function on the subsets of a set `X`. While defining ``maximal_faces=None``, + you can thus set ``from_characteristic_function=(f,X)`` where ``X`` is the + set of points and ``f`` a boolean monotone hereditary function that accepts + a list of elements from ``X`` as input (see + :func:`~sage.combinat.subsets_hereditary.subsets_with_hereditary_property` + for more information). + + If ``maximality_check`` is ``True``, check that each maximal face is, + in fact, maximal. In this case, when producing the internal + representation of the simplicial complex, omit those that are not. + It is highly recommended that this be ``True``; various methods for + this class may fail if faces which are claimed to be maximal are + in fact not. + + ``sort_facets``: if not set to ``None``, the default, this should + be a dictionary, used for sorting the vertices in each facet. The + keys must be the vertices for the simplicial complex, and the + values should be distinct sortable objects, for example + integers. This should not need to be specified except in very + special circumstances; currently the only use in the Sage library + is when defining the product of a simplicial complex with itself: + in this case, the vertices in the product must be sorted + compatibly with the vertices in each factor so that the diagonal + map is properly defined. + + If ``name_check`` is ``True``, check the names of the vertices to see + if they can be easily converted to generators of a polynomial ring + -- use this if you plan to use the Stanley-Reisner ring for the + simplicial complex. + + EXAMPLES:: + + sage: SimplicialComplex([[1,2], [1,4]]) + Simplicial complex with vertex set (1, 2, 4) and facets {(1, 2), (1, 4)} + sage: SimplicialComplex([[0,2], [0,3], [0]]) + Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2), (0, 3)} + sage: SimplicialComplex([[0,2], [0,3], [0]], maximality_check=False) + Simplicial complex with vertex set (0, 2, 3) and facets {(0,), (0, 2), (0, 3)} + + Finally, if the first argument is a simplicial complex, return + that complex. If it is an object with a built-in conversion to + simplicial complexes (via a ``_simplicial_`` method), then the + resulting simplicial complex is returned:: + + sage: S = SimplicialComplex([[0,2], [0,3], [0,6]]) + sage: SimplicialComplex(S) == S + True + sage: Tc = cubical_complexes.Torus(); Tc + Cubical complex with 16 vertices and 64 cubes + sage: Ts = SimplicialComplex(Tc); Ts + Simplicial complex with 16 vertices and 32 facets + sage: Ts.homology() + {0: 0, 1: Z x Z, 2: Z} + + In the situation where the first argument is a simplicial complex + or another object with a built-in conversion, most of the other + arguments are ignored. The only exception is ``is_mutable``:: + + sage: S.is_mutable() + True + sage: SimplicialComplex(S, is_mutable=False).is_mutable() + False + + From a characteristic monotone boolean function, e.g. the simplicial complex + of all subsets `S\subseteq \{0,1,2,3,4\}` such that `sum(S)\leq 4`:: + + sage: SimplicialComplex(from_characteristic_function=(lambda x:sum(x)<=4, range(5))) + Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 4), (0, 1, 2), (0, 1, 3)} + + or e.g. the simplicial complex of all 168 hyperovals of the projective plane of order 4:: + + sage: l = designs.ProjectiveGeometryDesign(2,1,GF(4,name='a')) + sage: f = lambda S: not any(len(set(S).intersection(x))>2 for x in l) + sage: SimplicialComplex(from_characteristic_function=(f, l.ground_set())) + Simplicial complex with 21 vertices and 168 facets + + TESTS: + + Check that we can make mutable copies (see :trac:`14142`):: + + sage: S = SimplicialComplex([[0,2], [0,3]], is_mutable=False) + sage: S.is_mutable() + False + sage: C = copy(S) + sage: C.is_mutable() + True + sage: SimplicialComplex(S, is_mutable=True).is_mutable() + True + sage: SimplicialComplex(S, is_immutable=False).is_mutable() + True + + .. WARNING:: + + Simplicial complexes are not proper parents as they do + not possess element classes. In particular, parents are assumed + to be hashable (and hence immutable) by the coercion framework. + However this is close enough to being a parent with elements + being the faces of ``self`` that we currently allow this abuse. + """ + + def __init__(self, + maximal_faces=None, + from_characteristic_function=None, + maximality_check=True, + sort_facets=None, + name_check=False, + is_mutable=True, + is_immutable=False, + category=None): + """ + Define a simplicial complex. See ``SimplicialComplex`` for more + documentation. + + EXAMPLES:: + + sage: SimplicialComplex([[0,2], [0,3], [0]]) + Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2), (0, 3)} + sage: SimplicialComplex((('a', 'b'), ['a', 'c'], ('b', 'c'))) == SimplicialComplex((('a', 'b'), ('b', 'c'), ('a', 'c'))) + True + + TESTS:: + + sage: S = SimplicialComplex([[1,4], [2,4]]) + sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) + sage: S == S2 + True + sage: S3 = SimplicialComplex(maximal_faces=[[1,4], [2,4]]) + sage: S == S3 + True + + Test that we have fixed a problem revealed in :trac:`20718`; + see also :trac:`20720`:: + + sage: SimplicialComplex([2]) + Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + + sage: S = SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c'))) + sage: S == loads(dumps(S)) + True + + sage: TestSuite(S).run() + sage: TestSuite(S3).run() + + Test ``sort_facets``:: + + sage: S = SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c')), sort_facets=True) + Traceback (most recent call last): + ... + TypeError: sort_facets must be a dict + sage: S = SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c')), sort_facets={'a': 1, 6: 3, 'c': 2}) + Traceback (most recent call last): + ... + ValueError: the set of keys of sort_facets must equal the set of vertices + sage: S = SimplicialComplex((('a', 'b'), ('a', 'c'), ('b', 'c')), sort_facets={'a': 1, 'b': 3, 'c': 2}) + sage: S._vertex_to_index['b'] + 3 + """ + if (maximal_faces is not None and + from_characteristic_function is not None): + raise ValueError("maximal_faces and from_characteristic_function cannot be both defined") + category = SimplicialComplexes().Finite().or_subcategory(category) + Parent.__init__(self, category=category) + + C = None + vertex_set = () + if from_characteristic_function is not None: + from sage.combinat.subsets_hereditary import subsets_with_hereditary_property + f, X = from_characteristic_function + maximal_faces = subsets_with_hereditary_property(f, X) + + if maximal_faces is None: + maximal_faces = [] + elif isinstance(maximal_faces, SimplicialComplex): + C = maximal_faces + else: + try: + C = maximal_faces._simplicial_() + except AttributeError: + if not isinstance(maximal_faces, (list, tuple, Simplex)): + # Convert it into a list (in case it is an iterable) + maximal_faces = list(maximal_faces) + if maximal_faces: + vertex_set = reduce(union, maximal_faces) + if C is not None: + self._facets = list(C.facets()) + self._faces = copy(C._faces) + self._gen_dict = copy(C._gen_dict) + self._complex = copy(C._complex) + self.__contractible = copy(C.__contractible) + self.__enlarged = copy(C.__enlarged) + self._graph = copy(C._graph) + self._vertex_to_index = copy(C._vertex_to_index) + self._is_immutable = False + if not is_mutable or is_immutable: + self.set_immutable() + return + + try: + # Check whether vertex_set is an integer + n = PyNumber_Index(vertex_set) + except TypeError: + pass + else: + vertex_set = range(n + 1) + + vertices = tuple(vertex_set) + + gen_dict = {} + for v in vertices: + if name_check: + try: + if int(v) < 0: + raise ValueError("the vertex %s does not have an appropriate name" % v) + except ValueError: # v is not an integer + try: + normalize_names(1, v) + except ValueError: + raise ValueError("the vertex %s does not have an appropriate name"%v) + # build dictionary of generator names + try: + gen_dict[v] = 'x%s' % int(v) + except Exception: + gen_dict[v] = v + # build set of facets + good_faces = [] + maximal_simplices = [Simplex(f) for f in maximal_faces] + + if maximality_check: # Sorting is useful to filter maximal faces + maximal_simplices.sort(key=lambda x: x.dimension(), reverse=True) + # Translate vertices to numbers, for use in sorting + # facets. Having a consistent ordering for the vertices in + # each facet is necessary for homology computations. + if sort_facets: + if not isinstance(sort_facets, dict): + raise TypeError("sort_facets must be a dict") + if set(sort_facets.keys()) != set(vertices): + raise ValueError("the set of keys of sort_facets must equal the set of vertices") + vertex_to_index = sort_facets + else: + vertex_to_index = {v: i for i, v in enumerate(vertices)} + + for face in maximal_simplices: + # check whether each given face is actually maximal + if (maximality_check and + any(face.is_face(other) for other in good_faces)): + continue + # This sorting is crucial for homology computations: + face = Simplex(sorted(face.tuple(), key=vertex_to_index.__getitem__)) + good_faces.append(face) + + # if no maximal faces, add the empty face as a facet + if len(maximal_simplices) == 0: + good_faces.append(Simplex(-1)) + # now record the attributes for self + # self._vertex_to_index: dictionary to convert vertices to integers + self._vertex_to_index = vertex_to_index + # self._facets: unsorted list of facets + self._facets = good_faces + # self._faces: dictionary of dictionaries of faces. The main + # dictionary is keyed by subcomplexes, and each value is a + # dictionary keyed by dimension. This should be empty until + # needed -- that is, until the faces method is called + self._faces = {} + # self._gen_dict: dictionary of names for the polynomial + # generators of the Stanley-Reisner ring + self._gen_dict = gen_dict + # self._complex: dictionary indexed by dimension d, subcomplex, + # etc.: differential from dim d to dim d-1 in the associated + # chain complex. thus to get the differential in the cochain + # complex from dim d-1 to dim d, take the transpose of this + # one. + self._complex = {} + # self.__contractible: if not None, a contractible subcomplex + # of self, as found by the _contractible_subcomplex method. + self.__contractible = None + # self.__enlarged: dictionary of enlarged subcomplexes, + # indexed by subcomplexes. For use in the _enlarge_subcomplex + # method. + self.__enlarged = {} + # initialize self._graph to None. + self._graph = None + + # Handle mutability keywords + self._is_immutable = False + if not is_mutable or is_immutable: + self.set_immutable() + + def __hash__(self): + """ + Compute the hash value of ``self``. + + If this simplicial complex is immutable, it computes the hash value + based upon the facets. Otherwise it raises a ``ValueError``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,4], [2,4]]) + sage: hash(S) + Traceback (most recent call last): + ... + ValueError: This simplicial complex must be immutable. Call set_immutable(). + sage: S.set_immutable() + sage: hash(S) == hash(S) + True + sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) + sage: S == S2 + True + sage: hash(S) == hash(S2) + True + """ + if not self._is_immutable: + raise ValueError("This simplicial complex must be immutable. Call set_immutable().") + return hash(frozenset(self._facets)) + + def __eq__(self, right): + """ + Two simplicial complexes are equal iff their vertex sets are + equal and their sets of facets are equal. + + EXAMPLES:: + + sage: SimplicialComplex([[1,2], [2,3], [4]]) == SimplicialComplex([[4], [2,3], [3], [2,1]]) + True + sage: X = SimplicialComplex() + sage: X.add_face([1,3]) + sage: X == SimplicialComplex([[1,3]]) + True + """ + return isinstance(right, SimplicialComplex) and set(self._facets) == set(right._facets) + + def __ne__(self, right): + """ + Return ``True`` if ``self`` and ``right`` are not equal. + + EXAMPLES:: + + sage: SimplicialComplex([[1,2], [2,3], [4]]) != SimplicialComplex([[4], [2,3], [3], [2,1]]) + False + sage: X = SimplicialComplex() + sage: X.add_face([1,3]) + sage: X != SimplicialComplex([[1,3]]) + False + """ + return not self.__eq__(right) + + def __copy__(self): + """ + Return a mutable copy of ``self``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[0,2], [0,3]], is_mutable=False) + sage: S.is_mutable() + False + sage: C = copy(S) + sage: C.is_mutable() + True + sage: C == S + True + sage: S.is_mutable() + False + sage: T = copy(C) + sage: T == C + True + """ + return SimplicialComplex(self, is_mutable=True) + + def vertices(self): + """ + The vertex set, as a tuple, of this simplicial complex. + + EXAMPLES:: + + sage: S = SimplicialComplex([[i] for i in range(16)] + [[0,1], [1,2]]) + sage: S + Simplicial complex with 16 vertices and 15 facets + sage: sorted(S.vertices()) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + """ + return tuple(self._vertex_to_index) + + def _an_element_(self): + """ + The first facet of this complex. + + EXAMPLES:: + + sage: SimplicialComplex()._an_element_() + () + sage: simplicial_complexes.Sphere(3)._an_element_() + (0, 1, 2, 3) + """ + try: + return sorted(self.facets())[0] + except TypeError: + return self.facets()[0] + + def __contains__(self, x): + """ + True if ``x`` is a simplex which is contained in this complex. + + EXAMPLES:: + + sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) + sage: Simplex((0,2)) in K + True + sage: Simplex((1,3)) in K + False + sage: 0 in K # not a simplex + False + """ + if not isinstance(x, Simplex): + return False + dim = x.dimension() + return dim in self.faces() and x in self.faces()[dim] + + def __call__(self, simplex): + """ + If ``simplex`` is a simplex in this complex, return it. + Otherwise, raise a ``ValueError``. + + EXAMPLES:: + + sage: K = SimplicialComplex([(0,1,2), (0,2,3)]) + sage: K(Simplex((1,2))) + (1, 2) + sage: K(Simplex((0,1,3))) + Traceback (most recent call last): + ... + ValueError: the simplex is not in this complex + """ + if simplex not in self: + raise ValueError('the simplex is not in this complex') + return simplex + + def maximal_faces(self): + """ + The maximal faces (a.k.a. facets) of this simplicial complex. + + This just returns the set of facets used in defining the + simplicial complex, so if the simplicial complex was defined + with no maximality checking, none is done here, either. + + EXAMPLES:: + + sage: Y = SimplicialComplex([[0,2], [1,4]]) + sage: sorted(Y.maximal_faces()) + [(0, 2), (1, 4)] + + ``facets`` is a synonym for ``maximal_faces``:: + + sage: S = SimplicialComplex([[0,1], [0,1,2]]) + sage: S.facets() + {(0, 1, 2)} + """ + return Set(self._facets) + + facets = maximal_faces + + def faces(self, subcomplex=None): + """ + The faces of this simplicial complex, in the form of a + dictionary of sets keyed by dimension. If the optional + argument ``subcomplex`` is present, then return only the + faces which are *not* in the subcomplex. + + :param subcomplex: a subcomplex of this simplicial complex. + Return faces which are not in this subcomplex. + + :type subcomplex: optional, default ``None`` + + EXAMPLES:: + + sage: Y = SimplicialComplex([[1,2], [1,4]]) + sage: Y.faces() + {-1: {()}, 0: {(1,), (2,), (4,)}, 1: {(1, 2), (1, 4)}} + sage: L = SimplicialComplex([[1,2]]) + sage: Y.faces(subcomplex=L) + {-1: set(), 0: {(4,)}, 1: {(1, 4)}} + """ + # Make the subcomplex immutable if it is not + if subcomplex is not None and not subcomplex._is_immutable: + subcomplex = SimplicialComplex(subcomplex._facets, maximality_check=False, + is_mutable=False) + + if subcomplex not in self._faces: + # Faces is the dictionary of faces in self but not in + # subcomplex, indexed by dimension + Faces = {} + # sub_facets is the dictionary of facets in the subcomplex + sub_facets = {} + dimension = max([face.dimension() for face in self._facets]) + for i in range(-1, dimension + 1): + Faces[i] = set([]) + sub_facets[i] = set([]) + for f in self._facets: + dim = f.dimension() + Faces[dim].add(f) + if subcomplex is not None: + for g in subcomplex._facets: + dim = g.dimension() + Faces[dim].discard(g) + sub_facets[dim].add(g) + # bad_faces is the set of faces in the subcomplex in the + # current dimension + bad_faces = sub_facets[dimension] + for dim in range(dimension, -1, -1): + # bad_bdries = boundaries of bad_faces: things to be + # discarded in dim-1 + bad_bdries = sub_facets[dim-1] + for f in bad_faces: + bad_bdries.update(f.faces()) + for f in Faces[dim]: + Faces[dim-1].update(set(f.faces()).difference(bad_bdries)) + bad_faces = bad_bdries + self._faces[subcomplex] = Faces + return self._faces[subcomplex] + + def face_iterator(self, increasing=True): + """ + An iterator for the faces in this simplicial complex. + + INPUT: + + - ``increasing`` -- (optional, default ``True``) if ``True``, return + faces in increasing order of dimension, thus starting with + the empty face. Otherwise it returns faces in decreasing order of + dimension. + + .. NOTE:: + + Among the faces of a fixed dimension, there is no sorting. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: sorted(S1.face_iterator()) + [(), (0,), (0, 1), (0, 2), (1,), (1, 2), (2,)] + """ + Fs = self.faces() + dim_index = range(-1, self.dimension() + 1) + if not increasing: + dim_index = reversed(dim_index) + for i in dim_index: + for F in Fs[i]: + yield F + + cells = faces + + n_faces = GenericCellComplex.n_cells + + def is_pure(self): + """ + Return ``True`` iff this simplicial complex is pure. + + A simplicial complex is pure if and only if all of its maximal faces + have the same dimension. + + .. WARNING:: + + This may give the wrong answer if the simplicial complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: U = SimplicialComplex([[1,2], [1, 3, 4]]) + sage: U.is_pure() + False + sage: X = SimplicialComplex([[0,1], [0,2], [1,2]]) + sage: X.is_pure() + True + + Demonstration of the warning:: + + sage: S = SimplicialComplex([[0,1], [0]], maximality_check=False) + sage: S.is_pure() + False + """ + dims = [face.dimension() for face in self._facets] + return max(dims) == min(dims) + + def h_vector(self): + r""" + The `h`-vector of this simplicial complex. + + If the complex has dimension `d` and `(f_{-1}, f_0, f_1, ..., + f_d)` is its `f`-vector (with `f_{-1} = 1`, representing the + empty simplex), then the `h`-vector `(h_0, h_1, ..., h_d, + h_{d+1})` is defined by + + .. MATH:: + + \sum_{i=0}^{d+1} h_i x^{d+1-i} = \sum_{i=0}^{d+1} f_{i-1} (x-1)^{d+1-i}. + + Alternatively, + + .. MATH:: + + h_j = \sum_{i=-1}^{j-1} (-1)^{j-i-1} \binom{d-i}{j-i-1} f_i. + + EXAMPLES: + + The `f`- and `h`-vectors of the boundary of an octahedron are + computed in :wikipedia:`Simplicial_complex`:: + + sage: square = SimplicialComplex([[0,1], [1,2], [2,3], [0,3]]) + sage: S0 = SimplicialComplex([[0], [1]]) + sage: octa = square.join(S0) # boundary of an octahedron + sage: octa.f_vector() + [1, 6, 12, 8] + sage: octa.h_vector() + [1, 3, 3, 1] + """ + from sage.arith.all import binomial + d = self.dimension() + f = self.f_vector() # indexed starting at 0, since it's a Python list + h = [] + for j in range(0, d + 2): + s = 0 + for i in range(-1, j): + s += (-1)**(j-i-1) * binomial(d-i, j-i-1) * f[i+1] + h.append(s) + return h + + def g_vector(self): + r""" + The `g`-vector of this simplicial complex. + + If the `h`-vector of the complex is `(h_0, h_1, ..., h_d, + h_{d+1})` -- see :meth:`h_vector` -- then its `g`-vector + `(g_0, g_1, ..., g_{[(d+1)/2]})` is defined by `g_0 = 1` and + `g_i = h_i - h_{i-1}` for `i > 0`. + + EXAMPLES:: + + sage: S3 = simplicial_complexes.Sphere(3).barycentric_subdivision() + sage: S3.f_vector() + [1, 30, 150, 240, 120] + sage: S3.h_vector() + [1, 26, 66, 26, 1] + sage: S3.g_vector() + [1, 25, 40] + """ + d = self.dimension() + h = self.h_vector() + g = [1] + for i in range(1, (d + 1) // 2 + 1): + g.append(h[i] - h[i-1]) + return g + + def face(self, simplex, i): + """ + The `i`-th face of ``simplex`` in this simplicial complex + + INPUT: + + - ``simplex`` -- a simplex in this simplicial complex + - ``i`` -- integer + + EXAMPLES:: + + sage: S = SimplicialComplex([[0,1,4], [0,1,2]]) + sage: S.face(Simplex((0,2)), 0) + (2,) + + sage: S.face(Simplex((0,3)), 0) + Traceback (most recent call last): + ... + ValueError: this simplex is not in this simplicial complex + """ + d = simplex.dimension() + if d in self.faces() and simplex in self.faces()[d]: + return simplex.face(i) + else: + raise ValueError('this simplex is not in this simplicial complex') + + def f_triangle(self): + r""" + Compute the `f`-triangle of ``self``. + + The `f`-triangle is given by `f_{i,j}` being the number of + faces `F` of size `j` such that `i = \max_{G \subseteq F} |G|`. + + EXAMPLES:: + + sage: X = SimplicialComplex([[1,2,3], [3,4,5], [1,4], [1,5], [2,4], [2,5]]) + sage: X.f_triangle() ## this complex is not pure + [[0], + [0, 0], + [0, 0, 4], + [1, 5, 6, 2]] + + A complex is pure if and only if the last row is nonzero:: + + sage: X = SimplicialComplex([[1,2,3], [3,4,5], [1,4,5]]) + sage: X.f_triangle() + [[0], [0, 0], [0, 0, 0], [1, 5, 8, 3]] + """ + ret = [[0]*(i+1) for i in range(self.dimension() + 2)] + facets = [set(F) for F in self.facets()] + faces = self.faces() + for d in faces: + for f in faces[d]: + f = set(f) + L = [len(F) for F in facets if f.issubset(F)] + i = max(L) + ret[i][len(f)] += 1 + return ret + + def h_triangle(self): + r""" + Compute the `h`-triangle of ``self``. + + The `h`-triangle of a simplicial complex `\Delta` is given by + + .. MATH:: + + h_{i,j} = \sum_{k=0}^j (-1)^{j-k} \binom{i-k}{j-k} f_{i,k}, + + where `f_{i,k}` is the `f`-triangle of `\Delta`. + + EXAMPLES:: + + sage: X = SimplicialComplex([[1,2,3], [3,4,5], [1,4], [1,5], [2,4], [2,5]]) + sage: X.h_triangle() + [[0], + [0, 0], + [0, 0, 4], + [1, 2, -1, 0]] + """ + from sage.arith.all import binomial + ret = [[0]*(i+1) for i in range(self.dimension() + 2)] + f = self.f_triangle() + for i, row in enumerate(ret): + for j in range(i+1): + row[j] = sum((-1)**(j-k) * binomial(i-k, j-k) * f[i][k] + for k in range(j+1)) + return ret + + def flip_graph(self): + """ + If ``self`` is pure, then it returns the flip graph of ``self``, + otherwise, it returns ``None``. + + The flip graph of a pure simplicial complex is the (undirected) graph + with vertices being the facets, such that two facets are joined by + an edge if they meet in a codimension `1` face. + + The flip graph is used to detect if ``self`` is a pseudomanifold. + + EXAMPLES:: + + sage: S0 = simplicial_complexes.Sphere(0) + sage: G = S0.flip_graph() + sage: G.vertices(); G.edges(labels=False) + [(0,), (1,)] + [((0,), (1,))] + + sage: G = (S0.wedge(S0)).flip_graph() + sage: G.vertices(); G.edges(labels=False) + [(0,), ('L1',), ('R1',)] + [((0,), ('L1',)), ((0,), ('R1',)), (('L1',), ('R1',))] + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S2 = simplicial_complexes.Sphere(2) + sage: G = (S1.wedge(S1)).flip_graph() + sage: len(G.vertices()) + 6 + sage: len(G.edges()) + 10 + + sage: (S1.wedge(S2)).flip_graph() is None + True + + sage: G = S2.flip_graph() + sage: G.vertices(); G.edges(labels=False) + [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)] + [((0, 1, 2), (0, 1, 3)), + ((0, 1, 2), (0, 2, 3)), + ((0, 1, 2), (1, 2, 3)), + ((0, 1, 3), (0, 2, 3)), + ((0, 1, 3), (1, 2, 3)), + ((0, 2, 3), (1, 2, 3))] + + sage: T = simplicial_complexes.Torus() + sage: G = T.suspension(4).flip_graph() + sage: len(G.vertices()); len(G.edges(labels=False)) + 46 + 161 + """ + from collections import defaultdict + if not self.is_pure(): + return None + d = self.dimension() + Fs = self.facets() + flipG = Graph() + flipG.add_vertices(Fs) + edges = defaultdict(list) + # go through all codim 1 faces to build the edge + for F in Fs: + try: + F_tuple = sorted(F._Simplex__set) + except TypeError: + F_tuple = tuple(F._Simplex__set) + for i in range(d+1): + coF = tuple(F_tuple[:i]+F_tuple[i+1:]) + if coF in edges: + for G in edges[coF]: + flipG.add_edge((F, G)) + edges[coF].append(F) + return flipG + + def is_pseudomanifold(self): + """ + Return True if self is a pseudomanifold. + + A pseudomanifold is a simplicial complex with the following properties: + + - it is pure of some dimension `d` (all of its facets are `d`-dimensional) + - every `(d-1)`-dimensional simplex is the face of exactly two facets + - for every two facets `S` and `T`, there is a sequence of + facets + + .. MATH:: + + S = f_0, f_1, ..., f_n = T + + such that for each `i`, `f_i` and `f_{i-1}` intersect in a + `(d-1)`-simplex. + + By convention, `S^0` is the only 0-dimensional pseudomanifold. + + EXAMPLES:: + + sage: S0 = simplicial_complexes.Sphere(0) + sage: S0.is_pseudomanifold() + True + sage: (S0.wedge(S0)).is_pseudomanifold() + False + sage: S1 = simplicial_complexes.Sphere(1) + sage: S2 = simplicial_complexes.Sphere(2) + sage: (S1.wedge(S1)).is_pseudomanifold() + False + sage: (S1.wedge(S2)).is_pseudomanifold() + False + sage: S2.is_pseudomanifold() + True + sage: T = simplicial_complexes.Torus() + sage: T.suspension(4).is_pseudomanifold() + True + """ + if not self.is_pure(): + return False + d = self.dimension() + if d == 0: + return len(self.facets()) == 2 + F = self.facets() + X = self.faces()[d-1] + # is each (d-1)-simplex is the face of exactly two facets? + for s in X: + if len([a for a in [s.is_face(f) for f in F] if a]) != 2: + return False + # construct a graph with one vertex for each facet, one edge + # when two facets intersect in a (d-1)-simplex, and see + # whether that graph is connected. + return self.flip_graph().is_connected() + + def product(self, right, rename_vertices=True, is_mutable=True): + """ + The product of this simplicial complex with another one. + + :param right: the other simplicial complex (the right-hand + factor) + + :param rename_vertices: If this is False, then the vertices in + the product are the set of ordered pairs `(v,w)` where `v` + is a vertex in ``self`` and `w` is a vertex in + ``right``. If this is ``True``, then the vertices are renamed + as "LvRw" (e.g., the vertex (1,2) would become "L1R2"). + This is useful if you want to define the Stanley-Reisner + ring of the complex: vertex names like (0,1) are not + suitable for that, while vertex names like "L0R1" are. + + :type rename_vertices: boolean; optional, default ``True`` + + :param is_mutable: Determines if the output is mutable + :type is_mutable: boolean; optional, default ``True`` + + The vertices in the product will be the set of ordered pairs + `(v,w)` where `v` is a vertex in self and `w` is a vertex in + right. + + .. WARNING:: + + If ``X`` and ``Y`` are simplicial complexes, then ``X*Y`` + returns their join, not their product. + + EXAMPLES:: + + sage: S = SimplicialComplex([[0,1], [1,2], [0,2]]) # circle + sage: K = SimplicialComplex([[0,1]]) # edge + sage: Cyl = S.product(K) # cylinder + sage: sorted(Cyl.vertices()) + ['L0R0', 'L0R1', 'L1R0', 'L1R1', 'L2R0', 'L2R1'] + sage: Cyl2 = S.product(K, rename_vertices=False) + sage: sorted(Cyl2.vertices()) + [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)] + sage: T = S.product(S) # torus + sage: T + Simplicial complex with 9 vertices and 18 facets + sage: T.homology() + {0: 0, 1: Z x Z, 2: Z} + + These can get large pretty quickly:: + + sage: T = simplicial_complexes.Torus(); T + Minimal triangulation of the torus + sage: K = simplicial_complexes.KleinBottle(); K + Minimal triangulation of the Klein bottle + sage: T.product(K) # long time: 5 or 6 seconds + Simplicial complex with 56 vertices and 1344 facets + """ + facets = [] + for f in self._facets: + for g in right._facets: + facets.extend(f.product(g, rename_vertices)) + if self != right: + return SimplicialComplex(facets, is_mutable=is_mutable) + else: + # Need to sort the vertices compatibly with the sorting in + # self, so that the diagonal map is defined properly. + V = self._vertex_to_index + L = len(V) + d = {} + for v in V.keys(): + for w in V.keys(): + if rename_vertices: + d['L' + str(v) + 'R' + str(w)] = V[v] * L + V[w] + else: + d[(v,w)] = V[v] * L + V[w] + return SimplicialComplex(facets, is_mutable=is_mutable, sort_facets=d) + + def join(self, right, rename_vertices=True, is_mutable=True): + """ + The join of this simplicial complex with another one. + + The join of two simplicial complexes `S` and `T` is the + simplicial complex `S*T` with simplices of the form `[v_0, + ..., v_k, w_0, ..., w_n]` for all simplices `[v_0, ..., v_k]` in + `S` and `[w_0, ..., w_n]` in `T`. + + :param right: the other simplicial complex (the right-hand factor) + + :param rename_vertices: If this is True, the vertices in the + join will be renamed by the formula: vertex "v" in the + left-hand factor --> vertex "Lv" in the join, vertex "w" in + the right-hand factor --> vertex "Rw" in the join. If this + is false, this tries to construct the join without renaming + the vertices; this will cause problems if the two factors + have any vertices with names in common. + + :type rename_vertices: boolean; optional, default ``True`` + + :param is_mutable: Determines if the output is mutable + :type is_mutable: boolean; optional, default ``True`` + + EXAMPLES:: + + sage: S = SimplicialComplex([[0], [1]]) + sage: T = SimplicialComplex([[2], [3]]) + sage: S.join(T) + Simplicial complex with vertex set ('L0', 'L1', 'R2', 'R3') and 4 facets + sage: S.join(T, rename_vertices=False) + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2), (0, 3), (1, 2), (1, 3)} + + The notation '*' may be used, as well:: + + sage: S * S + Simplicial complex with vertex set ('L0', 'L1', 'R0', 'R1') and 4 facets + sage: S * S * S * S * S * S * S * S + Simplicial complex with 16 vertices and 256 facets + """ + facets = [] + for f in self._facets: + for g in right._facets: + facets.append(f.join(g, rename_vertices)) + return SimplicialComplex(facets, is_mutable=is_mutable) + + # Use * to mean 'join': + __mul__ = join + + def cone(self, is_mutable=True): + """ + The cone on this simplicial complex. + + :param is_mutable: Determines if the output is mutable + :type is_mutable: boolean; optional, default ``True`` + + The cone is the simplicial complex formed by adding a new + vertex `C` and simplices of the form `[C, v_0, ..., v_k]` for + every simplex `[v_0, ..., v_k]` in the original simplicial + complex. That is, the cone is the join of the original + complex with a one-point simplicial complex. + + EXAMPLES:: + + sage: S = SimplicialComplex([[0], [1]]) + sage: CS = S.cone() + sage: sorted(CS.vertices()) + ['L0', 'L1', 'R0'] + sage: len(CS.facets()) + 2 + sage: CS.facets() == set([Simplex(['L0', 'R0']), Simplex(['L1', 'R0'])]) + True + """ + return self.join(SimplicialComplex([["0"]], is_mutable=is_mutable), + rename_vertices = True) + + def suspension(self, n=1, is_mutable=True): + r""" + The suspension of this simplicial complex. + + :param n: positive integer -- suspend this many times. + + :type n: optional, default 1 + + :param is_mutable: Determines if the output is mutable + :type is_mutable: boolean; optional, default ``True`` + + The suspension is the simplicial complex formed by adding two + new vertices `S_0` and `S_1` and simplices of the form `[S_0, + v_0, ..., v_k]` and `[S_1, v_0, ..., v_k]` for every simplex + `[v_0, ..., v_k]` in the original simplicial complex. That + is, the suspension is the join of the original complex with a + two-point simplicial complex. + + If the simplicial complex `M` happens to be a pseudomanifold + (see :meth:`is_pseudomanifold`), then this instead constructs + Datta's one-point suspension (see [Dat2007]_, p. 434): + choose a vertex `u` in `M` and choose a new vertex + `w` to add. Denote the join of simplices by "`*`". The + facets in the one-point suspension are of the two forms + + - `u * \alpha` where `\alpha` is a facet of `M` not containing + `u` + + - `w * \beta` where `\beta` is any facet of `M`. + + EXAMPLES:: + + sage: S0 = SimplicialComplex([[0], [1]]) + sage: S0.suspension() == simplicial_complexes.Sphere(1) + True + sage: S3 = S0.suspension(3) # the 3-sphere + sage: S3.homology() + {0: 0, 1: 0, 2: 0, 3: Z} + + For pseudomanifolds, the complex constructed here will be + smaller than that obtained by taking the join with the + 0-sphere: the join adds two vertices, while this construction + only adds one. :: + + sage: T = simplicial_complexes.Torus() + sage: sorted(T.join(S0).vertices()) # 9 vertices + ['L0', 'L1', 'L2', 'L3', 'L4', 'L5', 'L6', 'R0', 'R1'] + sage: T.suspension().vertices() # 8 vertices + (0, 1, 2, 3, 4, 5, 6, 7) + """ + if n < 0: + raise ValueError("n must be non-negative.") + if n == 0: + return self + if n == 1: + if self.is_pseudomanifold(): + # Use one-point compactification of Datta. The + # construction is a bit slower, but the resulting + # complex is smaller. + V = self.vertices() + u = V[0] + w = 0 + while w in V: + w += 1 + w = Simplex([w]) + new_facets = [] + for f in self.facets(): + if u not in f: + new_facets.append(f.join(Simplex([u]), rename_vertices=False)) + new_facets.append(f.join(w, rename_vertices=False)) + return SimplicialComplex(new_facets) + else: + return self.join(SimplicialComplex([["0"], ["1"]], is_mutable=is_mutable), + rename_vertices = True) + return self.suspension(1, is_mutable).suspension(int(n-1), is_mutable) + + def disjoint_union(self, right, rename_vertices=True, is_mutable=True): + """ + The disjoint union of this simplicial complex with another one. + + :param right: the other simplicial complex (the right-hand factor) + + :param rename_vertices: If this is True, the vertices in the + disjoint union will be renamed by the formula: vertex "v" + in the left-hand factor --> vertex "Lv" in the disjoint + union, vertex "w" in the right-hand factor --> vertex "Rw" + in the disjoint union. If this is false, this tries to + construct the disjoint union without renaming the vertices; + this will cause problems if the two factors have any + vertices with names in common. + + :type rename_vertices: boolean; optional, default True + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S2 = simplicial_complexes.Sphere(2) + sage: S1.disjoint_union(S2).homology() + {0: Z, 1: Z, 2: Z} + """ + facets = [] + for f in self._facets: + facets.append(tuple(["L" + str(v) for v in f])) + for f in right._facets: + facets.append(tuple(["R" + str(v) for v in f])) + return SimplicialComplex(facets, is_mutable=is_mutable) + + def wedge(self, right, rename_vertices=True, is_mutable=True): + """ + The wedge (one-point union) of this simplicial complex with + another one. + + :param right: the other simplicial complex (the right-hand factor) + + :param rename_vertices: If this is ``True``, the vertices in the + wedge will be renamed by the formula: first vertex in each + are glued together and called "0". Otherwise, each vertex + "v" in the left-hand factor --> vertex "Lv" in the wedge, + vertex "w" in the right-hand factor --> vertex "Rw" in the + wedge. If this is ``False``, this tries to construct the wedge + without renaming the vertices; this will cause problems if + the two factors have any vertices with names in common. + + :type rename_vertices: boolean; optional, default ``True`` + + :param is_mutable: Determines if the output is mutable + :type is_mutable: boolean; optional, default ``True`` + + .. NOTE:: + + This operation is not well-defined if ``self`` or + ``other`` is not path-connected. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S2 = simplicial_complexes.Sphere(2) + sage: S1.wedge(S2).homology() + {0: 0, 1: Z, 2: Z} + """ + left_vertices = list(self.vertices()) + left_0 = left_vertices.pop(0) + right_vertices = list(right.vertices()) + right_0 = right_vertices.pop(0) + left_dict = {left_0: 0} + right_dict = {right_0: 0} + if rename_vertices: + facets = [] + for v in left_vertices: + left_dict[v] = "L" + str(v) + for v in right_vertices: + right_dict[v] = "R" + str(v) + + for f in self._facets: + facets.append(tuple([left_dict[v] for v in f])) + for f in right._facets: + facets.append(tuple([right_dict[v] for v in f])) + else: + facets = self._facets + right._facets + return SimplicialComplex(facets, is_mutable=is_mutable) + + def chain_complex(self, subcomplex=None, augmented=False, + verbose=False, check=False, dimensions=None, + base_ring=ZZ, cochain=False): + """ + The chain complex associated to this simplicial complex. + + :param dimensions: if ``None``, compute the chain complex in all + dimensions. If a list or tuple of integers, compute the + chain complex in those dimensions, setting the chain groups + in all other dimensions to zero. + :param base_ring: commutative ring + :type base_ring: optional, default ``ZZ`` + :param subcomplex: a subcomplex of this simplicial complex. + Compute the chain complex relative to this subcomplex. + :type subcomplex: optional, default empty + :param augmented: If ``True``, return the augmented chain complex + (that is, include a class in dimension `-1` corresponding + to the empty cell). This is ignored if ``dimensions`` is + specified. + :type augmented: boolean; optional, default ``False`` + :param cochain: If ``True``, return the cochain complex (that is, + the dual of the chain complex). + :type cochain: boolean; optional, default ``False`` + :param verbose: If ``True``, print some messages as the chain + complex is computed. + :type verbose: boolean; optional, default ``False`` + :param check: If ``True``, make sure that the chain complex + is actually a chain complex: the differentials are + composable and their product is zero. + :type check: boolean; optional, default ``False`` + + .. NOTE:: + + If subcomplex is nonempty, then the argument ``augmented`` + has no effect: the chain complex relative to a nonempty + subcomplex is zero in dimension `-1`. + + The rows and columns of the boundary matrices are indexed by + the lists given by the :meth:`_n_cells_sorted` method, which by + default are sorted. + + EXAMPLES:: + + sage: circle = SimplicialComplex([[0,1], [1,2], [0, 2]]) + sage: circle.chain_complex() + Chain complex with at most 2 nonzero terms over Integer Ring + sage: circle.chain_complex()._latex_() + '\\Bold{Z}^{3} \\xrightarrow{d_{1}} \\Bold{Z}^{3}' + sage: circle.chain_complex(base_ring=QQ, augmented=True) + Chain complex with at most 3 nonzero terms over Rational Field + """ + # initialize subcomplex + if subcomplex is None: + subcomplex = SimplicialComplex(is_mutable=False) + else: + # subcomplex is not empty, so don't augment the chain complex + augmented = False + # Use an immutable copy of the subcomplex + if subcomplex._is_immutable: + subcomplex = SimplicialComplex(subcomplex._facets, maximality_check=False, + is_mutable=False) + # now construct the range of dimensions in which to compute + if dimensions is None: + dimensions = range(self.dimension() + 1) + first = 0 + else: + augmented = False + first = dimensions[0] + dimensions = list(dimensions) + differentials = {} + # in the chain complex, compute the first dimension by hand, + # and don't cache it: it may be differ from situation to + # situation because of boundary effects. + current = None + current_dim = None + if augmented: # then first == 0 + current = self._n_cells_sorted(0, subcomplex=subcomplex) + current_dim = 0 + if cochain: + differentials[-1] = matrix(base_ring, len(current), 1, + [1]*len(current)) + else: + differentials[0] = matrix(base_ring, 1, len(current), + [1]*len(current)) + elif first == 0 and not augmented: + current = self._n_cells_sorted(0, subcomplex=subcomplex) + current_dim = 0 + if not cochain: + differentials[0] = matrix(base_ring, 0, len(current)) + else: # first > 0 + current = self._n_cells_sorted(first, subcomplex=subcomplex) + current_dim = first + if not cochain: + differentials[first] = matrix(base_ring, 0, len(current)) + for n in dimensions[1:]: + if verbose: + print(" starting dimension %s" % n) + if (n, subcomplex) in self._complex: + if cochain: + differentials[n-1] = self._complex[(n, subcomplex)].transpose().change_ring(base_ring) + mat = differentials[n-1] + else: + differentials[n] = self._complex[(n, subcomplex)].change_ring(base_ring) + mat = differentials[n] + if verbose: + print(" boundary matrix (cached): it's %s by %s." % (mat.nrows(), mat.ncols())) + else: + # 'current' is the list of faces in dimension n + # + # 'old' is a dictionary, with keys the faces in the + # previous dimension (dim n-1 for the chain complex, + # n+1 for the cochain complex), values the integers 0, + # 1, 2, ... (the index of the face). finding an entry + # in a dictionary seems to be faster than finding the + # index of an entry in a list. + if current_dim == n-1: + old = dict(zip(current, range(len(current)))) + else: + set_of_faces = self._n_cells_sorted(n-1, subcomplex=subcomplex) + old = dict(zip(set_of_faces, range(len(set_of_faces)))) + current = self._n_cells_sorted(n, subcomplex=subcomplex) + current_dim = n + # construct matrix. it is easiest to construct it as + # a sparse matrix, specifying which entries are + # nonzero via a dictionary. + matrix_data = {} + col = 0 + if len(old) and len(current): + for simplex in current: + for i in range(n + 1): + face_i = simplex.face(i) + try: + matrix_data[(old[face_i], col)] = (-1)**i + except KeyError: + pass + col += 1 + mat = matrix(ZZ, len(old), len(current), matrix_data) + if cochain: + self._complex[(n, subcomplex)] = mat + differentials[n-1] = mat.transpose().change_ring(base_ring) + else: + self._complex[(n, subcomplex)] = mat + differentials[n] = mat.change_ring(base_ring) + if verbose: + print(" boundary matrix computed: it's %s by %s." % (mat.nrows(), mat.ncols())) + # now for the cochain complex, compute the last dimension by + # hand, and don't cache it. + if cochain: + n = dimensions[-1] + 1 + if current_dim != n-1: + current = self._n_cells_sorted(n-1, subcomplex=subcomplex) + differentials[n-1] = matrix(base_ring, 0, len(current)) + # finally, return the chain complex + if cochain: + return ChainComplex(data=differentials, degree=1, + base_ring=base_ring, check=check) + else: + return ChainComplex(data=differentials, degree=-1, + base_ring=base_ring, check=check) + + def _homology_(self, dim=None, base_ring=ZZ, subcomplex=None, + cohomology=False, enlarge=True, algorithm='pari', + verbose=False, reduced=True, generators=False): + """ + The (reduced) homology of this simplicial complex. + + :param dim: If ``None``, then return the homology in every + dimension. If ``dim`` is an integer or list, return the + homology in the given dimensions. (Actually, if ``dim`` is + a list, return the homology in the range from ``min(dim)`` + to ``max(dim)``.) + + :type dim: integer or list of integers or ``None``; optional, + default ``None`` + + :param base_ring: commutative ring. Must be ``ZZ`` or a field. + + :type base_ring: optional, default ``ZZ`` + + :param subcomplex: a subcomplex of this simplicial complex. + Compute homology relative to this subcomplex. + + :type subcomplex: optional, default ``None`` + + :param cohomology: If ``True``, compute cohomology rather than + homology. + + :type cohomology: boolean; optional, default ``False`` + + :param enlarge: If ``True``, find a new subcomplex homotopy + equivalent to, and probably larger than, the given one. + + :type enlarge: boolean; optional, default ``True`` + + :param algorithm: The options are ``'auto'``, ``'dhsw'``, + ``'pari'`` or ``'no_chomp'``. If ``'auto'``, first try CHomP, + then use the Dumas, Heckenbach, Saunders, and Welker elimination + algorithm for large matrices, Pari for small ones. If + ``'no_chomp'``, then don't try CHomP, but behave the same + otherwise. If ``'pari'``, then compute elementary divisors + using Pari. If ``'dhsw'``, then use the DHSW algorithm to + compute elementary divisors. (As of this writing, ``'pari'`` + is the fastest standard option. The optional CHomP package + may be better still.) + + :type algorithm: string; optional, default ``'pari'`` + + :param verbose: If ``True``, print some messages as the homology + is computed. + + :type verbose: boolean; optional, default ``False`` + + :param reduced: If ``True``, return the reduced homology. + + :type reduced: boolean; optional, default ``True`` + + :param generators: If ``True``, return the homology groups and + also generators for them. + + :type reduced: boolean; optional, default ``False`` + + Algorithm: if ``generators`` is ``True``, directly compute the + chain complex, compute its homology along with its generators, + and then convert the chain complex generators to chains in the + simplicial complex. + + Otherwise: if ``subcomplex`` is ``None``, replace it with a + facet -- a contractible subcomplex of the original complex. + Then as long as ``enlarge`` is ``True``, no matter what + ``subcomplex`` is, replace it with a subcomplex `L` which is + homotopy equivalent and as large as possible. Compute the + homology of the original complex relative to `L`: if `L` is + large, then the relative chain complex will be small enough to + speed up computations considerably. + + EXAMPLES:: + + sage: circle = SimplicialComplex([[0,1], [1,2], [0, 2]]) + sage: circle._homology_() + {0: 0, 1: Z} + sage: sphere = SimplicialComplex([[0,1,2,3]]) + sage: sphere.remove_face([0,1,2,3]) + sage: sphere + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)} + sage: sphere._homology_() + {0: 0, 1: 0, 2: Z} + sage: sphere._homology_(reduced=False) + {0: Z, 1: 0, 2: Z} + sage: sphere._homology_(base_ring=GF(2), reduced=False) + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 0 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + + We need an immutable complex to compute homology generators:: + + sage: sphere.set_immutable() + sage: sphere._homology_(generators=True, algorithm='no_chomp') + {0: [], 1: [], 2: [(Z, (0, 1, 2) - (0, 1, 3) + (0, 2, 3) - (1, 2, 3))]} + + Note that the answer may be formatted differently if the + optional package CHomP is installed. + + Another way to get a two-sphere: take a two-point space and take its + three-fold join with itself:: + + sage: S = SimplicialComplex([[0], [1]]) + sage: (S*S*S)._homology_(dim=2, cohomology=True) + Z + + The same computation, done without finding a contractible subcomplex:: + + sage: (S*S*S)._homology_(dim=2, cohomology=True, enlarge=False) + Z + + Relative homology:: + + sage: T = SimplicialComplex([[0,1,2]]) + sage: U = SimplicialComplex([[0,1], [1,2], [0,2]]) + sage: T._homology_(subcomplex=U) + {0: 0, 1: 0, 2: Z} + + Generators:: + + sage: simplicial_complexes.Torus().homology(generators=True, algorithm='no_chomp') + {0: [], + 1: [(Z, (2, 4) - (2, 6) + (4, 6)), (Z, (1, 4) - (1, 6) + (4, 6))], + 2: [(Z, + (0, 1, 2) - (0, 1, 5) + (0, 2, 6) - (0, 3, 4) + (0, 3, 5) - (0, 4, 6) - (1, 2, 4) + (1, 3, 4) - (1, 3, 6) + (1, 5, 6) - (2, 3, 5) + (2, 3, 6) + (2, 4, 5) - (4, 5, 6))]} + """ + from sage.homology.homology_group import HomologyGroup + + if dim is not None: + if isinstance(dim, (list, tuple, range)): + low = min(dim) - 1 + high = max(dim) + 2 + else: + low = dim - 1 + high = dim + 2 + dims = range(low, high) + else: + dims = None + + if generators: + enlarge = False + + if verbose: + print("starting calculation of the homology of this") + print("%s-dimensional simplicial complex" % self.dimension()) + if subcomplex is None: + if enlarge: + if verbose: + print("Constructing contractible subcomplex...") + L = self._contractible_subcomplex(verbose=verbose) + if verbose: + print("Done finding contractible subcomplex.") + vec = [len(self.faces(subcomplex=L)[n-1]) for n in range(self.dimension()+2)] + print("The difference between the f-vectors is:") + print(" %s" % vec) + else: + L = SimplicialComplex([[self.vertices()[0]]]) + else: + if enlarge: + if verbose: + print("Enlarging subcomplex...") + L = self._enlarge_subcomplex(subcomplex, verbose=verbose) + if verbose: + print("Done enlarging subcomplex:") + else: + L = subcomplex + L.set_immutable() + + if verbose: + print("Computing the chain complex...") + C = self.chain_complex(dimensions=dims, augmented=reduced, + cochain=cohomology, base_ring=base_ring, + subcomplex=L, verbose=verbose) + if verbose: + print(" Done computing the chain complex. ") + print("Now computing homology...") + answer = C.homology(base_ring=base_ring, verbose=verbose, + algorithm=algorithm, generators=generators) + + if generators: + # Convert chain complex information to simplicial complex + # information. + for i in answer: + H_with_gens = answer[i] + if H_with_gens: + chains = self.n_chains(i, base_ring=base_ring) + new_H = [] + for (H, gen) in H_with_gens: + v = gen.vector(i) + new_gen = chains.zero() + for (coeff, chain) in zip(v, chains.gens()): + new_gen += coeff * chain + new_H.append((H, new_gen)) + answer[i] = new_H + + if dim is None: + dim = range(self.dimension() + 1) + zero = HomologyGroup(0, base_ring) + if isinstance(dim, (list, tuple, range)): + # Fix non-reduced answer. + if subcomplex is None and not reduced and 0 in dim: + try: + if base_ring.is_field(): + rank = answer[0].dimension() + else: + rank = len(answer[0].invariants()) + except KeyError: + rank = 0 + answer[0] = HomologyGroup(rank + 1, base_ring) + return dict([d, answer.get(d, zero)] for d in dim) + return answer.get(dim, zero) + + # This is cached for speed reasons: it can be very slow to run + # this function. + @cached_method + def algebraic_topological_model(self, base_ring=None): + r""" + Algebraic topological model for this simplicial complex with + coefficients in ``base_ring``. + + The term "algebraic topological model" is defined by Pilarczyk + and Réal [PR2015]_. + + INPUT: + + - ``base_ring`` - coefficient ring (optional, default + ``QQ``). Must be a field. + + Denote by `C` the chain complex associated to this simplicial + complex. The algebraic topological model is a chain complex + `M` with zero differential, with the same homology as `C`, + along with chain maps `\pi: C \to M` and `\iota: M \to C` + satisfying `\iota \pi = 1_M` and `\pi \iota` chain homotopic + to `1_C`. The chain homotopy `\phi` must satisfy + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Such a chain homotopy is called a *chain contraction*. + + OUTPUT: a pair consisting of + + - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and + `\iota` + - the chain complex `M` + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: RP2 = simplicial_complexes.RealProjectivePlane() + sage: phi, M = RP2.algebraic_topological_model(GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = simplicial_complexes.Torus() + sage: phi, M = T.algebraic_topological_model(QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + """ + from sage.homology.algebraic_topological_model import algebraic_topological_model + if base_ring is None: + base_ring = QQ + return algebraic_topological_model(self, base_ring) + + def alexander_whitney(self, simplex, dim_left): + r""" + Subdivide this simplex into a pair of simplices. + + If this simplex has vertices `v_0`, `v_1`, ..., `v_n`, then + subdivide it into simplices `(v_0, v_1, ..., v_{dim})` and + `(v_{dim}, v_{dim + 1}, ..., v_n)`. + + See :meth:`Simplex.alexander_whitney` for more details. This + method just calls that one. + + INPUT: + + - ``simplex`` -- a simplex in this complex + - ``dim`` -- integer between 0 and one more than the + dimension of this simplex + + OUTPUT: a list containing just the triple ``(1, left, + right)``, where ``left`` and ``right`` are the two simplices + described above. + + EXAMPLES:: + + sage: s = Simplex((0,1,3,4)) + sage: X = SimplicialComplex([s]) + sage: X.alexander_whitney(s, 0) + [(1, (0,), (0, 1, 3, 4))] + sage: X.alexander_whitney(s, 2) + [(1, (0, 1, 3), (3, 4))] + """ + return simplex.alexander_whitney(dim_left) + + def add_face(self, face): + """ + Add a face to this simplicial complex. + + :param face: a subset of the vertex set + + This *changes* the simplicial complex, adding a new face and all + of its subfaces. + + EXAMPLES:: + + sage: X = SimplicialComplex([[0,1], [0,2]]) + sage: X.add_face([0,1,2,]); X + Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + sage: Y = SimplicialComplex(); Y + Simplicial complex with vertex set () and facets {()} + sage: Y.add_face([0,1]) + sage: Y.add_face([1,2,3]) + sage: Y + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (1, 2, 3)} + + If you add a face which is already present, there is no effect:: + + sage: Y.add_face([1,3]); Y + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (1, 2, 3)} + + TESTS: + + Check that the bug reported at :trac:`14354` has been fixed:: + + sage: T = SimplicialComplex([range(1,5)]).n_skeleton(1) + sage: T.homology(algorithm='no_chomp') + {0: 0, 1: Z x Z x Z} + sage: T.add_face([1,2,3]) + sage: T.homology(algorithm='no_chomp') + {0: 0, 1: Z x Z, 2: 0} + + Check that the ``_faces`` cache is treated correctly + (:trac:`20758`):: + + sage: T = SimplicialComplex([range(1,5)]).n_skeleton(1) + sage: _ = T.faces() # populate the _faces attribute + sage: _ = T.homology() # add more to _faces + sage: T.add_face((1,2,3)) + sage: all(Simplex((1,2,3)) in T._faces[L][2] for L in T._faces) + True + + Check that the ``__enlarged`` cache is treated correctly + (:trac:`20758`):: + + sage: T = SimplicialComplex([range(1,5)]).n_skeleton(1) + sage: T.homology(algorithm='no_chomp') # to populate the __enlarged attribute + {0: 0, 1: Z x Z x Z} + sage: T.add_face([1,2,3]) + sage: len(T._SimplicialComplex__enlarged) > 0 + True + + Check we've fixed the bug reported at :trac:`14578`:: + + sage: t0 = SimplicialComplex() + sage: t0.add_face(('a', 'b')) + sage: t0.add_face(('c', 'd', 'e')) + sage: t0.add_face(('e', 'f', 'c')) + sage: t0.homology() + {0: Z, 1: 0, 2: 0} + + Check that we've fixed the bug reported at :trac:`22880`:: + + sage: X = SimplicialComplex([[0], [1]]) + sage: temp = X.faces(SimplicialComplex(())) + sage: X.add_face([0,1]) + """ + if self._is_immutable: + raise ValueError("This simplicial complex is not mutable") + + vertex_to_index = self._translation_to_numeric() + + # Update vertex_to_index by giving each new vertex a larger + # entry than the existing ones. + if vertex_to_index: + idx = max(vertex_to_index.values()) + 1 + else: + idx = 0 + new_vertices = [] + for v in face: + if v not in self.vertices(): + new_vertices.append(v) + vertex_to_index[v] = idx + idx += 1 + + new_face = Simplex(sorted(face, key=vertex_to_index.__getitem__)) + + face_is_maximal = True + for other in self._facets: + if face_is_maximal: + face_is_maximal = not new_face.is_face(other) + if face_is_maximal: + # remove any old facets which are no longer maximal + Facets = list(self._facets) + for old_face in self._facets: + if old_face.is_face(new_face): + Facets.remove(old_face) + # add new_face to facet list + Facets.append(new_face) + self._facets = Facets + + # Update the vertex set + self._vertex_to_index = vertex_to_index + + # Update self._faces. + all_new_faces = SimplicialComplex([new_face]).faces() + for L in self._faces: + L_complex = self._faces[L] + for dim in range(new_face.dimension()+1): + if dim in L_complex: + if L is None: + new_faces = all_new_faces[dim] + else: + new_faces = all_new_faces[dim].difference(L.n_cells(dim)) + L_complex[dim] = L_complex[dim].union(new_faces) + else: + L_complex[dim] = all_new_faces[dim] + # update self._graph if necessary + if self._graph is not None: + d = new_face.dimension()+1 + for i in range(d): + for j in range(i + 1, d): + self._graph.add_edge(new_face[i], new_face[j]) + self._complex = {} + self.__contractible = None + + def remove_face(self, face, check=False): + """ + Remove a face from this simplicial complex. + + :param face: a face of the simplicial complex + + :param check: boolean; optional, default ``False``. If + ``True``, raise an error if ``face`` is not a + face of this simplicial complex + + This does not return anything; instead, it *changes* the + simplicial complex. + + ALGORITHM: + + The facets of the new simplicial complex are + the facets of the original complex not containing ``face``, + together with those of ``link(face)*boundary(face)``. + + EXAMPLES:: + + sage: S = range(1,5) + sage: Z = SimplicialComplex([S]); Z + Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)} + sage: Z.remove_face([1,2]) + sage: Z + Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 3, 4), (2, 3, 4)} + + sage: S = SimplicialComplex([[0,1,2],[2,3]]) + sage: S + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(2, 3), (0, 1, 2)} + sage: S.remove_face([0,1,2]) + sage: S + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 2), (1, 2), (2, 3)} + + TESTS: + + Check that the ``_faces`` cache is treated properly: see + :trac:`20758`:: + + sage: T = SimplicialComplex([range(1,5)]).n_skeleton(1) + sage: _ = T.faces() # populate the _faces attribute + sage: _ = T.homology(algorithm='no_chomp') # add more to _faces + sage: T.add_face((1,2,3)) + sage: T.remove_face((1,2,3)) + sage: len(T._faces) + 2 + sage: T.remove_face((1,2)) + sage: len(T._faces) + 1 + + Check that the face to be removed can be given with a + different vertex ordering:: + + sage: S = SimplicialComplex([[1,2], [1,3]]) + sage: S.remove_face([3,1]) + sage: S + Simplicial complex with vertex set (1, 2, 3) and facets {(3,), (1, 2)} + """ + if self._is_immutable: + raise ValueError("This simplicial complex is not mutable") + + getindex = self._translation_to_numeric().__getitem__ + simplex = Simplex(sorted(face, key=getindex)) + facets = self.facets() + if all(not simplex.is_face(F) for F in facets): + # face is not in self + if check: + raise ValueError('trying to remove a face which is not in the simplicial complex') + return + link = self.link(simplex) + join_facets = [] + for f in simplex.faces(): + for g in link.facets(): + join_facets.append(f.join(g, rename_vertices=False)) + # join_facets is the list of facets in the join bdry(face) * link(face) + remaining = join_facets + [elem for elem in facets if not simplex.is_face(elem)] + + # Check to see if there are any non-maximal faces + # build set of facets + self._facets = [] + for f in remaining: + face2 = Simplex(f) + face_is_maximal = True + faces_to_be_removed = [] + for other in self._facets: + if other.is_face(face2): + faces_to_be_removed.append(other) + elif face_is_maximal: + face_is_maximal = not face2.is_face(other) + for x in faces_to_be_removed: + self._facets.remove(x) + face2 = Simplex(sorted(face2.tuple())) + if face_is_maximal: + self._facets.append(face2) + # if no maximal faces, add the empty face as a facet + if len(remaining) == 0: + self._facets.append(Simplex(-1)) + + # Recreate the vertex set + from sage.misc.misc import union + vertices = tuple(reduce(union, self._facets)) + for v in self.vertices(): + if v not in vertices: + del self._vertex_to_index[v] + + # Update self._faces. + # Note: can't iterate over self._faces, because the dictionary + # size may change during iteration. + for L in list(self._faces): + del self._faces[L] + if L is None or Simplex(face) not in L: + self.faces(L) + # Update self._graph if necessary. + if self._graph is not None: + # Only if removing a 1 or 2 dim face will the graph be affected + if len(face) == 1: + self._graph.delete_vertex(face[0]) + self._graph.add_vertex(face[0]) + elif len(face) == 2: + self._graph.delete_edge(face[0], face[1]) + self._complex = {} + self.__contractible = None + self.__enlarged = {} + + def remove_faces(self, faces, check=False): + """ + Remove a collection of faces from this simplicial complex. + + :param faces: a list (or any iterable) of faces of the + simplicial complex + + :param check: boolean; optional, default ``False``. If + ``True``, raise an error if any element of ``faces`` is not a + face of this simplicial complex + + This does not return anything; instead, it *changes* the + simplicial complex. + + ALGORITHM: + + Run ``self.remove_face(f)`` repeatedly, for ``f`` in ``faces``. + + EXAMPLES:: + + sage: S = range(1,5) + sage: Z = SimplicialComplex([S]); Z + Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)} + sage: Z.remove_faces([[1,2]]) + sage: Z + Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 3, 4), (2, 3, 4)} + + sage: Z = SimplicialComplex([S]); Z + Simplicial complex with vertex set (1, 2, 3, 4) and facets {(1, 2, 3, 4)} + sage: Z.remove_faces([[1,2], [2,3]]) + sage: Z + Simplicial complex with vertex set (1, 2, 3, 4) and facets {(2, 4), (1, 3, 4)} + + TESTS: + + Check the ``check`` argument:: + + sage: Z = SimplicialComplex([[1,2,3,4]]) + sage: Z.remove_faces([[1,2], [3,4]]) + sage: Z.remove_faces([[1,2]]) + sage: Z.remove_faces([[1,2]], check=True) + Traceback (most recent call last): + ... + ValueError: trying to remove a face which is not in the simplicial complex + """ + for f in faces: + self.remove_face(f, check=check) + + def connected_sum(self, other, is_mutable=True): + """ + The connected sum of this simplicial complex with another one. + + :param other: another simplicial complex + :param is_mutable: Determines if the output is mutable + :type is_mutable: boolean; optional, default ``True`` + :return: the connected sum ``self # other`` + + .. WARNING:: + + This does not check that ``self`` and ``other`` are manifolds, + only that their facets all have the same dimension. Since a + (more or less) random facet is chosen from each complex and + then glued together, this method may return random + results if applied to non-manifolds, depending on which + facet is chosen. + + Algorithm: a facet is chosen from each surface, and removed. + The vertices of these two facets are relabeled to + ``(0,1,...,dim)``. Of the remaining vertices, the ones from + the left-hand factor are renamed by prepending an "L", and + similarly the remaining vertices in the right-hand factor are + renamed by prepending an "R". + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S1.connected_sum(S1.connected_sum(S1)).homology() + {0: 0, 1: Z} + sage: P = simplicial_complexes.RealProjectivePlane(); P + Minimal triangulation of the real projective plane + sage: P.connected_sum(P) # the Klein bottle + Simplicial complex with 9 vertices and 18 facets + + The notation '+' may be used for connected sum, also:: + + sage: P + P # the Klein bottle + Simplicial complex with 9 vertices and 18 facets + sage: (P + P).homology()[1] + Z x C2 + """ + if not (self.is_pure() and other.is_pure() and + self.dimension() == other.dimension()): + raise ValueError("complexes are not pure of the same dimension") + # first find a top-dimensional simplex to remove from each surface + keep_left = self._facets[0] + keep_right = other._facets[0] + # construct the set of facets: + left = set(self._facets).difference(set([keep_left])) + right = set(other._facets).difference(set([keep_right])) + facet_set = ([[rename_vertex(v, keep=list(keep_left)) + for v in face] for face in left] + + [[rename_vertex(v, keep=list(keep_right), left=False) + for v in face] for face in right]) + # return the new surface + return SimplicialComplex(facet_set, is_mutable=is_mutable) + + __add__ = connected_sum + + def link(self, simplex, is_mutable=True): + r""" + The link of a simplex in this simplicial complex. + + The link of a simplex `F` is the simplicial complex formed by + all simplices `G` which are disjoint from `F` but for which `F + \cup G` is a simplex. + + :param simplex: a simplex in this simplicial complex. + :param is_mutable: Determines if the output is mutable + :type is_mutable: boolean; optional, default ``True`` + + EXAMPLES:: + + sage: X = SimplicialComplex([[0,1,2], [1,2,3]]) + sage: X.link(Simplex([0])) + Simplicial complex with vertex set (1, 2) and facets {(1, 2)} + sage: X.link([1,2]) + Simplicial complex with vertex set (0, 3) and facets {(0,), (3,)} + sage: Y = SimplicialComplex([[0,1,2,3]]) + sage: Y.link([1]) + Simplicial complex with vertex set (0, 2, 3) and facets {(0, 2, 3)} + """ + faces = [] + s = Simplex(simplex) + for f in self._facets: + if s.is_face(f): + faces.append(Simplex(f.set().difference(s.set()))) + return SimplicialComplex(faces, is_mutable=is_mutable) + + def star(self, simplex, is_mutable=True): + """ + Return the star of a simplex in this simplicial complex. + + The star of ``simplex`` is the simplicial complex formed by + all simplices which contain ``simplex``. + + INPUT: + + - ``simplex`` -- a simplex in this simplicial complex + - ``is_mutable`` -- (default: ``True``) boolean; determines if the output + is mutable + + EXAMPLES:: + + sage: X = SimplicialComplex([[0,1,2], [1,2,3]]) + sage: X.star(Simplex([0])) + Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + sage: X.star(Simplex([1])) + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (1, 2, 3)} + sage: X.star(Simplex([1,2])) + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (1, 2, 3)} + sage: X.star(Simplex([])) + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (1, 2, 3)} + """ + faces = [] + s = Simplex(simplex) + for f in self._facets: + if s.is_face(f): + faces.append(f) + return SimplicialComplex(faces, is_mutable=is_mutable) + + def is_cohen_macaulay(self, base_ring=QQ, ncpus=0): + r""" + Return ``True`` if ``self`` is Cohen-Macaulay. + + A simplicial complex `\Delta` is Cohen-Macaulay over `R` iff + `\tilde{H}_i(\mathrm{lk}_\Delta(F);R) = 0` for all + `F \in \Delta` and `i < \dim\mathrm{lk}_\Delta(F)`. + Here, `\Delta` is ``self`` and `R` is ``base_ring``, and + `\mathrm{lk}` denotes the link operator on ``self``. + + INPUT: + + - ``base_ring`` -- (default: ``QQ``) the base ring. + + - ``ncpus`` -- (default: 0) number of cpus used for the + computation. If this is 0, determine the number of cpus + automatically based on the hardware being used. + + For finite simplicial complexes, this is equivalent to the + statement that the Stanley-Reisner ring of ``self`` is + Cohen-Macaulay. + + EXAMPLES: + + Spheres are Cohen-Macaulay:: + + sage: S = SimplicialComplex([[1,2],[2,3],[3,1]]) + sage: S.is_cohen_macaulay(ncpus=3) + True + + The following example is taken from Bruns, Herzog - Cohen-Macaulay + rings, Figure 5.3:: + + sage: S = SimplicialComplex([[1,2,3],[1,4,5]]) + sage: S.is_cohen_macaulay(ncpus=3) + False + + The choice of base ring can matter. The real projective plane `\RR P^2` + has `H_1(\RR P^2) = \ZZ/2`, hence is CM over `\QQ` but not over `\ZZ`. :: + + sage: X = simplicial_complexes.RealProjectivePlane() + sage: X.is_cohen_macaulay() + True + sage: X.is_cohen_macaulay(ZZ) + False + """ + from sage.parallel.decorate import parallel + + if not ncpus: + from sage.parallel.ncpus import ncpus as get_ncpus + ncpus = get_ncpus() + + facs = [ x for x in self.face_iterator() ] + n = len(facs) + facs_divided = [ [] for i in range(ncpus) ] + for i in range(n): + facs_divided[i % ncpus].append(facs[i]) + + def all_homologies_vanish(F): + S = self.link(F) + H = S.homology(base_ring=base_ring) + if base_ring.is_field(): + return all( H[j].dimension() == 0 for j in range(S.dimension()) ) + else: + return not any( H[j].invariants() for j in range(S.dimension()) ) + + @parallel(ncpus=ncpus) + def all_homologies_in_list_vanish(Fs): + return all( all_homologies_vanish(F) for F in Fs ) + + return all( answer[1] for answer in all_homologies_in_list_vanish(facs_divided) ) + + def generated_subcomplex(self, sub_vertex_set, is_mutable=True): + """ + Return the largest sub-simplicial complex of ``self`` containing + exactly ``sub_vertex_set`` as vertices. + + :param sub_vertex_set: The sub-vertex set. + :param is_mutable: Determines if the output is mutable + :type is_mutable: boolean; optional, default ``True`` + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(2) + sage: S + Minimal triangulation of the 2-sphere + sage: S.generated_subcomplex([0,1,2]) + Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + + """ + if not set(self.vertices()).issuperset(sub_vertex_set): + raise ValueError("input must be a subset of the vertex set") + faces = [] + for i in range(self.dimension() + 1): + for j in self.faces()[i]: + if j.set().issubset(sub_vertex_set): + faces.append(j) + return SimplicialComplex(faces, maximality_check=True, + is_mutable=is_mutable) + + def is_shelling_order(self, shelling_order, certificate=False): + r""" + Return if the order of the facets given by ``shelling_order`` + is a shelling order for ``self``. + + A sequence of facets `(F_i)_{i=1}^N` of a simplicial + complex of dimension `d` is a *shelling order* if for all + `i = 2, 3, 4, \ldots`, the complex + + .. MATH:: + + X_i = \left( \bigcup_{j=1}^{i-1} F_j \right) \cap F_i + + is pure and of dimension `\dim F_i - 1`. + + INPUT: + + - ``shelling_order`` -- an ordering of the facets of ``self`` + - ``certificate`` -- (default: ``False``) if ``True`` then returns + the index of the first facet that violate the condition + + .. SEEALSO:: + + :meth:`is_shellable` + + EXAMPLES:: + + sage: facets = [[1,2,5],[2,3,5],[3,4,5],[1,4,5]] + sage: X = SimplicialComplex(facets) + sage: X.is_shelling_order(facets) + True + + sage: b = [[1,2,5], [3,4,5], [2,3,5], [1,4,5]] + sage: X.is_shelling_order(b) + False + sage: X.is_shelling_order(b, True) + (False, 1) + + A non-pure example:: + + sage: facets = [[1,2,3], [3,4], [4,5], [5,6], [4,6]] + sage: X = SimplicialComplex(facets) + sage: X.is_shelling_order(facets) + True + + REFERENCES: + + - [BW1996]_ + """ + # Quick check by Lemma 2.2 in [BW1996] + if self.dimension() != len(list(shelling_order[0])) - 1: + return False + + cur_complex = SimplicialComplex([]) + for i, F in enumerate(shelling_order): + if i > 0: + # The shelling condition is precisely that intersection is + # a pure complex of one dimension less and stop if this fails + common = set(F).intersection(set(cur_complex.vertices())) + intersection = cur_complex.generated_subcomplex(list(common)) + + dim = len(list(F)) - 1 + if not intersection.is_pure() or dim - 1 != intersection.dimension(): + if certificate: + return (False, i) + return False + cur_complex.add_face(F) + return True + + @cached_method + def is_shellable(self, certificate=False): + r""" + Return if ``self`` is shellable. + + A simplicial complex is shellable if there exists a shelling + order. + + .. NOTE:: + + 1. This method can check all orderings of the facets by brute + force, hence can be very slow. + + 2. This is shellability in the general (nonpure) sense of + Bjorner and Wachs [BW1996]_. This method does not check purity. + + .. SEEALSO:: + + :meth:`is_shelling_order` + + INPUT: + + - ``certificate`` -- (default: ``False``) if ``True`` then + returns the shelling order (if it exists) + + EXAMPLES:: + + sage: X = SimplicialComplex([[1,2,5], [2,3,5], [3,4,5], [1,4,5]]) + sage: X.is_shellable() + True + sage: order = X.is_shellable(True); order + ((1, 2, 5), (2, 3, 5), (1, 4, 5), (3, 4, 5)) + sage: X.is_shelling_order(order) + True + + sage: X = SimplicialComplex([[1,2,3], [3,4,5]]) + sage: X.is_shellable() + False + + Examples from Figure 1 in [BW1996]_:: + + sage: X = SimplicialComplex([[1,2,3], [3,4], [4,5], [5,6], [4,6]]) + sage: X.is_shellable() + True + + sage: X = SimplicialComplex([[1,2,3], [3,4], [4,5,6]]) + sage: X.is_shellable() + False + + REFERENCES: + + - :wikipedia:`Shelling_(topology)` + """ + if not certificate: + return bool(self.is_shellable(certificate=True)) + + if self.is_pure(): + if any(x < 0 for x in self.h_vector()): + return False + else: # Non-pure complex + if any(x < 0 for row in self.h_triangle() for x in row): + return False + + facets = set(self.facets()) + cur_order = [] + # For consistency when using different Python versions, for example, sort 'faces'. + it = [iter(sorted(facets, key=str))] + cur_complex = SimplicialComplex([]) + while facets: + try: + F = next(it[-1]) + except StopIteration: + # Backtrace + if not cur_order: + return False + it.pop() + facets.add(cur_order.pop()) + cur_complex = SimplicialComplex(cur_order) + continue + + # First facet must be top dimensional + if not cur_order: + if self.dimension() == F.dimension(): + cur_complex.add_face(F) + cur_order.append(F) + facets.remove(F) + it.append(iter(set(facets))) + continue + + + # The shelling condition is precisely that intersection is + # a pure complex of one dimension less and stop if this fails + common = set(F).intersection(set(cur_complex.vertices())) + intersection = cur_complex.generated_subcomplex(list(common)) + + if (not intersection.is_pure() + or F.dimension() - 1 != intersection.dimension()): + continue + cur_complex.add_face(F) + cur_order.append(F) + facets.remove(F) + it.append(iter(set(facets))) # Iterate over a copy of the current facets + + return tuple(cur_order) + + def restriction_sets(self, order): + """ + Return the restriction sets of the facets according to ``order``. + + A restriction set of a shelling order is the sequence of + smallest new faces that are created during the shelling order. + + .. SEEALSO:: + + :meth:`is_shelling_order` + + EXAMPLES:: + + sage: facets = [[1,2,5], [2,3,5], [3,4,5], [1,4,5]] + sage: X = SimplicialComplex(facets) + sage: X.restriction_sets(facets) + [(), (3,), (4,), (1, 4)] + + sage: b = [[1,2,5], [3,4,5], [2,3,5], [1,4,5]] + sage: X.restriction_sets(b) + Traceback (most recent call last): + ... + ValueError: not a shelling order + """ + # It starts with the first empty + restrictions = [()] + + # Each time we hit a facet, the complement goes to the restriction + cur_complex = SimplicialComplex([]) + for i, F in enumerate(order): + if i > 0: + # The shelling condition is precisely that intersection is + # a pure complex of one dimension less and stop if this fails + common = set(F).intersection(set(cur_complex.vertices())) + intersection = cur_complex.generated_subcomplex(list(common)) + + if not intersection.is_pure() or self.dimension() - 1 > intersection.dimension(): + raise ValueError("not a shelling order") + faces = SimplicialComplex([F]).faces() + for k, v in intersection.faces().items(): + faces[k] = faces[k].difference(v) + for k in sorted(faces.keys()): + if faces[k]: + restrictions.append(faces[k].pop()) + break + cur_complex.add_face(F) + + return restrictions + + def _complement(self, simplex): + """ + Return the complement of a simplex in the vertex set of this + simplicial complex. + + :param simplex: a simplex (need not be in the simplicial complex) + + OUTPUT: its complement: the simplex formed by the vertices not + contained in ``simplex``. + + Note that this only depends on the vertex set of the + simplicial complex, not on its simplices. + + EXAMPLES:: + + sage: X = SimplicialComplex([[0,1,2,3,4,5]]) + sage: X._complement([1,2,3]) + (0, 4, 5) + sage: X._complement([0,1,3,4]) + (2, 5) + sage: X._complement([0,4,1,3]) + (2, 5) + """ + return Simplex(set(self.vertices()).difference(simplex)) + + def _transpose_simplices(self, *simplices): + """ + Given tuple ``L`` of simplices, returns new list, where each + simplex is formed by taking a vertex from each simplex from + ``L``. + + :param simplices: a bunch of simplices + + If ``simplices`` consists of `(f_0, f_1, f_2, ...)`, then the + output consists of all possible simplices of the form `(v_0, + v_1, v_2, ...)`, where `v_i` is a vertex of `f_i`. If a + vertex appears more than once in such a simplex, remove all + but one of its appearances. If such a simplex contains others + already produced, then ignore that larger simplex -- the + output should be a list of minimal simplices constructed in + this way. + + This is used in computing the minimal nonfaces and hence the + Stanley-Reisner ring. + + Note that this only depends on the vertex set of the + simplicial complex, not on its simplices. + + I don't know if there is a standard name for this, but it + looked sort of like the transpose of a matrix; hence the name + for this method. + + EXAMPLES:: + + sage: X = SimplicialComplex() + sage: X._transpose_simplices([1,2]) + [(1,), (2,)] + sage: X._transpose_simplices([1,2], [3,4]) + [(1, 3), (1, 4), (2, 3), (2, 4)] + + In the following example, one can construct the simplices + ``(1,2)`` and ``(1,3)``, but you can also construct ``(1,1) = (1,)``, + which is a face of both of the others. So the answer omits + ``(1,2)`` and ``(1,3)``:: + + sage: X._transpose_simplices([1,2], [1,3]) + [(1,), (2, 3)] + """ + answer = [] + if len(simplices) == 1: + answer = [Simplex((v,)) for v in simplices[0]] + elif len(simplices) > 1: + face = simplices[0] + rest = simplices[1:] + for v in face: + for partial in self._transpose_simplices(*rest): + if v not in partial: + L = sorted([v] + list(partial)) + simplex = Simplex(L) + else: + simplex = partial + add_simplex = True + simplices_to_delete = [] + for already in answer: + if add_simplex: + if already.is_face(simplex): + add_simplex = False + if add_simplex and simplex.is_face(already): + simplices_to_delete.append(already) + if add_simplex: + answer.append(simplex) + for x in simplices_to_delete: + answer.remove(x) + return answer + + def minimal_nonfaces(self): + """ + Set consisting of the minimal subsets of the vertex set of + this simplicial complex which do not form faces. + + Algorithm: Proceeds through the faces of the complex increasing the + dimension, starting from dimension 0, and add the faces that are not + contained in the complex and that are not already contained in a + previously seen minimal non-face. + + This is used in computing the + :meth:`Stanley-Reisner ring` and the + :meth:`Alexander dual`. + + EXAMPLES:: + + sage: X = SimplicialComplex([[1,3],[1,2]]) + sage: X.minimal_nonfaces() + {(2, 3)} + sage: Y = SimplicialComplex([[0,1], [1,2], [2,3], [3,0]]) + sage: sorted(Y.minimal_nonfaces()) + [(0, 2), (1, 3)] + + TESTS:: + + sage: SC = SimplicialComplex([(0,1,2),(0,2,3),(2,3,4),(1,2,4), \ + (1,4,5),(0,3,6),(3,6,7),(4,5,7)]) + + This was taking a long time before :trac:`20078`:: + + sage: sorted(SC.minimal_nonfaces()) + [(0, 4), + (0, 5), + (0, 7), + (1, 3), + (1, 6), + (1, 7), + (2, 5), + (2, 6), + (2, 7), + (3, 4, 7), + (3, 5), + (4, 6), + (5, 6)] + """ + face_dict = self.faces() + vertices = self.vertices() + dimension = self.dimension() + set_mnf = set() + + for dim in range(dimension + 1): + face_sets = frozenset(f.set() for f in face_dict[dim]) + for candidate in combinations(vertices, dim + 1): + set_candidate = frozenset(candidate) + if set_candidate not in face_sets: + new = not any(set_candidate.issuperset(mnf) for mnf in set_mnf) + if new: + set_mnf.add(set_candidate) + + for candidate in combinations(vertices, dimension+2): # Checks for minimal nonfaces in the remaining dimension + set_candidate = frozenset(candidate) + new = not any(set_candidate.issuperset(mnf) for mnf in set_mnf) + if new: + set_mnf.add(set_candidate) + + min_non_faces = Set([Simplex(mnf) for mnf in set_mnf]) + + return min_non_faces + + def _stanley_reisner_base_ring(self, base_ring=ZZ): + """ + The polynomial algebra of which the Stanley-Reisner ring is a + quotient. + + :param base_ring: a commutative ring + :type base_ring: optional, default ``ZZ`` + :return: a polynomial algebra with coefficients in base_ring, + with one generator for each vertex in the simplicial complex. + + See the documentation for :meth:`stanley_reisner_ring` for a + warning about the names of the vertices. + + EXAMPLES:: + + sage: X = SimplicialComplex([[1,2], [0], [3]]) + sage: X._stanley_reisner_base_ring() + Multivariate Polynomial Ring in x0, x1, x2, x3 over Integer Ring + sage: Y = SimplicialComplex([['a', 'b', 'c']]) + sage: Y._stanley_reisner_base_ring(base_ring=QQ) + Multivariate Polynomial Ring in a, b, c over Rational Field + """ + verts = self._gen_dict.values() + try: + verts = sorted(verts) + except TypeError: + verts = sorted(verts, key=str) + return PolynomialRing(base_ring, verts) + + def stanley_reisner_ring(self, base_ring=ZZ): + """ + The Stanley-Reisner ring of this simplicial complex. + + :param base_ring: a commutative ring + :type base_ring: optional, default ``ZZ`` + :return: a quotient of a polynomial algebra with coefficients + in ``base_ring``, with one generator for each vertex in the + simplicial complex, by the ideal generated by the products + of those vertices which do not form faces in it. + + Thus the ideal is generated by the products corresponding to + the minimal nonfaces of the simplicial complex. + + .. WARNING:: + + This may be quite slow! + + Also, this may behave badly if the vertices have the + 'wrong' names. To avoid this, define the simplicial complex + at the start with the flag ``name_check`` set to ``True``. + + More precisely, this is a quotient of a polynomial ring + with one generator for each vertex. If the name of a + vertex is a non-negative integer, then the corresponding + polynomial generator is named ``'x'`` followed by that integer + (e.g., ``'x2'``, ``'x3'``, ``'x5'``, ...). Otherwise, the + polynomial generators are given the same names as the vertices. + Thus if the vertex set is ``(2, 'x2')``, there will be problems. + + EXAMPLES:: + + sage: X = SimplicialComplex([[0,1,2], [0,2,3]]) + sage: X.stanley_reisner_ring() + Quotient of Multivariate Polynomial Ring in x0, x1, x2, x3 over Integer Ring by the ideal (x1*x3) + sage: Y = SimplicialComplex([[0,1,2,3,4]]); Y + Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 2, 3, 4)} + sage: Y.add_face([0,1,2,3,4]) + sage: Y.stanley_reisner_ring(base_ring=QQ) + Multivariate Polynomial Ring in x0, x1, x2, x3, x4 over Rational Field + """ + R = self._stanley_reisner_base_ring(base_ring) + products = [] + for f in self.minimal_nonfaces(): + prod = 1 + for v in f: + prod *= R(self._gen_dict[v]) + products.append(prod) + return R.quotient(products) + + def alexander_dual(self, is_mutable=True): + """ + The Alexander dual of this simplicial complex: according to + the Macaulay2 documentation, this is the simplicial complex + whose faces are the complements of its nonfaces. + + Thus find the minimal nonfaces and take their complements to + find the facets in the Alexander dual. + + :param is_mutable: Determines if the output is mutable + :type is_mutable: boolean; optional, default ``True`` + + EXAMPLES:: + + sage: Y = SimplicialComplex([[i] for i in range(5)]); Y + Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0,), (1,), (2,), (3,), (4,)} + sage: Y.alexander_dual() + Simplicial complex with vertex set (0, 1, 2, 3, 4) and 10 facets + sage: X = SimplicialComplex([[0,1], [1,2], [2,3], [3,0]]) + sage: X.alexander_dual() + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2), (1, 3)} + """ + nonfaces = self.minimal_nonfaces() + return SimplicialComplex([self._complement(f) for f in nonfaces], is_mutable=is_mutable) + + def barycentric_subdivision(self): + """ + The barycentric subdivision of this simplicial complex. + + See :wikipedia:`Barycentric_subdivision` for a + definition. + + EXAMPLES:: + + sage: triangle = SimplicialComplex([[0,1], [1,2], [0, 2]]) + sage: hexagon = triangle.barycentric_subdivision() + sage: hexagon + Simplicial complex with 6 vertices and 6 facets + sage: hexagon.homology(1) == triangle.homology(1) + True + + Barycentric subdivisions can get quite large, since each + `n`-dimensional facet in the original complex produces + `(n+1)!` facets in the subdivision:: + + sage: S4 = simplicial_complexes.Sphere(4) + sage: S4 + Minimal triangulation of the 4-sphere + sage: S4.barycentric_subdivision() + Simplicial complex with 62 vertices and 720 facets + """ + return self.face_poset().order_complex() + + def stellar_subdivision(self, simplex, inplace=False, is_mutable=True): + """ + Return the stellar subdivision of a simplex in this simplicial complex. + + The stellar subdivision of a face is obtained by adding a new vertex to the + simplicial complex ``self`` joined to the star of the face and then + deleting the face ``simplex`` to the result. + + INPUT: + + - ``simplex`` -- a simplex face of ``self`` + - ``inplace`` -- (default: ``False``) boolean; determines if the + operation is done on ``self`` or on a copy + - ``is_mutable`` -- (default: ``True``) boolean; determines if the + output is mutable + + OUTPUT: + + - A simplicial complex obtained by the stellar subdivision of the face + ``simplex`` + + EXAMPLES:: + + sage: SC = SimplicialComplex([[0,1,2],[1,2,3]]) + sage: F1 = Simplex([1,2]) + sage: F2 = Simplex([1,3]) + sage: F3 = Simplex([1,2,3]) + sage: SC.stellar_subdivision(F1) + Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 4), (0, 2, 4), (1, 3, 4), (2, 3, 4)} + sage: SC.stellar_subdivision(F2) + Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 2), (1, 2, 4), (2, 3, 4)} + sage: SC.stellar_subdivision(F3) + Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 2), (1, 2, 4), (1, 3, 4), (2, 3, 4)} + sage: SC.stellar_subdivision(F3, inplace=True);SC + Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 2), (1, 2, 4), (1, 3, 4), (2, 3, 4)} + + The simplex to subdivide should be a face of self:: + + sage: SC = SimplicialComplex([[0,1,2],[1,2,3]]) + sage: F4 = Simplex([3,4]) + sage: SC.stellar_subdivision(F4) + Traceback (most recent call last): + ... + ValueError: the face to subdivide is not a face of self + + One can not modify an immutable simplicial complex:: + + sage: SC = SimplicialComplex([[0,1,2],[1,2,3]], is_mutable=False) + sage: SC.stellar_subdivision(F1, inplace=True) + Traceback (most recent call last): + ... + ValueError: this simplicial complex is not mutable + """ + + if inplace and self._is_immutable: + raise ValueError("this simplicial complex is not mutable") + + if not Simplex(simplex) in self: + raise ValueError("the face to subdivide is not a face of self") + + if inplace: + working_complex = self + else: + working_complex = copy(self) + + vertices = working_complex.vertices() + not_found = True + vertex_label = 0 + while not_found: + if vertex_label not in vertices: + not_found = False + else: + vertex_label += 1 + new_vertex = SimplicialComplex([[vertex_label]]) + new_faces = new_vertex.join(working_complex.star(simplex), rename_vertices=False) + for face in new_faces.facets(): + working_complex.add_face(face) + + working_complex.remove_face(simplex) + + if not is_mutable: + working_complex.set_immutable() + + if not inplace: + return working_complex + + def graph(self): + """ + The 1-skeleton of this simplicial complex, as a graph. + + .. WARNING:: + + This may give the wrong answer if the simplicial complex + was constructed with ``maximality_check`` set to ``False``. + + EXAMPLES:: + + sage: S = SimplicialComplex([[0,1,2,3]]) + sage: G = S.graph(); G + Graph on 4 vertices + sage: G.edges() + [(0, 1, None), (0, 2, None), (0, 3, None), (1, 2, None), (1, 3, None), (2, 3, None)] + """ + if self._graph is None: + edges = self.n_cells(1) + vertices = [min(f) for f in self._facets if f.dimension() == 0] + used_vertices = [] # vertices which are in an edge + d = {} + for e in edges: + try: + v = min(e) + max_e = max(e) + except TypeError: + v = min(e, key=str) + max_e = max(e, key=str) + if v in d: + d[v].append(max_e) + else: + d[v] = [max_e] + used_vertices.extend(list(e)) + for v in vertices: + if v not in used_vertices: + d[v] = [] + self._graph = Graph(d) + return self._graph + + def delta_complex(self, sort_simplices=False): + r""" + Return ``self`` as a `\Delta`-complex. + + The `\Delta`-complex is essentially identical to the + simplicial complex: it has same simplices with the same + boundaries. + + :param sort_simplices: if ``True``, sort the list of simplices in + each dimension + :type sort_simplices: boolean; optional, default ``False`` + + EXAMPLES:: + + sage: T = simplicial_complexes.Torus() + sage: Td = T.delta_complex() + sage: Td + Delta complex with 7 vertices and 43 simplices + sage: T.homology() == Td.homology() + True + """ + from .delta_complex import DeltaComplex + data = {} + dim = self.dimension() + n_cells = self._n_cells_sorted(dim) + if sort_simplices: + n_cells.sort() + for n in range(dim, -1, -1): + bdries = self._n_cells_sorted(n-1) + if sort_simplices: + bdries.sort() + data[n] = [] + for f in n_cells: + data[n].append([bdries.index(f.face(i)) for i in range(n+1)]) + n_cells = bdries + return DeltaComplex(data) + + def is_flag_complex(self): + """ + Return ``True`` if and only if ``self`` is a flag complex. + + A flag complex is a simplicial complex that is the largest simplicial + complex on its 1-skeleton. Thus a flag complex is the clique complex + of its graph. + + EXAMPLES:: + + sage: h = Graph({0:[1,2,3,4],1:[2,3,4],2:[3]}) + sage: x = h.clique_complex() + sage: x + Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 1, 4), (0, 1, 2, 3)} + sage: x.is_flag_complex() + True + + sage: X = simplicial_complexes.ChessboardComplex(3,3) + sage: X.is_flag_complex() + True + """ + return self == self.graph().clique_complex() + + def n_skeleton(self, n): + """ + The `n`-skeleton of this simplicial complex. + + The `n`-skeleton of a simplicial complex is obtained by discarding + all of the simplices in dimensions larger than `n`. + + :param n: non-negative integer + + EXAMPLES:: + + sage: X = SimplicialComplex([[0,1], [1,2,3], [0,2,3]]) + sage: X.n_skeleton(1) + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)} + sage: X.set_immutable() + sage: X.n_skeleton(2) + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 2, 3), (1, 2, 3)} + sage: X.n_skeleton(4) + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1), (0, 2, 3), (1, 2, 3)} + """ + if n >= self.dimension(): + return self + # make sure it's a list (it will be a tuple if immutable) + facets = [f for f in self._facets if f.dimension() < n] + facets.extend(self.faces()[n]) + return SimplicialComplex(facets, is_immutable=self._is_immutable) + + def _contractible_subcomplex(self, verbose=False): + """ + Find a contractible subcomplex `L` of this simplicial complex, + preferably one which is as large as possible. + + :param verbose: If ``True``, print some messages as the simplicial + complex is computed. + :type verbose: boolean; optional, default ``False`` + + Motivation: if `K` is the original complex and if `L` is + contractible, then the relative homology `H_*(K,L)` is + isomorphic to the reduced homology of `K`. If `L` is large, + then the relative chain complex will be a good deal smaller + than the augmented chain complex for `K`, and this leads to a + speed improvement for computing the homology of `K`. + + This just passes an immutable subcomplex consisting of a facet to the + method ``_enlarge_subcomplex``. + + .. NOTE:: + + Thus when the simplicial complex is empty, so is the + resulting 'contractible subcomplex', which is therefore not + technically contractible. In this case, that doesn't + matter because the homology is computed correctly anyway. + + EXAMPLES:: + + sage: sphere = SimplicialComplex([[0,1,2,3]]) + sage: sphere.remove_face([0,1,2,3]) + sage: sphere + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)} + sage: L = sphere._contractible_subcomplex(); L + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (0, 1, 3), (0, 2, 3)} + sage: L.homology() + {0: 0, 1: 0, 2: 0} + """ + facets = [sorted(self._facets, key=str)[0]] + return self._enlarge_subcomplex(SimplicialComplex(facets, is_mutable=False), verbose=verbose) + + def _enlarge_subcomplex(self, subcomplex, verbose=False): + """ + Given a subcomplex `S` of this simplicial complex `K`, find a + subcomplex `L`, as large as possible, containing `S` which is + homotopy equivalent to `S` (so that `H_{*}(K,S)` is isomorphic + to `H_{*}(K,L)`). This way, the chain complex for computing + `H_{*}(K,L)` will be smaller than that for computing + `H_{*}(K,S)`, so the computations should be faster. + + :param subcomplex: a subcomplex of this simplicial complex + :param verbose: If ``True``, print some messages as the simplicial + complex is computed. + :type verbose: boolean; optional, default ``False`` + :return: a complex `L` containing ``subcomplex`` and contained + in ``self``, homotopy equivalent to ``subcomplex``. + + Algorithm: start with the subcomplex `S` and loop through the + facets of `K` which are not in `S`. For each one, see whether + its intersection with `S` is contractible, and if so, add it. + This is recursive: testing for contractibility calls this + routine again, via ``_contractible_subcomplex``. + + EXAMPLES:: + + sage: T = simplicial_complexes.Torus(); T + Minimal triangulation of the torus + + Inside the torus, define a subcomplex consisting of a loop:: + + sage: S = SimplicialComplex([[0,1], [1,2], [0,2]], is_mutable=False) + sage: S.homology() + {0: 0, 1: Z} + sage: L = T._enlarge_subcomplex(S) + sage: L + Simplicial complex with vertex set (0, 1, 2, 3, 4, 5, 6) and 8 facets + sage: sorted(L.facets()) + [(0, 1), (0, 1, 5), (0, 2), (0, 2, 6), (0, 3, 4), (0, 3, 5), (0, 4, 6), (1, 2)] + sage: L.homology()[1] + Z + """ + # Make the subcomplex immutable if not + if subcomplex is not None and not subcomplex._is_immutable: + subcomplex = SimplicialComplex(subcomplex._facets, + maximality_check=False, + is_mutable=False) + + if subcomplex in self.__enlarged: + return self.__enlarged[subcomplex] + faces = [x for x in list(self._facets) if x not in subcomplex._facets] + # For consistency when using different Python versions, for example, sort 'faces'. + faces = sorted(faces, key=str) + done = False + new_facets = sorted(subcomplex._facets, key=str) + while not done: + done = True + remove_these = [] + if verbose: + print(" looping through %s facets" % len(faces)) + for f in faces: + f_set = f.set() + int_facets = set( a.set().intersection(f_set) for a in new_facets ) + intersection = SimplicialComplex(int_facets) + if not intersection._facets[0].is_empty(): + if (len(intersection._facets) == 1 or + intersection == intersection._contractible_subcomplex()): + new_facets.append(f) + remove_these.append(f) + done = False + if verbose and not done: + print(" added %s facets" % len(remove_these)) + for f in remove_these: + faces.remove(f) + if verbose: + print(" now constructing a simplicial complex with %s vertices and %s facets" % (len(self.vertices()), len(new_facets))) + L = SimplicialComplex(new_facets, maximality_check=False, + is_immutable=self._is_immutable) + self.__enlarged[subcomplex] = L + # Use the same sorting on the vertices in L as in the ambient complex. + L._vertex_to_index = self._vertex_to_index + return L + + def _cubical_(self): + r""" + Cubical complex constructed from ``self``. + + ALGORITHM: + + The algorithm comes from a paper by Shtan'ko and Shtogrin, as + reported by Bukhshtaber and Panov. Let `I^m` denote the unit + `m`-cube, viewed as a cubical complex. Let `[m] = \{1, 2, + ..., m\}`; then each face of `I^m` has the following form, for + subsets `I \subset J \subset [m]`: + + .. MATH:: + + F_{I \subset J} = \{ (y_1,...,y_m) \in I^m \,:\, y_i =0 \text{ + for } i \in I, y_j = 1 \text{ for } j \not \in J\}. + + If `K` is a simplicial complex on vertex set `[m]` and if `I + \subset [m]`, write `I \in K` if `I` is a simplex of `K`. + Then we associate to `K` the cubical subcomplex of `I^m` with + faces + + .. MATH:: + + \{F_{I \subset J} \,:\, J \in K, I \neq \emptyset \} + + The geometric realization of this cubical complex is + homeomorphic to the geometric realization of the original + simplicial complex. + + REFERENCES: + + - [BP2000]_ + - [SS1992]_ + + EXAMPLES:: + + sage: T = simplicial_complexes.Torus() + sage: T.homology() + {0: 0, 1: Z x Z, 2: Z} + sage: Tc = T._cubical_() + sage: Tc + Cubical complex with 42 vertices and 168 cubes + sage: Tc.homology() + {0: 0, 1: Z x Z, 2: Z} + """ + from .cubical_complex import CubicalComplex + V = self.vertices() + embed = len(V) + # dictionary to translate vertices to the numbers 1, ..., embed + vd = dict(zip(V, range(1, embed + 1))) + cubes = [] + for JJ in self.facets(): + J = [vd[i] for i in JJ] + for i in J: + # loop over indices from 1 to embed. if equal to i, + # set to 0. if not in J, set to 1. Otherwise, range + # from 0 to 1 + cube = [] + for n in range(1, embed+1): + if n == i: + cube.append([0]) + elif n not in J: + cube.append([1]) + else: + cube.append([0, 1]) + cubes.append(cube) + return CubicalComplex(cubes) + + def connected_component(self, simplex=None): + """ + Return the connected component of this simplicial complex + containing ``simplex``. If ``simplex`` is omitted, then return + the connected component containing the zeroth vertex in the + vertex list. (If the simplicial complex is empty, raise an + error.) + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S1 == S1.connected_component() + True + sage: X = S1.disjoint_union(S1) + sage: X == X.connected_component() + False + sage: X.connected_component(Simplex(['L0'])) == X.connected_component(Simplex(['R0'])) + False + + sage: S0 = simplicial_complexes.Sphere(0) + sage: S0.vertices() + (0, 1) + sage: S0.connected_component() + Simplicial complex with vertex set (0,) and facets {(0,)} + sage: S0.connected_component(Simplex((1,))) + Simplicial complex with vertex set (1,) and facets {(1,)} + + sage: SimplicialComplex([[]]).connected_component() + Traceback (most recent call last): + ... + ValueError: the empty simplicial complex has no connected components + """ + if self.dimension() == -1: + raise ValueError("the empty simplicial complex has no connected components") + if simplex is None: + v = self.vertices()[0] + else: + v = simplex[0] + vertices = self.graph().connected_component_containing_vertex(v) + facets = [f for f in self.facets() if f.is_face(Simplex(vertices))] + return SimplicialComplex(facets) + + def fundamental_group(self, base_point=None, simplify=True): + r""" + Return the fundamental group of this simplicial complex. + + INPUT: + + - ``base_point`` (optional, default None) -- if this complex is + not path-connected, then specify a vertex; the fundamental + group is computed with that vertex as a base point. If the + complex is path-connected, then you may specify a vertex or + leave this as its default setting of ``None``. (If this + complex is path-connected, then this argument is ignored.) + + - ``simplify`` (bool, optional True) -- if False, then return a + presentation of the group in terms of generators and + relations. If True, the default, simplify as much as GAP is + able to. + + Algorithm: we compute the edge-path group -- see + :wikipedia:`Fundamental_group`. Choose a spanning tree for the + 1-skeleton, and then the group's generators are given by the + edges in the 1-skeleton; there are two types of relations: + `e=1` if `e` is in the spanning tree, and for every 2-simplex, + if its edges are `e_0`, `e_1`, and `e_2`, then we impose the + relation `e_0 e_1^{-1} e_2 = 1`. + + EXAMPLES:: + + sage: S1 = simplicial_complexes.Sphere(1) + sage: S1.fundamental_group() + Finitely presented group < e | > + + If we pass the argument ``simplify=False``, we get generators and + relations in a form which is not usually very helpful. Here is the + cyclic group of order 2, for instance:: + + sage: RP2 = simplicial_complexes.RealProjectiveSpace(2) + sage: C2 = RP2.fundamental_group(simplify=False) + sage: C2 + Finitely presented group < e0, e1, e2, e3, e4, e5, e6, e7, e8, e9 | e0, e3, e4, e7, e9, e5*e2^-1*e0, e7*e2^-1*e1, e8*e3^-1*e1, e8*e6^-1*e4, e9*e6^-1*e5 > + sage: C2.simplified() + Finitely presented group < e1 | e1^2 > + + This is the same answer given if the argument ``simplify`` is True + (the default):: + + sage: RP2.fundamental_group() + Finitely presented group < e1 | e1^2 > + + You must specify a base point to compute the fundamental group + of a non-connected complex:: + + sage: K = S1.disjoint_union(RP2) + sage: K.fundamental_group() + Traceback (most recent call last): + ... + ValueError: this complex is not connected, so you must specify a base point + sage: K.fundamental_group(base_point='L0') + Finitely presented group < e | > + sage: K.fundamental_group(base_point='R0').order() + 2 + + Some other examples:: + + sage: S1.wedge(S1).fundamental_group() + Finitely presented group < e0, e1 | > + sage: simplicial_complexes.Torus().fundamental_group() + Finitely presented group < e1, e4 | e4^-1*e1^-1*e4*e1 > + + sage: G = simplicial_complexes.MooreSpace(5).fundamental_group() + sage: G.ngens() + 1 + sage: x = G.gen(0) + sage: [(x**n).is_one() for n in range(1,6)] + [False, False, False, False, True] + """ + if not self.is_connected(): + if base_point is None: + raise ValueError("this complex is not connected, so you must specify a base point") + return self.connected_component(Simplex([base_point])).fundamental_group(simplify=simplify) + + from sage.groups.free_group import FreeGroup + from sage.interfaces.gap import gap + G = self.graph() + # If the vertices and edges of G are not sortable, e.g., a mix + # of str and int, Sage+Python 3 may raise a TypeError when + # trying to find the spanning tree. So create a graph + # isomorphic to G but with sortable vertices. Use a copy of G, + # because self.graph() is cached, and relabeling its vertices + # would relabel the cached version. + int_to_v = dict(enumerate(G.vertex_iterator())) + v_to_int = {v: i for i, v in int_to_v.items()} + G2 = G.copy(immutable=False) + G2.relabel(v_to_int) + spanning_tree = G2.min_spanning_tree() + gens = [(int_to_v[e[0]], int_to_v[e[1]]) for e in G2.edges() + if e not in spanning_tree] + if len(gens) == 0: + return gap.TrivialGroup() + + # Edges in the graph may be sorted differently than in the + # simplicial complex, so convert the edges to frozensets so we + # don't have to worry about it. Convert spanning_tree to a set + # to make lookup faster. + spanning_tree = set(frozenset((int_to_v[e[0]], int_to_v[e[1]])) + for e in spanning_tree) + gens_dict = {frozenset(g): i for i, g in enumerate(gens)} + FG = FreeGroup(len(gens), 'e') + rels = [] + for f in self._n_cells_sorted(2): + bdry = [tuple(e) for e in f.faces()] + z = dict() + for i in range(3): + x = frozenset(bdry[i]) + if (x in spanning_tree): + z[i] = FG.one() + else: + z[i] = FG.gen(gens_dict[x]) + rels.append(z[0]*z[1].inverse()*z[2]) + if simplify: + return FG.quotient(rels).simplified() + else: + return FG.quotient(rels) + + def is_isomorphic(self, other, certificate=False): + r""" + Check whether two simplicial complexes are isomorphic. + + INPUT: + + - ``certificate`` -- if ``True``, then output is ``(a, b)``, where ``a`` + is a boolean and ``b`` is either a map or ``None`` + + This is done by creating two graphs and checking whether they + are isomorphic. + + EXAMPLES:: + + sage: Z1 = SimplicialComplex([[0,1],[1,2],[2,3,4],[4,5]]) + sage: Z2 = SimplicialComplex([['a','b'],['b','c'],['c','d','e'],['e','f']]) + sage: Z3 = SimplicialComplex([[1,2,3]]) + sage: Z1.is_isomorphic(Z2) + True + sage: Z1.is_isomorphic(Z2, certificate=True) + (True, {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f'}) + sage: Z3.is_isomorphic(Z2) + False + + We check that :trac:`20751` is fixed:: + + sage: C1 = SimplicialComplex([[1,2,3], [2,4], [3,5], [5,6]]) + sage: C2 = SimplicialComplex([['a','b','c'], ['b','d'], ['c','e'], ['e','f']]) + sage: C1.is_isomorphic(C2, certificate=True) + (True, {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f'}) + """ + # Check easy invariants agree + if (sorted(x.dimension() for x in self._facets) + != sorted(x.dimension() for x in other._facets) + or len(self.vertices()) != len(other.vertices())): + return False + g1 = Graph() + g2 = Graph() + # With Python 3, "is_isomorphic" for graphs works best if the + # vertices and edges are sortable. So we translate them all to + # ints and then if a certificate is needed, we translate + # back at the end. + self_to_int = {v: i for i, v in enumerate(list(self.vertices()) + list(self._facets))} + other_to_int = {v: i for i, v in enumerate(list(other.vertices()) + list(other._facets))} + g1.add_edges((self_to_int[v], self_to_int[f], "generic edge") for f in self._facets for v in f) + g2.add_edges((other_to_int[v], other_to_int[f], "generic edge") for f in other._facets for v in f) + fake = -1 + g1.add_edges((fake, self_to_int[v], "special_edge") + for v in self.vertices()) + g2.add_edges((fake, other_to_int[v], "special_edge") + for v in other.vertices()) + if not certificate: + return g1.is_isomorphic(g2, edge_labels=True) + isisom, tr = g1.is_isomorphic(g2, edge_labels=True, certificate=True) + + if isisom: + for f in self.facets(): + tr.pop(self_to_int[f]) + tr.pop(fake) + + int_to_self = {idx: x for x, idx in self_to_int.items()} + int_to_other = {idx: x for x, idx in other_to_int.items()} + return isisom, {int_to_self[i]: int_to_other[tr[i]] for i in tr} + + def automorphism_group(self): + r""" + Return the automorphism group of the simplicial complex. + + This is done by creating a bipartite graph, whose vertices are + vertices and facets of the simplicial complex, and computing + its automorphism group. + + .. WARNING:: + + Since :trac:`14319` the domain of the automorphism group is equal to + the graph's vertex set, and the ``translation`` argument has become + useless. + + EXAMPLES:: + + sage: S = simplicial_complexes.Simplex(3) + sage: S.automorphism_group().is_isomorphic(SymmetricGroup(4)) + True + + sage: P = simplicial_complexes.RealProjectivePlane() + sage: P.automorphism_group().is_isomorphic(AlternatingGroup(5)) + True + + sage: Z = SimplicialComplex([['1','2'],['2','3','a']]) + sage: Z.automorphism_group().is_isomorphic(CyclicPermutationGroup(2)) + True + sage: group = Z.automorphism_group() + sage: sorted(group.domain()) + ['1', '2', '3', 'a'] + + Check that :trac:`17032` is fixed:: + + sage: s = SimplicialComplex([[(0,1),(2,3)]]) + sage: s.automorphism_group().cardinality() + 2 + """ + from sage.groups.perm_gps.permgroup import PermutationGroup + + G = Graph() + G.add_vertices(self.vertices()) + G.add_edges((f.tuple(), v) for f in self.facets() for v in f) + group = G.automorphism_group(partition=[list(self.vertices()), + [f.tuple() + for f in self.facets()]]) + + gens = [[tuple(c) for c in g.cycle_tuples() + if c[0] in self.vertices()] + for g in group.gens()] + + return PermutationGroup(gens=gens, domain=self.vertices()) + + def fixed_complex(self, G): + r""" + Return the fixed simplicial complex `Fix(G)` for a subgroup `G`. + + INPUT: + + - ``G`` -- a subgroup of the automorphism group of the simplicial + complex or a list of elements of the automorphism group + + OUTPUT: + + - a simplicial complex `Fix(G)` + + Vertices in `Fix(G)` are the orbits of `G` (acting on vertices + of ``self``) that form a simplex in ``self``. More generally, + simplices in `Fix(G)` correspond to simplices in ``self`` that + are union of such orbits. + + A basic example:: + + sage: S4 = simplicial_complexes.Sphere(4) + sage: S3 = simplicial_complexes.Sphere(3) + sage: fix = S4.fixed_complex([S4.automorphism_group()([(0,1)])]) + sage: fix + Simplicial complex with vertex set (0, 2, 3, 4, 5) and 5 facets + sage: fix.is_isomorphic(S3) + True + + Another simple example:: + + sage: T = SimplicialComplex([[1,2,3],[2,3,4]]) + sage: G = T.automorphism_group() + sage: T.fixed_complex([G([(1,4)])]) + Simplicial complex with vertex set (2, 3) and facets {(2, 3)} + + A more sophisticated example:: + + sage: RP2 = simplicial_complexes.ProjectivePlane() + sage: CP2 = simplicial_complexes.ComplexProjectivePlane() + sage: G = CP2.automorphism_group() + sage: H = G.subgroup([G([(2,3),(5,6),(8,9)])]) + sage: CP2.fixed_complex(H).is_isomorphic(RP2) + True + """ + from sage.categories.groups import Groups + if G in Groups(): + gens = G.gens() + else: + gens = G + G = self.automorphism_group().subgroup(gens) + + invariant_f = [list(u) for u in self.face_iterator() + if all(sorted(sigma(j) for j in u) == sorted(u) + for sigma in gens)] + new_verts = [min(o) for o in G.orbits() if o in invariant_f] + return SimplicialComplex([[s for s in f if s in new_verts] + for f in invariant_f]) + + def _Hom_(self, other, category=None): + """ + Return the set of simplicial maps between simplicial complexes + ``self`` and ``other``. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = simplicial_complexes.Sphere(2) + sage: H = Hom(S,T) # indirect doctest + sage: H + Set of Morphisms from Minimal triangulation of the 1-sphere + to Minimal triangulation of the 2-sphere + in Category of finite simplicial complexes + sage: f = {0:0,1:1,2:3} + sage: x = H(f) + sage: x + Simplicial complex morphism: + From: Minimal triangulation of the 1-sphere + To: Minimal triangulation of the 2-sphere + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 3 + + sage: S._Hom_(T, Objects()) + Traceback (most recent call last): + ... + TypeError: Category of objects is not a subcategory of SimplicialComplexes() + sage: type(Hom(S, T, Objects())) + + """ + if not category.is_subcategory(SimplicialComplexes()): + raise TypeError("{} is not a subcategory of SimplicialComplexes()".format(category)) + from sage.topology.simplicial_complex_homset import SimplicialComplexHomset + return SimplicialComplexHomset(self, other) + + # @cached_method when we switch to immutable SimplicialComplex + def _is_numeric(self): + """ + Test whether all vertices are labeled by integers + + OUTPUT: + + Boolean. Whether all vertices are labeled by (not necessarily + consecutive) integers. + + EXAMPLES:: + + sage: s = SimplicialComplex() + sage: s._is_numeric() + True + sage: s.add_face(['a', 'b', 123]) + sage: s._is_numeric() + False + """ + return all(isinstance(v, (int, Integer)) + for v in self.vertices()) + + # @cached_method when we switch to immutable SimplicialComplex + def _translation_to_numeric(self): + """ + Return a dictionary enumerating the vertices + + See also :meth:`_translation_from_numeric`, which returns the + inverse map. + + OUTPUT: + + A dictionary. The keys are the vertices, and the associated + values are integers from 0 to number of vertices - 1. + + EXAMPLES:: + + sage: s = SimplicialComplex() + sage: s._translation_to_numeric() + {} + sage: s.add_face(['a', 'b', 123]) + sage: s._translation_to_numeric() # random output + {'a': 1, 123: 0, 'b': 2} + sage: set(s._translation_to_numeric().keys()) == set(['a', 'b', 123]) + True + sage: sorted(s._translation_to_numeric().values()) + [0, 1, 2] + """ + return self._vertex_to_index + + # @cached_method when we switch to immutable SimplicialComplex + def _translation_from_numeric(self): + """ + Return a dictionary mapping vertex indices to vertices + + See also :meth:`_translation_to_numeric`, which returns the + inverse map. + + OUTPUT: + + A dictionary. The keys are integers from 0 to the number of + vertices - 1. The associated values are the vertices. + + EXAMPLES:: + + sage: s = SimplicialComplex() + sage: s._translation_from_numeric() + {} + sage: s.add_face(['a', 'b', 123]) + sage: s._translation_from_numeric() # random output + {0: 123, 1: 'a', 2: 'b'} + sage: sorted(s._translation_from_numeric().keys()) + [0, 1, 2] + sage: set(s._translation_from_numeric().values()) == set(['a', 'b', 123]) + True + """ + d = self._vertex_to_index + return {idx: v for v, idx in d.items()} + + def _chomp_repr_(self): + r""" + String representation of ``self`` suitable for use by the CHomP + program. This lists each facet on its own line, and makes + sure vertices are listed as numbers. + + EXAMPLES:: + + sage: S = SimplicialComplex([(0,1,2), (2,3,5)]) + sage: print(S._chomp_repr_()) + (2, 3, 5) + (0, 1, 2) + + A simplicial complex whose vertices are tuples, not integers:: + + sage: S = SimplicialComplex([[(0,1), (1,2), (3,4)]]) + sage: S._chomp_repr_() + '(0, 1, 2)\n' + """ + s = "" + numeric = self._is_numeric() + if not numeric: + d = self._translation_to_numeric() + for f in self.facets(): + if numeric: + s += str(f) + else: + s += '(' + ', '.join(str(d[a]) for a in f) + ')' + s += '\n' + return s + + # this function overrides the standard one for GenericCellComplex, + # because it lists the maximal faces, not the total number of faces. + def _repr_(self): + """ + Print representation. + + If there are only a few vertices or faces, they are listed. If + there are lots, the number is given. + + Facets are sorted in increasing order of dimension, and within + each dimension, they are sorted using the underlying tuple. + + EXAMPLES:: + + sage: X = SimplicialComplex([[0,1], [1,2]]) + sage: X._repr_() + 'Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1), (1, 2)}' + sage: SimplicialComplex([[i for i in range(16)]]) + Simplicial complex with 16 vertices and 1 facets + """ + vertex_limit = 45 + facet_limit = 55 + vertices = self.vertices() + try: + vertices = sorted(vertices) + except TypeError: + vertices = sorted(vertices, key=str) + try: + facets = sorted(self._facets, key=lambda f: (f.dimension(), f.tuple())) + except TypeError: + # Sorting failed. + facets = self._facets + + vertex_string = "with vertex set {}".format(tuple(vertices)) + if len(vertex_string) > vertex_limit: + vertex_string = "with %s vertices" % len(vertices) + facet_string = 'facets {' + repr(facets)[1:-1] + '}' + if len(facet_string) > facet_limit: + facet_string = "%s facets" % len(facets) + return "Simplicial complex " + vertex_string + " and " + facet_string + + def set_immutable(self): + """ + Make this simplicial complex immutable. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,4], [2,4]]) + sage: S.is_mutable() + True + sage: S.set_immutable() + sage: S.is_mutable() + False + """ + self._is_immutable = True + self._facets = tuple(self._facets) + + def is_mutable(self): + """ + Return ``True`` if mutable. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,4], [2,4]]) + sage: S.is_mutable() + True + sage: S.set_immutable() + sage: S.is_mutable() + False + sage: S2 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) + sage: S2.is_mutable() + False + sage: S3 = SimplicialComplex([[1,4], [2,4]], is_mutable=False) + sage: S3.is_mutable() + False + """ + return not self._is_immutable + + def is_immutable(self): + """ + Return ``True`` if immutable. + + EXAMPLES:: + + sage: S = SimplicialComplex([[1,4], [2,4]]) + sage: S.is_immutable() + False + sage: S.set_immutable() + sage: S.is_immutable() + True + """ + return self._is_immutable + + def cone_vertices(self): + r""" + Return the list of cone vertices of ``self``. + + A vertex is a cone vertex if and only if it appears in every facet. + + EXAMPLES:: + + sage: SimplicialComplex([[1,2,3]]).cone_vertices() + [1, 2, 3] + sage: SimplicialComplex([[1,2,3], [1,3,4], [1,5,6]]).cone_vertices() + [1] + sage: SimplicialComplex([[1,2,3], [1,3,4], [2,5,6]]).cone_vertices() + [] + """ + F = self.facets() + C = set(self.vertices()) + for f in F: + C = C.intersection(list(f)) + if not C: + break + return sorted(C) + + def decone(self): + r""" + Return the subcomplex of ``self`` induced by the non-cone vertices. + + EXAMPLES:: + + sage: SimplicialComplex([[1,2,3]]).decone() + Simplicial complex with vertex set () and facets {()} + sage: SimplicialComplex([[1,2,3], [1,3,4], [1,5,6]]).decone() + Simplicial complex with vertex set (2, 3, 4, 5, 6) and facets {(2, 3), (3, 4), (5, 6)} + sage: X = SimplicialComplex([[1,2,3], [1,3,4], [2,5,6]]) + sage: X.decone() == X + True + """ + V = set(self.vertices()).difference(self.cone_vertices()) + return self.generated_subcomplex(V) + + def is_balanced(self, check_purity=False, certificate=False): + r""" + Determine whether ``self`` is balanced. + + A simplicial complex `X` of dimension `d-1` is balanced if and + only if its vertices can be colored with `d` colors such that + every face contains at most one vertex of each color. An + equivalent condition is that the 1-skeleton of `X` is + `d`-colorable. In some contexts, it is also required that `X` + be pure (i.e., that all maximal faces of `X` have the same + dimension). + + INPUT: + + - ``check_purity`` -- (default: ``False``) if this is ``True``, + require that ``self`` be pure as well as balanced + + - ``certificate`` -- (default: ``False``) if this is ``True`` and + ``self`` is balanced, then return a `d`-coloring of the 1-skeleton. + + EXAMPLES: + + A 1-dim simplicial complex is balanced iff it is bipartite:: + + sage: X = SimplicialComplex([[1,2],[1,4],[3,4],[2,5]]) + sage: X.is_balanced() + True + sage: sorted(X.is_balanced(certificate=True)) + [[1, 3, 5], [2, 4]] + sage: X = SimplicialComplex([[1,2],[1,4],[3,4],[2,4]]) + sage: X.is_balanced() + False + + Any barycentric division is balanced:: + + sage: X = SimplicialComplex([[1,2,3],[1,2,4],[2,3,4]]) + sage: X.is_balanced() + False + sage: X.barycentric_subdivision().is_balanced() + True + + A non-pure balanced complex:: + + sage: X=SimplicialComplex([[1,2,3],[3,4]]) + sage: X.is_balanced(check_purity=True) + False + sage: sorted(X.is_balanced(certificate=True)) + [[1, 4], [2], [3]] + """ + d = 1 + self.dimension() + if check_purity and not self.is_pure(): + return False + Skel = self.graph() + if certificate: + C = Skel.coloring() + C = C if len(C) == d else False + return C + else: + return Skel.chromatic_number() == d + + def is_partitionable(self, certificate=False): + r""" + Determine whether ``self`` is partitionable. + + A partitioning of a simplicial complex `X` is a decomposition + of its face poset into disjoint Boolean intervals `[R,F]`, + where `F` ranges over all facets of `X`. + + The method sets up an integer program with: + + - a variable `y_i` for each pair `(R,F)`, where `F` is a facet of `X` + and `R` is a subface of `F` + + - a constraint `y_i+y_j \leq 1` for each pair `(R_i,F_i)`, `(R_j,F_j)` + whose Boolean intervals intersect nontrivially (equivalent to + `(R_i\subseteq F_j and R_j\subseteq F_i))` + + - objective function equal to the sum of all `y_i` + + INPUT: + + - ``certificate`` -- (default: ``False``) If ``True``, + and ``self`` is partitionable, then return a list of pairs `(R,F)` + that form a partitioning. + + EXAMPLES: + + Simplices are trivially partitionable:: + + sage: X = SimplicialComplex([ [1,2,3,4] ]) + sage: X.is_partitionable() + True + sage: X.is_partitionable(certificate=True) + [((), (1, 2, 3, 4), 4)] + + Shellable complexes are partitionable:: + + sage: X = SimplicialComplex([ [1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5] ]) + sage: X.is_partitionable() + True + sage: P = X.is_partitionable(certificate=True) + sage: n_intervals_containing = lambda f: len([ RF for RF in P if RF[0].is_face(f) and f.is_face(RF[1]) ]) + sage: all( n_intervals_containing(f)==1 for k in X.faces().keys() for f in X.faces()[k] ) + True + + A non-shellable, non-Cohen-Macaulay, partitionable example, constructed by Björner:: + + sage: X = SimplicialComplex([ [1,2,3],[1,2,4],[1,3,4],[2,3,4],[1,5,6] ]) + sage: X.is_partitionable() + True + + The bowtie complex is not partitionable:: + + sage: X = SimplicialComplex([ [1,2,3],[1,4,5] ]) + sage: X.is_partitionable() + False + """ + from sage.numerical.mip import MixedIntegerLinearProgram + RFPairs = [(Simplex(r), f, f.dimension() - len(r) + 1) + for f in self.facets() for r in Set(f).subsets()] + n = len(RFPairs) + IP = MixedIntegerLinearProgram() + y = IP.new_variable(binary=True) + for i0, pair0 in enumerate(RFPairs): + for i1, pair1 in enumerate(RFPairs): + if (i0 < i1 and pair0[0].is_face(pair1[1]) and + pair1[0].is_face(pair0[1])): + IP.add_constraint(y[i0] + y[i1] <= 1) + IP.set_objective(sum(2**RFPairs[i][2] * y[i] for i in range(n))) + sol = round(IP.solve()) + if sol < sum(self.f_vector()): + return False + elif not certificate: + return True + else: + x = IP.get_values(y) + return [RFPairs[i] for i in range(n) if x[i] == 1] + + def intersection(self, other): + r""" + Calculate the intersection of two simplicial complexes. + + EXAMPLES:: + + sage: X = SimplicialComplex([[1,2,3],[1,2,4]]) + sage: Y = SimplicialComplex([[1,2,3],[1,4,5]]) + sage: Z = SimplicialComplex([[1,2,3],[1,4],[2,4]]) + sage: sorted(X.intersection(Y).facets()) + [(1, 2, 3), (1, 4)] + sage: X.intersection(X) == X + True + sage: X.intersection(Z) == X + False + sage: X.intersection(Z) == Z + True + """ + F = [] + for k in range(1 + min(self.dimension(), other.dimension())): + F = F + [s for s in self.faces()[k] if s in other.faces()[k]] + return SimplicialComplex(F) + +# Miscellaneous utility functions. + +# The following two functions can be used to generate the facets for +# the corresponding examples in sage.homology.examples. These take a +# few seconds to run, so the actual examples have the facets +# hard-coded. Thus the following functions are not currently used in +# the Sage library. + +def facets_for_RP4(): + """ + Return the list of facets for a minimal triangulation of 4-dimensional + real projective space. + + We use vertices numbered 1 through 16, define two facets, and define + a certain subgroup `G` of the symmetric group `S_{16}`. Then the set + of all facets is the `G`-orbit of the two given facets. + + See the description in Example 3.12 in Datta [Dat2007]_. + + EXAMPLES:: + + sage: from sage.topology.simplicial_complex import facets_for_RP4 + sage: A = facets_for_RP4() # long time (1 or 2 seconds) + sage: SimplicialComplex(A) == simplicial_complexes.RealProjectiveSpace(4) # long time + True + """ + # Define the group: + from sage.groups.perm_gps.permgroup import PermutationGroup + g1 = '(2,7)(4,10)(5,6)(11,12)' + g2 = '(1, 2, 3, 4, 5, 10)(6, 8, 9)(11, 12, 13, 14, 15, 16)' + G = PermutationGroup([g1, g2]) + # Define the two simplices: + t1 = (1, 2, 4, 5, 11) + t2 = (1, 2, 4, 11, 13) + # Apply the group elements to the simplices: + facets = [] + for g in G: + d = g.dict() + for t in [t1, t2]: + new = tuple([d[j] for j in t]) + if new not in facets: + facets.append(new) + return facets + + +def facets_for_K3(): + """ + Return the facets for a minimal triangulation of the K3 surface. + + This is a pure simplicial complex of dimension 4 with 16 + vertices and 288 facets. The facets are obtained by constructing a + few facets and a permutation group `G`, and then computing the + `G`-orbit of those facets. + + See Casella and Kühnel in [CK2001]_ and Spreer and Kühnel [SK2011]_; + the construction here uses the labeling from Spreer and Kühnel. + + EXAMPLES:: + + sage: from sage.topology.simplicial_complex import facets_for_K3 + sage: A = facets_for_K3() # long time (a few seconds) + sage: SimplicialComplex(A) == simplicial_complexes.K3Surface() # long time + True + """ + from sage.groups.perm_gps.permgroup import PermutationGroup + G = PermutationGroup([[(1,3,8,4,9,16,15,2,14,12,6,7,13,5,10)], + [(1,11,16),(2,10,14),(3,12,13),(4,9,15),(5,7,8)]]) + return ([tuple([g(i) for i in (1,2,3,8,12)]) for g in G] + +[tuple([g(i) for i in (1,2,5,8,14)]) for g in G]) diff --git a/src/sage/homology/simplicial_complexes_catalog.py b/src/sage/topology/simplicial_complex_catalog.py similarity index 58% rename from src/sage/homology/simplicial_complexes_catalog.py rename to src/sage/topology/simplicial_complex_catalog.py index 955d5716703..01ade2e3667 100644 --- a/src/sage/homology/simplicial_complexes_catalog.py +++ b/src/sage/topology/simplicial_complex_catalog.py @@ -21,31 +21,31 @@ All of these examples are accessible by typing ``simplicial_complexes.NAME``, where ``NAME`` is the name of the example. -- :meth:`~sage.homology.examples.BarnetteSphere` -- :meth:`~sage.homology.examples.BrucknerGrunbaumSphere` -- :meth:`~sage.homology.examples.ChessboardComplex` -- :meth:`~sage.homology.examples.ComplexProjectivePlane` -- :meth:`~sage.homology.examples.DunceHat` -- :meth:`~sage.homology.examples.FareyMap` -- :meth:`~sage.homology.examples.K3Surface` -- :meth:`~sage.homology.examples.KleinBottle` -- :meth:`~sage.homology.examples.MatchingComplex` -- :meth:`~sage.homology.examples.MooreSpace` -- :meth:`~sage.homology.examples.NotIConnectedGraphs` -- :meth:`~sage.homology.examples.PoincareHomologyThreeSphere` -- :meth:`~sage.homology.examples.PseudoQuaternionicProjectivePlane` -- :meth:`~sage.homology.examples.RandomComplex` -- :meth:`~sage.homology.examples.RandomTwoSphere` -- :meth:`~sage.homology.examples.RealProjectivePlane` -- :meth:`~sage.homology.examples.RealProjectiveSpace` -- :meth:`~sage.homology.examples.RudinBall` -- :meth:`~sage.homology.examples.ShiftedComplex` -- :meth:`~sage.homology.examples.Simplex` -- :meth:`~sage.homology.examples.Sphere` -- :meth:`~sage.homology.examples.SumComplex` -- :meth:`~sage.homology.examples.SurfaceOfGenus` -- :meth:`~sage.homology.examples.Torus` -- :meth:`~sage.homology.examples.ZieglerBall` +- :meth:`~sage.topology.examples.BarnetteSphere` +- :meth:`~sage.topology.examples.BrucknerGrunbaumSphere` +- :meth:`~sage.topology.examples.ChessboardComplex` +- :meth:`~sage.topology.examples.ComplexProjectivePlane` +- :meth:`~sage.topology.examples.DunceHat` +- :meth:`~sage.topology.examples.FareyMap` +- :meth:`~sage.topology.examples.K3Surface` +- :meth:`~sage.topology.examples.KleinBottle` +- :meth:`~sage.topology.examples.MatchingComplex` +- :meth:`~sage.topology.examples.MooreSpace` +- :meth:`~sage.topology.examples.NotIConnectedGraphs` +- :meth:`~sage.topology.examples.PoincareHomologyThreeSphere` +- :meth:`~sage.topology.examples.PseudoQuaternionicProjectivePlane` +- :meth:`~sage.topology.examples.RandomComplex` +- :meth:`~sage.topology.examples.RandomTwoSphere` +- :meth:`~sage.topology.examples.RealProjectivePlane` +- :meth:`~sage.topology.examples.RealProjectiveSpace` +- :meth:`~sage.topology.examples.RudinBall` +- :meth:`~sage.topology.examples.ShiftedComplex` +- :meth:`~sage.topology.examples.Simplex` +- :meth:`~sage.topology.examples.Sphere` +- :meth:`~sage.topology.examples.SumComplex` +- :meth:`~sage.topology.examples.SurfaceOfGenus` +- :meth:`~sage.topology.examples.Torus` +- :meth:`~sage.topology.examples.ZieglerBall` You can also get a list by typing ``simplicial_complexes.`` and hitting the TAB key. @@ -64,7 +64,8 @@ {0: 0, 1: Z^16, 2: 0} """ -from sage.homology.examples import (Sphere, Simplex, Torus, ProjectivePlane, +from sage.topology.simplicial_complex_examples import (Sphere, Simplex, Torus, + ProjectivePlane, RealProjectivePlane, KleinBottle, FareyMap, SurfaceOfGenus, MooreSpace, ComplexProjectivePlane, PseudoQuaternionicProjectivePlane, diff --git a/src/sage/topology/simplicial_complex_examples.py b/src/sage/topology/simplicial_complex_examples.py new file mode 100644 index 00000000000..5b49a8de836 --- /dev/null +++ b/src/sage/topology/simplicial_complex_examples.py @@ -0,0 +1,1619 @@ +# -*- coding: utf-8 -*- +""" +Examples of simplicial complexes + +There are two main types: manifolds and examples related to graph +theory. + +For manifolds, there are functions defining the `n`-sphere for any +`n`, the torus, `n`-dimensional real projective space for any `n`, the +complex projective plane, surfaces of arbitrary genus, and some other +manifolds, all as simplicial complexes. + +Aside from surfaces, this file also provides functions for +constructing some other simplicial complexes: the simplicial complex +of not-`i`-connected graphs on `n` vertices, the matching complex on n +vertices, the chessboard complex for an `n` by `i` chessboard, and +others. These provide examples of large simplicial complexes; for +example, ``simplicial_complexes.NotIConnectedGraphs(7, 2)`` has over a +million simplices. + +All of these examples are accessible by typing +``simplicial_complexes.NAME``, where ``NAME`` is the name of the example. + +- :func:`BarnetteSphere` +- :func:`BrucknerGrunbaumSphere` +- :func:`ChessboardComplex` +- :func:`ComplexProjectivePlane` +- :func:`DunceHat` +- :func:`FareyMap` +- :func:`K3Surface` +- :func:`KleinBottle` +- :func:`MatchingComplex` +- :func:`MooreSpace` +- :func:`NotIConnectedGraphs` +- :func:`PoincareHomologyThreeSphere` +- :func:`PseudoQuaternionicProjectivePlane` +- :func:`RandomComplex` +- :func:`RandomTwoSphere` +- :func:`RealProjectivePlane` +- :func:`RealProjectiveSpace` +- :func:`RudinBall` +- :func:`ShiftedComplex` +- :func:`Simplex` +- :func:`Sphere` +- :func:`SumComplex` +- :func:`SurfaceOfGenus` +- :func:`Torus` +- :func:`ZieglerBall` + +You can also get a list by typing ``simplicial_complexes.`` and hitting the +TAB key. + +EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(2) # the 2-sphere + sage: S.homology() + {0: 0, 1: 0, 2: Z} + sage: simplicial_complexes.SurfaceOfGenus(3) + Triangulation of an orientable surface of genus 3 + sage: M4 = simplicial_complexes.MooreSpace(4) + sage: M4.homology() + {0: 0, 1: C4, 2: 0} + sage: simplicial_complexes.MatchingComplex(6).homology() + {0: 0, 1: Z^16, 2: 0} +""" + +from .simplicial_complex import SimplicialComplex +from sage.structure.unique_representation import UniqueRepresentation +# Below we define a function Simplex to construct a simplex as a +# simplicial complex. We also need to use actual simplices as +# simplices, hence: +from .simplicial_complex import Simplex as TrueSimplex +from sage.sets.set import Set +from sage.misc.functional import is_even +from sage.combinat.subset import Subsets +import sage.misc.prandom as random + +# Miscellaneous utility functions. + +# The following two functions can be used to generate the facets for +# the corresponding examples in sage.homology.examples. These take a +# few seconds to run, so the actual examples have the facets +# hard-coded. Thus the following functions are not currently used in +# the Sage library. + +def facets_for_RP4(): + """ + Return the list of facets for a minimal triangulation of 4-dimensional + real projective space. + + We use vertices numbered 1 through 16, define two facets, and define + a certain subgroup `G` of the symmetric group `S_{16}`. Then the set + of all facets is the `G`-orbit of the two given facets. + + See the description in Example 3.12 in Datta [Dat2007]_. + + EXAMPLES:: + + sage: from sage.topology.simplicial_complex_examples import facets_for_RP4 + sage: A = facets_for_RP4() # long time (1 or 2 seconds) + sage: SimplicialComplex(A) == simplicial_complexes.RealProjectiveSpace(4) # long time + True + """ + # Define the group: + from sage.groups.perm_gps.permgroup import PermutationGroup + g1 = '(2, 7)(4, 10)(5, 6)(11, 12)' + g2 = '(1, 2, 3, 4, 5, 10)(6, 8, 9)(11, 12, 13, 14, 15, 16)' + G = PermutationGroup([g1, g2]) + # Define the two simplices: + t1 = (1, 2, 4, 5, 11) + t2 = (1, 2, 4, 11, 13) + # Apply the group elements to the simplices: + facets = [] + for g in G: + d = g.dict() + for t in [t1, t2]: + new = tuple([d[j] for j in t]) + if new not in facets: + facets.append(new) + return facets + + +def facets_for_K3(): + """ + Return the facets for a minimal triangulation of the K3 surface. + + This is a pure simplicial complex of dimension 4 with 16 + vertices and 288 facets. The facets are obtained by constructing a + few facets and a permutation group `G`, and then computing the + `G`-orbit of those facets. + + See Casella and Kühnel in [CK2001]_ and Spreer and Kühnel [SK2011]_; + the construction here uses the labeling from Spreer and Kühnel. + + EXAMPLES:: + + sage: from sage.topology.simplicial_complex_examples import facets_for_K3 + sage: A = facets_for_K3() # long time (a few seconds) + sage: SimplicialComplex(A) == simplicial_complexes.K3Surface() # long time + True + """ + from sage.groups.perm_gps.permgroup import PermutationGroup + G = PermutationGroup([[(1, 3, 8, 4, 9, 16, 15, 2, 14, 12, 6, 7, 13, 5, 10)], + [(1, 11, 16), (2, 10, 14), (3, 12, 13), (4, 9, 15), (5, 7, 8)]]) + return ([tuple([g(i) for i in (1, 2, 3, 8, 12)]) for g in G] + + [tuple([g(i) for i in (1, 2, 5, 8, 14)]) for g in G]) + +def matching(A, B): + r""" + List of maximal matchings between the sets ``A`` and ``B``. + + A matching is a set of pairs `(a, b) \in A \times B` where each `a` and + `b` appears in at most one pair. A maximal matching is one which is + maximal with respect to inclusion of subsets of `A \times B`. + + INPUT: + + - ``A``, ``B`` -- list, tuple, or indeed anything which can be + converted to a set. + + EXAMPLES:: + + sage: from sage.topology.simplicial_complex_examples import matching + sage: matching([1, 2], [3, 4]) + [{(1, 3), (2, 4)}, {(1, 4), (2, 3)}] + sage: matching([0, 2], [0]) + [{(0, 0)}, {(2, 0)}] + """ + answer = [] + if len(A) == 0 or len(B) == 0: + return [set([])] + for v in A: + for w in B: + for M in matching(set(A).difference([v]), set(B).difference([w])): + new = M.union([(v, w)]) + if new not in answer: + answer.append(new) + return answer + + +class UniqueSimplicialComplex(SimplicialComplex, UniqueRepresentation): + """ + This combines :class:`SimplicialComplex` and + :class:`UniqueRepresentation`. It is intended to be used to make + standard examples of simplicial complexes unique. See :trac:`13566`. + + INPUT: + + - the inputs are the same as for a :class:`SimplicialComplex`, + with one addition and two exceptions. The exceptions are that + ``is_mutable`` and ``is_immutable`` are ignored: all instances + of this class are immutable. The addition: + + - ``name`` -- string (optional), the string representation for this complex. + + EXAMPLES:: + + sage: from sage.topology.simplicial_complex_examples import UniqueSimplicialComplex + sage: SimplicialComplex([[0, 1]]) is SimplicialComplex([[0, 1]]) + False + sage: UniqueSimplicialComplex([[0, 1]]) is UniqueSimplicialComplex([[0, 1]]) + True + sage: UniqueSimplicialComplex([[0, 1]]) + Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + sage: UniqueSimplicialComplex([[0, 1]], name='The 1-simplex') + The 1-simplex + """ + @staticmethod + def __classcall__(self, maximal_faces=None, name=None, **kwds): + """ + TESTS:: + + sage: from sage.topology.simplicial_complex_examples import UniqueSimplicialComplex + sage: UniqueSimplicialComplex([[1, 2, 3], [0, 1, 3]]) is UniqueSimplicialComplex([(1, 2, 3), (0, 1, 3)]) + True + sage: X = UniqueSimplicialComplex([[1, 2, 3], [0, 1, 3]]) + sage: X is UniqueSimplicialComplex(X) + True + + Testing ``from_characteristic_function``:: + + sage: UniqueSimplicialComplex(from_characteristic_function=(lambda x:sum(x)<=4, range(5))) + Simplicial complex with vertex set (0, 1, 2, 3, 4) and facets {(0, 4), (0, 1, 2), (0, 1, 3)} + """ + char_fcn = kwds.get('from_characteristic_function', None) + if char_fcn: + kwds['from_characteristic_function'] = (char_fcn[0], tuple(char_fcn[1])) + if maximal_faces: + # Test to see if maximal_faces is a cell complex or another + # object which can be converted to a simplicial complex: + C = None + if isinstance(maximal_faces, SimplicialComplex): + C = maximal_faces + else: + try: + C = maximal_faces._simplicial_() + except AttributeError: + if not isinstance(maximal_faces, (list, tuple, Simplex)): + # Convert it into a list (in case it is an iterable) + maximal_faces = list(maximal_faces) + if C is not None: + maximal_faces = C.facets() + # Now convert maximal_faces to a tuple of tuples, so that it is hashable. + maximal_faces = tuple(tuple(mf) for mf in maximal_faces) + return super(UniqueSimplicialComplex, self).__classcall__(self, maximal_faces, + name=name, + **kwds) + + def __init__(self, maximal_faces=None, name=None, **kwds): + """ + TESTS:: + + sage: from sage.topology.simplicial_complex_examples import UniqueSimplicialComplex + sage: UniqueSimplicialComplex([[1, 2, 3], [0, 1, 3]], is_mutable=True).is_mutable() + False + """ + if 'is_mutable' in kwds: + del kwds['is_mutable'] + if 'is_immutable' in kwds: + del kwds['is_immutable'] + self._name = name + SimplicialComplex.__init__(self, maximal_faces=maximal_faces, is_mutable=False, **kwds) + + def _repr_(self): + """ + Print representation + + If the argument ``name`` was specified when defining the + complex, use that. Otherwise, use the print representation + from the class :class:`SimplicialComplex`. + + TESTS:: + + sage: from sage.topology.simplicial_complex_examples import UniqueSimplicialComplex + sage: UniqueSimplicialComplex([[0, 1]]) + Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + sage: UniqueSimplicialComplex([[0, 1]], name='Joe') + Joe + """ + if self._name: + return self._name + return SimplicialComplex._repr_(self) + +# Now the functions that produce the actual examples... + +def Sphere(n): + """ + A minimal triangulation of the `n`-dimensional sphere. + + INPUT: + + - ``n`` -- positive integer + + EXAMPLES:: + + sage: simplicial_complexes.Sphere(2) + Minimal triangulation of the 2-sphere + sage: simplicial_complexes.Sphere(5).homology() + {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: Z} + sage: [simplicial_complexes.Sphere(n).euler_characteristic() for n in range(6)] + [2, 0, 2, 0, 2, 0] + sage: [simplicial_complexes.Sphere(n).f_vector() for n in range(6)] + [[1, 2], + [1, 3, 3], + [1, 4, 6, 4], + [1, 5, 10, 10, 5], + [1, 6, 15, 20, 15, 6], + [1, 7, 21, 35, 35, 21, 7]] + """ + S = TrueSimplex(n+1) + facets = tuple(S.faces()) + return UniqueSimplicialComplex(facets, + name='Minimal triangulation of the {}-sphere'.format(n)) + +def Simplex(n): + """ + An `n`-dimensional simplex, as a simplicial complex. + + INPUT: + + - ``n`` -- a non-negative integer + + OUTPUT: the simplicial complex consisting of the `n`-simplex + on vertices `(0, 1, ..., n)` and all of its faces. + + EXAMPLES:: + + sage: simplicial_complexes.Simplex(3) + The 3-simplex + sage: simplicial_complexes.Simplex(5).euler_characteristic() + 1 + """ + return UniqueSimplicialComplex([TrueSimplex(n)], + name='The {}-simplex'.format(n)) + +def Torus(): + r""" + A minimal triangulation of the torus. + + This is a simplicial complex with 7 vertices, 21 edges and 14 + faces. It is the unique triangulation of the torus with 7 + vertices, and has been found by Möbius in 1861. + + This is also the combinatorial structure of the Császár + polyhedron (see :wikipedia:`Császár_polyhedron`). + + EXAMPLES:: + + sage: T = simplicial_complexes.Torus(); T.homology(1) + Z x Z + sage: T.f_vector() + [1, 7, 21, 14] + + TESTS:: + + sage: T.flip_graph().is_isomorphic(graphs.HeawoodGraph()) + True + + REFERENCES: + + - [Lut2002]_ + """ + return UniqueSimplicialComplex([[0, 1, 2], [1, 2, 4], [1, 3, 4], [1, 3, 6], + [0, 1, 5], [1, 5, 6], [2, 3, 5], [2, 4, 5], + [2, 3, 6], [0, 2, 6], [0, 3, 4], [0, 3, 5], + [4, 5, 6], [0, 4, 6]], + name='Minimal triangulation of the torus') + +def RealProjectivePlane(): + """ + A minimal triangulation of the real projective plane. + + EXAMPLES:: + + sage: P = simplicial_complexes.RealProjectivePlane() + sage: Q = simplicial_complexes.ProjectivePlane() + sage: P == Q + True + sage: P.cohomology(1) + 0 + sage: P.cohomology(2) + C2 + sage: P.cohomology(1, base_ring=GF(2)) + Vector space of dimension 1 over Finite Field of size 2 + sage: P.cohomology(2, base_ring=GF(2)) + Vector space of dimension 1 over Finite Field of size 2 + """ + return UniqueSimplicialComplex([[0, 1, 2], [0, 2, 3], [0, 1, 5], [0, 4, 5], + [0, 3, 4], [1, 2, 4], [1, 3, 4], [1, 3, 5], + [2, 3, 5], [2, 4, 5]], + name='Minimal triangulation of the real projective plane') + +ProjectivePlane = RealProjectivePlane + +def KleinBottle(): + """ + A minimal triangulation of the Klein bottle, as presented for example + in Davide Cervone's thesis [Cer1994]_. + + EXAMPLES:: + + sage: simplicial_complexes.KleinBottle() + Minimal triangulation of the Klein bottle + """ + return UniqueSimplicialComplex([[2, 3, 7], [1, 2, 3], [1, 3, 5], [1, 5, 7], + [1, 4, 7], [2, 4, 6], [1, 2, 6], [1, 6, 0], + [1, 4, 0], [2, 4, 0], [3, 4, 7], [3, 4, 6], + [3, 5, 6], [5, 6, 0], [2, 5, 0], [2, 5, 7]], + name='Minimal triangulation of the Klein bottle') + +def SurfaceOfGenus(g, orientable=True): + """ + A surface of genus `g`. + + INPUT: + + - ``g`` -- a non-negative integer. The desired genus + + - ``orientable`` -- boolean (optional, default ``True``). If + ``True``, return an orientable surface, and if ``False``, + return a non-orientable surface. + + In the orientable case, return a sphere if `g` is zero, and + otherwise return a `g`-fold connected sum of a torus with itself. + + In the non-orientable case, raise an error if `g` is zero. If + `g` is positive, return a `g`-fold connected sum of a + real projective plane with itself. + + EXAMPLES:: + + sage: simplicial_complexes.SurfaceOfGenus(2) + Triangulation of an orientable surface of genus 2 + sage: simplicial_complexes.SurfaceOfGenus(1, orientable=False) + Triangulation of a non-orientable surface of genus 1 + """ + if g == 0: + if not orientable: + raise ValueError("No non-orientable surface of genus zero.") + else: + return Sphere(2) + if orientable: + T = Torus() + else: + T = RealProjectivePlane() + S = T + for i in range(g-1): + S = S.connected_sum(T) + if orientable: + orient_str = 'n orientable' + else: + orient_str = ' non-orientable' + return UniqueSimplicialComplex(S, + name='Triangulation of a{} surface of genus {}'.format(orient_str, g)) + +def MooreSpace(q): + """ + Triangulation of the mod `q` Moore space. + + INPUT: + + - ``q`` -0 integer, at least 2 + + This is a simplicial complex with simplices of dimension 0, 1, + and 2, such that its reduced homology is isomorphic to + `\\ZZ/q\\ZZ` in dimension 1, zero otherwise. + + If `q=2`, this is the real projective plane. If `q>2`, then + construct it as follows: start with a triangle with vertices + 1, 2, 3. We take a `3q`-gon forming a `q`-fold cover of the + triangle, and we form the resulting complex as an + identification space of the `3q`-gon. To triangulate this + identification space, put `q` vertices `A_0`, ..., `A_{q-1}`, + in the interior, each of which is connected to 1, 2, 3 (two + facets each: `[1, 2, A_i]`, `[2, 3, A_i]`). Put `q` more + vertices in the interior: `B_0`, ..., `B_{q-1}`, with facets + `[3, 1, B_i]`, `[3, B_i, A_i]`, `[1, B_i, A_{i+1}]`, `[B_i, + A_i, A_{i+1}]`. Then triangulate the interior polygon with + vertices `A_0`, `A_1`, ..., `A_{q-1}`. + + EXAMPLES:: + + sage: simplicial_complexes.MooreSpace(2) + Minimal triangulation of the real projective plane + sage: simplicial_complexes.MooreSpace(3).homology()[1] + C3 + sage: simplicial_complexes.MooreSpace(4).suspension().homology()[2] + C4 + sage: simplicial_complexes.MooreSpace(8) + Triangulation of the mod 8 Moore space + """ + if q <= 1: + raise ValueError("The mod q Moore space is only defined if q is at least 2") + if q == 2: + return RealProjectivePlane() + facets = [] + for i in range(q): + Ai = "A" + str(i) + Aiplus = "A" + str((i+1) % q) + Bi = "B" + str(i) + facets.append([1, 2, Ai]) + facets.append([2, 3, Ai]) + facets.append([3, 1, Bi]) + facets.append([3, Bi, Ai]) + facets.append([1, Bi, Aiplus]) + facets.append([Bi, Ai, Aiplus]) + for i in range(1, q-1): + Ai = "A" + str(i) + Aiplus = "A" + str((i+1) % q) + facets.append(["A0", Ai, Aiplus]) + return UniqueSimplicialComplex(facets, + name='Triangulation of the mod {} Moore space'.format(q)) + +def ComplexProjectivePlane(): + """ + A minimal triangulation of the complex projective plane. + + This was constructed by Kühnel and Banchoff [KB1983]_. + + EXAMPLES:: + + sage: C = simplicial_complexes.ComplexProjectivePlane() + sage: C.f_vector() + [1, 9, 36, 84, 90, 36] + sage: C.homology(2) + Z + sage: C.homology(4) + Z + """ + return UniqueSimplicialComplex( + [[1, 2, 4, 5, 6], [2, 3, 5, 6, 4], [3, 1, 6, 4, 5], + [1, 2, 4, 5, 9], [2, 3, 5, 6, 7], [3, 1, 6, 4, 8], + [2, 3, 6, 4, 9], [3, 1, 4, 5, 7], [1, 2, 5, 6, 8], + [3, 1, 5, 6, 9], [1, 2, 6, 4, 7], [2, 3, 4, 5, 8], + [4, 5, 7, 8, 9], [5, 6, 8, 9, 7], [6, 4, 9, 7, 8], + [4, 5, 7, 8, 3], [5, 6, 8, 9, 1], [6, 4, 9, 7, 2], + [5, 6, 9, 7, 3], [6, 4, 7, 8, 1], [4, 5, 8, 9, 2], + [6, 4, 8, 9, 3], [4, 5, 9, 7, 1], [5, 6, 7, 8, 2], + [7, 8, 1, 2, 3], [8, 9, 2, 3, 1], [9, 7, 3, 1, 2], + [7, 8, 1, 2, 6], [8, 9, 2, 3, 4], [9, 7, 3, 1, 5], + [8, 9, 3, 1, 6], [9, 7, 1, 2, 4], [7, 8, 2, 3, 5], + [9, 7, 2, 3, 6], [7, 8, 3, 1, 4], [8, 9, 1, 2, 5]], + name='Minimal triangulation of the complex projective plane') + + +def PseudoQuaternionicProjectivePlane(): + r""" + Return a pure simplicial complex of dimension 8 with 490 facets. + + .. WARNING:: + + This is expected to be a triangulation of the projective plane + `HP^2` over the ring of quaternions, but this has not been + proved yet. + + This simplicial complex has the same homology as `HP^2`. Its + automorphism group is isomorphic to the alternating group `A_5` + and acts transitively on vertices. + + This is defined here using the description in [BK1992]_. This + article deals with three different triangulations. This procedure + returns the only one which has a transitive group of + automorphisms. + + EXAMPLES:: + + sage: HP2 = simplicial_complexes.PseudoQuaternionicProjectivePlane() ; HP2 + Simplicial complex with 15 vertices and 490 facets + sage: HP2.f_vector() + [1, 15, 105, 455, 1365, 3003, 4515, 4230, 2205, 490] + + Checking its automorphism group:: + + sage: HP2.automorphism_group().is_isomorphic(AlternatingGroup(5)) + True + """ + from sage.groups.perm_gps.permgroup import PermutationGroup + P = [(1, 2, 3, 4, 5), (6, 7, 8, 9, 10), (11, 12, 13, 14, 15)] + S = [(1, 6, 11), (2, 15, 14), (3, 13, 8), (4, 7, 5), (9, 12, 10)] + start_list = [ + (1, 2, 3, 6, 8, 11, 13, 14, 15), # A + (1, 3, 6, 8, 9, 10, 11, 12, 13), # B + (1, 2, 6, 9, 10, 11, 12, 14, 15), # C + (1, 2, 3, 4, 7, 9, 12, 14, 15), # D + (1, 2, 4, 7, 9, 10, 12, 13, 14), # E + (1, 2, 6, 8, 9, 10, 11, 14, 15), # F + (1, 2, 3, 4, 5, 6, 9, 11, 13), # G + (1, 3, 5, 6, 8, 9, 10, 11, 12), # H + (1, 3, 5, 6, 7, 8, 9, 10, 11), # I + (1, 2, 3, 4, 5, 7, 10, 12, 15), # J + (1, 2, 3, 7, 8, 10, 12, 13, 14), # K + (2, 5, 6, 7, 8, 9, 10, 13, 14), # M + + (3, 4, 6, 7, 11, 12, 13, 14, 15), # L + (3, 4, 6, 7, 10, 12, 13, 14, 15)] # N + return UniqueSimplicialComplex([[g(index) for index in tuple] + for tuple in start_list + for g in PermutationGroup([P, S])]) + +def PoincareHomologyThreeSphere(): + """ + A triangulation of the Poincaré homology 3-sphere. + + This is a manifold whose integral homology is identical to the + ordinary 3-sphere, but it is not simply connected. In particular, + its fundamental group is the binary icosahedral group, which has + order 120. The triangulation given here has 16 vertices and is + due to Björner and Lutz [BL2000]_. + + EXAMPLES:: + + sage: S3 = simplicial_complexes.Sphere(3) + sage: Sigma3 = simplicial_complexes.PoincareHomologyThreeSphere() + sage: S3.homology() == Sigma3.homology() + True + sage: Sigma3.fundamental_group().cardinality() # long time + 120 + """ + return UniqueSimplicialComplex( + [[1, 2, 4, 9], [1, 2, 4, 15], [1, 2, 6, 14], [1, 2, 6, 15], + [1, 2, 9, 14], [1, 3, 4, 12], [1, 3, 4, 15], [1, 3, 7, 10], + [1, 3, 7, 12], [1, 3, 10, 15], [1, 4, 9, 12], [1, 5, 6, 13], + [1, 5, 6, 14], [1, 5, 8, 11], [1, 5, 8, 13], [1, 5, 11, 14], + [1, 6, 13, 15], [1, 7, 8, 10], [1, 7, 8, 11], [1, 7, 11, 12], + [1, 8, 10, 13], [1, 9, 11, 12], [1, 9, 11, 14], [1, 10, 13, 15], + [2, 3, 5, 10], [2, 3, 5, 11], [2, 3, 7, 10], [2, 3, 7, 13], + [2, 3, 11, 13], [2, 4, 9, 13], [2, 4, 11, 13], [2, 4, 11, 15], + [2, 5, 8, 11], [2, 5, 8, 12], [2, 5, 10, 12], [2, 6, 10, 12], + [2, 6, 10, 14], [2, 6, 12, 15], [2, 7, 9, 13], [2, 7, 9, 14], + [2, 7, 10, 14], [2, 8, 11, 15], [2, 8, 12, 15], [3, 4, 5, 14], + [3, 4, 5, 15], [3, 4, 12, 14], [3, 5, 10, 15], [3, 5, 11, 14], + [3, 7, 12, 13], [3, 11, 13, 14], [3, 12, 13, 14], [4, 5, 6, 7], + [4, 5, 6, 14], [4, 5, 7, 15], [4, 6, 7, 11], [4, 6, 10, 11], + [4, 6, 10, 14], [4, 7, 11, 15], [4, 8, 9, 12], [4, 8, 9, 13], + [4, 8, 10, 13], [4, 8, 10, 14], [4, 8, 12, 14], [4, 10, 11, 13], + [5, 6, 7, 13], [5, 7, 9, 13], [5, 7, 9, 15], [5, 8, 9, 12], + [5, 8, 9, 13], [5, 9, 10, 12], [5, 9, 10, 15], [6, 7, 11, 12], + [6, 7, 12, 13], [6, 10, 11, 12], [6, 12, 13, 15], [7, 8, 10, 14], + [7, 8, 11, 15], [7, 8, 14, 15], [7, 9, 14, 15], [8, 12, 14, 15], + [9, 10, 11, 12], [9, 10, 11, 16], [9, 10, 15, 16], [9, 11, 14, 16], + [9, 14, 15, 16], [10, 11, 13, 16], [10, 13, 15, 16], + [11, 13, 14, 16], [12, 13, 14, 15], [13, 14, 15, 16]], + name='Triangulation of the Poincare homology 3-sphere') + +def RealProjectiveSpace(n): + r""" + A triangulation of `\Bold{R}P^n` for any `n \geq 0`. + + INPUT: + + - ``n`` -- integer, the dimension of the real projective space + to construct + + The first few cases are pretty trivial: + + - `\Bold{R}P^0` is a point. + + - `\Bold{R}P^1` is a circle, triangulated as the boundary of a + single 2-simplex. + + - `\Bold{R}P^2` is the real projective plane, here given its + minimal triangulation with 6 vertices, 15 edges, and 10 + triangles. + + - `\Bold{R}P^3`: any triangulation has at least 11 vertices by + a result of Walkup [Wal1970]_; this function returns a + triangulation with 11 vertices, as given by Lutz [Lut2005]_. + + - `\Bold{R}P^4`: any triangulation has at least 16 vertices by + a result of Walkup; this function returns a triangulation + with 16 vertices as given by Lutz; see also Datta [Dat2007]_, + Example 3.12. + + - `\Bold{R}P^n`: Lutz has found a triangulation of + `\Bold{R}P^5` with 24 vertices, but it does not seem to have + been published. Kühnel [Kuh1987]_ has described a triangulation of + `\Bold{R}P^n`, in general, with `2^{n+1}-1` vertices; see + also Datta, Example 3.21. This triangulation is presumably + not minimal, but it seems to be the best in the published + literature as of this writing. So this function returns it + when `n > 4`. + + ALGORITHM: For `n < 4`, these are constructed explicitly by + listing the facets. For `n = 4`, this is constructed by + specifying 16 vertices, two facets, and a certain subgroup `G` + of the symmetric group `S_{16}`. Then the set of all facets + is the `G`-orbit of the two given facets. This is implemented + here by explicitly listing all of the facets; the facets + can be computed by the function :func:`~sage.homology.simplicial_complex.facets_for_RP4`, but + running the function takes a few seconds. + + For `n > 4`, the construction is as follows: let `S` denote + the simplicial complex structure on the `n`-sphere given by + the first barycentric subdivision of the boundary of an + `(n+1)`-simplex. This has a simplicial antipodal action: if + `V` denotes the vertices in the boundary of the simplex, then + the vertices in its barycentric subdivision `S` correspond to + nonempty proper subsets `U` of `V`, and the antipodal action + sends any subset `U` to its complement. One can show that + modding out by this action results in a triangulation for + `\Bold{R}P^n`. To find the facets in this triangulation, find + the facets in `S`. These are identified in pairs to form + `\Bold{R}P^n`, so choose a representative from each pair: for + each facet in `S`, replace any vertex in `S` containing 0 with + its complement. + + Of course these complexes increase in size pretty quickly as + `n` increases. + + EXAMPLES:: + + sage: P3 = simplicial_complexes.RealProjectiveSpace(3) + sage: P3.f_vector() + [1, 11, 51, 80, 40] + sage: P3.homology() + {0: 0, 1: C2, 2: 0, 3: Z} + sage: P4 = simplicial_complexes.RealProjectiveSpace(4) + sage: P4.f_vector() + [1, 16, 120, 330, 375, 150] + sage: P4.homology() # long time + {0: 0, 1: C2, 2: 0, 3: C2, 4: 0} + sage: P5 = simplicial_complexes.RealProjectiveSpace(5) # long time (44s on sage.math, 2012) + sage: P5.f_vector() # long time + [1, 63, 903, 4200, 8400, 7560, 2520] + + The following computation can take a long time -- over half an + hour -- with Sage's default computation of homology groups, + but if you have CHomP installed, Sage will use that and the + computation should only take a second or two. (You can + download CHomP from http://chomp.rutgers.edu/, or you can + install it as a Sage package using ``sage -i chomp``). :: + + sage: P5.homology() # long time # optional - CHomP + {0: 0, 1: C2, 2: 0, 3: C2, 4: 0, 5: Z} + sage: simplicial_complexes.RealProjectiveSpace(2).dimension() + 2 + sage: P3.dimension() + 3 + sage: P4.dimension() # long time + 4 + sage: P5.dimension() # long time + 5 + """ + if n == 0: + return Simplex(0) + if n == 1: + return Sphere(1) + if n == 2: + return RealProjectivePlane() + if n == 3: + # Minimal triangulation found by Walkup and given + # explicitly by Lutz + return UniqueSimplicialComplex( + [[1, 2, 3, 7], [1, 4, 7, 9], [2, 3, 4, 8], [2, 5, 8, 10], + [3, 6, 7, 10], [1, 2, 3, 11], [1, 4, 7, 10], [2, 3, 4, 11], + [2, 5, 9, 10], [3, 6, 8, 9], [1, 2, 6, 9], [1, 4, 8, 9], + [2, 3, 7, 8], [2, 6, 9, 10], [3, 6, 9, 10], [1, 2, 6, 11], + [1, 4, 8, 10], [2, 4, 6, 10], [3, 4, 5, 9], [4, 5, 6, 7], + [1, 2, 7, 9], [1, 5, 6, 8], [2, 4, 6, 11], [3, 4, 5, 11], + [4, 5, 6, 11], [1, 3, 5, 10], [1, 5, 6, 11], [2, 4, 8, 10], + [3, 4, 8, 9], [4, 5, 7, 9], [1, 3, 5, 11], [1, 5, 8, 10], + [2, 5, 7, 8], [3, 5, 9, 10], [4, 6, 7, 10], [1, 3, 7, 10], + [1, 6, 8, 9], [2, 5, 7, 9], [3, 6, 7, 8], [5, 6, 7, 8]], + name='Minimal triangulation of RP^3') + if n == 4: + return UniqueSimplicialComplex( + [(1, 3, 8, 12, 13), (2, 7, 8, 13, 16), (4, 8, 9, 12, 14), + (2, 6, 10, 12, 16), (5, 7, 9, 10, 13), (1, 2, 7, 8, 15), + (1, 3, 9, 11, 16), (5, 6, 8, 13, 16), (1, 3, 8, 11, 13), + (3, 4, 10, 13, 15), (4, 6, 9, 12, 15), (2, 4, 6, 11, 13), + (2, 3, 9, 12, 16), (1, 6, 9, 12, 15), (2, 5, 10, 11, 12), + (1, 7, 8, 12, 15), (2, 6, 9, 13, 16), (1, 5, 9, 11, 15), + (4, 9, 10, 13, 14), (2, 7, 8, 15, 16), (2, 3, 9, 12, 14), + (1, 6, 7, 10, 14), (2, 5, 10, 11, 15), (1, 2, 4, 13, 14), + (1, 6, 10, 14, 16), (2, 6, 9, 12, 16), (1, 3, 9, 12, 16), + (4, 5, 7, 11, 16), (5, 9, 10, 11, 15), (3, 5, 8, 12, 14), + (5, 6, 9, 13, 16), (5, 6, 9, 13, 15), (1, 3, 4, 10, 16), + (1, 6, 10, 12, 16), (2, 4, 6, 9, 13), (2, 4, 6, 9, 12), + (1, 2, 4, 11, 13), (7, 9, 10, 13, 14), (1, 7, 8, 12, 13), + (4, 6, 7, 11, 12), (3, 4, 6, 11, 13), (1, 5, 6, 9, 15), + (1, 6, 7, 14, 15), (2, 3, 7, 14, 15), (2, 6, 10, 11, 12), + (5, 7, 9, 10, 11), (1, 2, 4, 5, 14), (3, 5, 10, 13, 15), + (3, 8, 9, 12, 14), (5, 9, 10, 13, 15), (2, 6, 8, 13, 16), + (1, 2, 7, 13, 14), (1, 7, 10, 12, 13), (3, 4, 6, 13, 15), + (4, 9, 10, 13, 15), (2, 3, 10, 12, 16), (1, 2, 5, 14, 15), + (2, 6, 8, 10, 11), (1, 3, 10, 12, 13), (4, 8, 9, 12, 15), + (1, 3, 8, 9, 11), (4, 6, 7, 12, 15), (1, 8, 9, 11, 15), + (4, 5, 8, 14, 16), (1, 2, 8, 11, 13), (3, 6, 8, 11, 13), + (3, 6, 8, 11, 14), (3, 5, 8, 12, 13), (3, 7, 9, 11, 14), + (4, 6, 9, 13, 15), (2, 3, 5, 10, 12), (4, 7, 8, 15, 16), + (1, 2, 7, 14, 15), (3, 7, 9, 11, 16), (3, 6, 7, 14, 15), + (2, 6, 8, 11, 13), (4, 8, 9, 10, 14), (1, 4, 10, 13, 14), + (4, 8, 9, 10, 15), (2, 7, 9, 13, 16), (1, 6, 9, 12, 16), + (2, 3, 7, 9, 14), (4, 8, 10, 15, 16), (1, 5, 9, 11, 16), + (1, 5, 6, 14, 15), (5, 7, 9, 11, 16), (4, 5, 7, 11, 12), + (5, 7, 10, 11, 12), (2, 3, 10, 15, 16), (1, 2, 7, 8, 13), + (1, 6, 7, 10, 12), (1, 3, 10, 12, 16), (7, 9, 10, 11, 14), + (1, 7, 10, 13, 14), (1, 2, 4, 5, 11), (3, 4, 6, 7, 11), + (1, 6, 7, 12, 15), (1, 3, 4, 10, 13), (1, 4, 10, 14, 16), + (2, 4, 6, 11, 12), (5, 6, 8, 14, 16), (3, 5, 6, 8, 13), + (3, 5, 6, 8, 14), (1, 2, 8, 11, 15), (1, 4, 5, 14, 16), + (2, 3, 7, 15, 16), (8, 9, 10, 11, 14), (1, 3, 4, 11, 16), + (6, 8, 10, 14, 16), (8, 9, 10, 11, 15), (1, 3, 4, 11, 13), + (2, 4, 5, 12, 14), (2, 4, 9, 13, 14), (3, 4, 7, 11, 16), + (3, 6, 7, 11, 14), (3, 8, 9, 11, 14), (2, 8, 10, 11, 15), + (1, 3, 8, 9, 12), (4, 5, 7, 8, 16), (4, 5, 8, 12, 14), + (2, 4, 9, 12, 14), (6, 8, 10, 11, 14), (3, 5, 6, 13, 15), + (1, 4, 5, 11, 16), (3, 5, 6, 14, 15), (2, 4, 5, 11, 12), + (4, 5, 7, 8, 12), (1, 8, 9, 12, 15), (5, 7, 8, 13, 16), + (2, 3, 5, 12, 14), (3, 5, 10, 12, 13), (6, 7, 10, 11, 12), + (5, 7, 9, 13, 16), (6, 7, 10, 11, 14), (5, 7, 10, 12, 13), + (1, 2, 5, 11, 15), (1, 5, 6, 9, 16), (5, 7, 8, 12, 13), + (4, 7, 8, 12, 15), (2, 3, 5, 10, 15), (2, 6, 8, 10, 16), + (3, 4, 10, 15, 16), (1, 5, 6, 14, 16), (2, 3, 5, 14, 15), + (2, 3, 7, 9, 16), (2, 7, 9, 13, 14), (3, 4, 6, 7, 15), + (4, 8, 10, 14, 16), (3, 4, 7, 15, 16), (2, 8, 10, 15, 16)], + name='Minimal triangulation of RP^4') + if n >= 5: + # Use the construction given by Datta in Example 3.21. + V = set(range(0, n+2)) + S = Sphere(n).barycentric_subdivision() + X = S.facets() + facets = set([]) + for f in X: + new = [] + for v in f: + if 0 in v: + new.append(tuple(V.difference(v))) + else: + new.append(v) + facets.add(tuple(new)) + return UniqueSimplicialComplex(list(facets), + name='Triangulation of RP^{}'.format(n)) + + +def K3Surface(): + """ + Return a minimal triangulation of the K3 surface. + + This is a pure simplicial complex of dimension 4 with 16 vertices + and 288 facets. It was constructed by Casella and Kühnel + in [CK2001]_. The construction here uses the labeling from + Spreer and Kühnel [SK2011]_. + + EXAMPLES:: + + sage: K3=simplicial_complexes.K3Surface() ; K3 + Minimal triangulation of the K3 surface + sage: K3.f_vector() + [1, 16, 120, 560, 720, 288] + + This simplicial complex is implemented just by listing all 288 + facets. The list of facets can be computed by the function + :func:`~sage.homology.simplicial_complex.facets_for_K3`, but running the function takes a few + seconds. + """ + return UniqueSimplicialComplex( + [(2, 10, 13, 15, 16), (2, 8, 11, 15, 16), (2, 5, 7, 8, 10), + (1, 9, 11, 13, 14), (1, 2, 8, 10, 12), (1, 3, 5, 6, 11), + (1, 5, 6, 9, 12), (1, 2, 6, 13, 16), (1, 4, 10, 13, 14), + (1, 9, 10, 14, 15), (2, 4, 7, 8, 12), (3, 4, 6, 10, 12), + (1, 6, 7, 8, 9), (3, 4, 5, 7, 15), (1, 7, 12, 15, 16), + (4, 5, 7, 13, 16), (5, 8, 11, 12, 15), (2, 4, 7, 12, 14), + (1, 4, 5, 14, 16), (2, 5, 6, 10, 11), (1, 6, 8, 12, 14), + (5, 8, 9, 14, 16), (5, 10, 11, 12, 13), (2, 4, 8, 9, 12), + (7, 9, 12, 15, 16), (1, 2, 6, 9, 15), (1, 5, 14, 15, 16), + (2, 3, 4, 5, 9), (6, 8, 10, 11, 15), (1, 5, 8, 10, 12), + (1, 3, 7, 9, 10), (6, 7, 8, 9, 13), (1, 2, 9, 11, 15), + (2, 8, 11, 14, 16), (2, 4, 5, 13, 16), (1, 4, 8, 13, 15), + (4, 7, 8, 10, 11), (2, 3, 9, 11, 14), (2, 3, 4, 9, 13), + (2, 8, 10, 12, 13), (1, 2, 4, 11, 15), (2, 3, 9, 11, 15), + (3, 5, 10, 13, 15), (3, 4, 5, 9, 11), (6, 10, 13, 15, 16), + (8, 10, 11, 15, 16), (6, 7, 11, 13, 15), (1, 5, 7, 15, 16), + (4, 5, 7, 9, 15), (3, 4, 6, 7, 16), (2, 3, 11, 14, 16), + (3, 4, 9, 11, 13), (1, 2, 5, 14, 15), (2, 3, 9, 13, 14), + (1, 2, 5, 13, 16), (2, 3, 7, 8, 12), (2, 9, 11, 12, 14), + (1, 9, 11, 15, 16), (4, 6, 9, 14, 16), (1, 4, 9, 13, 14), + (1, 2, 3, 12, 16), (8, 11, 12, 14, 15), (2, 4, 11, 12, 14), + (1, 4, 10, 12, 13), (1, 2, 6, 7, 13), (1, 3, 6, 10, 11), + (1, 6, 8, 9, 12), (1, 4, 5, 6, 14), (3, 9, 10, 12, 15), + (5, 8, 11, 12, 16), (5, 9, 10, 14, 15), (3, 9, 12, 15, 16), + (3, 6, 8, 14, 15), (2, 4, 9, 10, 16), (5, 8, 9, 13, 15), + (2, 3, 6, 9, 15), (6, 11, 12, 14, 16), (2, 3, 10, 13, 15), + (2, 8, 9, 10, 13), (3, 4, 8, 11, 13), (3, 4, 5, 7, 13), + (5, 7, 8, 10, 14), (4, 12, 13, 14, 15), (6, 7, 10, 14, 16), + (5, 10, 11, 13, 14), (3, 4, 7, 13, 16), (6, 8, 9, 12, 13), + (1, 3, 4, 10, 14), (2, 4, 6, 11, 12), (1, 7, 9, 10, 14), + (4, 6, 8, 13, 14), (4, 9, 10, 11, 16), (3, 7, 8, 10, 16), + (5, 7, 9, 15, 16), (1, 7, 9, 11, 14), (6, 8, 10, 15, 16), + (5, 8, 9, 10, 14), (7, 8, 10, 14, 16), (2, 6, 7, 9, 11), + (7, 9, 10, 13, 15), (3, 6, 7, 10, 12), (2, 4, 6, 10, 11), + (4, 5, 8, 9, 11), (1, 2, 3, 8, 16), (3, 7, 9, 10, 12), + (1, 2, 6, 8, 14), (3, 5, 6, 13, 15), (1, 5, 6, 12, 14), + (2, 5, 7, 14, 15), (1, 5, 10, 11, 12), (3, 7, 8, 10, 11), + (1, 2, 6, 14, 15), (1, 2, 6, 8, 16), (7, 9, 10, 12, 15), + (3, 4, 6, 8, 14), (3, 7, 13, 14, 16), (2, 5, 7, 8, 14), + (6, 7, 9, 10, 14), (2, 3, 7, 12, 14), (4, 10, 12, 13, 14), + (2, 5, 6, 11, 13), (4, 5, 6, 7, 16), (1, 3, 12, 13, 16), + (1, 4, 11, 15, 16), (1, 3, 4, 6, 10), (1, 10, 11, 12, 13), + (6, 9, 11, 12, 14), (1, 4, 7, 8, 15), (5, 8, 9, 10, 13), + (1, 2, 5, 7, 15), (1, 7, 12, 13, 16), (3, 11, 13, 14, 16), + (1, 2, 5, 7, 13), (4, 7, 8, 9, 15), (1, 5, 6, 10, 11), + (6, 7, 10, 13, 15), (3, 4, 7, 14, 15), (7, 11, 13, 14, 16), + (3, 4, 10, 12, 14), (3, 6, 8, 10, 16), (2, 7, 8, 14, 16), + (2, 3, 4, 5, 13), (5, 8, 12, 13, 15), (4, 6, 9, 13, 14), + (2, 4, 5, 6, 12), (1, 3, 7, 8, 9), (8, 11, 12, 14, 16), + (1, 7, 12, 13, 15), (8, 12, 13, 14, 15), (2, 8, 9, 12, 13), + (4, 6, 10, 12, 15), (2, 8, 11, 14, 15), (2, 6, 9, 11, 12), + (8, 9, 10, 11, 16), (2, 3, 6, 13, 15), (2, 3, 12, 15, 16), + (1, 3, 5, 9, 12), (2, 5, 6, 9, 12), (2, 10, 12, 13, 14), + (2, 6, 13, 15, 16), (2, 3, 11, 15, 16), (3, 5, 6, 8, 15), + (2, 4, 5, 9, 12), (5, 6, 8, 11, 15), (6, 8, 12, 13, 14), + (1, 2, 3, 8, 12), (1, 4, 7, 8, 11), (3, 5, 7, 14, 15), + (3, 5, 7, 13, 14), (1, 7, 10, 11, 14), (6, 7, 11, 12, 15), + (3, 4, 6, 7, 12), (1, 2, 4, 7, 11), (6, 9, 10, 14, 16), + (4, 10, 12, 15, 16), (5, 6, 7, 12, 16), (3, 9, 11, 13, 14), + (5, 9, 14, 15, 16), (4, 5, 6, 7, 12), (1, 3, 9, 10, 15), + (4, 7, 8, 9, 12), (5, 9, 10, 13, 15), (1, 3, 8, 13, 16), + (2, 9, 12, 13, 14), (6, 7, 10, 12, 15), (2, 6, 8, 14, 15), + (3, 5, 6, 8, 11), (3, 4, 7, 12, 14), (1, 3, 10, 14, 15), + (7, 11, 12, 13, 16), (3, 11, 12, 13, 16), (3, 4, 5, 8, 15), + (2, 4, 7, 8, 10), (2, 4, 7, 14, 15), (1, 2, 10, 12, 16), + (1, 6, 8, 13, 16), (1, 7, 8, 13, 15), (3, 9, 11, 15, 16), + (4, 6, 10, 11, 15), (2, 4, 11, 14, 15), (1, 3, 8, 9, 12), + (1, 3, 6, 14, 15), (2, 4, 5, 6, 10), (1, 4, 9, 14, 16), + (5, 7, 9, 12, 16), (1, 3, 7, 10, 11), (7, 8, 9, 13, 15), + (3, 5, 10, 14, 15), (1, 4, 10, 12, 16), (3, 4, 5, 8, 11), + (1, 2, 6, 7, 9), (1, 3, 11, 12, 13), (1, 5, 7, 13, 16), + (5, 7, 10, 11, 14), (2, 10, 12, 15, 16), (3, 6, 7, 10, 16), + (1, 2, 5, 8, 10), (4, 10, 11, 15, 16), (5, 8, 10, 12, 13), + (3, 6, 8, 10, 11), (4, 5, 7, 9, 12), (6, 7, 11, 12, 16), + (3, 5, 9, 11, 16), (8, 9, 10, 14, 16), (3, 4, 6, 8, 16), + (1, 10, 11, 13, 14), (2, 9, 10, 13, 16), (1, 2, 5, 8, 14), + (2, 4, 5, 10, 16), (1, 2, 7, 9, 11), (1, 3, 5, 6, 9), + (5, 7, 11, 13, 14), (3, 5, 10, 13, 14), (2, 4, 8, 9, 10), + (4, 11, 12, 14, 15), (2, 3, 7, 14, 16), (3, 4, 8, 13, 16), + (6, 7, 9, 11, 14), (5, 6, 11, 13, 15), (4, 5, 6, 14, 16), + (3, 4, 8, 14, 15), (4, 5, 8, 9, 15), (1, 4, 8, 11, 13), + (5, 6, 12, 14, 16), (2, 3, 10, 12, 14), (1, 2, 5, 10, 16), + (2, 5, 7, 10, 11), (2, 6, 7, 11, 13), (1, 4, 5, 10, 16), + (2, 6, 8, 15, 16), (2, 3, 10, 12, 15), (7, 11, 12, 13, 15), + (1, 3, 8, 11, 13), (4, 8, 9, 10, 11), (1, 9, 14, 15, 16), + (1, 3, 6, 9, 15), (6, 9, 12, 13, 14), (2, 3, 10, 13, 14), + (2, 5, 7, 11, 13), (2, 3, 5, 6, 13), (4, 6, 8, 13, 16), + (6, 7, 9, 10, 13), (5, 8, 12, 14, 16), (4, 6, 9, 13, 16), + (5, 8, 9, 11, 16), (2, 3, 5, 6, 9), (1, 3, 5, 11, 12), + (3, 7, 8, 9, 12), (4, 6, 11, 12, 15), (3, 5, 9, 12, 16), + (5, 11, 12, 13, 15), (1, 3, 4, 6, 14), (3, 5, 11, 12, 16), + (1, 5, 8, 12, 14), (4, 8, 13, 14, 15), (1, 3, 7, 8, 11), + (6, 9, 10, 13, 16), (2, 4, 9, 13, 16), (1, 6, 7, 8, 13), + (1, 4, 12, 13, 15), (2, 4, 7, 10, 11), (1, 4, 9, 11, 13), + (6, 7, 11, 14, 16), (1, 4, 9, 11, 16), (1, 4, 12, 15, 16), + (1, 2, 4, 7, 15), (2, 3, 7, 8, 16), (1, 4, 5, 6, 10)], + name='Minimal triangulation of the K3 surface') + + +def BarnetteSphere(): + r""" + Return Barnette's triangulation of the 3-sphere. + + This is a pure simplicial complex of dimension 3 with 8 + vertices and 19 facets, which is a non-polytopal triangulation + of the 3-sphere. It was constructed by Barnette in + [Bar1970]_. The construction here uses the labeling from De + Loera, Rambau and Santos [DLRS2010]_. Another reference is chapter + III.4 of Ewald [Ewa1996]_. + + EXAMPLES:: + + sage: BS = simplicial_complexes.BarnetteSphere() ; BS + Barnette's triangulation of the 3-sphere + sage: BS.f_vector() + [1, 8, 27, 38, 19] + + TESTS: + + Checks that this is indeed the same Barnette Sphere as the one + given on page 87 of [Ewa1996]_.:: + + sage: BS2 = SimplicialComplex([[1, 2, 3, 4], [3, 4, 5, 6], [1, 2, 5, 6], + ....: [1, 2, 4, 7], [1, 3, 4, 7], [3, 4, 6, 7], + ....: [3, 5, 6, 7], [1, 2, 5, 7], [2, 5, 6, 7], + ....: [2, 4, 6, 7], [1, 2, 3, 8], [2, 3, 4, 8], + ....: [3, 4, 5, 8], [4, 5, 6, 8], [1, 2, 6, 8], + ....: [1, 5, 6, 8], [1, 3, 5, 8], [2, 4, 6, 8], + ....: [1, 3, 5, 7]]) + sage: BS.is_isomorphic(BS2) + True + """ + return UniqueSimplicialComplex([ + (1, 2, 4, 5), (2, 3, 5, 6), (1, 3, 4, 6), (1, 2, 3, 7), (4, 5, 6, 7), (1, 2, 4, 7), + (2, 4, 5, 7), (2, 3, 5, 7), (3, 5, 6, 7), (3, 1, 6, 7), (1, 6, 4, 7), (1, 2, 3, 8), + (4, 5, 6, 8), (1, 2, 5, 8), (1, 4, 5, 8), (2, 3, 6, 8), (2, 5, 6, 8), (3, 1, 4, 8), + (3, 6, 4, 8)], + name="Barnette's triangulation of the 3-sphere") + + +def BrucknerGrunbaumSphere(): + r""" + Return Bruckner and Grunbaum's triangulation of the 3-sphere. + + This is a pure simplicial complex of dimension 3 with 8 + vertices and 20 facets, which is a non-polytopal triangulation + of the 3-sphere. It appeared first in [Br1910]_ and was studied in + [GrS1967]_. + + It is defined here as the link of any vertex in the unique minimal + triangulation of the complex projective plane, see chapter 4 of + [Kuh1995]_. + + EXAMPLES:: + + sage: BGS = simplicial_complexes.BrucknerGrunbaumSphere() ; BGS + Bruckner and Grunbaum's triangulation of the 3-sphere + sage: BGS.f_vector() + [1, 8, 28, 40, 20] + """ + # X = ComplexProjectivePlane().link([9]) + # return UniqueSimplicialComplex(X.facets(), + # name="Bruckner and Grunbaum's triangulation of the 3-sphere") + return UniqueSimplicialComplex(ComplexProjectivePlane().link([9]), + name="Bruckner and Grunbaum's triangulation of the 3-sphere") + +############################################################### +# examples from graph theory: + +def NotIConnectedGraphs(n, i): + """ + The simplicial complex of all graphs on `n` vertices which are + not `i`-connected. + + Fix an integer `n>0` and consider the set of graphs on `n` + vertices. View each graph as its set of edges, so it is a + subset of a set of size `n` choose 2. A graph is + `i`-connected if, for any `j 0 and len(B) < len(G_minus_A): + C = G_minus_A.difference(B) + facet = E + for v in B: + for w in C: + bad_edge = (min(v, w), max(v, w)) + facet = facet.difference(Set([bad_edge])) + facets.append(facet) + return UniqueSimplicialComplex(facets, name='Simplicial complex of not {}-connected graphs on {} vertices'.format(i, n)) + +def MatchingComplex(n): + """ + The matching complex of graphs on `n` vertices. + + Fix an integer `n>0` and consider a set `V` of `n` vertices. + A 'partial matching' on `V` is a graph formed by edges so that + each vertex is in at most one edge. If `G` is a partial + matching, then so is any graph obtained by deleting edges from + `G`. Thus the set of all partial matchings on `n` vertices, + viewed as a set of subsets of the `n` choose 2 possible edges, + is closed under taking subsets, and thus forms a simplicial + complex called the 'matching complex'. This function produces + that simplicial complex. + + INPUT: + + - ``n`` -- positive integer. + + See Dumas et al. [DHSW2003]_ for information on computing its homology + by computer, and see Wachs [Wac2003]_ for an expository article about + the theory. For example, the homology of these complexes seems to + have only mod 3 torsion, and this has been proved for the + bottom non-vanishing homology group for the matching complex `M_n`. + + EXAMPLES:: + + sage: M = simplicial_complexes.MatchingComplex(7) + sage: H = M.homology() + sage: H + {0: 0, 1: C3, 2: Z^20} + sage: H[2].ngens() + 20 + sage: simplicial_complexes.MatchingComplex(8).homology(2) # long time (6s on sage.math, 2012) + Z^132 + """ + G_vertices = Set(range(1, n+1)) + facets = [] + if is_even(n): + half = int(n/2) + half_n_sets = list(G_vertices.subsets(size=half)) + else: + half = int((n-1)/2) + half_n_sets = list(G_vertices.subsets(size=half)) + for X in half_n_sets: + Xcomp = G_vertices.difference(X) + if is_even(n): + if 1 in X: + A = X + B = Xcomp + else: + A = Xcomp + B = X + for M in matching(A, B): + facet = [] + for pair in M: + facet.append(tuple(sorted(pair))) + facets.append(facet) + else: + for w in Xcomp: + if 1 in X or (w == 1 and 2 in X): + A = X + B = Xcomp.difference([w]) + else: + B = X + A = Xcomp.difference([w]) + for M in matching(A, B): + facet = [] + for pair in M: + facet.append(tuple(sorted(pair))) + facets.append(facet) + return UniqueSimplicialComplex(facets, name='Matching complex on {} vertices'.format(n)) + +def ChessboardComplex(n, i): + r""" + The chessboard complex for an `n \times i` chessboard. + + Fix integers `n, i > 0` and consider sets `V` of `n` vertices + and `W` of `i` vertices. A 'partial matching' between `V` and + `W` is a graph formed by edges `(v, w)` with `v \in V` and `w + \in W` so that each vertex is in at most one edge. If `G` is + a partial matching, then so is any graph obtained by deleting + edges from `G`. Thus the set of all partial matchings on `V` + and `W`, viewed as a set of subsets of the `n+i` choose 2 + possible edges, is closed under taking subsets, and thus forms + a simplicial complex called the 'chessboard complex'. This + function produces that simplicial complex. (It is called the + chessboard complex because such graphs also correspond to ways + of placing rooks on an `n` by `i` chessboard so that none of + them are attacking each other.) + + INPUT: + + - ``n, i`` -- positive integers. + + See Dumas et al. [DHSW2003]_ for information on computing its homology + by computer, and see Wachs [Wac2003]_ for an expository article about + the theory. + + EXAMPLES:: + + sage: C = simplicial_complexes.ChessboardComplex(5, 5) + sage: C.f_vector() + [1, 25, 200, 600, 600, 120] + sage: simplicial_complexes.ChessboardComplex(3, 3).homology() + {0: 0, 1: Z x Z x Z x Z, 2: 0} + """ + A = range(n) + B = range(i) + E_dict = {} + index = 0 + for v in A: + for w in B: + E_dict[(v, w)] = index + index += 1 + facets = [] + for M in matching(A, B): + facet = [] + for pair in M: + facet.append(E_dict[pair]) + facets.append(facet) + return UniqueSimplicialComplex(facets, name='Chessboard complex for an {}x{} chessboard'.format(n, i)) + +def RandomComplex(n, d, p=0.5): + """ + A random ``d``-dimensional simplicial complex on ``n`` vertices. + + INPUT: + + - ``n`` -- number of vertices + + - ``d`` -- dimension of the complex + + - ``p`` -- floating point number between 0 and 1 + (optional, default 0.5) + + A random `d`-dimensional simplicial complex on `n` vertices, + as defined for example by Meshulam and Wallach [MW2009]_, is + constructed as follows: take `n` vertices and include all of + the simplices of dimension strictly less than `d`, and then for each + possible simplex of dimension `d`, include it with probability `p`. + + EXAMPLES:: + + sage: X = simplicial_complexes.RandomComplex(6, 2); X + Random 2-dimensional simplicial complex on 6 vertices + sage: len(list(X.vertices())) + 6 + + If `d` is too large (if `d+1 > n`, so that there are no + `d`-dimensional simplices), then return the simplicial complex + with a single `(n+1)`-dimensional simplex:: + + sage: simplicial_complexes.RandomComplex(6, 12) + The 5-simplex + """ + if d+1 > n: + return Simplex(n-1) + else: + vertices = range(n) + facets = Subsets(vertices, d).list() + maybe = Subsets(vertices, d+1) + facets.extend([f for f in maybe if random.random() <= p]) + return UniqueSimplicialComplex(facets, + name='Random {}-dimensional simplicial complex on {} vertices'.format(d, n)) + +def SumComplex(n, A): + r""" + The sum complexes of Linial, Meshulam, and Rosenthal [LMR2010]_. + + If `k+1` is the cardinality of `A`, then this returns a + `k`-dimensional simplicial complex `X_A` with vertices + `\ZZ/(n)`, and facets given by all `k+1`-tuples `(x_0, x_1, + ..., x_k)` such that the sum `\sum x_i` is in `A`. See the + paper by Linial, Meshulam, and Rosenthal [LMR2010]_, in which + they prove various results about these complexes; for example, + if `n` is prime, then `X_A` is rationally acyclic, and if in + addition `A` forms an arithmetic progression in `\ZZ/(n)`, + then `X_A` is `\ZZ`-acyclic. Throughout their paper, they + assume that `n` and `k` are relatively prime, but the + construction makes sense in general. + + In addition to the results from the cited paper, these + complexes can have large torsion, given the number of + vertices; for example, if `n=10`, and `A=\{0, 1, 2, 3, 6\}`, then + `H_3(X_A)` is cyclic of order 2728, and there is a + 4-dimensional complex on 13 vertices with `H_3` having a + cyclic summand of order + + .. MATH:: + + 706565607945 = 3 \cdot 5 \cdot 53 \cdot 79 \cdot 131 + \cdot 157 \cdot 547. + + See the examples. + + INPUT: + + - ``n`` -- a positive integer + + - ``A`` -- a subset of `\ZZ/(n)` + + EXAMPLES:: + + sage: S = simplicial_complexes.SumComplex(10, [0, 1, 2, 3, 6]); S + Sum complex on vertices Z/10Z associated to {0, 1, 2, 3, 6} + sage: S.homology() + {0: 0, 1: 0, 2: 0, 3: C2728, 4: 0} + sage: factor(2728) + 2^3 * 11 * 31 + + sage: S = simplicial_complexes.SumComplex(11, [0, 1, 3]); S + Sum complex on vertices Z/11Z associated to {0, 1, 3} + sage: S.homology(1) + C23 + sage: S = simplicial_complexes.SumComplex(11, [0, 1, 2, 3, 4, 7]); S + Sum complex on vertices Z/11Z associated to {0, 1, 2, 3, 4, 7} + sage: S.homology(algorithm='no_chomp') # long time + {0: 0, 1: 0, 2: 0, 3: 0, 4: C645679, 5: 0} + sage: factor(645679) + 23 * 67 * 419 + + sage: S = simplicial_complexes.SumComplex(13, [0, 1, 3]); S + Sum complex on vertices Z/13Z associated to {0, 1, 3} + sage: S.homology(1) + C159 + sage: factor(159) + 3 * 53 + sage: S = simplicial_complexes.SumComplex(13, [0, 1, 2, 5]); S + Sum complex on vertices Z/13Z associated to {0, 1, 2, 5} + sage: S.homology(algorithm='no_chomp') # long time + {0: 0, 1: 0, 2: C146989209, 3: 0} + sage: factor(1648910295) + 3^2 * 5 * 53 * 521 * 1327 + sage: S = simplicial_complexes.SumComplex(13, [0, 1, 2, 3, 5]); S + Sum complex on vertices Z/13Z associated to {0, 1, 2, 3, 5} + sage: S.homology(algorithm='no_chomp') # long time + {0: 0, 1: 0, 2: 0, 3: C3 x C237 x C706565607945, 4: 0} + sage: factor(706565607945) + 3 * 5 * 53 * 79 * 131 * 157 * 547 + + sage: S = simplicial_complexes.SumComplex(17, [0, 1, 4]); S + Sum complex on vertices Z/17Z associated to {0, 1, 4} + sage: S.homology(1, algorithm='no_chomp') + C140183 + sage: factor(140183) + 103 * 1361 + sage: S = simplicial_complexes.SumComplex(19, [0, 1, 4]); S + Sum complex on vertices Z/19Z associated to {0, 1, 4} + sage: S.homology(1, algorithm='no_chomp') + C5670599 + sage: factor(5670599) + 11 * 191 * 2699 + sage: S = simplicial_complexes.SumComplex(31, [0, 1, 4]); S + Sum complex on vertices Z/31Z associated to {0, 1, 4} + sage: S.homology(1, algorithm='no_chomp') # long time + C5 x C5 x C5 x C5 x C26951480558170926865 + sage: factor(26951480558170926865) + 5 * 311 * 683 * 1117 * 11657 * 1948909 + """ + from sage.rings.all import Integers + Zn = Integers(n) + A = frozenset([Zn(x) for x in A]) + facets = [] + for f in Set(Zn).subsets(len(A)): + if sum(f) in A: + facets.append(tuple(f)) + return UniqueSimplicialComplex(facets, name='Sum complex on vertices Z/{}Z associated to {}'.format(n, Set(A))) + + +def RandomTwoSphere(n): + r""" + Return a random triangulation of the 2-dimensional sphere with `n` + vertices. + + INPUT: + + `n` -- an integer + + OUTPUT: + + A random triangulation of the sphere chosen uniformly among + the *rooted* triangulations on `n` vertices. Because some + triangulations have nontrivial automorphism groups, this may + not be equal to the uniform distribution among unrooted + triangulations. + + ALGORITHM: + + The algorithm is taken from [PS2006]_, section 2.1. + + Starting from a planar tree (represented by its contour as a + sequence of vertices), one first performs local closures, until no + one is possible. A local closure amounts to replace in the cyclic + contour word a sequence ``in1, in2, in3, lf, in3`` by + ``in1, in3``. After all local closures are done, one has reached + the partial closure, as in [PS2006]_, figure 5 (a). + + Then one has to perform complete closure by adding two more + vertices, in order to reach the situation of [PS2006]_, figure 5 + (b). For this, it is necessary to find inside the final contour + one of the two subsequences ``lf, in, lf``. + + At every step of the algorithm, newly created triangles are added + in a simplicial complex. + + This algorithm is implemented in + :meth:`~sage.graphs.generators.random.RandomTriangulation`, which + creates an embedded graph. The triangles of the simplicial + complex are recovered from this embedded graph. + + EXAMPLES:: + + sage: G = simplicial_complexes.RandomTwoSphere(6); G + Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and 8 facets + sage: G.homology() + {0: 0, 1: 0, 2: Z} + sage: G.is_pure() + True + sage: fg = G.flip_graph(); fg + Graph on 8 vertices + sage: fg.is_planar() and fg.is_regular(3) + True + """ + from sage.graphs.generators.random import RandomTriangulation + + graph = RandomTriangulation(n) + + graph = graph.relabel(inplace=False) + triangles = [(u, v, w) for u, L in graph._embedding.items() + for v, w in zip(L, L[1:] + [L[0]]) if u < v and u < w] + + return SimplicialComplex(triangles, maximality_check=False) + +def ShiftedComplex(generators): + r""" + Return the smallest shifted simplicial complex containing ``generators`` + as faces. + + Let `V` be a set of vertices equipped with a total order. The + 'componentwise partial ordering' on k-subsets of `V` is defined as + follows: if `A = \{a_1 < \cdots < a_k\}` and `B = \{b_1 < \cdots < b_k\}`, + then `A \leq_C B` iff `a_i \leq b_i` for all `i`. A simplicial complex + `X` on vertex set `[n]` is *shifted* if its faces form an order ideal + under the componentwise partial ordering, i.e., if `B \in X` and + `A \leq_C B` then `A \in X`. Shifted complexes of dimension 1 are also + known as threshold graphs. + + .. NOTE:: + + This method assumes that `V` consists of positive integers + with the natural ordering. + + INPUT: + + - ``generators`` -- a list of generators of the order ideal, which may + be lists, tuples or simplices + + EXAMPLES:: + + sage: X = simplicial_complexes.ShiftedComplex([ Simplex([1, 6]), (2, 4), [8] ]) + sage: sorted(X.facets()) + [(1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (2, 3), (2, 4), (7,), (8,)] + sage: X = simplicial_complexes.ShiftedComplex([ [2, 3, 5] ]) + sage: sorted(X.facets()) + [(1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (2, 3, 4), (2, 3, 5)] + sage: X = simplicial_complexes.ShiftedComplex([ [1, 3, 5], [2, 6] ]) + sage: sorted(X.facets()) + [(1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 6), (2, 6)] + """ + from sage.combinat.partition import Partitions + Facets = [] + for G in generators: + G = list(reversed(sorted(G))) + L = len(G) + for k in range(L * (L+1) // 2, sum(G) + 1): + for P in Partitions(k, length=L, max_slope=-1, outer=G): + Facets.append(list(reversed(P))) + return SimplicialComplex(Facets) + +def RudinBall(): + r""" + Return the non-shellable ball constructed by Rudin. + + This complex is a non-shellable triangulation of the 3-ball + with 14 vertices and 41 facets, constructed by Rudin in + [Rud1958]_. + + EXAMPLES:: + + sage: R = simplicial_complexes.RudinBall(); R + Rudin ball + sage: R.f_vector() + [1, 14, 66, 94, 41] + sage: R.homology() + {0: 0, 1: 0, 2: 0, 3: 0} + sage: R.is_cohen_macaulay() + True + """ + return UniqueSimplicialComplex( + [[1, 9, 2, 5], [1, 10, 2, 5], [1, 10, 5, 11], [1, 10, 7, 11], [1, 13, 5, 11], + [1, 13, 7, 11], [2, 10, 3, 6], [2, 11, 3, 6], [2, 11, 6, 12], [2, 11, 8, 12], + [2, 14, 6, 12], [2, 14, 8, 12], [3, 11, 4, 7], [3, 12, 4, 7], [3, 12, 5, 9], + [3, 12, 7, 9], [3, 13, 5, 9], [3, 13, 7, 9], [4, 9, 1, 8], [4, 9, 6, 10], + [4, 9, 8, 10], [4, 12, 1, 8], [4, 14, 6, 10], [4, 14, 8, 10], [9, 10, 2, 5], + [9, 10, 2, 6], [9, 10, 5, 11], [9, 10, 11, 12], [9, 13, 5, 11], [10, 11, 3, 6], + [10, 11, 3, 7], [10, 11, 6, 12], [10, 14, 6, 12], [11, 12, 4, 7], [11, 12, 4, 8], + [11, 12, 7, 9], [11, 13, 7, 9], [12, 9, 1, 5], [12, 9, 1, 8], [12, 9, 8, 10], + [12, 14, 8, 10]], + name="Rudin ball" + ) + +def ZieglerBall(): + r""" + Return the non-shellable ball constructed by Ziegler. + + This complex is a non-shellable triangulation of the 3-ball + with 10 vertices and 21 facets, constructed by Ziegler in + [Zie1998]_ and the smallest such complex known. + + EXAMPLES:: + + sage: Z = simplicial_complexes.ZieglerBall(); Z + Ziegler ball + sage: Z.f_vector() + [1, 10, 38, 50, 21] + sage: Z.homology() + {0: 0, 1: 0, 2: 0, 3: 0} + sage: Z.is_cohen_macaulay() + True + """ + + return UniqueSimplicialComplex( + [[1, 2, 3, 4], [1, 2, 5, 6], [1, 5, 6, 9], [2, 5, 6, 0], [3, 6, 7, 8], [4, 5, 7, 8], + [2, 3, 6, 7], [1, 6, 2, 9], [2, 6, 7, 0], [3, 2, 4, 8], [4, 1, 3, 7], [3, 4, 7, 8], + [1, 2, 4, 9], [2, 7, 3, 0], [3, 2, 6, 8], [4, 1, 5, 7], [4, 1, 8, 5], [1, 4, 8, 9], + [2, 3, 1, 0], [1, 8, 5, 9], [2, 1, 5, 0]], + name="Ziegler ball" + ) + +def DunceHat(): + r""" + Return the minimal triangulation of the dunce hat given by Hachimori + [Hac2016]_. + + This is a standard example of a space that is contractible + but not collapsible. + + EXAMPLES:: + + sage: D = simplicial_complexes.DunceHat(); D + Minimal triangulation of the dunce hat + sage: D.f_vector() + [1, 8, 24, 17] + sage: D.homology() + {0: 0, 1: 0, 2: 0} + sage: D.is_cohen_macaulay() + True + """ + return UniqueSimplicialComplex( + [[1, 3, 5], [2, 3, 5], [2, 4, 5], [1, 2, 4], [1, 3, 4], [3, 4, 8], + [1, 2, 8], [1, 7, 8], [1, 2, 7], [2, 3, 7], [3, 6, 7], [1, 3, 6], + [1, 5, 6], [4, 5, 6], [4, 6, 8], [6, 7, 8], [2, 3, 8]], + name="Minimal triangulation of the dunce hat" + ) + + +def FareyMap(p): + r""" + Return a discrete surface associated with `PSL(2, \GF(p))`. + + INPUT: + + - `p` -- a prime number + + The vertices are the non-zero pairs `(x,y)` in `\GF(p)^2` modulo + the identification of `(-x, -y)` with `(x,y)`. + + The triangles are the images of the base triangle ((1,0),(0,1),(1,1)) + under the action of `PSL(2, \GF(p))`. + + For `p = 3`, the result is a tetrahedron, for `p = 5` an icosahedron, + and for `p = 7` a triangulation of the Klein quartic of genus `3`. + + As a Riemann surface, this is the quotient of the upper half plane + by the principal congruence subgroup `\Gamma(p)`. + + EXAMPLES:: + + sage: S5 = simplicial_complexes.FareyMap(5); S5 + Simplicial complex with 12 vertices and 20 facets + sage: S5.automorphism_group().cardinality() + 120 + + sage: S7 = simplicial_complexes.FareyMap(7); S7 + Simplicial complex with 24 vertices and 56 facets + sage: S7.f_vector() + [1, 24, 84, 56] + + REFERENCES: + + - [ISS2019] Ioannis Ivrissimtzis, David Singerman and James Strudwick, + *From Farey Fractions to the Klein Quartic and Beyond*. + :arxiv:`1909.08568` + """ + from sage.combinat.permutation import Permutation + from sage.groups.perm_gps.permgroup import PermutationGroup + from sage.matrix.constructor import matrix + from sage.modules.free_module_element import vector + from sage.rings.finite_rings.finite_field_constructor import GF + from sage.libs.gap.libgap import libgap + + def normalise(pair): + x, y = pair + if x != 0 and p - x < x: + return ((-x) % p, (-y) % p) + elif x == 0 and p - y < y: + return (0, (-y) % p) + return (x, y) + + points = [(x, y) for x in range(p) for y in range(p) + if (x, y) != (0, 0) and + (x != 0 and p - x >= x or (x == 0 and p - y >= y))] + convert = {pt: i + 1 for i, pt in enumerate(points)} + + F = GF(p) + S = matrix(F, 2, 2, [0, -1, 1, 0]) + T = matrix(F, 2, 2, [1, 1, 0, 1]) + perm_S = Permutation([convert[normalise(S * vector(pt))] + for pt in points]) + perm_T = Permutation([convert[normalise(T * vector(pt))] + for pt in points]) + group = PermutationGroup([perm_S, perm_T]) + triangle = [convert[normalise(pt)] for pt in [(1, 0), (0, 1), (1, 1)]] + triangle = libgap.Set(triangle) + triangles = libgap.Orbit(group, triangle, libgap.OnSets).sage() + return SimplicialComplex(triangles) diff --git a/src/sage/topology/simplicial_complex_homset.py b/src/sage/topology/simplicial_complex_homset.py new file mode 100644 index 00000000000..f97db05461d --- /dev/null +++ b/src/sage/topology/simplicial_complex_homset.py @@ -0,0 +1,196 @@ +r""" +Homsets between simplicial complexes + +AUTHORS: + +- Travis Scrimshaw (2012-08-18): Made all simplicial complexes immutable to + work with the homset cache. + +EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = simplicial_complexes.Sphere(2) + sage: H = Hom(S,T) + sage: f = {0:0,1:1,2:3} + sage: x = H(f) + sage: x + Simplicial complex morphism: + From: Minimal triangulation of the 1-sphere + To: Minimal triangulation of the 2-sphere + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 3 + sage: x.is_injective() + True + sage: x.is_surjective() + False + sage: x.image() + Simplicial complex with vertex set (0, 1, 3) and facets {(0, 1), (0, 3), (1, 3)} + sage: from sage.topology.simplicial_complex import Simplex + sage: s = Simplex([1,2]) + sage: x(s) + (1, 3) + +TESTS:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = simplicial_complexes.Sphere(2) + sage: H = Hom(S,T) + sage: loads(dumps(H)) == H + True + +""" + +#***************************************************************************** +# Copyright (C) 2009 D. Benjamin Antieau +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +# +#***************************************************************************** + +import sage.categories.homset +from .simplicial_complex_morphism import SimplicialComplexMorphism + +def is_SimplicialComplexHomset(x): + """ + Return ``True`` if and only if ``x`` is a simplicial complex homspace. + + EXAMPLES:: + + sage: S = SimplicialComplex(is_mutable=False) + sage: T = SimplicialComplex(is_mutable=False) + sage: H = Hom(S, T) + sage: H + Set of Morphisms from Simplicial complex with vertex set () and facets {()} + to Simplicial complex with vertex set () and facets {()} + in Category of finite simplicial complexes + sage: from sage.topology.simplicial_complex_homset import is_SimplicialComplexHomset + sage: is_SimplicialComplexHomset(H) + True + """ + return isinstance(x, SimplicialComplexHomset) + +class SimplicialComplexHomset(sage.categories.homset.Homset): + def __call__(self, f): + """ + INPUT: + + - ``f`` -- a dictionary with keys exactly the vertices of the domain + and values vertices of the codomain + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(3) + sage: T = simplicial_complexes.Sphere(2) + sage: f = {0:0,1:1,2:2,3:2,4:2} + sage: H = Hom(S,T) + sage: x = H(f) + sage: x + Simplicial complex morphism: + From: Minimal triangulation of the 3-sphere + To: Minimal triangulation of the 2-sphere + Defn: [0, 1, 2, 3, 4] --> [0, 1, 2, 2, 2] + """ + return SimplicialComplexMorphism(f,self.domain(),self.codomain()) + + def diagonal_morphism(self,rename_vertices=True): + r""" + Return the diagonal morphism in `Hom(S, S \times S)`. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(2) + sage: H = Hom(S,S.product(S, is_mutable=False)) + sage: d = H.diagonal_morphism() + sage: d + Simplicial complex morphism: + From: Minimal triangulation of the 2-sphere + To: Simplicial complex with 16 vertices and 96 facets + Defn: 0 |--> L0R0 + 1 |--> L1R1 + 2 |--> L2R2 + 3 |--> L3R3 + + sage: T = SimplicialComplex([[0], [1]], is_mutable=False) + sage: U = T.product(T,rename_vertices = False, is_mutable=False) + sage: G = Hom(T,U) + sage: e = G.diagonal_morphism(rename_vertices = False) + sage: e + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1) and facets {(0,), (1,)} + To: Simplicial complex with 4 vertices and facets {((0, 0),), ((0, 1),), ((1, 0),), ((1, 1),)} + Defn: 0 |--> (0, 0) + 1 |--> (1, 1) + """ + # Preserve whether the codomain is mutable when renaming the vertices. + mutable = self._codomain.is_mutable() + X = self._domain.product(self._domain,rename_vertices=rename_vertices, is_mutable=mutable) + if self._codomain != X: + raise TypeError("diagonal morphism is only defined for Hom(X,XxX)") + f = {} + if rename_vertices: + f = {i: "L{0}R{0}".format(i) for i in self._domain.vertices()} + else: + f = {i: (i,i) for i in self._domain.vertices()} + return SimplicialComplexMorphism(f, self._domain, X) + + def identity(self): + """ + Return the identity morphism of `Hom(S,S)`. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(2) + sage: H = Hom(S,S) + sage: i = H.identity() + sage: i.is_identity() + True + + sage: T = SimplicialComplex([[0,1]], is_mutable=False) + sage: G = Hom(T,T) + sage: G.identity() + Simplicial complex endomorphism of Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + Defn: 0 |--> 0 + 1 |--> 1 + """ + if not self.is_endomorphism_set(): + raise TypeError("identity map is only defined for endomorphism sets") + f = {i: i for i in self._domain.vertices()} + return SimplicialComplexMorphism(f, self._domain, self._codomain) + + def an_element(self): + """ + Return a (non-random) element of ``self``. + + EXAMPLES:: + + sage: S = simplicial_complexes.KleinBottle() + sage: T = simplicial_complexes.Sphere(5) + sage: H = Hom(S,T) + sage: x = H.an_element() + sage: x + Simplicial complex morphism: + From: Minimal triangulation of the Klein bottle + To: Minimal triangulation of the 5-sphere + Defn: [0, 1, 2, 3, 4, 5, 6, 7] --> [0, 0, 0, 0, 0, 0, 0, 0] + """ + X_vertices = self._domain.vertices() + try: + i = next(iter(self._codomain.vertices())) + except StopIteration: + if not X_vertices: + return {} + else: + raise TypeError("there are no morphisms from a non-empty simplicial complex to an empty simplicial complex") + f = {x: i for x in X_vertices} + return SimplicialComplexMorphism(f, self._domain, self._codomain) + diff --git a/src/sage/topology/simplicial_complex_morphism.py b/src/sage/topology/simplicial_complex_morphism.py new file mode 100644 index 00000000000..272cae39f14 --- /dev/null +++ b/src/sage/topology/simplicial_complex_morphism.py @@ -0,0 +1,801 @@ +r""" +Morphisms of simplicial complexes + +AUTHORS: + +- Benjamin Antieau (2009.06) + +- Travis Scrimshaw (2012-08-18): Made all simplicial complexes immutable to + work with the homset cache. + +This module implements morphisms of simplicial complexes. The input is given +by a dictionary on the vertex set of a simplicial complex. The initialization +checks that faces are sent to faces. + +There is also the capability to create the fiber product of two morphisms with +the same codomain. + +EXAMPLES:: + + sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) + sage: H = Hom(S,S.product(S, is_mutable=False)) + sage: H.diagonal_morphism() + Simplicial complex morphism: + From: Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(0, 2), (1, 5), (3, 4)} + To: Simplicial complex with 36 vertices and 18 facets + Defn: [0, 1, 2, 3, 4, 5] --> ['L0R0', 'L1R1', 'L2R2', 'L3R3', 'L4R4', 'L5R5'] + + sage: S = SimplicialComplex([[0,2],[1,5],[3,4]], is_mutable=False) + sage: T = SimplicialComplex([[0,2],[1,3]], is_mutable=False) + sage: f = {0:0,1:1,2:2,3:1,4:3,5:3} + sage: H = Hom(S,T) + sage: x = H(f) + sage: x.image() + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 2), (1, 3)} + sage: x.is_surjective() + True + sage: x.is_injective() + False + sage: x.is_identity() + False + + sage: S = simplicial_complexes.Sphere(2) + sage: H = Hom(S,S) + sage: i = H.identity() + sage: i.image() + Simplicial complex with vertex set (0, 1, 2, 3) and facets {(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)} + sage: i.is_surjective() + True + sage: i.is_injective() + True + sage: i.is_identity() + True + + sage: S = simplicial_complexes.Sphere(2) + sage: H = Hom(S,S) + sage: i = H.identity() + sage: j = i.fiber_product(i) + sage: j + Simplicial complex morphism: + From: Simplicial complex with 4 vertices and 4 facets + To: Minimal triangulation of the 2-sphere + Defn: L0R0 |--> 0 + L1R1 |--> 1 + L2R2 |--> 2 + L3R3 |--> 3 + sage: S = simplicial_complexes.Sphere(2) + sage: T = S.product(SimplicialComplex([[0,1]]), rename_vertices = False, is_mutable=False) + sage: H = Hom(T,S) + sage: T + Simplicial complex with 8 vertices and 12 facets + sage: sorted(T.vertices()) + [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1)] + sage: f = {(0, 0): 0, (0, 1): 0, (1, 0): 1, (1, 1): 1, (2, 0): 2, (2, 1): 2, (3, 0): 3, (3, 1): 3} + sage: x = H(f) + sage: U = simplicial_complexes.Sphere(1) + sage: G = Hom(U,S) + sage: U + Minimal triangulation of the 1-sphere + sage: g = {0:0,1:1,2:2} + sage: y = G(g) + sage: z = y.fiber_product(x) + sage: z # this is the mapping path space + Simplicial complex morphism: + From: Simplicial complex with 6 vertices and ... facets + To: Minimal triangulation of the 2-sphere + Defn: ['L0R(0, 0)', 'L0R(0, 1)', 'L1R(1, 0)', 'L1R(1, 1)', 'L2R(2, 0)', 'L2R(2, 1)'] --> [0, 0, 1, 1, 2, 2] +""" + +#***************************************************************************** +# Copyright (C) 2009 D. Benjamin Antieau +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +# +#***************************************************************************** + +from .simplicial_complex import Simplex, SimplicialComplex +from sage.matrix.constructor import matrix, zero_matrix +from sage.rings.integer_ring import ZZ +from sage.homology.chain_complex_morphism import ChainComplexMorphism +from sage.combinat.permutation import Permutation +from sage.algebras.steenrod.steenrod_algebra_misc import convert_perm +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.categories.simplicial_complexes import SimplicialComplexes + + +def is_SimplicialComplexMorphism(x): + """ + Return ``True`` if and only if ``x`` is a morphism of simplicial complexes. + + EXAMPLES:: + + sage: from sage.topology.simplicial_complex_morphism import is_SimplicialComplexMorphism + sage: S = SimplicialComplex([[0,1],[3,4]], is_mutable=False) + sage: H = Hom(S,S) + sage: f = {0:0,1:1,3:3,4:4} + sage: x = H(f) + sage: is_SimplicialComplexMorphism(x) + True + + """ + return isinstance(x, SimplicialComplexMorphism) + + +class SimplicialComplexMorphism(Morphism): + """ + An element of this class is a morphism of simplicial complexes. + """ + def __init__(self,f,X,Y): + """ + Input is a dictionary ``f``, the domain ``X``, and the codomain ``Y``. + + One can define the dictionary on the vertices of `X`. + + EXAMPLES:: + + sage: S = SimplicialComplex([[0,1],[2],[3,4],[5]], is_mutable=False) + sage: H = Hom(S,S) + sage: f = {0:0,1:1,2:2,3:3,4:4,5:5} + sage: g = {0:0,1:1,2:0,3:3,4:4,5:0} + sage: x = H(f) + sage: y = H(g) + sage: x == y + False + sage: x.image() + Simplicial complex with vertex set (0, 1, 2, 3, 4, 5) and facets {(2,), (5,), (0, 1), (3, 4)} + sage: y.image() + Simplicial complex with vertex set (0, 1, 3, 4) and facets {(0, 1), (3, 4)} + sage: x.image() == y.image() + False + """ + if not isinstance(X,SimplicialComplex) or not isinstance(Y,SimplicialComplex): + raise ValueError("X and Y must be SimplicialComplexes") + if not set(f.keys()) == set(X.vertices()): + raise ValueError("f must be a dictionary from the vertex set of X to single values in the vertex set of Y") + dim = X.dimension() + Y_faces = Y.faces() + for k in range(dim+1): + for i in X.faces()[k]: + tup = i.tuple() + fi = [] + for j in tup: + fi.append(f[j]) + v = Simplex(set(fi)) + if v not in Y_faces[v.dimension()]: + raise ValueError("f must be a dictionary from the vertices of X to the vertices of Y") + self._vertex_dictionary = f + Morphism.__init__(self, Hom(X,Y,SimplicialComplexes())) + + def __eq__(self,x): + """ + Return ``True`` if and only if ``self == x``. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(2) + sage: H = Hom(S,S) + sage: i = H.identity() + sage: i + Simplicial complex endomorphism of Minimal triangulation of the 2-sphere + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + 3 |--> 3 + sage: f = {0:0,1:1,2:2,3:2} + sage: j = H(f) + sage: i==j + False + + sage: T = SimplicialComplex([[1,2]], is_mutable=False) + sage: T + Simplicial complex with vertex set (1, 2) and facets {(1, 2)} + sage: G = Hom(T,T) + sage: k = G.identity() + sage: g = {1:1,2:2} + sage: l = G(g) + sage: k == l + True + """ + if not isinstance(x,SimplicialComplexMorphism) or self.codomain() != x.codomain() or self.domain() != x.domain() or self._vertex_dictionary != x._vertex_dictionary: + return False + else: + return True + + def __call__(self,x,orientation=False): + """ + Input is a simplex of the domain. Output is the image simplex. + + If the optional argument ``orientation`` is ``True``, then this + returns a pair ``(image simplex, oriented)`` where ``oriented`` + is 1 or `-1` depending on whether the map preserves or reverses + the orientation of the image simplex. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(2) + sage: T = simplicial_complexes.Sphere(3) + sage: S + Minimal triangulation of the 2-sphere + sage: T + Minimal triangulation of the 3-sphere + sage: f = {0:0,1:1,2:2,3:3} + sage: H = Hom(S,T) + sage: x = H(f) + sage: from sage.topology.simplicial_complex import Simplex + sage: x(Simplex([0,2,3])) + (0, 2, 3) + + An orientation-reversing example:: + + sage: X = SimplicialComplex([[0,1]], is_mutable=False) + sage: g = Hom(X,X)({0:1, 1:0}) + sage: g(Simplex([0,1])) + (0, 1) + sage: g(Simplex([0,1]), orientation=True) + ((0, 1), -1) + """ + dim = self.domain().dimension() + if not isinstance(x, Simplex) or x.dimension() > dim or x not in self.domain().faces()[x.dimension()]: + raise ValueError("x must be a simplex of the source of f") + tup = x.tuple() + fx = [] + for j in tup: + fx.append(self._vertex_dictionary[j]) + if orientation: + if len(set(fx)) == len(tup): + oriented = Permutation(convert_perm(fx)).signature() + else: + oriented = 1 + return (Simplex(set(fx)), oriented) + else: + return Simplex(set(fx)) + + def _repr_type(self): + """ + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = simplicial_complexes.Sphere(2) + sage: H = Hom(S,T) + sage: f = {0:0,1:1,2:2} + sage: H(f)._repr_type() + 'Simplicial complex' + """ + return "Simplicial complex" + + def _repr_defn(self): + """ + If there are fewer than 5 vertices, print the image of each vertex + on a separate line. Otherwise, print the map as a single line. + + EXAMPLES:: + + sage: S = simplicial_complexes.Simplex(1) + sage: print(Hom(S,S).identity()._repr_defn()) + 0 |--> 0 + 1 |--> 1 + sage: T = simplicial_complexes.Torus() + sage: print(Hom(T,T).identity()._repr_defn()) + [0, 1, 2, 3, 4, 5, 6] --> [0, 1, 2, 3, 4, 5, 6] + """ + vd = self._vertex_dictionary + try: + keys = sorted(vd.keys()) + except TypeError: + keys = sorted(vd.keys(), key=str) + if len(vd) < 5: + return '\n'.join("{} |--> {}".format(v, vd[v]) for v in keys) + domain = list(vd.keys()) + try: + domain = sorted(domain) + except TypeError: + domain = sorted(domain, key=str) + codomain = [vd[v] for v in domain] + return "{} --> {}".format(domain, codomain) + + def associated_chain_complex_morphism(self,base_ring=ZZ,augmented=False,cochain=False): + """ + Return the associated chain complex morphism of ``self``. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = simplicial_complexes.Sphere(2) + sage: H = Hom(S,T) + sage: f = {0:0,1:1,2:2} + sage: x = H(f) + sage: x + Simplicial complex morphism: + From: Minimal triangulation of the 1-sphere + To: Minimal triangulation of the 2-sphere + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + sage: a = x.associated_chain_complex_morphism() + sage: a + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring + sage: a._matrix_dictionary + {0: [1 0 0] + [0 1 0] + [0 0 1] + [0 0 0], 1: [1 0 0] + [0 1 0] + [0 0 0] + [0 0 1] + [0 0 0] + [0 0 0], 2: []} + sage: x.associated_chain_complex_morphism(augmented=True) + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 4 nonzero terms over Integer Ring + sage: x.associated_chain_complex_morphism(cochain=True) + Chain complex morphism: + From: Chain complex with at most 3 nonzero terms over Integer Ring + To: Chain complex with at most 2 nonzero terms over Integer Ring + sage: x.associated_chain_complex_morphism(augmented=True,cochain=True) + Chain complex morphism: + From: Chain complex with at most 4 nonzero terms over Integer Ring + To: Chain complex with at most 3 nonzero terms over Integer Ring + sage: x.associated_chain_complex_morphism(base_ring=GF(11)) + Chain complex morphism: + From: Chain complex with at most 2 nonzero terms over Finite Field of size 11 + To: Chain complex with at most 3 nonzero terms over Finite Field of size 11 + + Some simplicial maps which reverse the orientation of a few simplices:: + + sage: g = {0:1, 1:2, 2:0} + sage: H(g).associated_chain_complex_morphism()._matrix_dictionary + {0: [0 0 1] + [1 0 0] + [0 1 0] + [0 0 0], 1: [ 0 -1 0] + [ 0 0 -1] + [ 0 0 0] + [ 1 0 0] + [ 0 0 0] + [ 0 0 0], 2: []} + sage: X = SimplicialComplex([[0, 1]], is_mutable=False) + sage: Hom(X,X)({0:1, 1:0}).associated_chain_complex_morphism()._matrix_dictionary + {0: [0 1] + [1 0], 1: [-1]} + """ + max_dim = max(self.domain().dimension(),self.codomain().dimension()) + min_dim = min(self.domain().dimension(),self.codomain().dimension()) + matrices = {} + if augmented is True: + m = matrix(base_ring,1,1,1) + if not cochain: + matrices[-1] = m + else: + matrices[-1] = m.transpose() + for dim in range(min_dim+1): + X_faces = self.domain()._n_cells_sorted(dim) + Y_faces = self.codomain()._n_cells_sorted(dim) + num_faces_X = len(X_faces) + num_faces_Y = len(Y_faces) + mval = [0 for i in range(num_faces_X*num_faces_Y)] + for i in X_faces: + y, oriented = self(i, orientation=True) + if y.dimension() < dim: + pass + else: + mval[X_faces.index(i)+(Y_faces.index(y)*num_faces_X)] = oriented + m = matrix(base_ring,num_faces_Y,num_faces_X,mval,sparse=True) + if not cochain: + matrices[dim] = m + else: + matrices[dim] = m.transpose() + for dim in range(min_dim+1,max_dim+1): + try: + l1 = len(self.codomain().n_cells(dim)) + except KeyError: + l1 = 0 + try: + l2 = len(self.domain().n_cells(dim)) + except KeyError: + l2 = 0 + m = zero_matrix(base_ring,l1,l2,sparse=True) + if not cochain: + matrices[dim] = m + else: + matrices[dim] = m.transpose() + if not cochain: + return ChainComplexMorphism(matrices, + self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain), + self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) + return ChainComplexMorphism(matrices, + self.codomain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain), + self.domain().chain_complex(base_ring=base_ring,augmented=augmented,cochain=cochain)) + + def image(self): + """ + Computes the image simplicial complex of `f`. + + EXAMPLES:: + + sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) + sage: T = SimplicialComplex([[0,1]], is_mutable=False) + sage: f = {0:0,1:1,2:0,3:1} + sage: H = Hom(S,T) + sage: x = H(f) + sage: x.image() + Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + + sage: S = SimplicialComplex(is_mutable=False) + sage: H = Hom(S,S) + sage: i = H.identity() + sage: i.image() + Simplicial complex with vertex set () and facets {()} + sage: i.is_surjective() + True + sage: S = SimplicialComplex([[0,1]], is_mutable=False) + sage: T = SimplicialComplex([[0,1], [0,2]], is_mutable=False) + sage: f = {0:0,1:1} + sage: g = {0:0,1:1} + sage: k = {0:0,1:2} + sage: H = Hom(S,T) + sage: x = H(f) + sage: y = H(g) + sage: z = H(k) + sage: x == y + True + sage: x == z + False + sage: x.image() + Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + sage: y.image() + Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + sage: z.image() + Simplicial complex with vertex set (0, 2) and facets {(0, 2)} + + """ + fa = [self(i) for i in self.domain().facets()] + return SimplicialComplex(fa, maximality_check=True) + + def is_surjective(self): + """ + Return ``True`` if and only if ``self`` is surjective. + + EXAMPLES:: + + sage: S = SimplicialComplex([(0,1,2)], is_mutable=False) + sage: S + Simplicial complex with vertex set (0, 1, 2) and facets {(0, 1, 2)} + sage: T = SimplicialComplex([(0,1)], is_mutable=False) + sage: T + Simplicial complex with vertex set (0, 1) and facets {(0, 1)} + sage: H = Hom(S,T) + sage: x = H({0:0,1:1,2:1}) + sage: x.is_surjective() + True + + sage: S = SimplicialComplex([[0,1],[2,3]], is_mutable=False) + sage: T = SimplicialComplex([[0,1]], is_mutable=False) + sage: f = {0:0,1:1,2:0,3:1} + sage: H = Hom(S,T) + sage: x = H(f) + sage: x.is_surjective() + True + """ + return self.codomain() == self.image() + + def is_injective(self): + """ + Return ``True`` if and only if ``self`` is injective. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = simplicial_complexes.Sphere(2) + sage: U = simplicial_complexes.Sphere(3) + sage: H = Hom(T,S) + sage: G = Hom(T,U) + sage: f = {0:0,1:1,2:0,3:1} + sage: x = H(f) + sage: g = {0:0,1:1,2:2,3:3} + sage: y = G(g) + sage: x.is_injective() + False + sage: y.is_injective() + True + + """ + v = [self._vertex_dictionary[i[0]] for i in self.domain().faces()[0]] + for i in v: + if v.count(i) > 1: + return False + return True + + def is_identity(self): + """ + If ``self`` is an identity morphism, returns ``True``. + Otherwise, ``False``. + + EXAMPLES:: + + sage: T = simplicial_complexes.Sphere(1) + sage: G = Hom(T,T) + sage: T + Minimal triangulation of the 1-sphere + sage: j = G({0:0,1:1,2:2}) + sage: j.is_identity() + True + + sage: S = simplicial_complexes.Sphere(2) + sage: T = simplicial_complexes.Sphere(3) + sage: H = Hom(S,T) + sage: f = {0:0,1:1,2:2,3:3} + sage: x = H(f) + sage: x + Simplicial complex morphism: + From: Minimal triangulation of the 2-sphere + To: Minimal triangulation of the 3-sphere + Defn: 0 |--> 0 + 1 |--> 1 + 2 |--> 2 + 3 |--> 3 + sage: x.is_identity() + False + """ + if self.domain() != self.codomain(): + return False + else: + f = dict() + for i in self.domain().vertices(): + f[i] = i + if self._vertex_dictionary != f: + return False + else: + return True + + def fiber_product(self, other, rename_vertices = True): + """ + Fiber product of ``self`` and ``other``. Both morphisms should have + the same codomain. The method returns a morphism of simplicial + complexes, which is the morphism from the space of the fiber product + to the codomain. + + EXAMPLES:: + + sage: S = SimplicialComplex([[0,1],[1,2]], is_mutable=False) + sage: T = SimplicialComplex([[0,2],[1]], is_mutable=False) + sage: U = SimplicialComplex([[0,1],[2]], is_mutable=False) + sage: H = Hom(S,U) + sage: G = Hom(T,U) + sage: f = {0:0,1:1,2:0} + sage: g = {0:0,1:1,2:1} + sage: x = H(f) + sage: y = G(g) + sage: z = x.fiber_product(y) + sage: z + Simplicial complex morphism: + From: Simplicial complex with 4 vertices and facets {...} + To: Simplicial complex with vertex set (0, 1, 2) and facets {(2,), (0, 1)} + Defn: L0R0 |--> 0 + L1R1 |--> 1 + L1R2 |--> 1 + L2R0 |--> 0 + """ + if self.codomain() != other.codomain(): + raise ValueError("self and other must have the same codomain.") + X = self.domain().product(other.domain(),rename_vertices = rename_vertices) + v = [] + f = dict() + eff1 = self.domain().vertices() + eff2 = other.domain().vertices() + for i in eff1: + for j in eff2: + if self(Simplex([i])) == other(Simplex([j])): + if rename_vertices: + v.append("L"+str(i)+"R"+str(j)) + f["L"+str(i)+"R"+str(j)] = self._vertex_dictionary[i] + else: + v.append((i,j)) + f[(i,j)] = self._vertex_dictionary[i] + return SimplicialComplexMorphism(f, X.generated_subcomplex(v), self.codomain()) + + def mapping_torus(self): + r""" + The mapping torus of a simplicial complex endomorphism + + The mapping torus is the simplicial complex formed by taking + the product of the domain of ``self`` with a `4` point + interval `[I_0, I_1, I_2, I_3]` and identifying vertices of + the form `(I_0, v)` with `(I_3, w)` where `w` is the image of + `v` under the given morphism. + + See :wikipedia:`Mapping torus` + + EXAMPLES:: + + sage: C = simplicial_complexes.Sphere(1) # Circle + sage: T = Hom(C,C).identity().mapping_torus() ; T # Torus + Simplicial complex with 9 vertices and 18 facets + sage: T.homology() == simplicial_complexes.Torus().homology() + True + + sage: f = Hom(C,C)({0:0,1:2,2:1}) + sage: K = f.mapping_torus() ; K # Klein Bottle + Simplicial complex with 9 vertices and 18 facets + sage: K.homology() == simplicial_complexes.KleinBottle().homology() + True + + TESTS:: + + sage: g = Hom(simplicial_complexes.Simplex([1]),C)({1:0}) + sage: g.mapping_torus() + Traceback (most recent call last): + ... + ValueError: self must have the same domain and codomain. + """ + if self.domain() != self.codomain(): + raise ValueError("self must have the same domain and codomain.") + map_dict = self._vertex_dictionary + interval = SimplicialComplex([["I0","I1"],["I1","I2"]]) + product = interval.product(self.domain(),False) + facets = list(product.maximal_faces()) + for facet in self.domain()._facets: + left = [ ("I0",v) for v in facet ] + right = [ ("I2",map_dict[v]) for v in facet ] + for i in range(facet.dimension()+1): + facets.append(tuple(left[:i+1]+right[i:])) + return SimplicialComplex(facets) + + def induced_homology_morphism(self, base_ring=None, cohomology=False): + """ + The map in (co)homology induced by this map + + INPUT: + + - ``base_ring`` -- must be a field (optional, default ``QQ``) + + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, the map induced in cohomology rather than homology. + + EXAMPLES:: + + sage: S = simplicial_complexes.Sphere(1) + sage: T = S.product(S, is_mutable=False) + sage: H = Hom(S,T) + sage: diag = H.diagonal_morphism() + sage: h = diag.induced_homology_morphism(QQ) + sage: h + Graded vector space morphism: + From: Homology module of Minimal triangulation of the 1-sphere over Rational Field + To: Homology module of Simplicial complex with 9 vertices and 18 facets over Rational Field + Defn: induced by: + Simplicial complex morphism: + From: Minimal triangulation of the 1-sphere + To: Simplicial complex with 9 vertices and 18 facets + Defn: 0 |--> L0R0 + 1 |--> L1R1 + 2 |--> L2R2 + + We can view the matrix form for the homomorphism:: + + sage: h.to_matrix(0) # in degree 0 + [1] + sage: h.to_matrix(1) # in degree 1 + [1] + [1] + sage: h.to_matrix() # the entire homomorphism + [1|0] + [-+-] + [0|1] + [0|1] + [-+-] + [0|0] + + The map on cohomology should be dual to the map on homology:: + + sage: coh = diag.induced_homology_morphism(QQ, cohomology=True) + sage: coh.to_matrix(1) + [1 1] + sage: h.to_matrix() == coh.to_matrix().transpose() + True + + We can evaluate the map on (co)homology classes:: + + sage: x,y = list(T.cohomology_ring(QQ).basis(1)) + sage: coh(x) + h^{1,0} + sage: coh(2*x+3*y) + 5*h^{1,0} + + Note that the complexes must be immutable for this to + work. Many, but not all, complexes are immutable when + constructed:: + + sage: S.is_immutable() + True + sage: S.barycentric_subdivision().is_immutable() + False + sage: S2 = S.suspension() + sage: S2.is_immutable() + False + sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() + Traceback (most recent call last): + ... + ValueError: the domain and codomain complexes must be immutable + sage: S2.set_immutable(); S2.is_immutable() + True + sage: h = Hom(S,S2)({0: 0, 1:1, 2:2}).induced_homology_morphism() + """ + from sage.homology.homology_morphism import InducedHomologyMorphism + return InducedHomologyMorphism(self, base_ring, cohomology) + + def is_contiguous_to(self, other): + r""" + Return ``True`` if ``self`` is contiguous to ``other``. + + Two morphisms `f_0, f_1: K \to L` are *contiguous* if for any + simplex `\sigma \in K`, the union `f_0(\sigma) \cup + f_1(\sigma)` is a simplex in `L`. This is not a transitive + relation, but it induces an equivalence relation on simplicial + maps: `f` is equivalent to `g` if there is a finite sequence + `f_0 = f`, `f_1`, ..., `f_n = g` such that `f_i` and `f_{i+1}` + are contiguous for each `i`. + + This is related to maps being homotopic: if they are + contiguous, then they induce homotopic maps on the geometric + realizations. Given two homotopic maps on the geometric + realizations, then after barycentrically subdividing `n` times + for some `n`, the maps have simplicial approximations which + are in the same contiguity class. (This last fact is only true + if the domain is a *finite* simplicial complex, by the way.) + + See Section 3.5 of Spanier [Spa1966]_ for details. + + ALGORITHM: + + It is enough to check when `\sigma` ranges over the facets. + + INPUT: + + - ``other`` -- a simplicial complex morphism with the same + domain and codomain as ``self`` + + EXAMPLES:: + + sage: K = simplicial_complexes.Simplex(1) + sage: L = simplicial_complexes.Sphere(1) + sage: H = Hom(K, L) + sage: f = H({0: 0, 1: 1}) + sage: g = H({0: 0, 1: 0}) + sage: f.is_contiguous_to(f) + True + sage: f.is_contiguous_to(g) + True + sage: h = H({0: 1, 1: 2}) + sage: f.is_contiguous_to(h) + False + + TESTS:: + + sage: one = Hom(K,K).identity() + sage: one.is_contiguous_to(f) + False + sage: one.is_contiguous_to(3) # nonsensical input + False + """ + if not isinstance(other, SimplicialComplexMorphism): + return False + if self.codomain() != other.codomain() or self.domain() != other.domain(): + return False + domain = self.domain() + codomain = self.codomain() + return all(Simplex(self(sigma).set().union(other(sigma))) in codomain + for sigma in domain.facets()) + diff --git a/src/sage/topology/simplicial_set.py b/src/sage/topology/simplicial_set.py new file mode 100644 index 00000000000..4b58d63684c --- /dev/null +++ b/src/sage/topology/simplicial_set.py @@ -0,0 +1,4062 @@ +# -*- coding: utf-8 -*- +r""" +Simplicial sets + +AUTHORS: + +- John H. Palmieri (2016-07) + +This module implements simplicial sets. + +A *simplicial set* `X` is a collection of sets `X_n` indexed by the +non-negative integers; the set `X_n` is called the set of +`n`-simplices. These sets are connected by maps + +.. MATH:: + + d_i: X_n \to X_{n-1}, \ \ 0 \leq i \leq n \ \ \text{(face maps)} \\ + s_j: X_n \to X_{n+1}, \ \ 0 \leq j \leq n \ \ \text{(degeneracy maps)} + +satisfying the *simplicial identities*: + +.. MATH:: + + d_i d_j &= d_{j-1} d_i \ \ \text{if } ij+1 \\ + s_i s_j &= s_{j+1} s_{i} \ \ \text{if } i + + sage: Sigma3 = groups.permutation.Symmetric(3) + sage: BSigma3 = Sigma3.nerve() + sage: pi = BSigma3.fundamental_group(); pi + Finitely presented group < e0, e1 | e0^2, e1^3, (e0*e1^-1)^2 > + sage: pi.order() + 6 + sage: pi.is_abelian() + False + + sage: RP6 = simplicial_sets.RealProjectiveSpace(6) + sage: RP6.homology(reduced=False, base_ring=GF(2)) + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2, + 3: Vector space of dimension 1 over Finite Field of size 2, + 4: Vector space of dimension 1 over Finite Field of size 2, + 5: Vector space of dimension 1 over Finite Field of size 2, + 6: Vector space of dimension 1 over Finite Field of size 2} + sage: RP6.homology(reduced=False, base_ring=QQ) + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 0 over Rational Field, + 2: Vector space of dimension 0 over Rational Field, + 3: Vector space of dimension 0 over Rational Field, + 4: Vector space of dimension 0 over Rational Field, + 5: Vector space of dimension 0 over Rational Field, + 6: Vector space of dimension 0 over Rational Field} + +When infinite simplicial sets are involved, most computations are done +by taking an `n`-skeleton for an appropriate `n`, either implicitly or +explicitly:: + + sage: B3 = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([3])) + sage: B3.disjoint_union(B3).n_skeleton(3) + Disjoint union: (Simplicial set with 15 non-degenerate simplices u Simplicial set with 15 non-degenerate simplices) + sage: S1 = simplicial_sets.Sphere(1) + sage: B3.product(S1).homology(range(4)) + {0: 0, 1: Z x C3, 2: C3, 3: C3} + +Without the ``range`` argument, this would raise an error, since +``B3`` is infinite:: + + sage: B3.product(S1).homology() + Traceback (most recent call last): + ... + NotImplementedError: this simplicial set may be infinite, so specify dimensions when computing homology + +It should be easy to construct many simplicial sets from the +predefined ones using pushouts, pullbacks, etc., but they can also be +constructed "by hand": first define some simplices, then define a +simplicial set by specifying their faces:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: f = AbstractSimplex(1, name='f') + sage: X = SimplicialSet({e: (v,w), f: (w,w)}) + +Now `e` is an edge from `v` to `w` and `f` is an edge starting and +ending at `w`. Therefore the first homology group of `X` should be a +copy of the integers:: + + sage: X.homology(1) + Z +""" +#***************************************************************************** +# Copyright (C) 2016 John H. Palmieri +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +# +#***************************************************************************** + +import copy + +from sage.graphs.graph import Graph +from sage.matrix.constructor import matrix +from sage.misc.cachefunc import cached_method +from sage.misc.fast_methods import WithEqualityById +from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.rings.rational_field import QQ +from sage.structure.parent import Parent +from sage.structure.sage_object import SageObject +from sage.homology.algebraic_topological_model import algebraic_topological_model_delta_complex +from sage.homology.chain_complex import ChainComplex +from sage.homology.chains import Chains, Cochains + +from .cell_complex import GenericCellComplex +from .delta_complex import DeltaComplex +from .simplicial_complex import SimplicialComplex + +from sage.misc.lazy_import import lazy_import +lazy_import('sage.categories.simplicial_sets', 'SimplicialSets') + +######################################################################## +# The classes for simplices. + +class AbstractSimplex_class(SageObject): + """ + A simplex of dimension ``dim``. + + INPUT: + + - ``dim`` -- integer, the dimension + - ``degeneracies`` (optional) -- iterable, the indices of the + degeneracy maps + - ``underlying`` (optional) -- a non-degenerate simplex + - ``name`` (optional) -- string + - ``latex_name`` (optional) -- string + + Users should not call this directly, but instead use + :func:`AbstractSimplex`. See that function for more documentation. + """ + def __init__(self, dim, degeneracies=(), underlying=None, name=None, + latex_name=None): + """ + A simplex of dimension ``dim``. + + INPUT: + + - ``dim`` -- integer, the dimension + - ``degeneracies`` (optional) -- iterable, the indices of the degeneracy maps + - ``underlying`` (optional) -- a non-degenerate simplex + - ``name`` (optional) -- string + - ``latex_name`` (optional) -- string + + Users should not call this directly, but instead use + :func:`AbstractSimplex`. See that function for more + documentation. + + TESTS:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: AbstractSimplex(3, (1,2)) + s_3 s_1 Delta^3 + sage: AbstractSimplex(3, None) + Delta^3 + + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(0, (0,), underlying=v) + sage: e + s_0 v + sage: e.nondegenerate() is v + True + + sage: AbstractSimplex(3.2, None) + Traceback (most recent call last): + ... + ValueError: the dimension must be an integer + sage: AbstractSimplex(-3, None) + Traceback (most recent call last): + ... + ValueError: the dimension must be non-negative + + sage: AbstractSimplex(0, (1,)) + Traceback (most recent call last): + ... + ValueError: invalid list of degeneracy maps on 0-simplex + + Distinct non-degenerate simplices should never be equal, even + if they have the same starting data:: + + sage: from sage.topology.simplicial_set import AbstractSimplex_class + sage: AbstractSimplex_class(3) == AbstractSimplex_class(3) + False + sage: AbstractSimplex(3) == AbstractSimplex(3) + False + + Hashing:: + + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: hash(v) == hash(w) + False + sage: x = v.apply_degeneracies(2,1,0) + sage: hash(x) == hash(v.apply_degeneracies(2,1,0)) + True + + Equality:: + + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: v == w + False + sage: v.apply_degeneracies(2,1,0) is v.apply_degeneracies(2,1,0) + False + sage: v.apply_degeneracies(2,1,0) == v.apply_degeneracies(2,1,0) + True + sage: v == None + False + """ + try: + Integer(dim) + except TypeError: + raise ValueError('the dimension must be an integer') + if dim < 0: + raise ValueError('the dimension must be non-negative') + self._dim = dim + if degeneracies: + self._degens = standardize_degeneracies(*degeneracies) + for (d, s) in enumerate(reversed(self._degens)): + if d + dim < s: + raise ValueError('invalid list of degeneracy maps ' + 'on {}-simplex'.format(dim)) + if underlying is None: + self._underlying = NonDegenerateSimplex(dim) + else: + self._underlying = underlying + else: + self._degens = () + if underlying is None: + self._underlying = self + else: + self._underlying = underlying + if name is not None: + self.rename(name) + self._latex_name = latex_name + + def __hash__(self): + """ + If nondegenerate: return the id of this simplex. + + Otherwise, combine the id of its underlying nondegenerate + simplex with the tuple of indeterminacies. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: hash(v) == hash(w) + False + sage: x = v.apply_degeneracies(2,1,0) + sage: id(x) == id(v.apply_degeneracies(2,1,0)) + False + sage: hash(x) == hash(v.apply_degeneracies(2,1,0)) + True + """ + if self.is_nondegenerate(): + return id(self) + return hash(self.nondegenerate()) ^ hash(self._degens) + + def __eq__(self, other): + """ + Two nondegenerate simplices are equal if they are identical. + Two degenerate simplices are equal if their underlying + nondegenerate simplices are identical and their tuples of + degeneracies are equal. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: v == w + False + sage: v.apply_degeneracies(2,1,0) is v.apply_degeneracies(2,1,0) + False + sage: v.apply_degeneracies(2,1,0) == v.apply_degeneracies(2,1,0) + True + + TESTS:: + + sage: v == None + False + """ + if not isinstance(other, AbstractSimplex_class): + return False + return (self._degens == other._degens + and self.nondegenerate() is other.nondegenerate()) + + def __ne__(self, other): + """ + This returns the negation of `__eq__`. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: v != w + True + sage: x = v.apply_degeneracies(1, 0) + sage: y = v.apply_degeneracies(1, 0) + sage: x != y + False + """ + return not self == other + + def __lt__(self, other): + """ + We implement sorting in the hopes that sorted lists of simplices, + for example as defining data for a simplicial set, will be + well-defined invariants. + + Sort by dimension first. If dimensions are equal, if only one + has a custom name (as set by specifying ``name=NAME`` upon + creation or by calling ``object.rename('NAME')``, put it + first. If both have custom names, sort by the names. As a last + resort, sort by their id. + + TESTS:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + + At this point, comparison between v and w is random, based on + their location in memory. :: + + sage: v < w and w < v + False + sage: v < w or w < v + True + sage: (v < w and w > v) or (w < v and v > w) + True + + Now we add names to force an ordering:: + + sage: w.rename('w') + sage: v < w + False + sage: v > w + True + sage: v >= w + True + sage: v.rename('v') + sage: v < w + True + sage: v <= w + True + sage: v > w + False + + Test other sorting. Dimensions:: + + sage: AbstractSimplex(0) < AbstractSimplex(3) + True + sage: AbstractSimplex(0, ((0,0,0))) <= AbstractSimplex(2) + False + + Degenerate comes after non-degenerate, and if both are + degenerate, sort on the degeneracies:: + + sage: AbstractSimplex(0, ((0,0))) <= AbstractSimplex(2) + False + sage: AbstractSimplex(1, ((0,))) < AbstractSimplex(1, ((1,))) + True + sage: v.apply_degeneracies(1,0) > w.apply_degeneracies(1,0) + False + sage: w.rename('a') + sage: v.apply_degeneracies(1,0) > w.apply_degeneracies(1,0) + True + + Testing `<=`, `>`, `>=`:: + + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: v <= v + True + sage: w <= v + False + sage: v.apply_degeneracies(1,0) <= w.apply_degeneracies(1,0) + True + + sage: v > v + False + sage: w > v + True + sage: v.apply_degeneracies(1,0) > w.apply_degeneracies(1,0) + False + + sage: v >= v + True + sage: w >= v + True + sage: v.apply_degeneracies(1,0) >= w.apply_degeneracies(1,0) + False + """ + if self.dimension() < other.dimension(): + return True + if self.dimension() > other.dimension(): + return False + if self.degeneracies() and not other.degeneracies(): + return False + if other.degeneracies() and not self.degeneracies(): + return True + if self.degeneracies() and other.degeneracies() and self.degeneracies() != other.degeneracies(): + return self.degeneracies() < other.degeneracies() + if hasattr(self.nondegenerate(), '__custom_name'): + if hasattr(other.nondegenerate(), '__custom_name'): + return str(self) < str(other) + return True + else: + if (hasattr(other, '__custom_name') + or hasattr(other.nondegenerate(), '__custom_name')): + return False + return id(self) < id(other) + + def __gt__(self, other): + """ + See :meth:`__lt__` for more doctests. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: e = AbstractSimplex(1, (1,0), name='e') + sage: f = AbstractSimplex(1, (2,1), name='f') + sage: e > f + False + """ + return not (self < other or self == other) + + def __le__(self, other): + """ + See :meth:`__lt__` for more doctests. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: e = AbstractSimplex(1, (1,0), name='e') + sage: f = AbstractSimplex(1, (2,1), name='f') + sage: e <= f + True + """ + return self < other or self == other + + def __ge__(self, other): + """ + See :meth:`__lt__` for more doctests. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: e = AbstractSimplex(1, (1,0), name='e') + sage: f = AbstractSimplex(1, (2,1), name='f') + sage: e >= f + False + """ + return not self < other + + def nondegenerate(self): + """ + The non-degenerate simplex underlying this one. + + Therefore return itself if this simplex is non-degenerate. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: v = AbstractSimplex(0, name='v') + sage: sigma = v.apply_degeneracies(1, 0) + sage: sigma.nondegenerate() + v + sage: tau = AbstractSimplex(1, (3,2,1)) + sage: x = tau.nondegenerate(); x + Delta^1 + sage: x == tau.nondegenerate() + True + + sage: AbstractSimplex(1, None) + Delta^1 + sage: AbstractSimplex(1, None) == x + False + sage: AbstractSimplex(1, None) == tau.nondegenerate() + False + """ + return self._underlying + + def degeneracies(self): + """ + Return the list of indices for the degeneracy maps for this + simplex. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: AbstractSimplex(4, (0,0,0)).degeneracies() + [2, 1, 0] + sage: AbstractSimplex(4, None).degeneracies() + [] + """ + return list(self._degens) + + def is_degenerate(self): + """ + True if this simplex is degenerate. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: AbstractSimplex(3, (2,1)).is_degenerate() + True + sage: AbstractSimplex(3, None).is_degenerate() + False + """ + return bool(self.degeneracies()) + + def is_nondegenerate(self): + """ + True if this simplex is non-degenerate. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: AbstractSimplex(3, (2,1)).is_nondegenerate() + False + sage: AbstractSimplex(3, None).is_nondegenerate() + True + sage: AbstractSimplex(5).is_nondegenerate() + True + """ + return not self.is_degenerate() + + def dimension(self): + """ + The dimension of this simplex. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: AbstractSimplex(3, (2,1)).dimension() + 5 + sage: AbstractSimplex(3, None).dimension() + 3 + sage: AbstractSimplex(7).dimension() + 7 + """ + return self._dim + len(self.degeneracies()) + + def apply_degeneracies(self, *args): + """ + Apply the degeneracies given by the arguments ``args`` to this simplex. + + INPUT: + + - ``args`` -- integers + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: v = AbstractSimplex(0) + sage: e = v.apply_degeneracies(0) + sage: e.nondegenerate() == v + True + sage: f = e.apply_degeneracies(0) + sage: f + s_1 s_0 Delta^0 + sage: f.degeneracies() + [1, 0] + sage: f.nondegenerate() == v + True + sage: v.apply_degeneracies(1, 0) + s_1 s_0 Delta^0 + + TESTS:: + + sage: e.apply_degeneracies() == e + True + + Do not pass an explicit list or tuple as the argument: call + this with the syntax ``x.apply_degeneracies(1,0)``, not + ``x.apply_degeneracies([1,0])``:: + + sage: e.apply_degeneracies([1,0]) + Traceback (most recent call last): + ... + TypeError: degeneracies are indexed by non-negative integers; do not use an explicit list or tuple + """ + if not args: + return self + underlying = self.nondegenerate() + return AbstractSimplex(underlying.dimension(), + degeneracies= list(args) + self.degeneracies(), + underlying=underlying) + + def __copy__(self): + """ + Return a copy of this simplex. + + Forget the "underlying" non-degenerate simplex. If this + simplex has a name, then its copy's name is obtained by adding + a prime ``'`` at the end. + + TESTS:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: v = AbstractSimplex(0) + sage: copy(v) == v + False + sage: copy(v).nondegenerate() == v + False + sage: x = v.apply_degeneracies(1, 0) + sage: y = copy(v).apply_degeneracies(1, 0) + sage: z = copy(x) + sage: x == y or x == z or y == z + False + sage: x.nondegenerate() == copy(v) + False + sage: y.nondegenerate() == v + False + + sage: v.rename('v') + sage: copy(v) + v' + sage: copy(copy(v)) + v'' + """ + # Don't preserve the underlying simplex when copying, just the + # dimension, the degeneracies, and the name (with a prime + # added). + sigma = AbstractSimplex(self._dim, degeneracies=self.degeneracies()) + if hasattr(self, '__custom_name'): + sigma.rename(str(self) + "'") + return sigma + + def __deepcopy__(self, memo): + """ + Return a "deep" copy of this simplex. + + INPUT: + + - ``memo`` -- "memo" dictionary required by the ``copy.deepcopy`` method + + This returns the same object as the :meth:`__copy__` method + and also updates ``memo``. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: import copy + sage: v = AbstractSimplex(0) + sage: copy.deepcopy(v) == v + False + + TESTS: + + The purpose for this method is to be able to make distinct + copies of simplicial sets:: + + sage: from sage.topology.simplicial_set import SimplicialSet + sage: RP3 = simplicial_sets.RealProjectiveSpace(3) + sage: dict(copy.copy(RP3._data)) == dict(RP3._data) + True + sage: dict(copy.deepcopy(RP3._data)) == dict(RP3._data) + False + sage: SimplicialSet(RP3) == RP3 + False + sage: copy.copy(RP3) == RP3 + False + """ + underlying = self.nondegenerate() + degens = self.degeneracies() + try: + return memo[underlying].apply_degeneracies(*degens) + except KeyError: + sigma = AbstractSimplex(underlying._dim) + if hasattr(underlying, '__custom_name'): + sigma.rename(str(self) + "'") + memo[underlying] = sigma + return sigma.apply_degeneracies(*degens) + + def _repr_(self): + """ + Print representation. + + TESTS:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: AbstractSimplex(3, None) + Delta^3 + sage: AbstractSimplex(3, (0,)) + s_0 Delta^3 + sage: AbstractSimplex(3, (0, 0)) + s_1 s_0 Delta^3 + + Test renaming:: + + sage: v = AbstractSimplex(0) + sage: v + Delta^0 + sage: v.rename('v') + sage: v + v + sage: v.apply_degeneracies(1, 0) + s_1 s_0 v + """ + if self.degeneracies(): + degens = ' '.join(['s_{}'.format(i) for i in self.degeneracies()]) + return degens + ' {}'.format(self.nondegenerate()) + return 'Delta^{}'.format(self._dim) + + def _latex_(self): + r""" + LaTeX representation. + + TESTS:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: latex(AbstractSimplex(18, None)) + \Delta^{18} + sage: latex(AbstractSimplex(3, (0, 0,))) + s_{1} s_{0} \Delta^{3} + sage: latex(AbstractSimplex(3, (0, 0,), name='x')) + x + sage: latex(AbstractSimplex(3, name='x').apply_degeneracies(0, 0)) + s_{1} s_{0} x + sage: latex(AbstractSimplex(3, (0, 0,), name='x', latex_name='y')) + y + sage: latex(AbstractSimplex(3, name='x', latex_name='y').apply_degeneracies(0, 0)) + s_{1} s_{0} y + """ + if self._latex_name is not None: + return self._latex_name + if hasattr(self, '__custom_name'): + return str(self) + if self.nondegenerate()._latex_name is not None: + simplex = self.nondegenerate()._latex_name + elif hasattr(self.nondegenerate(), '__custom_name'): + simplex = str(self.nondegenerate()) + else: + simplex = "\\Delta^{{{}}}".format(self._dim) + if self.degeneracies(): + degens = ' '.join(['s_{{{}}}'.format(i) for i in self.degeneracies()]) + return degens + ' ' + simplex + return simplex + + +# If we inherit from AbstractSimplex_class first in the following, +# then we have to override __eq__ and __hash__. If we inherit from +# WithEqualityById first, then we have to override __lt__, __gt__, +# __ge__, __le__. Inheriting from AbstractSimplex_class first seems to +# be slightly faster. +class NonDegenerateSimplex(AbstractSimplex_class, WithEqualityById): + def __init__(self, dim, name=None, latex_name=None): + """ + A nondegenerate simplex. + + INPUT: + + - ``dim`` -- non-negative integer, the dimension + + - ``name`` (optional) -- string, a name for this simplex. + + - ``latex_name`` (optional) -- string, a name for this simplex to + use in the LaTeX representation. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: v = AbstractSimplex(0, name='v') + sage: v + v + sage: type(v) + + + Distinct non-degenerate simplices should never be equal, even + if they have the same starting data. :: + + sage: v == AbstractSimplex(0, name='v') + False + sage: AbstractSimplex(3) == AbstractSimplex(3) + False + + sage: from sage.topology.simplicial_set import NonDegenerateSimplex + sage: x = NonDegenerateSimplex(0, name='x') + sage: x == NonDegenerateSimplex(0, name='x') + False + """ + AbstractSimplex_class.__init__(self, dim, name=name, latex_name=latex_name) + + __eq__ = WithEqualityById.__eq__ + __hash__ = WithEqualityById.__hash__ + + +# The following function returns an instance of either +# AbstractSimplex_class or NonDegenerateSimplex. + +def AbstractSimplex(dim, degeneracies=(), underlying=None, + name=None, latex_name=None): + r""" + An abstract simplex, a building block of a simplicial set. + + In a simplicial set, a simplex either is non-degenerate or is + obtained by applying degeneracy maps to a non-degenerate simplex. + + INPUT: + + - ``dim`` -- a non-negative integer, the dimension of the + underlying non-degenerate simplex. + + - ``degeneracies`` (optional, default ``None``) -- a list or tuple of + non-negative integers, the degeneracies to be applied. + + - ``underlying`` (optional) -- a non-degenerate simplex to which + the degeneracies are being applied. + + - ``name`` (optional) -- string, a name for this simplex. + + - ``latex_name`` (optional) -- string, a name for this simplex to + use in the LaTeX representation. + + So to define a simplex formed by applying the degeneracy maps `s_2 + s_1` to a 1-simplex, call ``AbstractSimplex(1, (2, 1))``. + + Specify ``underlying`` if you need to keep explicit track of the + underlying non-degenerate simplex, for example when computing + faces of another simplex. This is mainly for use by the method + :meth:`AbstractSimplex_class.apply_degeneracies`. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex + sage: AbstractSimplex(3, (3, 1)) + s_3 s_1 Delta^3 + sage: AbstractSimplex(3, None) + Delta^3 + sage: AbstractSimplex(3) + Delta^3 + + Simplices may be named (or renamed), affecting how they are printed:: + + sage: AbstractSimplex(0) + Delta^0 + sage: v = AbstractSimplex(0, name='v') + sage: v + v + sage: v.rename('w_0') + sage: v + w_0 + sage: latex(v) + w_0 + sage: latex(AbstractSimplex(0, latex_name='\\sigma')) + \sigma + + The simplicial identities are used to put the degeneracies in + standard decreasing form:: + + sage: x = AbstractSimplex(0, (0, 0, 0)) + sage: x + s_2 s_1 s_0 Delta^0 + sage: x.degeneracies() + [2, 1, 0] + + Use of the ``underlying`` argument:: + + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(0, (0,), underlying=v) + sage: e + s_0 v + sage: e.nondegenerate() is v + True + + sage: e.dimension() + 1 + sage: e.is_degenerate() + True + + Distinct non-degenerate simplices are never equal:: + + sage: AbstractSimplex(0, None) == AbstractSimplex(0, None) + False + sage: AbstractSimplex(0, (2,1,0)) == AbstractSimplex(0, (2,1,0)) + False + + sage: e = AbstractSimplex(0, ((0,))) + sage: f = AbstractSimplex(0, ((0,))) + sage: e == f + False + sage: e.nondegenerate() == f.nondegenerate() + False + + This means that if, when defining a simplicial set, you specify + the faces of a 2-simplex as:: + + (e, e, e) + + then the faces are the same degenerate vertex, but if you specify + the faces as:: + + (AbstractSimplex(0, ((0,))), AbstractSimplex(0, ((0,))), AbstractSimplex(0, ((0,)))) + + then the faces are three different degenerate vertices. + + View a command like ``AbstractSimplex(0, (2,1,0))`` as first + constructing ``AbstractSimplex(0)`` and then applying degeneracies + to it, and you always get distinct simplices from different calls + to ``AbstractSimplex(0)``. On the other hand, if you apply + degeneracies to the same non-degenerate simplex, the resulting + simplices are equal:: + + sage: v = AbstractSimplex(0) + sage: v.apply_degeneracies(1, 0) == v.apply_degeneracies(1, 0) + True + sage: AbstractSimplex(1, (0,), underlying=v) == AbstractSimplex(1, (0,), underlying=v) + True + """ + if degeneracies: + if underlying is None: + underlying = NonDegenerateSimplex(dim) + return AbstractSimplex_class(dim, degeneracies=degeneracies, + underlying=underlying, + name=name, + latex_name=latex_name) + else: + return NonDegenerateSimplex(dim, name=name, + latex_name=latex_name) + + +######################################################################## +# The main classes for simplicial sets. + +class SimplicialSet_arbitrary(Parent): + r""" + A simplicial set. + + A simplicial set `X` is a collection of sets `X_n`, the + *n-simplices*, indexed by the non-negative integers, together with + maps + + .. MATH:: + + d_i: X_n \to X_{n-1}, \ \ 0 \leq i \leq n \ \ \text{(face maps)} \\ + s_j: X_n \to X_{n+1}, \ \ 0 \leq j \leq n \ \ \text{(degeneracy maps)} + + satisfying the *simplicial identities*: + + .. MATH:: + + d_i d_j &= d_{j-1} d_i \ \ \text{if } ij+1 \\ + s_i s_j &= s_{j+1} s_{i} \ \ \text{if } i simplex.dimension(): + raise ValueError('cannot compute face {} of {}-dimensional ' + 'simplex'.format(i, simplex.dimension())) + faces = self.faces(simplex) + if faces is not None: + return self.faces(simplex)[i] + return None + + def __contains__(self, x): + """ + Return ``True`` if ``x`` is a simplex which is contained in this complex. + + EXAMPLES:: + + sage: S0 = simplicial_sets.Sphere(0) + sage: S1 = simplicial_sets.Sphere(1) + sage: v0 = S0.n_cells(0)[0] + sage: v0 in S0 + True + sage: v0 in S1 + False + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: e = AbstractSimplex(1) + sage: K = SimplicialSet({e: (v, v)}) # the circle + sage: v in K + True + sage: v0 in K + False + sage: S1.n_cells(1)[0] in K + False + + TESTS: + + Make sure we answer gracefully for unexpected input:: + + sage: 248 in K + False + """ + try: + underlying = x.nondegenerate() + return underlying in self.n_cells(underlying.dimension()) + except AttributeError: + return False + + def alexander_whitney(self, simplex, dim_left): + r""" + Return the 'subdivision' of ``simplex`` in this simplicial set + into a pair of simplices. + + The left factor should have dimension ``dim_left``, so the + right factor should have dimension ``dim - dim_left``, if + ``dim`` is the dimension of the starting simplex. The results + are obtained by applying iterated face maps to + ``simplex``. Writing `d` for ``dim`` and `j` for ``dim_left``: + apply `d_{j+1} d_{j+2} ... d_{d}` to get the left factor, + `d_0 ... d_0` to get the right factor. + + INPUT: + + - ``dim_left`` -- integer, the dimension of the left-hand factor + + OUTPUT: a list containing the triple ``(c, left, right)``, + where ``left`` and ``right`` are the two simplices described + above. If either ``left`` or ``right`` is degenerate, ``c`` is + 0; otherwise, ``c`` is 1. This is so that, when used to + compute cup products, it is easy to ignore terms which have + degenerate factors. + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: sigma = S2.n_cells(2)[0] + sage: S2.alexander_whitney(sigma, 0) + [(1, v_0, sigma_2)] + sage: S2.alexander_whitney(sigma, 1) + [(0, s_0 v_0, s_0 v_0)] + """ + dim = simplex.dimension() + if dim_left < 0 or dim_left > dim: + raise ValueError('alexander_whitney is only valid if dim_left ' + 'is between 0 and the dimension of the simplex') + left = simplex + for i in range(dim, dim_left, -1): + left = self.face(left, i) + right = simplex + for i in range(dim_left): + right = self.face(right, 0) + if left.is_degenerate() or right.is_degenerate(): + c = ZZ.zero() + else: + c = ZZ.one() + return [(c, left, right)] + + def nondegenerate_simplices(self, max_dim=None): + """ + Return the sorted list of non-degenerate simplices in this simplicial set. + + INPUT: + + - ``max_dim`` -- optional, default ``None``. If specified, + return the non-degenerate simplices of this dimension or + smaller. This argument is required if this simplicial set is + infinite. + + The sorting is in increasing order of dimension, and within + each dimension, by the name (if present) of each simplex. + + .. NOTE:: + + The sorting is done when the simplicial set is + constructed, so changing the name of a simplex after + construction will not affect the ordering. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: S0 = SimplicialSet({v: None, w: None}) + sage: S0.nondegenerate_simplices() + [Delta^0, Delta^0] + + Name the vertices and reconstruct the simplicial set: they + should be ordered alphabetically:: + + sage: v.rename('v') + sage: w.rename('w') + sage: S0 = SimplicialSet({v: None, w: None}) + sage: S0.nondegenerate_simplices() + [v, w] + + Rename but do not reconstruct the set; the ordering does not + take the new names into account:: + + sage: v.rename('z') + sage: S0.nondegenerate_simplices() # old ordering is used + [z, w] + + sage: X0 = SimplicialSet({v: None, w: None}) + sage: X0.nondegenerate_simplices() # new ordering is used + [w, z] + + Test an infinite example:: + + sage: C3 = groups.misc.MultiplicativeAbelian([3]) + sage: BC3 = simplicial_sets.ClassifyingSpace(C3) + sage: BC3.nondegenerate_simplices(2) + [1, f, f^2, f * f, f * f^2, f^2 * f, f^2 * f^2] + sage: BC3.nondegenerate_simplices() + Traceback (most recent call last): + ... + NotImplementedError: this simplicial set may be infinite, so specify max_dim + """ + if self.is_finite(): + if max_dim is None: + return list(self._simplices) + return list(sigma for sigma in self._simplices if sigma.dimension() <= max_dim) + if max_dim is None: + raise NotImplementedError('this simplicial set may be ' + 'infinite, so specify max_dim') + return list(sigma for sigma in self.n_skeleton(max_dim)._simplices) + + def cells(self, subcomplex=None, max_dim=None): + """ + Return a dictionary of all non-degenerate simplices. + + INPUT: + + - ``subcomplex`` (optional) -- a subsimplicial set of this + simplicial set. If ``subcomplex`` is specified, then return the + simplices in the quotient by the subcomplex. + + - ``max_dim`` -- optional, default ``None``. If specified, + return the non-degenerate simplices of this dimension or + smaller. This argument is required if this simplicial set is + infinite. + + Each key is a dimension, and the corresponding value is the + list of simplices in that dimension. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: S0 = SimplicialSet({v: None, w: None}) + sage: S0.cells() + {0: [Delta^0, Delta^0]} + + sage: v.rename('v') + sage: w.rename('w') + sage: S0.cells() + {0: [v, w]} + + sage: e = AbstractSimplex(1, name='e') + sage: S1 = SimplicialSet({e: (v, v)}) + sage: S1.cells() + {0: [v], 1: [e]} + + sage: S0.cells(S0.subsimplicial_set([v, w])) + {0: [*]} + + sage: X = SimplicialSet({e: (v,w)}) + sage: X.cells(X.subsimplicial_set([v, w])) + {0: [*], 1: [e]} + + Test an infinite example:: + + sage: C3 = groups.misc.MultiplicativeAbelian([3]) + sage: BC3 = simplicial_sets.ClassifyingSpace(C3) + sage: BC3.cells(max_dim=2) + {0: [1], 1: [f, f^2], 2: [f * f, f * f^2, f^2 * f, f^2 * f^2]} + sage: BC3.cells() + Traceback (most recent call last): + ... + NotImplementedError: this simplicial set may be infinite, so specify max_dim + """ + if subcomplex is None: + if self.is_finite(): + simplices = {} + for sigma in self.nondegenerate_simplices(): + if sigma.dimension() in simplices: + simplices[sigma.dimension()].append(sigma) + else: + simplices[sigma.dimension()] = [sigma] + if max_dim is not None: + return {d: sorted(simplices[d]) for d in simplices + if d <= max_dim} + return {d: sorted(simplices[d]) for d in simplices} + # Infinite case: + if max_dim is None: + raise NotImplementedError('this simplicial set may be ' + 'infinite, so specify max_dim') + return self.n_skeleton(max_dim).cells() + # subcomplex is not None: + return self.quotient(subcomplex).cells(max_dim=max_dim) + + def n_cells(self, n, subcomplex=None): + """ + Return the list of cells of dimension ``n`` of this cell complex. + If the optional argument ``subcomplex`` is present, then + return the ``n``-dimensional faces in the quotient by this + subcomplex. + + INPUT: + + - ``n`` -- the dimension + + - ``subcomplex`` (optional, default ``None``) -- a subcomplex + of this cell complex. Return the cells which are in the + quotient by this subcomplex. + + EXAMPLES:: + + sage: simplicial_sets.Sphere(3).n_cells(3) + [sigma_3] + sage: simplicial_sets.Sphere(3).n_cells(2) + [] + sage: C2 = groups.misc.MultiplicativeAbelian([2]) + sage: BC2 = C2.nerve() + sage: BC2.n_cells(3) + [f * f * f] + """ + cells = self.cells(subcomplex=subcomplex, max_dim=n) + try: + return list(cells[n]) + except KeyError: + # Don't barf if someone asks for n_cells in a dimension + # where there are none. + return [] + + def _an_element_(self): + """ + Return an element: a vertex of this simplicial set. + + Return ``None`` if the simplicial set is empty. + + EXAMPLES:: + + sage: S4 = simplicial_sets.Sphere(4) + sage: S4._an_element_() + v_0 + sage: S4._an_element_() in S4 + True + sage: from sage.topology.simplicial_set_examples import Empty + sage: Empty()._an_element_() is None + True + """ + vertices = self.n_cells(0) + if vertices: + return vertices[0] + return None + + def all_n_simplices(self, n): + """ + Return a list of all simplices, non-degenerate and degenerate, in dimension ``n``. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: degen = v.apply_degeneracies(0) + sage: tau = AbstractSimplex(2, name='tau') + sage: Y = SimplicialSet({tau: (degen, degen, degen), w: None}) + + ``Y`` is the disjoint union of a 2-sphere, with vertex ``v`` + and non-degenerate 2-simplex ``tau``, and a point ``w``. :: + + sage: Y.all_n_simplices(0) + [v, w] + sage: Y.all_n_simplices(1) + [s_0 v, s_0 w] + sage: Y.all_n_simplices(2) + [tau, s_1 s_0 v, s_1 s_0 w] + + An example involving an infinite simplicial set:: + + sage: C3 = groups.misc.MultiplicativeAbelian([3]) + sage: BC3 = simplicial_sets.ClassifyingSpace(C3) + sage: BC3.all_n_simplices(2) + [f * f, + f * f^2, + f^2 * f, + f^2 * f^2, s_0 f, s_0 f^2, s_1 f, s_1 f^2, s_1 s_0 1] + """ + non_degen = [_ for _ in self.nondegenerate_simplices(max_dim=n)] + ans = set([_ for _ in non_degen if _.dimension() == n]) + for sigma in non_degen: + d = sigma.dimension() + ans.update([sigma.apply_degeneracies(*_) + for _ in all_degeneracies(d, n-d)]) + return sorted(ans) + + def _map_from_empty_set(self): + """ + Return the unique map from the empty set to this simplicial set. + + This is used to in the method :meth:`disjoint_union` to + construct disjoint unions as pushouts. + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: T._map_from_empty_set() + Simplicial set morphism: + From: Empty simplicial set + To: Torus + Defn: [] --> [] + """ + from sage.topology.simplicial_set_examples import Empty + return Empty().Hom(self)({}) + + def identity(self): + """ + Return the identity map on this simplicial set. + + EXAMPLES:: + + sage: S3 = simplicial_sets.Sphere(3) + sage: S3.identity() + Simplicial set endomorphism of S^3 + Defn: Identity map + + sage: BC3 = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([3])) + sage: one = BC3.identity() + sage: [(sigma, one(sigma)) for sigma in BC3.n_cells(2)] + [(f * f, f * f), + (f * f^2, f * f^2), + (f^2 * f, f^2 * f), + (f^2 * f^2, f^2 * f^2)] + """ + return self.Hom(self).identity() + + def constant_map(self, codomain=None, point=None): + """ + Return a constant map with this simplicial set as its domain. + + INPUT: + + - ``codomain`` -- optional, default ``None``. If ``None``, the + codomain is the standard one-point space constructed by + :func:`Point`. Otherwise, either the codomain must be a + pointed simplicial set, in which case the map is constant at + the base point, or ``point`` must be specified. + - ``point`` -- optional, default ``None``. If specified, it + must be a 0-simplex in the codomain, and it will be the + target of the constant map. + + EXAMPLES:: + + sage: S4 = simplicial_sets.Sphere(4) + sage: S4.constant_map() + Simplicial set morphism: + From: S^4 + To: Point + Defn: Constant map at * + sage: S0 = simplicial_sets.Sphere(0) + sage: S4.constant_map(codomain=S0) + Simplicial set morphism: + From: S^4 + To: S^0 + Defn: Constant map at v_0 + + sage: Sigma3 = groups.permutation.Symmetric(3) + sage: Sigma3.nerve().constant_map() + Simplicial set morphism: + From: Nerve of Symmetric group of order 3! as a permutation group + To: Point + Defn: Constant map at * + + TESTS:: + + sage: S0 = S0.unset_base_point() + sage: S4.constant_map(codomain=S0) + Traceback (most recent call last): + ... + ValueError: codomain is not pointed, so specify a target for the constant map + """ + from sage.topology.simplicial_set_examples import Point + if codomain is None: + codomain = Point() + return self.Hom(codomain).constant_map(point) + + def is_reduced(self): + """ + Return ``True`` if this simplicial set has only one vertex. + + EXAMPLES:: + + sage: simplicial_sets.Sphere(0).is_reduced() + False + sage: simplicial_sets.Sphere(3).is_reduced() + True + """ + return len(self.n_cells(0)) == 1 + + def graph(self): + """ + Return the 1-skeleton of this simplicial set, as a graph. + + EXAMPLES:: + + sage: Delta3 = simplicial_sets.Simplex(3) + sage: G = Delta3.graph() + sage: G.edges() + [((0,), (1,), (0, 1)), + ((0,), (2,), (0, 2)), + ((0,), (3,), (0, 3)), + ((1,), (2,), (1, 2)), + ((1,), (3,), (1, 3)), + ((2,), (3,), (2, 3))] + + sage: T = simplicial_sets.Torus() + sage: T.graph() + Looped multi-graph on 1 vertex + sage: len(T.graph().edges()) + 3 + + sage: CP3 = simplicial_sets.ComplexProjectiveSpace(3) + sage: G = CP3.graph() + sage: len(G.vertices()) + 1 + sage: len(G.edges()) + 0 + + sage: Sigma3 = groups.permutation.Symmetric(3) + sage: Sigma3.nerve().is_connected() + True + """ + skel = self.n_skeleton(1) + edges = skel.n_cells(1) + vertices = skel.n_cells(0) + used_vertices = set() # vertices which are in an edge + d = {} + for e in edges: + v = skel.face(e, 0) + w = skel.face(e, 1) + if v in d: + if w in d[v]: + d[v][w] = d[v][w] + [e] + else: + d[v][w] = [e] + else: + d[v] = {w: [e]} + used_vertices.update([v, w]) + for v in vertices: + if v not in used_vertices: + d[v] = {} + return Graph(d, format='dict_of_dicts') + + def is_connected(self): + """ + Return ``True`` if this simplicial set is connected. + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: K = simplicial_sets.KleinBottle() + sage: X = T.disjoint_union(K) + sage: T.is_connected() + True + sage: K.is_connected() + True + sage: X.is_connected() + False + sage: simplicial_sets.Sphere(0).is_connected() + False + """ + return self.graph().is_connected() + + def subsimplicial_set(self, simplices): + """ + Return the sub-simplicial set of this simplicial set + determined by ``simplices``, a set of nondegenerate simplices. + + INPUT: + + - ``simplices`` -- set, list, or tuple of nondegenerate + simplices in this simplicial set, or a simplicial + complex -- see below. + + Each sub-simplicial set comes equipped with an inclusion map + to its ambient space, and you can easily recover its ambient + space. + + If ``simplices`` is a simplicial complex, then the original + simplicial set should itself have been converted from a + simplicial complex, and ``simplices`` should be a subcomplex + of that. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: f = AbstractSimplex(1, name='f') + + sage: X = SimplicialSet({e: (v, w), f: (w, v)}) + sage: Y = X.subsimplicial_set([e]) + sage: Y + Simplicial set with 3 non-degenerate simplices + sage: Y.nondegenerate_simplices() + [v, w, e] + + sage: S3 = simplicial_complexes.Sphere(3) + sage: K = SimplicialSet(S3) + sage: tau = K.n_cells(3)[0] + sage: tau.dimension() + 3 + sage: K.subsimplicial_set([tau]) + Simplicial set with 15 non-degenerate simplices + + A subsimplicial set knows about its ambient space and the + inclusion map into it:: + + sage: RP4 = simplicial_sets.RealProjectiveSpace(4) + sage: M = RP4.n_skeleton(2) + sage: M + Simplicial set with 3 non-degenerate simplices + sage: M.ambient_space() + RP^4 + sage: M.inclusion_map() + Simplicial set morphism: + From: Simplicial set with 3 non-degenerate simplices + To: RP^4 + Defn: [1, f, f * f] --> [1, f, f * f] + + An infinite ambient simplicial set:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: BxB = B.product(B) + sage: BxB.n_cells(2)[5:] + [(s_0 f, s_1 f), (s_1 f, f * f), (s_1 f, s_0 f), (s_1 s_0 1, f * f)] + sage: BxB.subsimplicial_set(BxB.n_cells(2)[5:]) + Simplicial set with 8 non-degenerate simplices + + TESTS: + + Make sure vertices are treated properly:: + + sage: X.subsimplicial_set([v]).nondegenerate_simplices() + [v] + sage: X.subsimplicial_set([v, w]).nondegenerate_simplices() + [v, w] + sage: S0 = SimplicialSet({v: None, w: None}) + sage: S0.subsimplicial_set([w]).nondegenerate_simplices() + [w] + + Raise an error if an element of ``simplices`` is not actually + in the original simplicial set:: + + sage: sigma = AbstractSimplex(2, name='sigma_2') + sage: Z = X.subsimplicial_set([e, sigma]) + Traceback (most recent call last): + ... + ValueError: not all simplices are in the original simplicial set + + Simplicial complexes:: + + sage: X = simplicial_complexes.ComplexProjectivePlane() + sage: Y = X._contractible_subcomplex() + sage: CP2 = SimplicialSet(X) + sage: sub = CP2.subsimplicial_set(Y) + sage: CP2.f_vector() + [9, 36, 84, 90, 36] + sage: K = CP2.quotient(sub) + sage: K.f_vector() + [1, 0, 16, 30, 16] + sage: K.homology() + {0: 0, 1: 0, 2: Z, 3: 0, 4: Z} + + Try to construct a subcomplex from a simplicial complex which + is not actually contained in ``self``:: + + sage: Z = SimplicialComplex([[0,1,2,3,4]]) + sage: CP2.subsimplicial_set(Z) + Traceback (most recent call last): + ... + ValueError: not all simplices are in the original simplicial set + """ + # If simplices is a simplicial complex, turn it into a list of + # nondegenerate simplices. + from .simplicial_set_constructions import SubSimplicialSet + if isinstance(simplices, SimplicialComplex): + new = [] + for f in simplices.facets(): + d = f.dimension() + found = False + for x in self.n_cells(d): + if str(x) == str(tuple(sorted(tuple(f), key=str))): + new.append(x) + found = True + break + if not found: + raise ValueError('not all simplices are in the original simplicial set') + simplices = new + + if not self.is_finite(): + max_dim = max(sigma.dimension() for sigma in simplices) + data = self.n_skeleton(max_dim).face_data() + nondegenerate_simplices = self.nondegenerate_simplices(max_dim) + else: + data = self.face_data() + nondegenerate_simplices = self.nondegenerate_simplices() + vertices = set() + keep = set(simplices) + old_keep = set() + while keep != old_keep: + old_keep = copy.copy(keep) + for x in old_keep: + underlying = x.nondegenerate() + if underlying not in data.keys(): + raise ValueError('not all simplices are in the original simplicial set') + keep.add(underlying) + if underlying in data and data[underlying]: + keep.update([f.nondegenerate() for f in data[underlying]]) + else: + # x is a vertex + assert(underlying.dimension() == 0) + vertices.add(underlying) + missing = set(nondegenerate_simplices).difference(keep) + for x in missing: + if x in data: + del data[x] + for x in vertices: + data[x] = None + return SubSimplicialSet(data, self) + + def chain_complex(self, dimensions=None, base_ring=ZZ, augmented=False, + cochain=False, verbose=False, subcomplex=None, + check=False): + r""" + Return the normalized chain complex. + + INPUT: + + - ``dimensions`` -- if ``None``, compute the chain complex in all + dimensions. If a list or tuple of integers, compute the + chain complex in those dimensions, setting the chain groups + in all other dimensions to zero. + + - ``base_ring`` (optional, default ``ZZ``) -- commutative ring + + - ``augmented`` (optional, default ``False``) -- if ``True``, + return the augmented chain complex (that is, include a class + in dimension `-1` corresponding to the empty cell). + + - ``cochain`` (optional, default ``False``) -- if ``True``, + return the cochain complex (that is, the dual of the chain + complex). + + - ``verbose`` (optional, default ``False``) -- ignored. + + - ``subcomplex`` (optional, default ``None``) -- if present, + compute the chain complex relative to this subcomplex. + + - ``check`` (optional, default ``False``) -- If ``True``, make + sure that the chain complex is actually a chain complex: + the differentials are composable and their product is zero. + + .. NOTE:: + + If this simplicial set is not finite, you must specify + dimensions in which to compute its chain complex via the + argument ``dimensions``. + + EXAMPLES:: + + sage: simplicial_sets.Sphere(5).chain_complex() + Chain complex with at most 3 nonzero terms over Integer Ring + + sage: C3 = groups.misc.MultiplicativeAbelian([3]) + sage: BC3 = simplicial_sets.ClassifyingSpace(C3) + sage: BC3.chain_complex(range(4), base_ring=GF(3)) + Chain complex with at most 4 nonzero terms over Finite Field of size 3 + + TESTS:: + + sage: BC3.chain_complex() + Traceback (most recent call last): + ... + NotImplementedError: this simplicial set may be infinite, so specify dimensions when computing its chain complex + """ + kwds = {'base_ring': base_ring, 'augmented': augmented, 'cochain': cochain, + 'verbose': verbose, 'subcomplex': subcomplex, 'check': check} + if not self.is_finite(): + if dimensions is None: + raise NotImplementedError('this simplicial set may be infinite, ' + 'so specify dimensions when computing ' + 'its chain complex') + else: + max_dim = max(dimensions) + return SimplicialSet_finite.chain_complex(self.n_skeleton(max_dim+1), + dimensions=dimensions, + **kwds) + return SimplicialSet_finite.chain_complex(self, dimensions=dimensions, + **kwds) + + def homology(self, dim=None, **kwds): + r""" + Return the (reduced) homology of this simplicial set. + + INPUT: + + - ``dim`` (optional, default ``None`` -- If ``None``, then + return the homology in every dimension. If ``dim`` is an + integer or list, return the homology in the given + dimensions. (Actually, if ``dim`` is a list, return the + homology in the range from ``min(dim)`` to ``max(dim)``.) + + - ``base_ring`` (optional, default ``ZZ``) -- commutative + ring, must be ``ZZ`` or a field. + + Other arguments are also allowed: see the documentation for + :meth:`.cell_complex.GenericCellComplex.homology`. + + .. NOTE:: + + If this simplicial set is not finite, you must specify + dimensions in which to compute homology via the argument + ``dim``. + + EXAMPLES:: + + sage: simplicial_sets.Sphere(5).homology() + {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: Z} + + sage: C3 = groups.misc.MultiplicativeAbelian([3]) + sage: BC3 = simplicial_sets.ClassifyingSpace(C3) + sage: BC3.homology(range(4), base_ring=GF(3)) + {0: Vector space of dimension 0 over Finite Field of size 3, + 1: Vector space of dimension 1 over Finite Field of size 3, + 2: Vector space of dimension 1 over Finite Field of size 3, + 3: Vector space of dimension 1 over Finite Field of size 3} + + sage: BC2 = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: BK = BC2.product(BC2) + sage: BK.homology(range(4)) + {0: 0, 1: C2 x C2, 2: C2, 3: C2 x C2 x C2} + + TESTS:: + + sage: S3 = simplicial_sets.Sphere(3) + sage: S3.homology(0) + 0 + sage: S3.homology((0,)) + {0: 0} + sage: S3.homology(0, reduced=False) + Z + + sage: BC3.homology() + Traceback (most recent call last): + ... + NotImplementedError: this simplicial set may be infinite, so specify dimensions when computing homology + """ + if not self.is_finite(): + if dim is None: + raise NotImplementedError('this simplicial set may be infinite, so ' + 'specify dimensions when computing homology') + else: + if isinstance(dim, (list, tuple, range)): + dim = list(dim) + max_dim = max(dim) + space = self.n_skeleton(max_dim+1) + min_dim = min(dim) + H = GenericCellComplex.homology(space, **kwds) + return {n: H[n] for n in H if n<=max_dim and n >= min_dim} + else: + max_dim = dim + space = self.n_skeleton(max_dim+1) + else: + space = self + return GenericCellComplex.homology(space, dim=dim, **kwds) + + def cohomology(self, dim=None, **kwds): + r""" + Return the cohomology of this simplicial set. + + INPUT: + + - ``dim`` (optional, default ``None`` -- If ``None``, then + return the homology in every dimension. If ``dim`` is an + integer or list, return the homology in the given + dimensions. (Actually, if ``dim`` is a list, return the + homology in the range from ``min(dim)`` to ``max(dim)``.) + + - ``base_ring`` (optional, default ``ZZ``) -- commutative + ring, must be ``ZZ`` or a field. + + Other arguments are also allowed, the same as for the + :meth:`homology` method -- see + :meth:`.cell_complex.GenericCellComplex.homology` for complete + documentation -- except that :meth:`homology` accepts a + ``cohomology`` key word, while this function does not: + ``cohomology`` is automatically true here. Indeed, this + function just calls :meth:`homology` with argument + ``cohomology=True``. + + .. NOTE:: + + If this simplicial set is not finite, you must specify + dimensions in which to compute homology via the argument + ``dim``. + + EXAMPLES:: + + sage: simplicial_sets.KleinBottle().homology(1) + Z x C2 + sage: simplicial_sets.KleinBottle().cohomology(1) + Z + sage: simplicial_sets.KleinBottle().cohomology(2) + C2 + + TESTS:: + + sage: C3 = groups.misc.MultiplicativeAbelian([3]) + sage: BC3 = simplicial_sets.ClassifyingSpace(C3) + sage: BC3.cohomology() + Traceback (most recent call last): + ... + NotImplementedError: this simplicial set may be infinite, so specify dimensions when computing homology + """ + return self.homology(dim=dim, cohomology=True, **kwds) + + def betti(self, dim=None, subcomplex=None): + r""" + The Betti numbers of this simplicial complex as a dictionary + (or a single Betti number, if only one dimension is given): + the ith Betti number is the rank of the ith homology group. + + INPUT: + + - ``dim`` (optional, default ``None`` -- If ``None``, then + return the homology in every dimension. If ``dim`` is an + integer or list, return the homology in the given + dimensions. (Actually, if ``dim`` is a list, return the + homology in the range from ``min(dim)`` to ``max(dim)``.) + + - ``subcomplex`` (optional, default ``None``) -- a subcomplex + of this cell complex. Compute the Betti numbers of the + homology relative to this subcomplex. + + .. NOTE:: + + If this simplicial set is not finite, you must specify + dimensions in which to compute Betti numbers via the + argument ``dim``. + + EXAMPLES: + + Build the two-sphere as a three-fold join of a + two-point space with itself:: + + sage: simplicial_sets.Sphere(5).betti() + {0: 1, 1: 0, 2: 0, 3: 0, 4: 0, 5: 1} + + sage: C3 = groups.misc.MultiplicativeAbelian([3]) + sage: BC3 = simplicial_sets.ClassifyingSpace(C3) + sage: BC3.betti(range(4)) + {0: 1, 1: 0, 2: 0, 3: 0} + """ + dict = {} + H = self.homology(dim, base_ring=QQ, subcomplex=subcomplex) + try: + for n in H.keys(): + dict[n] = H[n].dimension() + if n == 0: + dict[n] += 1 + return dict + except AttributeError: + return H.dimension() + + def n_chains(self, n, base_ring=ZZ, cochains=False): + r""" + Return the free module of (normalized) chains in degree ``n`` + over ``base_ring``. + + This is the free module on the nondegenerate simplices in the + given dimension. + + INPUT: + + - ``n`` -- integer + - ``base_ring`` -- ring (optional, default `\ZZ`) + - ``cochains`` -- boolean (optional, default ``False``); if + ``True``, return cochains instead + + The only difference between chains and cochains is notation: + the generator corresponding to the dual of a simplex + ``sigma`` is written as ``"\chi_sigma"`` in the group of + cochains. + + EXAMPLES:: + + sage: S3 = simplicial_sets.Sphere(3) + sage: C = S3.n_chains(3, cochains=True) + sage: list(C.basis()) + [\chi_sigma_3] + sage: Sigma3 = groups.permutation.Symmetric(3) + sage: BSigma3 = simplicial_sets.ClassifyingSpace(Sigma3) + sage: list(BSigma3.n_chains(1).basis()) + [(1,2), (1,2,3), (1,3), (1,3,2), (2,3)] + sage: list(BSigma3.n_chains(1, cochains=True).basis()) + [\chi_(1,2), \chi_(1,2,3), \chi_(1,3), \chi_(1,3,2), \chi_(2,3)] + """ + if self.is_finite(): + return GenericCellComplex.n_chains(self, n=n, + base_ring=base_ring, + cochains=cochains) + n_cells = tuple(self.n_cells(n)) + if cochains: + return Cochains(self, n, n_cells, base_ring) + else: + return Chains(self, n, n_cells, base_ring) + + def quotient(self, subcomplex, vertex_name='*'): + """ + Return the quotient of this simplicial set by ``subcomplex``. + + That is, ``subcomplex`` is replaced by a vertex. + + INPUT: + + - ``subcomplex`` -- subsimplicial set of this simplicial set, + or a list, tuple, or set of simplices defining a + subsimplicial set. + + - ``vertex_name`` (optional) -- string, name to be given to the new + vertex. By default, use ``'*'``. + + In Sage, from a quotient simplicial set, you can recover the + ambient space, the subcomplex, and (if the ambient space is + finite) the quotient map. + + Base points: if the original simplicial set has a base point + not contained in ``subcomplex`` and if the original simplicial + set is finite, then use its image as the base point for the + quotient. In all other cases, ``*`` is the base point. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: f = AbstractSimplex(1, name='f') + sage: X = SimplicialSet({e: (v, w), f: (v, w)}) + sage: Y = X.quotient([f]) + sage: Y.nondegenerate_simplices() + [*, e] + sage: Y.homology(1) + Z + + sage: E = SimplicialSet({e: (v, w)}) + sage: Z = E.quotient([v, w]) + sage: Z.nondegenerate_simplices() + [*, e] + sage: Z.homology(1) + Z + + sage: F = E.quotient([v]) + sage: F.nondegenerate_simplices() + [*, w, e] + sage: F.base_point() + * + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP2 = RP5.n_skeleton(2) + sage: RP5_2 = RP5.quotient(RP2) + sage: RP5_2.homology(base_ring=GF(2)) + {0: Vector space of dimension 0 over Finite Field of size 2, + 1: Vector space of dimension 0 over Finite Field of size 2, + 2: Vector space of dimension 0 over Finite Field of size 2, + 3: Vector space of dimension 1 over Finite Field of size 2, + 4: Vector space of dimension 1 over Finite Field of size 2, + 5: Vector space of dimension 1 over Finite Field of size 2} + + sage: RP5_2.ambient() + RP^5 + sage: RP5_2.subcomplex() + Simplicial set with 3 non-degenerate simplices + sage: RP5_2.quotient_map() + Simplicial set morphism: + From: RP^5 + To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) + Defn: [1, f, f * f, f * f * f, f * f * f * f, f * f * f * f * f] --> [*, s_0 *, s_1 s_0 *, f * f * f, f * f * f * f, f * f * f * f * f] + + Behavior of base points:: + + sage: K = simplicial_sets.Simplex(3) + sage: K.is_pointed() + False + sage: L = K.subsimplicial_set([K.n_cells(1)[-1]]) + sage: L.nondegenerate_simplices() + [(2,), (3,), (2, 3)] + sage: K.quotient([K.n_cells(1)[-1]]).base_point() + * + + sage: K = K.set_base_point(K.n_cells(0)[0]) + sage: K.base_point() + (0,) + sage: L = K.subsimplicial_set([K.n_cells(1)[-1]]) + sage: L.nondegenerate_simplices() + [(2,), (3,), (2, 3)] + sage: K.quotient(L).base_point() + (0,) + + TESTS:: + + sage: pt = RP5.quotient(RP5.n_skeleton(5)) + sage: pt + Quotient: (RP^5/RP^5) + sage: len(pt.nondegenerate_simplices()) + 1 + """ + from .simplicial_set_constructions import SubSimplicialSet + from .simplicial_set_constructions import QuotientOfSimplicialSet, \ + QuotientOfSimplicialSet_finite + if not isinstance(subcomplex, SimplicialSet_finite): + # If it's not a simplicial set, subcomplex should be a + # list, tuple, or set of simplices, so form the actual + # subcomplex: + subcomplex = self.subsimplicial_set(subcomplex) + else: + # Test whether subcomplex is actually a subcomplex of + # self. + if (not isinstance(subcomplex, SubSimplicialSet) + and subcomplex.ambient_space() == self): + raise ValueError('the "subcomplex" is not actually a subcomplex') + if self.is_finite(): + return QuotientOfSimplicialSet_finite(subcomplex.inclusion_map(), + vertex_name=vertex_name) + else: + return QuotientOfSimplicialSet(subcomplex.inclusion_map(), + vertex_name=vertex_name) + + def disjoint_union(self, *others): + """ + Return the disjoint union of this simplicial set with ``others``. + + INPUT: + + - ``others`` -- one or several simplicial sets + + As long as the factors are all finite, the inclusion map from + each factor is available. Any factors which are empty are + ignored completely: they do not appear in the list of factors, + etc. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: f = AbstractSimplex(1, name='f') + sage: X = SimplicialSet({e: (v, v)}) + sage: Y = SimplicialSet({f: (v, w)}) + sage: Z = X.disjoint_union(Y) + + Since ``X`` and ``Y`` have simplices in common, Sage uses a + copy of ``Y`` when constructing the disjoint union. Note the + name conflict in the list of simplices: ``v`` appears twice:: + + sage: Z = X.disjoint_union(Y) + sage: Z.nondegenerate_simplices() + [v, v, w, e, f] + + Factors and inclusion maps:: + + sage: T = simplicial_sets.Torus() + sage: S2 = simplicial_sets.Sphere(2) + sage: A = T.disjoint_union(S2) + sage: A.factors() + (Torus, S^2) + sage: i = A.inclusion_map(0) + sage: i.domain() + Torus + sage: i.codomain() + Disjoint union: (Torus u S^2) + + Empty factors are ignored:: + + sage: from sage.topology.simplicial_set_examples import Empty + sage: E = Empty() + sage: K = S2.disjoint_union(S2, E, E, S2) + sage: K == S2.disjoint_union(S2, S2) + True + sage: K.factors() + (S^2, S^2, S^2) + """ + from .simplicial_set_constructions import DisjointUnionOfSimplicialSets, \ + DisjointUnionOfSimplicialSets_finite + if all(space.is_finite() for space in [self] + list(others)): + return DisjointUnionOfSimplicialSets_finite((self,) + others) + else: + return DisjointUnionOfSimplicialSets((self,) + others) + + def coproduct(self, *others): + """ + Return the coproduct of this simplicial set with ``others``. + + INPUT: + + - ``others`` -- one or several simplicial sets + + If these simplicial sets are pointed, return their wedge sum; + if they are not, return their disjoint union. If some are + pointed and some are not, raise an error: it is not clear in + which category to work. + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: K = simplicial_sets.KleinBottle() + sage: D3 = simplicial_sets.Simplex(3) + sage: Y = S2.unset_base_point() + sage: Z = K.unset_base_point() + + sage: S2.coproduct(K).is_pointed() + True + sage: S2.coproduct(K) + Wedge: (S^2 v Klein bottle) + sage: D3.coproduct(Y, Z).is_pointed() + False + sage: D3.coproduct(Y, Z) + Disjoint union: (3-simplex u Simplicial set with 2 non-degenerate simplices u Simplicial set with 6 non-degenerate simplices) + + The coproduct comes equipped with an inclusion map from each + summand, as long as the summands are all finite:: + + sage: S2.coproduct(K).inclusion_map(0) + Simplicial set morphism: + From: S^2 + To: Wedge: (S^2 v Klein bottle) + Defn: [v_0, sigma_2] --> [*, sigma_2] + sage: D3.coproduct(Y, Z).inclusion_map(2) + Simplicial set morphism: + From: Simplicial set with 6 non-degenerate simplices + To: Disjoint union: (3-simplex u Simplicial set with 2 non-degenerate simplices u Simplicial set with 6 non-degenerate simplices) + Defn: [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] --> [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] + + TESTS:: + + sage: D3.coproduct(S2, Z) + Traceback (most recent call last): + ... + ValueError: some, but not all, of the simplicial sets are pointed, so the categorical coproduct is not defined: the category is ambiguous + """ + if self.is_pointed() and all(X.is_pointed() for X in others): + return self.wedge(*others) + if self.is_pointed() or any(X.is_pointed() for X in others): + raise ValueError('some, but not all, of the simplicial sets are pointed, ' + 'so the categorical coproduct is not defined: the ' + 'category is ambiguous') + return self.disjoint_union(*others) + + def product(self, *others): + r""" + Return the product of this simplicial set with ``others``. + + INPUT: + + - ``others`` -- one or several simplicial sets + + If `X` and `Y` are simplicial sets, then their product `X + \times Y` is defined to be the simplicial set with + `n`-simplices `X_n \times Y_n`. See + :class:`.simplicial_set_constructions.ProductOfSimplicialSets` + for more information. + + If a simplicial set is constructed as a product, the factors + are recorded and are accessible via the method + :meth:`.simplicial_set_constructions.Factors.factors`. + If each factor is finite, then you can also construct the + projection maps onto each factor, the wedge as a subcomplex, + and the fat wedge as a subcomplex. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: X = SimplicialSet({e: (v, w)}) + sage: square = X.product(X) + + ``square`` is now the standard triangulation of the square: 4 + vertices, 5 edges (the four on the border and the diagonal), 2 + triangles:: + + sage: square.f_vector() + [4, 5, 2] + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = S1.product(S1) + sage: T.homology(reduced=False) + {0: Z, 1: Z x Z, 2: Z} + + Since ``S1`` is pointed, so is ``T``:: + + sage: S1.is_pointed() + True + sage: S1.base_point() + v_0 + sage: T.is_pointed() + True + sage: T.base_point() + (v_0, v_0) + + sage: S2 = simplicial_sets.Sphere(2) + sage: S3 = simplicial_sets.Sphere(3) + sage: S2xS3 = S2.product(S3) + sage: S2xS3.homology(reduced=False) + {0: Z, 1: 0, 2: Z, 3: Z, 4: 0, 5: Z} + + sage: S2xS3.factors() == (S2, S3) + True + sage: S2xS3.factors() == (S3, S2) + False + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: B.rename('RP^oo') + sage: X = B.product(B, S2) + sage: X + RP^oo x RP^oo x S^2 + sage: X.factor(1) + RP^oo + sage: X.factors() + (RP^oo, RP^oo, S^2) + + Projection maps and wedges:: + + sage: S2xS3.projection_map(0) + Simplicial set morphism: + From: S^2 x S^3 + To: S^2 + Defn: ... + sage: S2xS3.wedge_as_subset().homology() + {0: 0, 1: 0, 2: Z, 3: Z} + + In the case of pointed simplicial sets, there is an inclusion + of each factor into the product. These are not automatically + defined in Sage, but they are easy to construct using identity + maps and constant maps and the universal property of the + product:: + + sage: one = S2.identity() + sage: const = S2.constant_map(codomain=S3) + sage: S2xS3.universal_property(one, const) + Simplicial set morphism: + From: S^2 + To: S^2 x S^3 + Defn: [v_0, sigma_2] --> [(v_0, v_0), (sigma_2, s_1 s_0 v_0)] + """ + from .simplicial_set_constructions import ProductOfSimplicialSets, \ + ProductOfSimplicialSets_finite + if self.is_finite() and all(X.is_finite() for X in others): + return ProductOfSimplicialSets_finite((self,) + others) + else: + return ProductOfSimplicialSets((self,) + others) + + cartesian_product = product + + def pushout(self, *maps): + r""" + Return the pushout obtained from given ``maps``. + + INPUT: + + - ``maps`` -- several maps of simplicial sets, each of which + has this simplicial set as its domain + + If only a single map `f: X \to Y` is given, then return + `Y`. If more than one map is given, say `f_i: X \to Y_i` for + `0 \leq i \leq m`, then return the pushout defined by those + maps. If no maps are given, return the empty simplicial set. + + In addition to the defining maps `f_i` used to construct the + pushout `P`, there are also maps `\bar{f}_i: Y_i \to P`, which + we refer to as *structure maps*. The pushout also has a + universal property: given maps `g_i: Y_i \to Z` such that `g_i + f_i = g_j f_j` for all `i`, `j`, then there is a unique map + `g: P \to Z` making the appropriate diagram commute: that is, + `g \bar{f}_i = g_i` for all `i`. + + In Sage, a pushout is equipped with its defining maps, and as + long as the simplicial sets involved are finite, you can also + access the structure maps and the universal property. + + EXAMPLES: + + Construct the 4-sphere as a quotient of a 4-simplex:: + + sage: K = simplicial_sets.Simplex(4) + sage: L = K.n_skeleton(3) + sage: S4 = L.pushout(L.constant_map(), L.inclusion_map()) + sage: S4 + Pushout of maps: + Simplicial set morphism: + From: Simplicial set with 30 non-degenerate simplices + To: Point + Defn: Constant map at * + Simplicial set morphism: + From: Simplicial set with 30 non-degenerate simplices + To: 4-simplex + Defn: [(0,), (1,), (2,), (3,), (4,), (0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 2, 3), (0, 2, 4), (0, 3, 4), (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4), (0, 1, 2, 3), (0, 1, 2, 4), (0, 1, 3, 4), (0, 2, 3, 4), (1, 2, 3, 4)] --> [(0,), (1,), (2,), (3,), (4,), (0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4), (0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 2, 3), (0, 2, 4), (0, 3, 4), (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4), (0, 1, 2, 3), (0, 1, 2, 4), (0, 1, 3, 4), (0, 2, 3, 4), (1, 2, 3, 4)] + sage: len(S4.nondegenerate_simplices()) + 2 + sage: S4.homology(4) + Z + + The associated maps:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = S1.product(S1) + sage: K = T.factor(0, as_subset=True) + sage: W = S1.wedge(T) # wedge, constructed as a pushout + sage: W.defining_map(1) + Simplicial set morphism: + From: Point + To: S^1 x S^1 + Defn: Constant map at (v_0, v_0) + sage: W.structure_map(0) + Simplicial set morphism: + From: S^1 + To: Wedge: (S^1 v S^1 x S^1) + Defn: [v_0, sigma_1] --> [*, sigma_1] + + sage: f = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) + + The maps `f: S^1 \to T` and `1: T \to T` induce a map `S^1 \vee T \to T`:: + + sage: g = W.universal_property(f, Hom(T,T).identity()) + sage: g.domain() == W + True + sage: g.codomain() == T + True + + TESTS:: + + sage: K = simplicial_sets.Simplex(5) + sage: K.pushout() + Empty simplicial set + + sage: S0 = simplicial_sets.Sphere(0) + sage: pt_map = S0.base_point_map() + sage: pt_map.domain().pushout(pt_map) == S0 + True + + sage: K.pushout(K.constant_map(), pt_map) + Traceback (most recent call last): + ... + ValueError: the domains of the maps must be equal + """ + from .simplicial_set_constructions import PushoutOfSimplicialSets, \ + PushoutOfSimplicialSets_finite + if any(self != f.domain() for f in maps): + raise ValueError('the domains of the maps must be equal') + if not maps: + return PushoutOfSimplicialSets_finite() + if all(f.codomain().is_finite() for f in maps): + return PushoutOfSimplicialSets_finite(maps) + else: + return PushoutOfSimplicialSets(maps) + + def pullback(self, *maps): + r""" + Return the pullback obtained from given ``maps``. + + INPUT: + + - ``maps`` -- several maps of simplicial sets, each of which + has this simplicial set as its codomain + + If only a single map `f: X \to Y` is given, then return + `X`. If more than one map is given, say `f_i: X_i \to Y` for + `0 \leq i \leq m`, then return the pullback defined by those + maps. If no maps are given, return the one-point simplicial + set. + + In addition to the defining maps `f_i` used to construct the + pullback `P`, there are also maps `\bar{f}_i: P \to X_i`, + which we refer to as *structure maps* or *projection + maps*. The pullback also has a universal property: given maps + `g_i: Z \to X_i` such that `f_i g_i = f_j g_j` for all `i`, + `j`, then there is a unique map `g: Z \to P` making the + appropriate diagram commute: that is, `\bar{f}_i g = g_i` for + all `i`. For example, given maps `f: X \to Y` and `g: X \to + Z`, there is an induced map `g: X \to Y \times Z`. + + In Sage, a pullback is equipped with its defining maps, and as + long as the simplicial sets involved are finite, you can also + access the structure maps and the universal property. + + EXAMPLES: + + Construct a product as a pullback:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: pt = simplicial_sets.Point() + sage: P = pt.pullback(S2.constant_map(), S2.constant_map()) + sage: P.homology(2) + Z x Z + + If the pullback is defined via maps `f_i: X_i \to Y`, then + there are structure maps `\bar{f}_i: Y_i \to P`. The structure + maps are only available in Sage when all of the maps involved + have finite domains. :: + + sage: S2 = simplicial_sets.Sphere(2) + sage: one = S2.Hom(S2).identity() + sage: P = S2.pullback(one, one) + sage: P.homology() + {0: 0, 1: 0, 2: Z} + + sage: P.defining_map(0) == one + True + sage: P.structure_map(1) + Simplicial set morphism: + From: Pullback of maps: + Simplicial set endomorphism of S^2 + Defn: Identity map + Simplicial set endomorphism of S^2 + Defn: Identity map + To: S^2 + Defn: [(v_0, v_0), (sigma_2, sigma_2)] --> [v_0, sigma_2] + sage: P.structure_map(0).domain() == P + True + sage: P.structure_map(0).codomain() == S2 + True + + The universal property:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = S1.product(S1) + sage: K = T.factor(0, as_subset=True) + sage: f = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) + sage: D = S1.cone() # the cone C(S^1) + sage: g = D.map_from_base() # map from S^1 to C(S^1) + sage: P = T.product(D) + sage: h = P.universal_property(f, g) + sage: h.domain() == S1 + True + sage: h.codomain() == P + True + + TESTS:: + + sage: pt.pullback(S2.constant_map(), S2.base_point_map()) + Traceback (most recent call last): + ... + ValueError: the codomains of the maps must be equal + """ + from .simplicial_set_constructions import PullbackOfSimplicialSets, \ + PullbackOfSimplicialSets_finite + if any(self != f.codomain() for f in maps): + raise ValueError('the codomains of the maps must be equal') + if not maps: + return PullbackOfSimplicialSets_finite() + if self.is_finite() and all(f.domain().is_finite() for f in maps): + return PullbackOfSimplicialSets_finite(maps) + else: + return PullbackOfSimplicialSets(maps) + + # Ideally, this would be defined at the category level and only + # for pointed simplicial sets, but the abstract_method "wedge" in + # cell_complex.py would shadow that. + def wedge(self, *others): + r""" + Return the wedge sum of this pointed simplicial set with ``others``. + + - ``others`` -- one or several simplicial sets + + This constructs the quotient of the disjoint union in which + the base points of all of the simplicial sets have been + identified. This is the coproduct in the category of pointed + simplicial sets. + + This raises an error if any of the factors is not pointed. + + From the wedge, you can access the factors, and if the + simplicial sets involved are all finite, you can also access + the inclusion map of each factor into the wedge, as well as + the projection map onto each factor. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(1, name='e') + sage: w = AbstractSimplex(0, name='w') + sage: f = AbstractSimplex(1, name='f') + sage: X = SimplicialSet({e: (v, v)}, base_point=v) + sage: Y = SimplicialSet({f: (w, w)}, base_point=w) + sage: W = X.wedge(Y) + sage: W.nondegenerate_simplices() + [*, e, f] + sage: W.homology() + {0: 0, 1: Z x Z} + sage: S2 = simplicial_sets.Sphere(2) + sage: X.wedge(S2).homology(reduced=False) + {0: Z, 1: Z, 2: Z} + sage: X.wedge(X).nondegenerate_simplices() + [*, e, e] + + sage: S3 = simplicial_sets.Sphere(3) + sage: W = S2.wedge(S3, S2) + sage: W.inclusion_map(2) + Simplicial set morphism: + From: S^2 + To: Wedge: (S^2 v S^3 v S^2) + Defn: [v_0, sigma_2] --> [*, sigma_2] + sage: W.projection_map(1) + Simplicial set morphism: + From: Wedge: (S^2 v S^3 v S^2) + To: Quotient: (Wedge: (S^2 v S^3 v S^2)/Simplicial set with 3 non-degenerate simplices) + Defn: [*, sigma_2, sigma_2, sigma_3] --> [*, s_1 s_0 *, s_1 s_0 *, sigma_3] + + Note that the codomain of the projection map is not identical + to the original ``S2``, but is instead a quotient of the wedge + which is isomorphic to ``S2``:: + + sage: S2.f_vector() + [1, 0, 1] + sage: W.projection_map(2).codomain().f_vector() + [1, 0, 1] + sage: (W.projection_map(2) * W.inclusion_map(2)).is_bijective() + True + + TESTS:: + + sage: Z = SimplicialSet({e: (v,w)}) + sage: X.wedge(Z) + Traceback (most recent call last): + ... + ValueError: the simplicial sets must be pointed + """ + from .simplicial_set_constructions import WedgeOfSimplicialSets, \ + WedgeOfSimplicialSets_finite + if all(space.is_finite() for space in [self] + list(others)): + return WedgeOfSimplicialSets_finite((self,) + others) + else: + return WedgeOfSimplicialSets((self,) + others) + + def cone(self): + r""" + Return the (reduced) cone on this simplicial set. + + If this simplicial set `X` is not pointed, construct the + ordinary cone: add a point `v` (which will become the base + point) and for each simplex `\sigma` in `X`, add both `\sigma` + and a simplex made up of `v` and `\sigma` (topologically, form + the join of `v` and `\sigma`). + + If this simplicial set is pointed, then construct the reduced + cone: take the quotient of the unreduced cone by the 1-simplex + connecting the old base point to the new one. + + In either case, as long as the simplicial set is finite, it + comes equipped in Sage with a map from it into the cone. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(1, name='e') + sage: X = SimplicialSet({e: (v, v)}) + sage: CX = X.cone() # unreduced cone, since X not pointed + sage: CX.nondegenerate_simplices() + [*, v, (v,*), e, (e,*)] + sage: CX.base_point() + * + + `X` as a subset of the cone, and also the map from `X`, in the + unreduced case:: + + sage: CX.base_as_subset() + Simplicial set with 2 non-degenerate simplices + sage: CX.map_from_base() + Simplicial set morphism: + From: Simplicial set with 2 non-degenerate simplices + To: Cone of Simplicial set with 2 non-degenerate simplices + Defn: [v, e] --> [v, e] + + In the reduced case, only the map from `X` is available:: + + sage: X = X.set_base_point(v) + sage: CX = X.cone() # reduced cone + sage: CX.nondegenerate_simplices() + [*, e, (e,*)] + sage: CX.map_from_base() + Simplicial set morphism: + From: Simplicial set with 2 non-degenerate simplices + To: Reduced cone of Simplicial set with 2 non-degenerate simplices + Defn: [v, e] --> [*, e] + """ + from .simplicial_set_constructions import \ + ConeOfSimplicialSet, ConeOfSimplicialSet_finite, \ + ReducedConeOfSimplicialSet, ReducedConeOfSimplicialSet_finite + if self.is_pointed(): + if self.is_finite(): + return ReducedConeOfSimplicialSet_finite(self) + else: + return ReducedConeOfSimplicialSet(self) + if self.is_finite(): + return ConeOfSimplicialSet_finite(self) + else: + return ConeOfSimplicialSet(self) + + def suspension(self, n=1): + """ + Return the (reduced) `n`-th suspension of this simplicial set. + + INPUT: + + - ``n`` (optional, default 1) -- integer, suspend this many + times. + + If this simplicial set `X` is not pointed, return the + suspension: the quotient `CX/X`, where `CX` is the (ordinary, + unreduced) cone on `X`. If `X` is pointed, then use the + reduced cone instead, and so return the reduced suspension. + + EXAMPLES:: + + sage: RP4 = simplicial_sets.RealProjectiveSpace(4) + sage: S1 = simplicial_sets.Sphere(1) + sage: SigmaRP4 = RP4.suspension() + sage: S1_smash_RP4 = S1.smash_product(RP4) + sage: SigmaRP4.homology() == S1_smash_RP4.homology() + True + + The version of the suspension obtained by the smash product is + typically less efficient than the reduced suspension produced + here:: + + sage: SigmaRP4.f_vector() + [1, 0, 1, 1, 1, 1] + sage: S1_smash_RP4.f_vector() + [1, 1, 4, 6, 8, 5] + + TESTS:: + + sage: RP4.suspension(-3) + Traceback (most recent call last): + ... + ValueError: n must be non-negative + """ + from .simplicial_set_constructions import \ + SuspensionOfSimplicialSet, SuspensionOfSimplicialSet_finite + if n < 0: + raise ValueError('n must be non-negative') + if n == 0: + return self + if self.is_finite(): + Sigma = SuspensionOfSimplicialSet_finite(self) + else: + Sigma = SuspensionOfSimplicialSet(self) + if n == 1: + return Sigma + return Sigma.suspension(n-1) + + def join(self, *others): + """ + The join of this simplicial set with ``others``. + + Not implemented. See + https://ncatlab.org/nlab/show/join+of+simplicial+sets for a + few descriptions, for anyone interested in implementing + this. See also P. J. Ehlers and Tim Porter, Joins for + (Augmented) Simplicial Sets, Jour. Pure Applied Algebra, 145 + (2000) 37-44 :arxiv:`9904039`. + + - ``others`` -- one or several simplicial sets + + EXAMPLES:: + + sage: K = simplicial_sets.Simplex(2) + sage: K.join(K) + Traceback (most recent call last): + ... + NotImplementedError: joins are not implemented for simplicial sets + """ + raise NotImplementedError('joins are not implemented for simplicial sets') + + def reduce(self): + """ + Reduce this simplicial set. + + That is, take the quotient by a spanning tree of the + 1-skeleton, so that the resulting simplicial set has only one + vertex. This only makes sense if the simplicial set is + connected, so raise an error if not. If already reduced, + return itself. + + EXAMPLES:: + + sage: K = simplicial_sets.Simplex(2) + sage: K.is_reduced() + False + sage: X = K.reduce() + sage: X.is_reduced() + True + + ``X`` is reduced, so calling ``reduce`` on it again + returns ``X`` itself:: + + sage: X is X.reduce() + True + sage: K is K.reduce() + False + + Raise an error for disconnected simplicial sets:: + + sage: S0 = simplicial_sets.Sphere(0) + sage: S0.reduce() + Traceback (most recent call last): + ... + ValueError: this simplicial set is not connected + """ + if self.is_reduced(): + return self + if not self.is_connected(): + raise ValueError("this simplicial set is not connected") + graph = self.graph() + spanning_tree = [e[2] for e in graph.min_spanning_tree()] + return self.quotient(spanning_tree) + + def _Hom_(self, other, category=None): + """ + Return the set of simplicial maps between simplicial sets + ``self`` and ``other``. + + INPUT: + + - ``other`` -- another simplicial set + - ``category`` -- optional, the category in which to compute + the maps. By default this is ``SimplicialSets``, and it must + be a subcategory of this or else an error is raised. + + EXAMPLES:: + + sage: S3 = simplicial_sets.Sphere(3) + sage: S2 = simplicial_sets.Sphere(2) + sage: S3._Hom_(S2) + Set of Morphisms from S^3 to S^2 in Category of finite pointed simplicial sets + sage: Hom(S3, S2) + Set of Morphisms from S^3 to S^2 in Category of finite pointed simplicial sets + sage: K4 = simplicial_sets.Simplex(4) + sage: S3._Hom_(K4) + Set of Morphisms from S^3 to 4-simplex in Category of finite simplicial sets + """ + # Import this here to prevent circular imports. + from sage.topology.simplicial_set_morphism import SimplicialSetHomset + # Error-checking on the ``category`` argument is done when + # calling Hom(X,Y), so no need to do it again here. + if category is None: + if self.is_finite() and other.is_finite(): + if self.is_pointed() and other.is_pointed(): + category = SimplicialSets().Finite().Pointed() + else: + category = SimplicialSets().Finite() + else: + if self.is_pointed() and other.is_pointed(): + category = SimplicialSets().Pointed() + else: + category = SimplicialSets() + return SimplicialSetHomset(self, other, category=category) + + def rename_latex(self, s): + """ + Rename or set the LaTeX name for this simplicial set. + + INPUT: + + - ``s`` -- string, the LaTeX representation. Or ``s`` can be + ``None``, in which case the LaTeX name is unset. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: X = SimplicialSet({v: None}, latex_name='*') + sage: latex(X) + * + sage: X.rename_latex('x_0') + sage: latex(X) + x_0 + """ + self._latex_name = s + + def _latex_(self): + r""" + LaTeX representation. + + If ``latex_name`` is set when the simplicial set is defined, + or if :meth:`rename_latex` is used to set the LaTeX name, use + that. Otherwise, use its string representation. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: X = SimplicialSet({v: None}, latex_name='*') + sage: latex(X) + * + sage: X.rename_latex('y_0') + sage: latex(X) + y_0 + sage: X.rename_latex(None) + sage: latex(X) + Simplicial set with 1 non-degenerate simplex + sage: X.rename('v') + sage: latex(X) + v + """ + if hasattr(self, '_latex_name') and self._latex_name is not None: + return self._latex_name + return str(self) + + def _repr_(self): + """ + Print representation. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: degen = v.apply_degeneracies(0) + sage: tau = AbstractSimplex(2) + sage: SimplicialSet({tau: (degen, degen, degen), w: None}) + Simplicial set with 3 non-degenerate simplices + sage: SimplicialSet({w: None}) + Simplicial set with 1 non-degenerate simplex + + Test names and renaming:: + + sage: SimplicialSet({w: None}, name='pt') + pt + sage: K = SimplicialSet({w: None}, name='pt') + sage: K.rename('point') + sage: K + point + """ + num = len(self.nondegenerate_simplices()) + if num == 1: + return "Simplicial set with 1 non-degenerate simplex" + return "Simplicial set with {} non-degenerate simplices".format(num) + + +class SimplicialSet_finite(SimplicialSet_arbitrary, GenericCellComplex): + r""" + A finite simplicial set. + + A simplicial set `X` is a collection of sets `X_n`, the + *n-simplices*, indexed by the non-negative integers, together with + face maps `d_i` and degeneracy maps `s_j`. A simplex is + *degenerate* if it is in the image of some `s_j`, and a simplicial + set is *finite* if there are only finitely many non-degenerate + simplices. + + INPUT: + + - ``data`` -- the data defining the simplicial set. See below for + details. + + - ``base_point`` (optional, default ``None``) -- 0-simplex in this + simplicial set, its base point + + - ``name`` (optional, default ``None``) -- string, the name of the + simplicial set + + - ``check`` (optional, default ``True``) -- boolean. If ``True``, + check the simplicial identity on the face maps when defining the + simplicial set. + + - ``category`` (optional, default ``None``) -- the category in + which to define this simplicial set. The default is either + finite simplicial sets or finite pointed simplicial sets, + depending on whether a base point is defined. + + - ``latex_name`` (optional, default ``None``) -- string, the LaTeX + representation of the simplicial set. + + ``data`` should have one of the following forms: it could be a + simplicial complex or `\Delta`-complex, in case it is converted to + a simplicial set. Alternatively, it could be a dictionary. The + keys are the nondegenerate simplices of the simplicial set, and + the value corresponding to a simplex `\sigma` is a tuple listing + the faces of `\sigma`. The 0-dimensional simplices may be omitted + from ``data`` if they (or their degeneracies) are faces of other + simplices; otherwise they must be included with value ``None``. + + See :mod:`.simplicial_set` and the methods for simplicial sets for + more information and examples. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: u = AbstractSimplex(0, name='u') + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: f = AbstractSimplex(1, name='f') + + In the following simplicial set, ``u`` is an isolated vertex:: + + sage: X = SimplicialSet({e: (v,w), f: (w,w), u: None}) + sage: X + Simplicial set with 5 non-degenerate simplices + sage: X.rename('X') + sage: X + X + sage: X = SimplicialSet({e: (v,w), f: (w,w), u: None}, name='Y') + sage: X + Y + """ + def __init__(self, data, base_point=None, name=None, check=True, + category=None, latex_name=None): + r""" + TESTS:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: e = AbstractSimplex(1) + sage: SimplicialSet({e: (v, v, v)}) + Traceback (most recent call last): + ... + ValueError: wrong number of faces for simplex in dimension 1 + sage: SimplicialSet({e: (v,)}) + Traceback (most recent call last): + ... + ValueError: wrong number of faces for simplex in dimension 1 + + Base points:: + + sage: SimplicialSet({e: (v,v)}, base_point=AbstractSimplex(0)) + Traceback (most recent call last): + ... + ValueError: the base point is not a simplex in this simplicial set + sage: SimplicialSet({e: (v,v)}, base_point=e) + Traceback (most recent call last): + ... + ValueError: the base "point" is not a zero-simplex + + Simplicial identity:: + + sage: sigma = AbstractSimplex(2) + sage: w = AbstractSimplex(0) + sage: K = SimplicialSet({sigma: (v.apply_degeneracies(0), + ....: v.apply_degeneracies(0), + ....: v.apply_degeneracies(0))}) + sage: SimplicialSet({sigma: (v.apply_degeneracies(0), + ....: v.apply_degeneracies(0), + ....: w.apply_degeneracies(0))}) + Traceback (most recent call last): + ... + ValueError: simplicial identity d_i d_j = d_{j-1} d_i fails in dimension 2 + + Returning a copy of the original:: + + sage: v = AbstractSimplex(0) + sage: e = AbstractSimplex(1) + sage: S1 = SimplicialSet({e: (v, v)}) + sage: SimplicialSet(S1) == S1 + False + + Test suites:: + + sage: skip = ["_test_pickling", "_test_elements"] + sage: TestSuite(S1).run(skip=skip) + sage: TestSuite(simplicial_sets.Sphere(5)).run(skip=skip) + sage: TestSuite(simplicial_sets.RealProjectiveSpace(6)).run(skip=skip) + """ + def face(sigma, i): + """ + Return the i-th face of sigma, a simplex in this simplicial set. + + Once the simplicial set has been fully initialized, use + the :meth:`face` method instead. + """ + if sigma.is_nondegenerate(): + return data[sigma][i] + else: + underlying = sigma.nondegenerate() + J, t = face_degeneracies(i, sigma.degeneracies()) + if t is None: + return underlying.apply_degeneracies(*J) + else: + return data[underlying][t].apply_degeneracies(*J) + + if isinstance(data, GenericCellComplex): + # Construct new data appropriately. + if isinstance(data, SimplicialComplex): + simplices = {} + faces = {} + for d in range(data.dimension()+1): + old_faces = faces + faces = {} + for idx, sigma in enumerate(data.n_cells(d)): + new_sigma = AbstractSimplex(d) + new_sigma.rename(str(tuple(sorted(tuple(sigma), key=str)))) + if d > 0: + simplices[new_sigma] = [old_faces[_] for _ in sigma.faces()] + else: + simplices[new_sigma] = None + faces[sigma] = new_sigma + data = simplices + + elif isinstance(data, DeltaComplex): + simplices = {} + current = [] + for d in range(data.dimension()+1): + faces = tuple(current) + current = [] + for idx, sigma in enumerate(data.n_cells(d)): + new_sigma = AbstractSimplex(d) + # Name: Delta_{d,idx} where d is dimension, + # idx is its index in the list of d-simplices. + new_sigma.rename('Delta_{{{},{}}}'.format(d, idx)) + if d > 0: + simplices[new_sigma] = [faces[_] for _ in sigma] + else: + simplices[new_sigma] = None + current.append(new_sigma) + data = simplices + elif isinstance(data, SimplicialSet_finite): + data = dict(copy.deepcopy(data._data)) + else: + raise NotImplementedError('I do not know how to convert this ' + 'to a simplicial set') + # Convert each value in data to a tuple, and then convert all + # of data to a tuple, so that it is hashable. + for x in data: + if data[x]: + if x.dimension() != len(data[x]) - 1: + raise ValueError('wrong number of faces for simplex ' + 'in dimension {}'.format(x.dimension())) + if not all(y.dimension() == x.dimension() - 1 for y in data[x]): + raise ValueError('faces of a {}-simplex have the wrong ' + 'dimension'.format(x.dimension())) + data[x] = tuple(data[x]) + + # To obtain the non-degenerate simplices, look at both the + # keys for data and also the underlying non-degenerate + # simplices in its values. + simplices = set(data.keys()) + for t in data.values(): + if t: + simplices.update([_.nondegenerate() for _ in t]) + + for x in simplices: + if x not in data: + # x had better be a vertex. + assert(x.dimension() == 0) + data[x] = None + + # Check the simplicial identity d_i d_j = d_{j-1} d_i. + if check: + for sigma in simplices: + d = sigma.dimension() + if d >= 2: + for j in range(d+1): + for i in range(j): + if face(face(sigma, j), i) != face(face(sigma, i), j-1): + raise ValueError('simplicial identity d_i d_j ' + '= d_{{j-1}} d_i fails ' + 'in dimension {}'.format(d)) + + # Now define the attributes for an instance of this class. + # self._data: a tuple representing the defining data of the + # simplicial set. + self._data = tuple(data.items()) + # self._simplices: a sorted tuple of non-degenerate simplices. + self._simplices = sorted(tuple(simplices)) + # self._basepoint: the base point, or None. + if base_point is not None: + if base_point not in simplices: + raise ValueError('the base point is not a simplex in ' + 'this simplicial set') + if base_point.dimension() != 0: + raise ValueError('the base "point" is not a zero-simplex') + self._basepoint = base_point + if category is None: + if base_point is None: + category = SimplicialSets().Finite() + else: + category = SimplicialSets().Finite().Pointed() + Parent.__init__(self, category=category) + if name: + self.rename(name) + self._latex_name = latex_name + + def __eq__(self, other): + """ + Return ``True`` if ``self`` and ``other`` are equal as simplicial sets. + + Two simplicial sets are equal if they have the same defining + data. This means that they have *the same* simplices in each + dimension, not just that they have the same numbers of + `n`-simplices for each `n` with corresponding face maps. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: e = AbstractSimplex(1) + sage: X = SimplicialSet({e: (v, v)}) + sage: Y = SimplicialSet({e: (w, w)}) + sage: X == X + True + sage: X == SimplicialSet({e: (v, v)}) + True + sage: X == Y + False + """ + if self.is_pointed(): + return (isinstance(other, SimplicialSet_finite) + and other.is_pointed() + and sorted(self._data) == sorted(other._data) + and self.base_point() == other.base_point()) + else: + return (isinstance(other, SimplicialSet_finite) + and not other.is_pointed() + and sorted(self._data) == sorted(other._data)) + + def __ne__(self, other): + """ + Return ``True`` if ``self`` and ``other`` are not equal as simplicial sets. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: X = SimplicialSet({v: None}) + sage: Y = SimplicialSet({w: None}) + sage: X != X + False + sage: X != SimplicialSet({v: None}) + False + sage: X != Y + True + """ + return not (self == other) + + # This is cached because it is used frequently in simplicial set + # construction: the last two lines in the __init__ method access + # dictionaries which use instances of SimplicialSet_finite as keys and so + # computes their hash. If the tuple self._data is long, this can + # take a long time. + @cached_method + def __hash__(self): + """ + The hash is formed from that of the tuple ``self._data``. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: X = SimplicialSet({v: None}) + sage: degen = v.apply_degeneracies(0) + sage: tau = AbstractSimplex(2) + sage: Y = SimplicialSet({tau: (degen, degen, degen)}) + + sage: hash(X) # random + 17 + sage: hash(X) != hash(Y) + True + """ + if self.is_pointed(): + return hash(self._data) ^ hash(self.base_point()) + else: + return hash(self._data) + + def __copy__(self): + """ + Return a distinct copy of this simplicial set. + + The copy will not be equal to the original simplicial set. + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: copy(T) == T + False + sage: T.n_cells(0)[0] == copy(T).n_cells(0)[0] + False + sage: T.homology() == copy(T).homology() + True + """ + return SimplicialSet(dict(copy.deepcopy(self._data))) + + def face_data(self): + """ + Return the face-map data -- a dictionary -- defining this simplicial set. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: X = SimplicialSet({e: (v, w)}) + sage: X.face_data()[e] + (v, w) + + sage: Y = SimplicialSet({v: None, w: None}) + sage: v in Y.face_data() + True + sage: Y.face_data()[v] is None + True + """ + return dict(self._data) + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the subsimplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: degen = v.apply_degeneracies(0) + sage: tau = AbstractSimplex(2, name='tau') + sage: Y = SimplicialSet({tau: (degen, degen, degen), w: None}) + + ``Y`` is the disjoint union of a 2-sphere, with vertex ``v`` + and non-degenerate 2-simplex ``tau``, and a point ``w``. :: + + sage: Y.nondegenerate_simplices() + [v, w, tau] + sage: Y.n_skeleton(1).nondegenerate_simplices() + [v, w] + sage: Y.n_skeleton(2).nondegenerate_simplices() + [v, w, tau] + """ + data = [x for x in self.nondegenerate_simplices() + if x.dimension() <= n] + return self.subsimplicial_set(data) + + def _facets_(self): + r""" + Return the list of facets of this simplicial set, where by + "facet" we mean a non-degenerate simplex which is not a face + of another non-degenerate simplex. + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: T._facets_() + [(s_0 sigma_1, s_1 sigma_1), + (s_1 sigma_1, s_0 sigma_1)] + sage: S5 = simplicial_sets.Sphere(5) + sage: S5._facets_() + [sigma_5] + sage: simplicial_sets.Sphere(0)._facets_() + [v_0, w_0] + """ + faces = set() + for dim in range(self.dimension(), 0, -1): + for sigma in self.n_cells(dim): + faces.update([tau.nondegenerate() for tau in self.faces(sigma)]) + return sorted(set(self.nondegenerate_simplices()).difference(faces)) + + def f_vector(self): + """ + Return the list of the number of non-degenerate simplices in each + dimension. + + Unlike for some other cell complexes in Sage, this does not + include the empty simplex in dimension `-1`; thus its `i`-th + entry is the number of `i`-dimensional simplices. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: w = AbstractSimplex(0) + sage: S0 = SimplicialSet({v: None, w: None}) + sage: S0.f_vector() + [2] + + sage: e = AbstractSimplex(1) + sage: S1 = SimplicialSet({e: (v, v)}) + sage: S1.f_vector() + [1, 1] + sage: simplicial_sets.Sphere(3).f_vector() + [1, 0, 0, 1] + """ + return [len(self.n_cells(_)) for _ in range(self.dimension()+1)] + + def euler_characteristic(self): + r""" + Return the Euler characteristic of this simplicial set: the + alternating sum over `n \geq 0` of the number of + nondegenerate `n`-simplices. + + EXAMPLES:: + + sage: simplicial_sets.RealProjectiveSpace(4).euler_characteristic() + 1 + sage: simplicial_sets.Sphere(6).euler_characteristic() + 2 + sage: simplicial_sets.KleinBottle().euler_characteristic() + 0 + """ + return sum([(-1)**n * num for (n, num) in enumerate(self.f_vector())]) + + def chain_complex(self, dimensions=None, base_ring=ZZ, augmented=False, + cochain=False, verbose=False, subcomplex=None, + check=False): + r""" + Return the normalized chain complex. + + INPUT: + + - ``dimensions`` -- if ``None``, compute the chain complex in all + dimensions. If a list or tuple of integers, compute the + chain complex in those dimensions, setting the chain groups + in all other dimensions to zero. + + - ``base_ring`` (optional, default ``ZZ``) -- commutative ring + + - ``augmented`` (optional, default ``False``) -- if ``True``, + return the augmented chain complex (that is, include a class + in dimension `-1` corresponding to the empty cell). + + - ``cochain`` (optional, default ``False``) -- if ``True``, + return the cochain complex (that is, the dual of the chain + complex). + + - ``verbose`` (optional, default ``False``) -- ignored. + + - ``subcomplex`` (optional, default ``None``) -- if present, + compute the chain complex relative to this subcomplex. + + - ``check`` (optional, default ``False``) -- If ``True``, make + sure that the chain complex is actually a chain complex: + the differentials are composable and their product is zero. + + The normalized chain complex of a simplicial set is isomorphic + to the chain complex obtained by modding out by degenerate + simplices, and the latter is what is actually constructed + here. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0) + sage: degen = v.apply_degeneracies(1, 0) # s_1 s_0 applied to v + sage: sigma = AbstractSimplex(3) + sage: S3 = SimplicialSet({sigma: (degen, degen, degen, degen)}) # the 3-sphere + sage: S3.chain_complex().homology() + {0: Z, 3: Z} + sage: S3.chain_complex(augmented=True).homology() + {-1: 0, 0: 0, 3: Z} + sage: S3.chain_complex(dimensions=range(3), base_ring=QQ).homology() + {0: Vector space of dimension 1 over Rational Field} + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP2 = RP5.n_skeleton(2) + sage: RP5.chain_complex(subcomplex=RP2).homology() + {0: Z, 3: C2, 4: 0, 5: Z} + + TESTS: + + Convert some simplicial complexes and `\Delta`-complexes to + simplicial sets, and compare homology calculations:: + + sage: T = simplicial_complexes.Torus() + sage: T.homology() == SimplicialSet(T).homology() + True + sage: RP2 = delta_complexes.RealProjectivePlane() + sage: RP2.homology() == SimplicialSet(RP2).homology() + True + sage: RP2.cohomology(base_ring=GF(2)) == SimplicialSet(RP2).cohomology(base_ring=GF(2)) + True + """ + if dimensions is None: + if not self.cells(): # Empty + if cochain: + return ChainComplex({-1: matrix(base_ring, 0, 0)}, + degree_of_differential=1) + return ChainComplex({0: matrix(base_ring, 0, 0)}, + degree_of_differential=-1) + dimensions = list(range(self.dimension() + 1)) + else: + if not isinstance(dimensions, (list, tuple, range)): + dimensions = list(range(dimensions - 1, dimensions + 2)) + else: + dimensions = [n for n in dimensions if n >= 0] + if not dimensions: + # Return the empty chain complex. + if cochain: + return ChainComplex(base_ring=base_ring, degree=1) + else: + return ChainComplex(base_ring=base_ring, degree=-1) + + differentials = {} + # Convert the tuple self._data to a dictionary indexed by the + # non-degenerate simplices. + if subcomplex: + X = self.quotient(subcomplex) + face_data = X.face_data() + nondegens = X.nondegenerate_simplices() + else: + face_data = self.face_data() + nondegens = self.nondegenerate_simplices() + # simplices: dictionary indexed by dimension, values the list + # of non-degenerate simplices in that dimension. + simplices = {} + for sigma in nondegens: + if sigma.dimension() in simplices: + simplices[sigma.dimension()].append(sigma) + else: + simplices[sigma.dimension()] = [sigma] + first = dimensions.pop(0) + if first in simplices: + rank = len(simplices[first]) + current = sorted(simplices[first]) + else: + rank = 0 + current = [] + if augmented and first == 0: + differentials[first-1] = matrix(base_ring, 0, 1) + differentials[first] = matrix(base_ring, 1, rank, + [1] * rank) + else: + differentials[first] = matrix(base_ring, 0, rank) + + for d in dimensions: + old_rank = rank + faces = {_[1]:_[0] for _ in enumerate(current)} + if d in simplices: + current = sorted(simplices[d]) + rank = len(current) + # old_rank: number of simplices in dimension d-1. + # faces: list of simplices in dimension d-1. + # rank: number of simplices in dimension d. + # current: list of simplices in dimension d. + if not faces: + differentials[d] = matrix(base_ring, old_rank, rank) + else: + matrix_data = {} + for col, sigma in enumerate(current): + sign = 1 + for tau in face_data[sigma]: + if tau.is_nondegenerate(): + row = faces[tau] + if (row, col) in matrix_data: + matrix_data[(row, col)] += sign + else: + matrix_data[(row, col)] = sign + sign *= -1 + + differentials[d] = matrix(base_ring, old_rank, + rank, matrix_data) + + else: + rank = 0 + current = [] + differentials[d] = matrix(base_ring, old_rank, rank) + + if cochain: + new_diffs = {} + for d in differentials: + new_diffs[d-1] = differentials[d].transpose() + return ChainComplex(new_diffs, degree_of_differential=1, + check=check) + return ChainComplex(differentials, degree_of_differential=-1, + check=check) + + @cached_method + def algebraic_topological_model(self, base_ring=None): + r""" + Return the algebraic topological model for this simplicial set + with coefficients in ``base_ring``. + + The term "algebraic topological model" is defined by Pilarczyk + and Réal [PR2015]_. + + INPUT: + + - ``base_ring`` - coefficient ring (optional, default + ``QQ``). Must be a field. + + Denote by `C` the chain complex associated to this simplicial + set. The algebraic topological model is a chain complex `M` + with zero differential, with the same homology as `C`, along + with chain maps `\pi: C \to M` and `\iota: M \to C` satisfying + `\iota \pi = 1_M` and `\pi \iota` chain homotopic to + `1_C`. The chain homotopy `\phi` must satisfy + + - `\phi \phi = 0`, + - `\pi \phi = 0`, + - `\phi \iota = 0`. + + Such a chain homotopy is called a *chain contraction*. + + OUTPUT: a pair consisting of + + - chain contraction ``phi`` associated to `C`, `M`, `\pi`, and + `\iota` + - the chain complex `M` + + Note that from the chain contraction ``phi``, one can recover the + chain maps `\pi` and `\iota` via ``phi.pi()`` and + ``phi.iota()``. Then one can recover `C` and `M` from, for + example, ``phi.pi().domain()`` and ``phi.pi().codomain()``, + respectively. + + EXAMPLES:: + + sage: RP2 = simplicial_sets.RealProjectiveSpace(2) + sage: phi, M = RP2.algebraic_topological_model(GF(2)) + sage: M.homology() + {0: Vector space of dimension 1 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2} + sage: T = simplicial_sets.Torus() + sage: phi, M = T.algebraic_topological_model(QQ) + sage: M.homology() + {0: Vector space of dimension 1 over Rational Field, + 1: Vector space of dimension 2 over Rational Field, + 2: Vector space of dimension 1 over Rational Field} + """ + if base_ring is None: + base_ring = QQ + return algebraic_topological_model_delta_complex(self, base_ring) + + +# TODO: possibly turn SimplicialSet into a function, for example +# allowing for the construction of infinite simplicial sets. +SimplicialSet = SimplicialSet_finite + + +######################################################################## +# Functions for manipulating face and degeneracy maps. + +def standardize_degeneracies(*L): + r""" + Return list of indices of degeneracy maps in standard (decreasing) + order. + + INPUT: + + - ``L`` -- list of integers, representing a composition of + degeneracies in a simplicial set. + + OUTPUT: an equivalent list of degeneracies, standardized to be + written in decreasing order, using the simplicial identity + + .. MATH:: + + s_i s_j = s_{j+1} s_i \ \ \text{if } i \leq j. + + For example, `s_0 s_2 = s_3 s_0` and `s_0 s_0 = s_1 s_0`. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import standardize_degeneracies + sage: standardize_degeneracies(0, 0) + (1, 0) + sage: standardize_degeneracies(0, 0, 0, 0) + (3, 2, 1, 0) + sage: standardize_degeneracies(1, 2) + (3, 1) + + TESTS:: + + sage: standardize_degeneracies() + () + sage: standardize_degeneracies(2, -1) + Traceback (most recent call last): + ... + ValueError: degeneracies are indexed by non-negative integers + sage: standardize_degeneracies([2, 1]) + Traceback (most recent call last): + ... + TypeError: degeneracies are indexed by non-negative integers; do not use an explicit list or tuple + """ + J = list(L) + for m in J: + try: + if Integer(m) < 0: + raise ValueError('degeneracies are indexed by non-negative integers') + except TypeError: + # Likely if called via standard_degeneracies([1,2,3]) + # rather than standard_degeneracies(1,2,3). + raise TypeError('degeneracies are indexed by non-negative integers; do not use an explicit list or tuple') + inadmissible = True + while inadmissible: + inadmissible = False + for idx in range(len(J)-1): + if J[idx] <= J[idx + 1]: + inadmissible = True + tmp = J[idx] + J[idx] = J[idx + 1] + 1 + J[idx + 1] = tmp + return tuple(J) + +def all_degeneracies(n, l=1): + r""" + Return list of all composites of degeneracies (written in + "admissible" form, i.e., as a strictly decreasing sequence) of + length `l` on an `n`-simplex. + + INPUT: + + - ``n``, ``l`` -- integers + + On an `n`-simplex, one may apply the degeneracies `s_i` for `0 + \leq i \leq n`. Then on the resulting `n+1`-simplex, one may apply + `s_i` for `0 \leq i \leq n+1`, and so on. But one also has to take + into account the simplicial identity + + .. MATH:: + + s_i s_j = s_{j+1} s_i \ \ \text{if } i \leq j. + + There are `\binom{l+n}{n}` such composites: each non-degenerate + `n`-simplex leads to `\binom{l+n}{n}` degenerate `l+n` simplices. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import all_degeneracies + sage: all_degeneracies(0, 3) + {(2, 1, 0)} + sage: all_degeneracies(1, 1) + {(0,), (1,)} + sage: all_degeneracies(1, 3) + {(2, 1, 0), (3, 1, 0), (3, 2, 0), (3, 2, 1)} + """ + if l == 0: + return set(()) + if l == 1: + return set([tuple([_]) for _ in range(n+1)]) + ans = set() + for i in range(n+l): + ans.update(set([tuple(standardize_degeneracies(*([i] + list(_)))) + for _ in all_degeneracies(n, l-1)])) + return ans + +def standardize_face_maps(*L): + r""" + Return list of indices of face maps in standard (non-increasing) + order. + + INPUT: + + - ``L`` -- list of integers, representing a composition of + face maps in a simplicial set. + + OUTPUT: an equivalent list of face maps, standardized to be + written in non-increasing order, using the simplicial identity + + .. MATH:: + + d_i d_j = d_{j-1} d_i \ \ \text{if } i + +See the main documentation for simplicial sets, as well as for the +classes for pushouts, pullbacks, etc., for more details. + +Many of the classes defined here inherit from +:class:`sage.structure.unique_representation.UniqueRepresentation`. This +means that they produce identical output if given the same input, so +for example, if ``K`` is a simplicial set, calling ``K.suspension()`` +twice returns the same result both times:: + + sage: CP2.suspension() is CP2.suspension() + True + +So on one hand, a command like ``simplicial_sets.Sphere(2)`` +constructs a distinct copy of a 2-sphere each time it is called; on +the other, once you have constructed a 2-sphere, then constructing its +cone, its suspension, its product with another simplicial set, etc., +will give you the same result each time:: + + sage: simplicial_sets.Sphere(2) == simplicial_sets.Sphere(2) + False + sage: S2 = simplicial_sets.Sphere(2) + sage: S2.product(S2) == S2.product(S2) + True + sage: S2.disjoint_union(CP2, S2) == S2.disjoint_union(CP2, S2) + True + +AUTHORS: + +- John H. Palmieri (2016-07) +""" +#***************************************************************************** +# Copyright (C) 2016 John H. Palmieri +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +# +#***************************************************************************** + +import itertools + +from sage.graphs.graph import Graph +from sage.misc.latex import latex +from sage.sets.set import Set +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation + +from .simplicial_set import AbstractSimplex, \ + SimplicialSet_arbitrary, SimplicialSet_finite, \ + standardize_degeneracies, face_degeneracies +from .simplicial_set_examples import Empty, Point + +from sage.misc.lazy_import import lazy_import +lazy_import('sage.categories.simplicial_sets', 'SimplicialSets') + +######################################################################## +# classes which inherit from SimplicialSet_arbitrary + +# Note: many of the classes below for infinite simplicial sets have an +# attribute '_n_skeleton'. This is used to cache the highest +# dimensional skeleton calculated so far for this simplicial set, +# along with its dimension, so for example, the starting value is +# often (-1, Empty()): the (-1)-skeleton is the empty simplicial +# set. It gets used and updated in the n_skeleton method. + +class SubSimplicialSet(SimplicialSet_finite, UniqueRepresentation): + @staticmethod + def __classcall__(self, data, ambient=None): + """ + Convert ``data`` from a dict to a tuple. + + TESTS:: + + sage: from sage.topology.simplicial_set_constructions import SubSimplicialSet + sage: K = simplicial_sets.Simplex(2) + sage: e = K.n_cells(1)[0] + sage: A = SubSimplicialSet({e: K.faces(e)}, ambient=K) + sage: B = SubSimplicialSet({e: list(K.faces(e))}, ambient=K) + sage: A == B + True + """ + L = [] + for x in data: + if data[x] is None: + L.append((x, None)) + else: + L.append((x, tuple(data[x]))) + return super(SubSimplicialSet, self).__classcall__(self, tuple(L), ambient) + + def __init__(self, data, ambient=None): + r""" + Return a finite simplicial set as a subsimplicial set of another + simplicial set. + + This keeps track of the ambient simplicial set and the + inclusion map from the subcomplex into it. + + INPUT: + + - ``data`` -- the data defining the subset: a dictionary where + the keys are simplices from the ambient simplicial set and + the values are their faces. + + - ``ambient`` -- the ambient simplicial set. If omitted, use + the same simplicial set as the subset and the ambient + complex. + + EXAMPLES:: + + sage: S3 = simplicial_sets.Sphere(3) + sage: K = simplicial_sets.KleinBottle() + sage: X = S3.disjoint_union(K) + sage: Y = X.structure_map(0).image() # the S3 summand + sage: Y.inclusion_map() + Simplicial set morphism: + From: Simplicial set with 2 non-degenerate simplices + To: Disjoint union: (S^3 u Klein bottle) + Defn: [v_0, sigma_3] --> [v_0, sigma_3] + sage: Y.ambient_space() + Disjoint union: (S^3 u Klein bottle) + + TESTS:: + + sage: T = simplicial_sets.Torus() + sage: latex(T.n_skeleton(2)) + S^{1} \times S^{1} + + sage: T.n_skeleton(1).n_skeleton(1) == T.n_skeleton(1) + True + + sage: T.n_skeleton(1) is T.n_skeleton(1) + True + """ + data = dict(data) + if ambient is None: + ambient = self + if (ambient.is_pointed() + and hasattr(ambient, '_basepoint') + and ambient.base_point() in data): + SimplicialSet_finite.__init__(self, data, base_point=ambient.base_point()) + else: + SimplicialSet_finite.__init__(self, data) + if self == ambient: + if hasattr(ambient, '__custom_name'): + self.rename(str(ambient)) + self._latex_name = latex(ambient) + # When constructing the inclusion map, we do not need to check + # the validity of the morphism, and more importantly, we + # cannot check it in the infinite case: the appropriate data + # may not have yet been constructed. So use "check=False". + self._inclusion = self.Hom(ambient)({x:x for x in data}, check=False) + + def inclusion_map(self): + r""" + Return the inclusion map from this subsimplicial set into its + ambient space. + + EXAMPLES:: + + sage: RP6 = simplicial_sets.RealProjectiveSpace(6) + sage: K = RP6.n_skeleton(2) + sage: K.inclusion_map() + Simplicial set morphism: + From: Simplicial set with 3 non-degenerate simplices + To: RP^6 + Defn: [1, f, f * f] --> [1, f, f * f] + + `RP^6` itself is constructed as a subsimplicial set of + `RP^\infty`:: + + sage: latex(RP6.inclusion_map()) + RP^{6} \to RP^{\infty} + """ + return self._inclusion + + def ambient_space(self): + """ + Return the simplicial set of which this is a subsimplicial set. + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: eight = T.wedge_as_subset() + sage: eight + Simplicial set with 3 non-degenerate simplices + sage: eight.fundamental_group() + Finitely presented group < e0, e1 | > + sage: eight.ambient_space() + Torus + """ + return self._inclusion.codomain() + + +class PullbackOfSimplicialSets(SimplicialSet_arbitrary, UniqueRepresentation): + @staticmethod + def __classcall_private__(self, maps=None): + """ + TESTS:: + + sage: from sage.topology.simplicial_set_constructions import PullbackOfSimplicialSets + sage: S2 = simplicial_sets.Sphere(2) + sage: one = S2.Hom(S2).identity() + sage: PullbackOfSimplicialSets([one, one]) == PullbackOfSimplicialSets((one, one)) + True + """ + if maps: + return super(PullbackOfSimplicialSets, self).__classcall__(self, tuple(maps)) + return super(PullbackOfSimplicialSets, self).__classcall__(self) + + def __init__(self, maps=None): + r""" + Return the pullback obtained from the morphisms ``maps``. + + INPUT: + + - ``maps`` -- a list or tuple of morphisms of simplicial sets + + If only a single map `f: X \to Y` is given, then return + `X`. If no maps are given, return the one-point simplicial + set. Otherwise, given a simplicial set `Y` and maps `f_i: X_i + \to Y` for `0 \leq i \leq m`, construct the pullback `P`: see + :wikipedia:`Pullback_(category_theory)`. This is constructed + as pullbacks of sets for each set of `n`-simplices, so `P_n` + is the subset of the product `\prod (X_i)_n` consisting of + those elements `(x_i)` for which `f_i(x_i) = f_j(x_j)` for all + `i`, `j`. + + This is pointed if the maps `f_i` are. + + EXAMPLES: + + The pullback of a quotient map by a subsimplicial set and the + base point map gives a simplicial set isomorphic to the + original subcomplex:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: K = RP5.quotient(RP5.n_skeleton(2)) + sage: X = K.pullback(K.quotient_map(), K.base_point_map()) + sage: X.homology() == RP5.n_skeleton(2).homology() + True + + Pullbacks of identity maps:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: one = S2.Hom(S2).identity() + sage: P = S2.pullback(one, one) + sage: P.homology() + {0: 0, 1: 0, 2: Z} + + The pullback is constructed in terms of the product -- of + course, the product is a special case of the pullback -- and + the simplices are named appropriately:: + + sage: P.nondegenerate_simplices() + [(v_0, v_0), (sigma_2, sigma_2)] + """ + # Import this here to prevent circular imports. + from sage.topology.simplicial_set_morphism import SimplicialSetMorphism + if maps and any(not isinstance(f, SimplicialSetMorphism) for f in maps): + raise ValueError('the maps must be morphisms of simplicial sets') + + Cat = SimplicialSets() + if maps: + if all(f.domain().is_finite() for f in maps): + Cat = Cat.Finite() + if all(f.is_pointed() for f in maps): + Cat = Cat.Pointed() + Parent.__init__(self, category=Cat) + self._maps = maps + self._n_skeleton = (-1, Empty()) + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the simplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + The `n`-skeleton of the pullback is computed as the pullback + of the `n`-skeleta of the component simplicial sets. + + EXAMPLES:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: one = Hom(B,B).identity() + sage: c = Hom(B,B).constant_map() + sage: P = B.pullback(one, c) + sage: P.n_skeleton(2) + Pullback of maps: + Simplicial set endomorphism of Simplicial set with 3 non-degenerate simplices + Defn: Identity map + Simplicial set endomorphism of Simplicial set with 3 non-degenerate simplices + Defn: Constant map at 1 + sage: P.n_skeleton(3).homology() + {0: 0, 1: C2, 2: 0, 3: Z} + """ + if self.is_finite(): + maps = self._maps + if maps: + codomain = SimplicialSet_finite.n_skeleton(maps[0].codomain(), n) + domains = [SimplicialSet_finite.n_skeleton(f.domain(), n) for f in maps] + new_maps = [f.n_skeleton(n, d, codomain) for (f, d) in zip(maps, domains)] + return PullbackOfSimplicialSets_finite(new_maps) + return PullbackOfSimplicialSets_finite(maps) + start, skel = self._n_skeleton + if start == n: + return skel + elif start > n: + return skel.n_skeleton(n) + ans = PullbackOfSimplicialSets_finite([f.n_skeleton(n) for f in self._maps]) + self._n_skeleton = (n, ans) + return ans + + def defining_map(self, i): + r""" + Return the `i`-th map defining the pullback. + + INPUT: + + - ``i`` -- integer + + If this pullback was constructed as ``Y.pullback(f_0, f_1, ...)``, + this returns `f_i`. + + EXAMPLES:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: K = RP5.quotient(RP5.n_skeleton(2)) + sage: Y = K.pullback(K.quotient_map(), K.base_point_map()) + sage: Y.defining_map(1) + Simplicial set morphism: + From: Point + To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) + Defn: Constant map at * + sage: Y.defining_map(0).domain() + RP^5 + """ + return self._maps[i] + + def _repr_(self): + """ + Print representation + + EXAMPLES:: + + sage: S3 = simplicial_sets.Sphere(3) + sage: c = Hom(S3,S3).constant_map() + sage: one = Hom(S3, S3).identity() + sage: S3.pullback(c, one) + Pullback of maps: + Simplicial set endomorphism of S^3 + Defn: Constant map at v_0 + Simplicial set endomorphism of S^3 + Defn: Identity map + """ + if not self._maps: + return 'Point' + s = 'Pullback of maps:' + for f in self._maps: + t = '\n' + str(f) + s += t.replace('\n', '\n ') + return s + + +class PullbackOfSimplicialSets_finite(PullbackOfSimplicialSets, SimplicialSet_finite): + """ + The pullback of finite simplicial sets obtained from ``maps``. + + When the simplicial sets involved are all finite, there are more + methods available to the resulting pullback, as compared to case + when some of the components are infinite: the structure maps from + the pullback and the pullback's universal property: see + :meth:`structure_map` and :meth:`universal_property`. + """ + @staticmethod + def __classcall_private__(self, maps=None): + """ + TESTS:: + + sage: from sage.topology.simplicial_set_constructions import PullbackOfSimplicialSets_finite + sage: S2 = simplicial_sets.Sphere(2) + sage: one = S2.Hom(S2).identity() + sage: PullbackOfSimplicialSets_finite([one, one]) == PullbackOfSimplicialSets_finite((one, one)) + True + """ + if maps: + return super(PullbackOfSimplicialSets_finite, self).__classcall__(self, tuple(maps)) + return super(PullbackOfSimplicialSets_finite, self).__classcall__(self) + + def __init__(self, maps=None): + r""" + Return the pullback obtained from the morphisms ``maps``. + + See :class:`PullbackOfSimplicialSets` for more information. + + INPUT: + + - ``maps`` -- a list or tuple of morphisms of simplicial sets + + EXAMPLES:: + + sage: eta = simplicial_sets.HopfMap() + sage: S3 = eta.domain() + sage: S2 = eta.codomain() + sage: c = Hom(S2,S2).constant_map() + sage: S2.pullback(eta, c).is_finite() + True + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: one = Hom(B,B).identity() + sage: c = Hom(B,B).constant_map() + sage: B.pullback(one, c).is_finite() + False + + TESTS:: + + sage: P = simplicial_sets.Point() + sage: P.pullback(P.constant_map(), P.constant_map()) + Pullback of maps: + Simplicial set endomorphism of Point + Defn: Identity map + Simplicial set endomorphism of Point + Defn: Identity map + """ + # Import this here to prevent circular imports. + from sage.topology.simplicial_set_morphism import SimplicialSetMorphism + if maps and any(not isinstance(f, SimplicialSetMorphism) for f in maps): + raise ValueError('the maps must be morphisms of simplicial sets') + if not maps: + star = AbstractSimplex(0, name='*') + SimplicialSet_finite.__init__(self, {star: None}, base_point=star, name='Point') + self._maps = () + self._translation = {} + return + if len(maps) == 1: + f = maps[0] + if f.is_pointed(): + SimplicialSet_finite.__init__(self, f.domain().face_data(), + base_point=f.domain().base_point()) + else: + SimplicialSet_finite.__init__(self, f.domain().face_data()) + self._maps = (f,) + return + codomain = maps[0].codomain() + if any(codomain != f.codomain() for f in maps[1:]): + raise ValueError('the codomains of the maps must be equal') + # Now construct the pullback by constructing the product and only + # keeping the appropriate simplices. + domains = [f.domain() for f in maps] + nondegen = [X.nondegenerate_simplices() for X in domains] + data_factors = [X.face_data() for X in domains] + # data: dictionary to construct the new simplicial set. + data = {} + # translate: keep track of the nondegenerate simplices in the + # new simplicial set for computing faces: keys are tuples of + # pairs (sigma, degens), with sigma a nondegenerate simplex in + # one of the factors, degens the tuple of applied + # degeneracies. The associated value is the actual simplex in + # the product. + translate = {} + for simplices in itertools.product(*nondegen): + dims = [_.dimension() for _ in simplices] + dim_max = max(dims) + sum_dims = sum(dims) + for d in range(dim_max, sum_dims + 1): + S = Set(range(d)) + # Is there a way to speed up the following? Given the + # tuple dims=(n_1, n_2, ..., n_k) and given d between + # max(dims) and sum(dims), we are trying to construct + # k-tuples of subsets (D_1, D_2, ..., D_k) of range(d) + # such that the intersection of all of the D_i's is + # empty. + for I in itertools.product(*[S.subsets(d - _) for _ in dims]): + if set.intersection(*[set(_) for _ in I]): + # To get a nondegenerate face, can't have a + # degeneracy in common for all the factors. + continue + degens = [tuple(sorted(_, reverse=True)) for _ in I] + + sigma = simplices[0].apply_degeneracies(*degens[0]) + target = maps[0](sigma) + if any(target != f(tau.apply_degeneracies(*degen)) + for (f, tau, degen) in zip(maps[1:], simplices[1:], degens[1:])): + continue + + simplex_factors = tuple(zip(simplices, tuple(degens))) + s = '(' + ', '.join(['{}'.format(_[0].apply_degeneracies(*_[1])) + for _ in simplex_factors]) + ')' + ls = '(' + ', '.join(['{}'.format(latex(_[0].apply_degeneracies(*_[1]))) + for _ in simplex_factors]) + ')' + simplex = AbstractSimplex(d, name=s, latex_name=ls) + translate[simplex_factors] = simplex + # Now compute the faces of simplex. + if d == 0: + # It's a vertex, so it has no faces. + faces = None + else: + faces = [] + for i in range(d+1): + # Compute d_i on simplex. + # + # face_degens: tuple of pairs (J, t): J is the + # list of degeneracies to apply to the + # corresponding entry in simplex_factors, t is + # the face map to apply. + face_degens = [face_degeneracies(i, _) for _ in degens] + face_factors = [] + new_degens = [] + for x, Face, face_dict in zip(simplices, face_degens, data_factors): + J = Face[0] + t = Face[1] + if t is None: + face_factors.append(x.nondegenerate()) + else: + underlying = face_dict[x][t] + temp_degens = underlying.degeneracies() + underlying = underlying.nondegenerate() + J = standardize_degeneracies(*(J + list(temp_degens))) + face_factors.append(underlying) + new_degens.append(J) + + # By the simplicial identities, s_{i_1} + # s_{i_2} ... s_{i_n} z (if decreasing) is in + # the image of s_{i_k} for each k. + # + # So find the intersection K of each J, the + # degeneracies applied to left_face and + # right_face. Then the face will be s_{K} + # (s_{J'_L} left_face, s_{J'_R} right_face), + # where you get J'_L from J_L by pulling out K + # from J_L. + # + # J'_L is obtained as follows: for each j in + # J_L, decrease j by q if q = #{x in K: x < j} + K = set.intersection(*[set(J) for J in new_degens]) + + face_degens = [] + for J in new_degens: + new_J = [] + for j in J: + if j not in K: + q = len([x for x in K if x < j]) + new_J.append(j - q) + face_degens.append(tuple(new_J)) + K = sorted(K, reverse=True) + underlying_face = translate[tuple(zip(tuple(face_factors), tuple(face_degens)))] + faces.append(underlying_face.apply_degeneracies(*K)) + data[simplex] = faces + + if all(f.is_pointed() for f in maps): + basept = translate[tuple([(sset.base_point(), ()) for sset in domains])] + if not data: + data = {basept: None} + SimplicialSet_finite.__init__(self, data, base_point=basept) + else: + SimplicialSet_finite.__init__(self, data) + self._maps = maps + # self._translation: tuple converted from dict. keys: tuples + # of pairs (sigma, degens), with sigma a nondegenerate simplex + # in one of the factors, degens the tuple of applied + # degeneracies. The associated value is the actual simplex in + # the product. + self._translation = tuple(translate.items()) + + def structure_map(self, i): + r""" + Return the `i`-th projection map of the pullback. + + INPUT: + + - ``i`` -- integer + + If this pullback `P` was constructed as ``Y.pullback(f_0, f_1, + ...)``, where `f_i: X_i \to Y`, then there are structure maps + `\bar{f}_i: P \to X_i`. This method constructs `\bar{f}_i`. + + EXAMPLES:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: K = RP5.quotient(RP5.n_skeleton(2)) + sage: Y = K.pullback(K.quotient_map(), K.base_point_map()) + sage: Y.structure_map(0) + Simplicial set morphism: + From: Pullback of maps: + Simplicial set morphism: + From: RP^5 + To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) + Defn: [1, f, f * f, f * f * f, f * f * f * f, f * f * f * f * f] --> [*, s_0 *, s_1 s_0 *, f * f * f, f * f * f * f, f * f * f * f * f] + Simplicial set morphism: + From: Point + To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) + Defn: Constant map at * + To: RP^5 + Defn: [(1, *), (f, s_0 *), (f * f, s_1 s_0 *)] --> [1, f, f * f] + sage: Y.structure_map(1).codomain() + Point + + These maps are also accessible via ``projection_map``:: + + sage: Y.projection_map(1).codomain() + Point + """ + if len(self._maps) == 1: + return self.Hom(self).identity() + f = {} + for x in self._translation: + f[x[1]] = x[0][i][0].apply_degeneracies(*x[0][i][1]) + codomain = self.defining_map(i).domain() + return self.Hom(codomain)(f) + + projection_map = structure_map + + def universal_property(self, *maps): + r""" + Return the map induced by ``maps``. + + INPUT: + + - ``maps`` -- maps from a simplicial set `Z` to the "factors" + `X_i` forming the pullback. + + If the pullback `P` is formed by maps `f_i: X_i \to Y`, then + given maps `g_i: Z \to X_i` such that `f_i g_i = f_j g_j` for + all `i`, `j`, then there is a unique map `g: Z \to P` making + the appropriate diagram commute. This constructs that map. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = S1.product(S1) + sage: K = T.factor(0, as_subset=True) + sage: f = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) + sage: P = S1.product(T) + sage: P.universal_property(S1.Hom(S1).identity(), f) + Simplicial set morphism: + From: S^1 + To: S^1 x S^1 x S^1 + Defn: [v_0, sigma_1] --> [(v_0, (v_0, v_0)), (sigma_1, (sigma_1, s_0 v_0))] + """ + if len(self._maps) != len(maps): + raise ValueError('wrong number of maps specified') + if len(self._maps) == 1: + return maps[0] + domain = maps[0].domain() + if any(g.domain() != domain for g in maps[1:]): + raise ValueError('the maps do not all have the same codomain') + composite = self._maps[0] * maps[0] + if any(f*g != composite for f,g in zip(self._maps[1:], maps[1:])): + raise ValueError('the maps are not compatible') + data = {} + translate = dict(self._translation) + for sigma in domain.nondegenerate_simplices(): + target = tuple([(f(sigma).nondegenerate(), tuple(f(sigma).degeneracies())) + for f in maps]) + # If there any degeneracies in common, remove them: the + # dictionary "translate" has nondegenerate simplices as + # its keys. + in_common = set.intersection(*[set(_[1]) for _ in target]) + if in_common: + target = tuple((tau, tuple(sorted(set(degens).difference(in_common), + reverse=True))) + for tau, degens in target) + in_common = sorted(in_common, reverse=True) + data[sigma] = translate[target].apply_degeneracies(*in_common) + return domain.Hom(self)(data) + +class Factors(object): + """ + Classes which inherit from this should define a ``_factors`` + attribute for their instances, and this class accesses that + attribute. This is used by :class:`ProductOfSimplicialSets`, + :class:`WedgeOfSimplicialSets`, and + :class:`DisjointUnionOfSimplicialSets`. + """ + def factors(self): + """ + Return the factors involved in this construction of simplicial sets. + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: S3 = simplicial_sets.Sphere(3) + sage: S2.wedge(S3).factors() == (S2, S3) + True + sage: S2.product(S3).factors()[0] + S^2 + """ + return self._factors + + def factor(self, i): + r""" + Return the $i$-th factor of this construction of simplicial sets. + + INPUT: + + - ``i`` -- integer, the index of the factor + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: S3 = simplicial_sets.Sphere(3) + sage: K = S2.disjoint_union(S3) + sage: K.factor(0) + S^2 + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: X = B.wedge(S3, B) + sage: X.factor(1) + S^3 + sage: X.factor(2) + Classifying space of Multiplicative Abelian group isomorphic to C2 + """ + return self.factors()[i] + + +class ProductOfSimplicialSets(PullbackOfSimplicialSets, Factors): + @staticmethod + def __classcall__(cls, factors=None): + """ + TESTS:: + + sage: from sage.topology.simplicial_set_constructions import ProductOfSimplicialSets + sage: S2 = simplicial_sets.Sphere(2) + sage: ProductOfSimplicialSets([S2, S2]) == ProductOfSimplicialSets((S2, S2)) + True + """ + if factors: + return super(ProductOfSimplicialSets, cls).__classcall__(cls, factors=tuple(factors)) + return super(ProductOfSimplicialSets, cls).__classcall__(cls) + + def __init__(self, factors=None): + r""" + Return the product of simplicial sets. + + INPUT: + + - ``factors`` -- a list or tuple of simplicial sets + + Return the product of the simplicial sets in ``factors``. + + If `X` and `Y` are simplicial sets, then their product `X + \times Y` is defined to be the simplicial set with + `n`-simplices `X_n \times Y_n`. Therefore the simplices in + the product have the form `(s_I \sigma, s_J \tau)`, where `s_I + = s_{i_1} ... s_{i_p}` and `s_J = s_{j_1} ... s_{j_q}` are + composites of degeneracy maps, written in decreasing order. + Such a simplex is nondegenerate if the indices `I` and `J` are + disjoint. Therefore if `\sigma` and `\tau` are nondegenerate + simplices of dimensions `m` and `n`, in the product they will + lead to nondegenerate simplices up to dimension `m+n`, and no + further. + + This extends in the more or less obvious way to products with + more than two factors: with three factors, a simplex `(s_I + \sigma, s_J \tau, s_K \rho)` is nondegenerate if `I \cap J + \cap K` is empty, etc. + + If a simplicial set is constructed as a product, the factors + are recorded and are accessible via the method + :meth:`Factors.factors`. If it is constructed as a product and then + copied, this information is lost. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: X = SimplicialSet({e: (v, w)}) + sage: square = X.product(X) + + ``square`` is now the standard triangulation of the square: 4 + vertices, 5 edges (the four on the border plus the diagonal), + 2 triangles:: + + sage: square.f_vector() + [4, 5, 2] + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = S1.product(S1) + sage: T.homology(reduced=False) + {0: Z, 1: Z x Z, 2: Z} + + Since ``S1`` is pointed, so is ``T``:: + + sage: S1.is_pointed() + True + sage: S1.base_point() + v_0 + sage: T.is_pointed() + True + sage: T.base_point() + (v_0, v_0) + + sage: S2 = simplicial_sets.Sphere(2) + sage: S3 = simplicial_sets.Sphere(3) + sage: Z = S2.product(S3) + sage: Z.homology() + {0: 0, 1: 0, 2: Z, 3: Z, 4: 0, 5: Z} + + Products involving infinite simplicial sets:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: B.rename('RP^oo') + sage: X = B.product(B) + sage: X + RP^oo x RP^oo + sage: X.n_cells(1) + [(f, f), (f, s_0 1), (s_0 1, f)] + sage: X.homology(range(3), base_ring=GF(2)) + {0: Vector space of dimension 0 over Finite Field of size 2, + 1: Vector space of dimension 2 over Finite Field of size 2, + 2: Vector space of dimension 3 over Finite Field of size 2} + sage: Y = B.product(S2) + sage: Y.homology(range(5), base_ring=GF(2)) + {0: Vector space of dimension 0 over Finite Field of size 2, + 1: Vector space of dimension 1 over Finite Field of size 2, + 2: Vector space of dimension 2 over Finite Field of size 2, + 3: Vector space of dimension 2 over Finite Field of size 2, + 4: Vector space of dimension 2 over Finite Field of size 2} + """ + PullbackOfSimplicialSets.__init__(self, [space.constant_map() + for space in factors]) + self._factors = factors + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the simplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + In the finite case, this returns the ordinary `n`-skeleton. In + the infinite case, it computes the `n`-skeleton of the product + of the `n`-skeleta of the factors. + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: S3 = simplicial_sets.Sphere(3) + sage: S2.product(S3).n_skeleton(2) + Simplicial set with 2 non-degenerate simplices + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: X = B.product(B) + sage: X.n_skeleton(2) + Simplicial set with 13 non-degenerate simplices + """ + n_skel = SimplicialSet_finite.n_skeleton + if self.is_finite(): + n_skel = SimplicialSet_finite.n_skeleton + return n_skel(self, n) + start, skel = self._n_skeleton + if start == n: + return skel + elif start > n: + return skel.n_skeleton(n) + ans = n_skel(ProductOfSimplicialSets_finite([X.n_skeleton(n) for X in self._factors]), n) + self._n_skeleton = (n, ans) + return ans + + def factor(self, i, as_subset=False): + r""" + Return the $i$-th factor of the product. + + INPUT: + + - ``i`` -- integer, the index of the factor + + - ``as_subset`` -- boolean, optional (default ``False``) + + If ``as_subset`` is ``True``, return the $i$-th factor as a + subsimplicial set of the product, identifying it with its + product with the base point in each other factor. As a + subsimplicial set, it comes equipped with an inclusion + map. This option will raise an error if any factor does not + have a base point. + + If ``as_subset`` is ``False``, return the $i$-th factor in + its original form as a simplicial set. + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: S3 = simplicial_sets.Sphere(3) + sage: K = S2.product(S3) + sage: K.factor(0) + S^2 + + sage: K.factor(0, as_subset=True) + Simplicial set with 2 non-degenerate simplices + sage: K.factor(0, as_subset=True).homology() + {0: 0, 1: 0, 2: Z} + + sage: K.factor(0) is S2 + True + sage: K.factor(0, as_subset=True) is S2 + False + """ + if as_subset: + if any(not _.is_pointed() for _ in self.factors()): + raise ValueError('"as_subset=True" is only valid ' + 'if each factor is pointed') + + basept_factors = [sset.base_point() for sset in self.factors()] + basept_factors = basept_factors[:i] + basept_factors[i+1:] + to_factors = dict((v,k) for k,v in self._translation) + simps = [] + for x in self.nondegenerate_simplices(): + simplices = [sigma[0] for sigma in to_factors[x]] + if simplices[:i] + simplices[i+1:] == basept_factors: + simps.append(x) + return self.subsimplicial_set(simps) + return self.factors()[i] + + def _repr_(self): + """ + Print representation + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: K = simplicial_sets.KleinBottle() + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: S2.product(S2) + S^2 x S^2 + sage: S2.product(K, B) + S^2 x Klein bottle x Classifying space of Multiplicative Abelian group isomorphic to C2 + """ + return ' x '.join([str(X) for X in self._factors]) + + def _latex_(self): + r""" + LaTeX representation + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: latex(S2.product(S2)) + S^{2} \times S^{2} + sage: RPoo = simplicial_sets.RealProjectiveSpace(Infinity) + sage: latex(S2.product(RPoo, S2)) + S^{2} \times RP^{\infty} \times S^{2} + """ + return ' \\times '.join([latex(X) for X in self._factors]) + + +class ProductOfSimplicialSets_finite(ProductOfSimplicialSets, PullbackOfSimplicialSets_finite): + r""" + The product of finite simplicial sets. + + When the factors are all finite, there are more methods available + for the resulting product, as compared to products with infinite + factors: projection maps, the wedge as a subcomplex, and the fat + wedge as a subcomplex. See :meth:`projection_map`, + :meth:`wedge_as_subset`, and :meth:`fat_wedge_as_subset` + """ + def __init__(self, factors=None): + r""" + Return the product of finite simplicial sets. + + See :class:`ProductOfSimplicialSets` for more information. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(1) + sage: X = SimplicialSet({e: (v, v)}) + sage: W = X.product(X, X) + sage: W.homology() + {0: 0, 1: Z x Z x Z, 2: Z x Z x Z, 3: Z} + sage: W.is_pointed() + False + + sage: X = X.set_base_point(v) + sage: w = AbstractSimplex(0, name='w') + sage: f = AbstractSimplex(1) + sage: Y = SimplicialSet({f: (v,w)}, base_point=w) + sage: Z = Y.product(X) + sage: Z.is_pointed() + True + sage: Z.base_point() + (w, v) + """ + PullbackOfSimplicialSets_finite.__init__(self, [space.constant_map() + for space in factors]) + self._factors = tuple([f.domain() for f in self._maps]) + + def projection_map(self, i): + """ + Return the map projecting onto the $i$-th factor. + + INPUT: + + - ``i`` -- integer, the index of the projection map + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: f_0 = T.projection_map(0) + sage: f_1 = T.projection_map(1) + sage: m_0 = f_0.induced_homology_morphism().to_matrix(1) # matrix in dim 1 + sage: m_1 = f_1.induced_homology_morphism().to_matrix(1) + sage: m_0.rank() + 1 + sage: m_0 == m_1 + False + """ + return self.structure_map(i) + + def wedge_as_subset(self): + """ + Return the wedge as a subsimplicial set of this product of pointed + simplicial sets. + + This will raise an error if any factor is not pointed. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(1, name='e') + sage: w = AbstractSimplex(0, name='w') + sage: f = AbstractSimplex(1, name='f') + sage: X = SimplicialSet({e: (v, v)}, base_point=v) + sage: Y = SimplicialSet({f: (w, w)}, base_point=w) + sage: P = X.product(Y) + sage: W = P.wedge_as_subset() + sage: W.nondegenerate_simplices() + [(v, w), (e, s_0 w), (s_0 v, f)] + sage: W.homology() + {0: 0, 1: Z x Z} + """ + basept_factors = [sset.base_point() for sset in self.factors()] + to_factors = dict((v,k) for k,v in self._translation) + simps = [] + for x in self.nondegenerate_simplices(): + simplices = to_factors[x] + not_base_pt = 0 + for sigma, star in zip(simplices, basept_factors): + if not_base_pt > 1: + continue + if sigma[0].nondegenerate() != star: + not_base_pt += 1 + if not_base_pt <= 1: + simps.append(x) + return self.subsimplicial_set(simps) + + def fat_wedge_as_subset(self): + """ + Return the fat wedge as a subsimplicial set of this product of + pointed simplicial sets. + + The fat wedge consists of those terms where at least one + factor is the base point. Thus with two factors this is the + ordinary wedge, but with more factors, it is larger. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: X = S1.product(S1, S1) + sage: W = X.fat_wedge_as_subset() + sage: W.homology() + {0: 0, 1: Z x Z x Z, 2: Z x Z x Z} + """ + basept_factors = [sset.base_point() for sset in self.factors()] + to_factors = {v: k for k, v in self._translation} + simps = [] + for x in self.nondegenerate_simplices(): + simplices = to_factors[x] + combined = zip(simplices, basept_factors) + if any(sigma[0] == pt for (sigma, pt) in combined): + simps.append(x) + return self.subsimplicial_set(simps) + + +class PushoutOfSimplicialSets(SimplicialSet_arbitrary, UniqueRepresentation): + @staticmethod + def __classcall_private__(cls, maps=None, vertex_name=None): + """ + TESTS:: + + sage: from sage.topology.simplicial_set_constructions import PushoutOfSimplicialSets + sage: S2 = simplicial_sets.Sphere(2) + sage: one = S2.Hom(S2).identity() + sage: PushoutOfSimplicialSets([one, one]) == PushoutOfSimplicialSets((one, one)) + True + """ + if maps: + return super(PushoutOfSimplicialSets, cls).__classcall__(cls, maps=tuple(maps), + vertex_name=vertex_name) + return super(PushoutOfSimplicialSets, cls).__classcall__(cls, vertex_name=vertex_name) + + def __init__(self, maps=None, vertex_name=None): + r""" + Return the pushout obtained from the morphisms ``maps``. + + INPUT: + + - ``maps`` -- a list or tuple of morphisms of simplicial sets + - ``vertex_name`` -- optional, default ``None`` + + If only a single map `f: X \to Y` is given, then return + `Y`. If no maps are given, return the empty simplicial + set. Otherwise, given a simplicial set `X` and maps `f_i: X + \to Y_i` for `0 \leq i \leq m`, construct the pushout `P`: see + :wikipedia:`Pushout_(category_theory)`. This is constructed as + pushouts of sets for each set of `n`-simplices, so `P_n` is + the disjoint union of the sets `(Y_i)_n`, with elements + `f_i(x)` identified for `n`-simplex `x` in `X`. + + Simplices in the pushout are given names as follows: if a + simplex comes from a single `Y_i`, it inherits its + name. Otherwise it must come from a simplex (or several) in + `X`, and then it inherits one of those names, and it should be + the first alphabetically. For example, if vertices `v`, `w`, + and `z` in `X` are glued together, then the resulting vertex + in the pushout will be called `v`. + + Base points are taken care of automatically: if each of the + maps `f_i` is pointed, so is the pushout. If `X` is a point or + if `X` is nonempty and any of the spaces `Y_i` is a point, use + those for the base point. In all of these cases, if + ``vertex_name`` is ``None``, generate the name of the base + point automatically; otherwise, use ``vertex_name`` for its + name. + + In all other cases, the pushout is not pointed. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: a = AbstractSimplex(0, name='a') + sage: b = AbstractSimplex(0, name='b') + sage: c = AbstractSimplex(0, name='c') + sage: e0 = AbstractSimplex(1, name='e_0') + sage: e1 = AbstractSimplex(1, name='e_1') + sage: e2 = AbstractSimplex(1, name='e_2') + sage: X = SimplicialSet({e2: (b, a)}) + sage: Y0 = SimplicialSet({e2: (b,a), e0: (c,b), e1: (c,a)}) + sage: Y1 = simplicial_sets.Simplex(0) + sage: f0_data = {a:a, b:b, e2: e2} + sage: v = Y1.n_cells(0)[0] + sage: f1_data = {a:v, b:v, e2:v.apply_degeneracies(0)} + sage: f0 = X.Hom(Y0)(f0_data) + sage: f1 = X.Hom(Y1)(f1_data) + sage: P = X.pushout(f0, f1) + sage: P.nondegenerate_simplices() + [a, c, e_0, e_1] + + There are defining maps `f_i: X \to Y_i` and structure maps + `\bar{f}_i: Y_i \to P`; the latter are only implemented in + Sage when each `Y_i` is finite. :: + + sage: P.defining_map(0) == f0 + True + sage: P.structure_map(1) + Simplicial set morphism: + From: 0-simplex + To: Pushout of maps: + Simplicial set morphism: + From: Simplicial set with 3 non-degenerate simplices + To: Simplicial set with 6 non-degenerate simplices + Defn: [a, b, e_2] --> [a, b, e_2] + Simplicial set morphism: + From: Simplicial set with 3 non-degenerate simplices + To: 0-simplex + Defn: Constant map at (0,) + Defn: Constant map at a + sage: P.structure_map(0).domain() == Y0 + True + sage: P.structure_map(0).codomain() == P + True + + An inefficient way of constructing a suspension for an + unpointed set: take the pushout of two copies of the inclusion + map `X \to CX`:: + + sage: T = simplicial_sets.Torus() + sage: T = T.unset_base_point() + sage: CT = T.cone() + sage: inc = CT.base_as_subset().inclusion_map() + sage: P = T.pushout(inc, inc) + sage: P.homology() + {0: 0, 1: 0, 2: Z x Z, 3: Z} + sage: len(P.nondegenerate_simplices()) + 20 + + It is more efficient to construct the suspension as the + quotient `CX/X`:: + + sage: len(CT.quotient(CT.base_as_subset()).nondegenerate_simplices()) + 8 + + It is more efficient still if the original simplicial set has + a base point:: + + sage: T = simplicial_sets.Torus() + sage: len(T.suspension().nondegenerate_simplices()) + 6 + + sage: S1 = simplicial_sets.Sphere(1) + sage: pt = simplicial_sets.Point() + sage: bouquet = pt.pushout(S1.base_point_map(), S1.base_point_map(), S1.base_point_map()) + sage: bouquet.homology(1) + Z x Z x Z + """ + # Import this here to prevent circular imports. + from sage.topology.simplicial_set_morphism import SimplicialSetMorphism + if maps and any(not isinstance(f, SimplicialSetMorphism) for f in maps): + raise ValueError('the maps must be morphisms of simplicial sets') + Cat = SimplicialSets() + if maps: + if all(f.codomain().is_finite() for f in maps): + Cat = Cat.Finite() + if all(f.is_pointed() for f in maps): + Cat = Cat.Pointed() + Parent.__init__(self, category=Cat) + self._maps = maps + self._n_skeleton = (-1, Empty()) + self._vertex_name = vertex_name + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the simplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + The `n`-skeleton of the pushout is computed as the pushout + of the `n`-skeleta of the component simplicial sets. + + EXAMPLES:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: K = B.n_skeleton(3) + sage: Q = K.pushout(K.inclusion_map(), K.constant_map()) + sage: Q.n_skeleton(5).homology() + {0: 0, 1: 0, 2: 0, 3: 0, 4: Z, 5: Z} + + Of course, computing the `n`-skeleton and then taking homology + need not yield the same answer as asking for homology through + dimension `n`, since the latter computation will use the + `(n+1)`-skeleton:: + + sage: Q.homology(range(6)) + {0: 0, 1: 0, 2: 0, 3: 0, 4: Z, 5: C2} + """ + if self.is_finite(): + maps = self._maps + if maps: + domain = SimplicialSet_finite.n_skeleton(maps[0].domain(), n) + codomains = [SimplicialSet_finite.n_skeleton(f.codomain(), n) for f in maps] + new_maps = [f.n_skeleton(n, domain, c) for (f, c) in zip(maps, codomains)] + return PushoutOfSimplicialSets_finite(new_maps, + vertex_name=self._vertex_name) + return PushoutOfSimplicialSets_finite(maps, + vertex_name=self._vertex_name) + start, skel = self._n_skeleton + if start == n: + return skel + elif start > n: + return skel.n_skeleton(n) + ans = PushoutOfSimplicialSets_finite([f.n_skeleton(n) for f in self._maps], + vertex_name=self._vertex_name) + self._n_skeleton = (n, ans) + return ans + + def defining_map(self, i): + r""" + Return the `i`-th map defining the pushout. + + INPUT: + + - ``i`` -- integer + + If this pushout was constructed as ``X.pushout(f_0, f_1, ...)``, + this returns `f_i`. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = simplicial_sets.Torus() + sage: X = S1.wedge(T) # a pushout + sage: X.defining_map(0) + Simplicial set morphism: + From: Point + To: S^1 + Defn: Constant map at v_0 + sage: X.defining_map(1).domain() + Point + sage: X.defining_map(1).codomain() + Torus + """ + return self._maps[i] + + def _repr_(self): + """ + Print representation + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: S3 = simplicial_sets.Sphere(3) + sage: pt = simplicial_sets.Point() + sage: pt.pushout(S2.base_point_map(), S3.base_point_map()) + Pushout of maps: + Simplicial set morphism: + From: Point + To: S^2 + Defn: Constant map at v_0 + Simplicial set morphism: + From: Point + To: S^3 + Defn: Constant map at v_0 + """ + if not self._maps: + return 'Empty simplicial set' + s = 'Pushout of maps:' + for f in self._maps: + t = '\n' + str(f) + s += t.replace('\n', '\n ') + return s + + +class PushoutOfSimplicialSets_finite(PushoutOfSimplicialSets, SimplicialSet_finite): + """ + The pushout of finite simplicial sets obtained from ``maps``. + + When the simplicial sets involved are all finite, there are more + methods available to the resulting pushout, as compared to case + when some of the components are infinite: the structure maps to the + pushout and the pushout's universal property: see + :meth:`structure_map` and :meth:`universal_property`. + """ + @staticmethod + def __classcall_private__(cls, maps=None, vertex_name=None): + """ + TESTS:: + + sage: from sage.topology.simplicial_set_constructions import PushoutOfSimplicialSets_finite + sage: S2 = simplicial_sets.Sphere(2) + sage: one = S2.Hom(S2).identity() + sage: PushoutOfSimplicialSets_finite([one, one]) == PushoutOfSimplicialSets_finite((one, one)) + True + """ + if maps: + return super(PushoutOfSimplicialSets_finite, cls).__classcall__(cls, maps=tuple(maps), + vertex_name=vertex_name) + return super(PushoutOfSimplicialSets_finite, cls).__classcall__(cls, vertex_name=vertex_name) + + def __init__(self, maps=None, vertex_name=None): + r""" + Return the pushout obtained from the morphisms ``maps``. + + See :class:`PushoutOfSimplicialSets` for more information. + + INPUT: + + - ``maps`` -- a list or tuple of morphisms of simplicial sets + - ``vertex_name`` -- optional, default ``None`` + + EXAMPLES:: + + sage: from sage.topology.simplicial_set_constructions import PushoutOfSimplicialSets_finite + sage: T = simplicial_sets.Torus() + sage: S2 = simplicial_sets.Sphere(2) + sage: PushoutOfSimplicialSets_finite([T.base_point_map(), S2.base_point_map()]).n_cells(0)[0] + * + sage: PushoutOfSimplicialSets_finite([T.base_point_map(), S2.base_point_map()], vertex_name='v').n_cells(0)[0] + v + """ + # Import this here to prevent circular imports. + from sage.topology.simplicial_set_morphism import SimplicialSetMorphism + if maps and any(not isinstance(f, SimplicialSetMorphism) for f in maps): + raise ValueError('the maps must be morphisms of simplicial sets') + if not maps: + SimplicialSet_finite.__init__(self, {}) + self._maps = () + self._structure = () + return + domain = maps[0].domain() + if len(maps) == 1: + # f: X --> Y + f = maps[0] + codomain = f.codomain() + if f.is_pointed(): + base_point=codomain.base_point() + if vertex_name is not None: + base_point.rename(vertex_name) + SimplicialSet_finite.__init__(self, codomain.face_data(), + base_point=base_point) + elif len(domain.nondegenerate_simplices()) == 1: + # X is a point. + base_point = f(domain().n_cells(0)[0]) + if vertex_name is not None: + base_point.rename(vertex_name) + SimplicialSet_finite.__init__(self, codomain.face_data(), + base_point=base_point) + elif len(codomain.nondegenerate_simplices()) == 1: + # Y is a point. + base_point = codomain.n_cells(0)[0] + if vertex_name is not None: + base_point.rename(vertex_name) + SimplicialSet_finite.__init__(self, codomain.face_data(), + base_point=base_point) + else: + SimplicialSet_finite.__init__(self, codomain.face_data()) + self._maps = (f,) + self._structure = (f,) + return + if any(domain != f.domain() for f in maps[1:]): + raise ValueError('the domains of the maps must be equal') + # Data to define the pushout: + data = {} + codomains = [f.codomain() for f in maps] + # spaces: indexed list of spaces. Entries are of the form + # (space, int) where int=-1 for the domain, and for the + # codomains, int is the corresponding index. + spaces = [(Y,i-1) for (i,Y) in enumerate([domain] + codomains)] + # Dictionaries to translate from simplices in domain, + # codomains to simplices in the pushout. The keys are of the + # form (space, int). int=-1 for the domain, and for the + # codomains, int is the corresponding index. + _to_P = {Y:{} for Y in spaces} + max_dim = max(Y.dimension() for Y in codomains) + for n in range(1 + max_dim): + # Now we impose an equivalence relation on the simplices, + # setting x equivalent to f_i(x) for each simplex x in X + # and each defining map f_i. We do this by constructing a + # graph and finding its connected components: the vertices + # of the graph are the n-cells of X and the Y_i, and + # there are edges from x to f_i(x). + vertices = [] + for (Y,i) in spaces: + vertices.extend([(cell,i) for cell in Y.n_cells(n)]) + edges = [] + for x in domain.n_cells(n): + edges.extend([[(x,-1), (f(x),i)] for (i,f) in enumerate(maps)]) + G = Graph([vertices, edges], format='vertices_and_edges') + data[n] = [set(_) for _ in G.connected_components()] + # data is now a dictionary indexed by dimension, and data[n] + # consists of sets of n-simplices of the domain and the + # codomains, each set an equivalence class of n-simplices + # under the gluing. So if any element of one of those sets is + # degenerate, we can throw the whole thing away. Otherwise, we + # can choose a representative to compute the faces. + simplices = {} + for dim in sorted(data): + for s in data[dim]: + degenerate = any(sigma[0].is_degenerate() for sigma in s) + if degenerate: + # Identify the degeneracies involved. + degens = [] + for (sigma, j) in s: + if len(sigma.degeneracies()) > len(degens): + degens = sigma.degeneracies() + space = spaces[j+1] + old = _to_P[space][sigma.nondegenerate()] + for (sigma,j) in s: + # Now update the _to_P[space] dictionaries. + space = spaces[j+1] + _to_P[space][sigma] = old.apply_degeneracies(*degens) + else: # nondegenerate + if len(s) == 1: + name = str(list(s)[0][0]) + latex_name = latex(list(s)[0][0]) + else: + # Choose a name from a simplex in domain. + for (sigma,j) in sorted(s): + if j == -1: + name = str(sigma) + latex_name = latex(sigma) + break + new = AbstractSimplex(dim, name=name, + latex_name=latex_name) + if dim == 0: + faces = None + for (sigma,j) in s: + space = spaces[j+1] + _to_P[space][sigma] = new + if dim > 0: + faces = [_to_P[space][tau.nondegenerate()].apply_degeneracies(*tau.degeneracies()) + for tau in space[0].faces(sigma)] + simplices[new] = faces + + some_Y_is_pt = False + if len(domain.nondegenerate_simplices()) > 1: + # Only investigate this if X is not empty and not a point. + for (Y,i) in spaces: + if len(Y.nondegenerate_simplices()) == 1: + some_Y_is_pt = True + break + if len(domain.nondegenerate_simplices()) == 1: + # X is a point. + base_point = _to_P[(domain,-1)][domain.n_cells(0)[0]] + if vertex_name is not None: + base_point.rename(vertex_name) + SimplicialSet_finite.__init__(self, simplices, base_point=base_point) + elif some_Y_is_pt: + # We found (Y,i) above. + base_point = _to_P[(Y,i)][Y.n_cells(0)[0]] + if vertex_name is not None: + base_point.rename(vertex_name) + SimplicialSet_finite.__init__(self, simplices, base_point=base_point) + elif all(f.is_pointed() for f in maps): + pt = _to_P[(codomains[0],0)][codomains[0].base_point()] + if any(_to_P[(Y,i)][Y.base_point()] != pt for (Y,i) in spaces[2:]): + raise ValueError('something unexpected went wrong ' + 'with base points') + base_point = _to_P[(domain,-1)][domain.base_point()] + if vertex_name is not None: + base_point.rename(vertex_name) + SimplicialSet_finite.__init__(self, simplices, base_point=base_point) + else: + SimplicialSet_finite.__init__(self, simplices) + # The relevant maps: + self._maps = maps + self._structure = tuple([Y.Hom(self)(_to_P[(Y,i)]) + for (Y,i) in spaces[1:]]) + self._vertex_name = vertex_name + + def structure_map(self, i): + r""" + Return the $i$-th structure map of the pushout. + + INPUT: + + - ``i`` -- integer + + If this pushout `Z` was constructed as ``X.pushout(f_0, f_1, ...)``, + where `f_i: X \to Y_i`, then there are structure maps + `\bar{f}_i: Y_i \to Z`. This method constructs `\bar{f}_i`. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = simplicial_sets.Torus() + sage: X = S1.disjoint_union(T) # a pushout + sage: X.structure_map(0) + Simplicial set morphism: + From: S^1 + To: Disjoint union: (S^1 u Torus) + Defn: [v_0, sigma_1] --> [v_0, sigma_1] + sage: X.structure_map(1).domain() + Torus + sage: X.structure_map(1).codomain() + Disjoint union: (S^1 u Torus) + """ + return self._structure[i] + + def universal_property(self, *maps): + r""" + Return the map induced by ``maps`` + + INPUT: + + - ``maps`` -- maps "factors" `Y_i` forming the pushout to a + fixed simplicial set `Z`. + + If the pushout `P` is formed by maps `f_i: X \to Y_i`, then + given maps `g_i: Y_i \to Z` such that `g_i f_i = g_j f_j` for + all `i`, `j`, then there is a unique map `g: P \to Z` making + the appropriate diagram commute. This constructs that map. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: x = AbstractSimplex(0, name='x') + sage: evw = AbstractSimplex(1, name='vw') + sage: evx = AbstractSimplex(1, name='vx') + sage: ewx = AbstractSimplex(1, name='wx') + sage: X = SimplicialSet({evw: (w, v), evx: (x, v)}) + sage: Y_0 = SimplicialSet({evw: (w, v), evx: (x, v), ewx: (x, w)}) + sage: Y_1 = SimplicialSet({evx: (x, v)}) + + sage: f_0 = Hom(X, Y_0)({v:v, w:w, x:x, evw:evw, evx:evx}) + sage: f_1 = Hom(X, Y_1)({v:v, w:v, x:x, evw:v.apply_degeneracies(0), evx:evx}) + sage: P = X.pushout(f_0, f_1) + + sage: one = Hom(Y_1, Y_1).identity() + sage: g = Hom(Y_0, Y_1)({v:v, w:v, x:x, evw:v.apply_degeneracies(0), evx:evx, ewx:evx}) + sage: P.universal_property(g, one) + Simplicial set morphism: + From: Pushout of maps: + Simplicial set morphism: + From: Simplicial set with 5 non-degenerate simplices + To: Simplicial set with 6 non-degenerate simplices + Defn: [v, w, x, vw, vx] --> [v, w, x, vw, vx] + Simplicial set morphism: + From: Simplicial set with 5 non-degenerate simplices + To: Simplicial set with 3 non-degenerate simplices + Defn: [v, w, x, vw, vx] --> [v, v, x, s_0 v, vx] + To: Simplicial set with 3 non-degenerate simplices + Defn: [v, x, vx, wx] --> [v, x, vx, vx] + """ + codomain = maps[0].codomain() + if any(g.codomain() != codomain for g in maps[1:]): + raise ValueError('the maps do not all have the same codomain') + composite = maps[0] * self._maps[0] + if any(g*f != composite for g,f in zip(maps[1:], self._maps[1:])): + raise ValueError('the maps are not compatible') + data = {} + for i,g in enumerate(maps): + f_i_dict = self.structure_map(i)._dictionary + for sigma in f_i_dict: + tau = f_i_dict[sigma] + # For sigma_i in Y_i, define the map G by + # G(\bar{f}_i)(sigma_i) = g_i(sigma_i). + if tau not in data: + data[tau] = g(sigma) + return self.Hom(codomain)(data) + + +class QuotientOfSimplicialSet(PushoutOfSimplicialSets): + def __init__(self, inclusion, vertex_name='*'): + r""" + Return the quotient of a simplicial set by a subsimplicial set. + + INPUT: + + - ``inclusion`` -- inclusion map of a subcomplex (= + subsimplicial set) of a simplicial set + - ``vertex_name`` -- optional, default ``'*'`` + + A subcomplex `A` comes equipped with the inclusion map `A \to + X` to its ambient complex `X`, and this constructs the + quotient `X/A`, collapsing `A` to a point. The resulting point + is called ``vertex_name``, which is ``'*'`` by default. + + When the simplicial sets involved are finite, there is a + :meth:`QuotientOfSimplicialSet_finite.quotient_map` method available. + + EXAMPLES:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP2 = RP5.n_skeleton(2) + sage: RP5_2 = RP5.quotient(RP2) + sage: RP5_2 + Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) + sage: RP5_2.quotient_map() + Simplicial set morphism: + From: RP^5 + To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) + Defn: [1, f, f * f, f * f * f, f * f * f * f, f * f * f * f * f] --> [*, s_0 *, s_1 s_0 *, f * f * f, f * f * f * f, f * f * f * f * f] + """ + subcomplex = inclusion.domain() + PushoutOfSimplicialSets.__init__(self, [inclusion, + subcomplex.constant_map()], + vertex_name=vertex_name) + + ambient = inclusion.codomain() + if ambient.is_pointed() and ambient.is_finite(): + if ambient.base_point() not in subcomplex: + self._basepoint = self.structure_map(0)(ambient.base_point()) + + def ambient(self): + """ + Return the ambient space. + + That is, if this quotient is `K/L`, return `K`. + + EXAMPLES:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP2 = RP5.n_skeleton(2) + sage: RP5_2 = RP5.quotient(RP2) + sage: RP5_2.ambient() + RP^5 + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: K = B.n_skeleton(3) + sage: Q = B.quotient(K) + sage: Q.ambient() + Classifying space of Multiplicative Abelian group isomorphic to C2 + """ + return self._maps[0].codomain() + + def subcomplex(self): + """ + Return the subcomplex space associated to this quotient. + + That is, if this quotient is `K/L`, return `L`. + + EXAMPLES:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP2 = RP5.n_skeleton(2) + sage: RP5_2 = RP5.quotient(RP2) + sage: RP5_2.subcomplex() + Simplicial set with 3 non-degenerate simplices + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: K = B.n_skeleton(3) + sage: Q = B.quotient(K) + sage: Q.subcomplex() + Simplicial set with 4 non-degenerate simplices + """ + return self._maps[0].domain() + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the simplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + The `n`-skeleton of the quotient is computed as the quotient + of the `n`-skeleta. + + EXAMPLES:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: K = B.n_skeleton(3) + sage: Q = B.quotient(K) + sage: Q.n_skeleton(6) + Quotient: (Simplicial set with 7 non-degenerate simplices/Simplicial set with 4 non-degenerate simplices) + sage: Q.n_skeleton(6).homology() + {0: 0, 1: 0, 2: 0, 3: 0, 4: Z, 5: C2, 6: 0} + """ + if self.is_finite(): + ambient = SimplicialSet_finite.n_skeleton(self.ambient(), n) + subcomplex = SimplicialSet_finite.n_skeleton(self.subcomplex(), n) + subcomplex = ambient.subsimplicial_set(subcomplex.nondegenerate_simplices()) + return QuotientOfSimplicialSet_finite(subcomplex.inclusion_map(), + vertex_name=self._vertex_name) + start, skel = self._n_skeleton + if start == n: + return skel + elif start > n: + return skel.n_skeleton(n) + ambient = self.ambient().n_skeleton(n) + subcomplex = ambient.subsimplicial_set(self.subcomplex().nondegenerate_simplices(n)) + ans = QuotientOfSimplicialSet_finite(subcomplex.inclusion_map(), + vertex_name=self._vertex_name) + self._n_skeleton = (n, ans) + return ans + + def _repr_(self): + """ + Print representation + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: T.quotient(T.n_skeleton(1)) + Quotient: (Torus/Simplicial set with 4 non-degenerate simplices) + """ + return 'Quotient: ({}/{})'.format(self.ambient(), self.subcomplex()) + + def _latex_(self): + r""" + LaTeX representation + + EXAMPLES:: + + sage: RPoo = simplicial_sets.RealProjectiveSpace(Infinity) + sage: RP3 = RPoo.n_skeleton(3) + sage: RP3.rename_latex('RP^{3}') + sage: latex(RPoo.quotient(RP3)) + RP^{\infty} / RP^{3} + """ + return '{} / {}'.format(latex(self.ambient()), latex(self.subcomplex())) + + +class QuotientOfSimplicialSet_finite(QuotientOfSimplicialSet, + PushoutOfSimplicialSets_finite): + """ + The quotient of finite simplicial sets. + + When the simplicial sets involved are finite, there is a + :meth:`quotient_map` method available. + """ + def __init__(self, inclusion, vertex_name='*'): + r""" + Return the quotient of a simplicial set by a subsimplicial set. + + See :class:`QuotientOfSimplicialSet` for more information. + + EXAMPLES:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP2 = RP5.n_skeleton(2) + sage: RP5_2 = RP5.quotient(RP2) + sage: RP5_2 + Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) + sage: RP5_2.quotient_map() + Simplicial set morphism: + From: RP^5 + To: Quotient: (RP^5/Simplicial set with 3 non-degenerate simplices) + Defn: [1, f, f * f, f * f * f, f * f * f * f, f * f * f * f * f] --> [*, s_0 *, s_1 s_0 *, f * f * f, f * f * f * f, f * f * f * f * f] + """ + subcomplex = inclusion.domain() + PushoutOfSimplicialSets_finite.__init__(self, [inclusion, + subcomplex.constant_map()], + vertex_name=vertex_name) + ambient = inclusion.codomain() + if ambient.is_pointed(): + if ambient.base_point() not in subcomplex: + self._basepoint = self.structure_map(0)(ambient.base_point()) + + def quotient_map(self): + """ + Return the quotient map from the original simplicial set to the + quotient. + + EXAMPLES:: + + sage: K = simplicial_sets.Simplex(1) + sage: S1 = K.quotient(K.n_skeleton(0)) + sage: q = S1.quotient_map() + sage: q + Simplicial set morphism: + From: 1-simplex + To: Quotient: (1-simplex/Simplicial set with 2 non-degenerate simplices) + Defn: [(0,), (1,), (0, 1)] --> [*, *, (0, 1)] + sage: q.domain() == K + True + sage: q.codomain() == S1 + True + """ + return self.structure_map(0) + + +class SmashProductOfSimplicialSets_finite(QuotientOfSimplicialSet_finite, + Factors): + @staticmethod + def __classcall__(cls, factors=None): + """ + TESTS:: + + sage: from sage.topology.simplicial_set_constructions import SmashProductOfSimplicialSets_finite as Smash + sage: S2 = simplicial_sets.Sphere(2) + sage: Smash([S2, S2]) == Smash((S2, S2)) + True + """ + if factors: + return super(SmashProductOfSimplicialSets_finite, cls).__classcall__(cls, factors=tuple(factors)) + return super(SmashProductOfSimplicialSets_finite, cls).__classcall__(cls) + + def __init__(self, factors=None): + r""" + Return the smash product of finite pointed simplicial sets. + + INPUT: + + - ``factors`` -- a list or tuple of simplicial sets + + Return the smash product of the simplicial sets in + ``factors``: the smash product `X \wedge Y` is defined to be + the quotient `(X \times Y) / (X \vee Y)`, where `X \vee Y` is + the wedge sum. + + Each element of ``factors`` must be finite and pointed. (As of + July 2016, constructing the wedge as a subcomplex of the + product is only possible in Sage for finite simplicial sets.) + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: S2 = simplicial_sets.Sphere(2) + sage: T.smash_product(S2).homology() == T.suspension(2).homology() + True + """ + if any(not space.is_pointed() for space in factors): + raise ValueError('the simplicial sets must be pointed') + prod = ProductOfSimplicialSets_finite(factors) + wedge = prod.wedge_as_subset() + QuotientOfSimplicialSet_finite.__init__(self, wedge.inclusion_map()) + self._factors = factors + + def _repr_(self): + """ + Print representation + + EXAMPLES:: + + sage: RP4 = simplicial_sets.RealProjectiveSpace(4) + sage: S1 = simplicial_sets.Sphere(1) + sage: S1.smash_product(RP4, S1) + Smash product: (S^1 ^ RP^4 ^ S^1) + """ + s = 'Smash product: (' + s += ' ^ '.join([str(X) for X in self._factors]) + s += ')' + return s + + def _latex_(self): + r""" + LaTeX representation + + EXAMPLES:: + + sage: RP4 = simplicial_sets.RealProjectiveSpace(4) + sage: S1 = simplicial_sets.Sphere(1) + sage: latex(S1.smash_product(RP4, S1)) + S^{1} \wedge RP^{4} \wedge S^{1} + """ + return ' \\wedge '.join([latex(X) for X in self._factors]) + + +class WedgeOfSimplicialSets(PushoutOfSimplicialSets, Factors): + @staticmethod + def __classcall__(cls, factors=None): + """ + TESTS:: + + sage: from sage.topology.simplicial_set_constructions import WedgeOfSimplicialSets + sage: S2 = simplicial_sets.Sphere(2) + sage: WedgeOfSimplicialSets([S2, S2]) == WedgeOfSimplicialSets((S2, S2)) + True + """ + if factors: + return super(WedgeOfSimplicialSets, cls).__classcall__(cls, factors=tuple(factors)) + return super(WedgeOfSimplicialSets, cls).__classcall__(cls) + + def __init__(self, factors=None): + r""" + Return the wedge sum of pointed simplicial sets. + + INPUT: + + - ``factors`` -- a list or tuple of simplicial sets + + Return the wedge of the simplicial sets in ``factors``: the + wedge sum `X \vee Y` is formed by taking the disjoint + union of `X` and `Y` and identifying their base points. In + this construction, the new base point is renamed '*'. + + The wedge comes equipped with maps to and from each factor, or + actually, maps from each factor, and maps to simplicial sets + isomorphic to each factor. The codomains of the latter maps + are quotients of the wedge, not identical to the original + factors. + + EXAMPLES:: + + sage: CP2 = simplicial_sets.ComplexProjectiveSpace(2) + sage: K = simplicial_sets.KleinBottle() + sage: W = CP2.wedge(K) + sage: W.homology() + {0: 0, 1: Z x C2, 2: Z, 3: 0, 4: Z} + + sage: W.inclusion_map(1) + Simplicial set morphism: + From: Klein bottle + To: Wedge: (CP^2 v Klein bottle) + Defn: [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] --> [*, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] + + sage: W.projection_map(0).domain() + Wedge: (CP^2 v Klein bottle) + sage: W.projection_map(0).codomain() # copy of CP^2 + Quotient: (Wedge: (CP^2 v Klein bottle)/Simplicial set with 6 non-degenerate simplices) + sage: W.projection_map(0).codomain().homology() + {0: 0, 1: 0, 2: Z, 3: 0, 4: Z} + + An error occurs if any of the factors is not pointed:: + + sage: CP2.wedge(simplicial_sets.Simplex(1)) + Traceback (most recent call last): + ... + ValueError: the simplicial sets must be pointed + """ + if any(not space.is_pointed() for space in factors): + raise ValueError('the simplicial sets must be pointed') + PushoutOfSimplicialSets.__init__(self, [space.base_point_map() + for space in factors]) + if factors: + vertices = PushoutOfSimplicialSets_finite([space.n_skeleton(0).base_point_map() + for space in factors]) + self._basepoint = vertices.base_point() + self.base_point().rename('*') + self._factors = factors + + summands = Factors.factors + summand = Factors.factor + + def _repr_(self): + """ + Print representation + + EXAMPLES:: + + sage: K = simplicial_sets.KleinBottle() + sage: K.wedge(K, K) + Wedge: (Klein bottle v Klein bottle v Klein bottle) + """ + s = 'Wedge: (' + s += ' v '.join([str(X) for X in self._factors]) + s += ')' + return s + + def _latex_(self): + """ + LaTeX representation + + EXAMPLES:: + + sage: RP4 = simplicial_sets.RealProjectiveSpace(4) + sage: S1 = simplicial_sets.Sphere(1) + sage: latex(S1.wedge(RP4, S1)) + S^{1} \vee RP^{4} \vee S^{1} + """ + return ' \\vee '.join([latex(X) for X in self._factors]) + + +class WedgeOfSimplicialSets_finite(WedgeOfSimplicialSets, PushoutOfSimplicialSets_finite): + """ + The wedge sum of finite pointed simplicial sets. + """ + def __init__(self, factors=None): + r""" + Return the wedge sum of finite pointed simplicial sets. + + INPUT: + + - ``factors`` -- a tuple of simplicial sets + + If there are no factors, a point is returned. + + See :class:`WedgeOfSimplicialSets` for more information. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set_constructions import WedgeOfSimplicialSets_finite + sage: K = simplicial_sets.Simplex(3) + sage: WedgeOfSimplicialSets_finite((K,K)) + Traceback (most recent call last): + ... + ValueError: the simplicial sets must be pointed + """ + if not factors: + # An empty wedge is a point, constructed as a pushout. + PushoutOfSimplicialSets_finite.__init__(self, [Point().identity()]) + else: + if any(not space.is_pointed() for space in factors): + raise ValueError('the simplicial sets must be pointed') + PushoutOfSimplicialSets_finite.__init__(self, [space.base_point_map() + for space in factors]) + self.base_point().rename('*') + self._factors = factors + + def inclusion_map(self, i): + """ + Return the inclusion map of the $i$-th factor. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: S2 = simplicial_sets.Sphere(2) + sage: W = S1.wedge(S2, S1) + sage: W.inclusion_map(1) + Simplicial set morphism: + From: S^2 + To: Wedge: (S^1 v S^2 v S^1) + Defn: [v_0, sigma_2] --> [*, sigma_2] + sage: W.inclusion_map(0).domain() + S^1 + sage: W.inclusion_map(2).domain() + S^1 + """ + return self.structure_map(i) + + def projection_map(self, i): + """ + Return the projection map onto the $i$-th factor. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: S2 = simplicial_sets.Sphere(2) + sage: W = S1.wedge(S2, S1) + sage: W.projection_map(1) + Simplicial set morphism: + From: Wedge: (S^1 v S^2 v S^1) + To: Quotient: (Wedge: (S^1 v S^2 v S^1)/Simplicial set with 3 non-degenerate simplices) + Defn: [*, sigma_1, sigma_1, sigma_2] --> [*, s_0 *, s_0 *, sigma_2] + sage: W.projection_map(1).image().homology(1) + 0 + sage: W.projection_map(1).image().homology(2) + Z + """ + m = len(self._factors) + simplices = ([self.inclusion_map(j).image().nondegenerate_simplices() + for j in range(i)] + + [self.inclusion_map(j).image().nondegenerate_simplices() + for j in range(i+1,m)]) + return self.quotient(list(itertools.chain(*simplices))).quotient_map() + + +class DisjointUnionOfSimplicialSets(PushoutOfSimplicialSets, Factors): + @staticmethod + def __classcall__(cls, factors=None): + """ + TESTS:: + + sage: from sage.topology.simplicial_set_constructions import DisjointUnionOfSimplicialSets + sage: from sage.topology.simplicial_set_examples import Empty + sage: S2 = simplicial_sets.Sphere(2) + sage: DisjointUnionOfSimplicialSets([S2, S2]) == DisjointUnionOfSimplicialSets((S2, S2)) + True + sage: DisjointUnionOfSimplicialSets([S2, Empty(), S2, Empty()]) == DisjointUnionOfSimplicialSets((S2, S2)) + True + """ + if factors: + # Discard any empty factors. + factors = [F for F in factors if F != Empty()] + if factors: + return super(DisjointUnionOfSimplicialSets, cls).__classcall__(cls, factors=tuple(factors)) + return super(DisjointUnionOfSimplicialSets, cls).__classcall__(cls) + + def __init__(self, factors=None): + r""" + Return the disjoint union of simplicial sets. + + INPUT: + + - ``factors`` -- a list or tuple of simplicial sets + + Discard any factors which are empty and return the disjoint + union of the remaining simplicial sets in ``factors``. The + disjoint union comes equipped with a map from each factor, as + long as all of the factors are finite. + + EXAMPLES:: + + sage: CP2 = simplicial_sets.ComplexProjectiveSpace(2) + sage: K = simplicial_sets.KleinBottle() + sage: W = CP2.disjoint_union(K) + sage: W.homology() + {0: Z, 1: Z x C2, 2: Z, 3: 0, 4: Z} + + sage: W.inclusion_map(1) + Simplicial set morphism: + From: Klein bottle + To: Disjoint union: (CP^2 u Klein bottle) + Defn: [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] --> [Delta_{0,0}, Delta_{1,0}, Delta_{1,1}, Delta_{1,2}, Delta_{2,0}, Delta_{2,1}] + """ + PushoutOfSimplicialSets.__init__(self, [space._map_from_empty_set() + for space in factors]) + self._factors = factors + self._n_skeleton = (-1, Empty()) + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the simplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + The `n`-skeleton of the disjoint union is computed as the + disjoint union of the `n`-skeleta of the component simplicial + sets. + + EXAMPLES:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: T = simplicial_sets.Torus() + sage: X = B.disjoint_union(T) + sage: X.n_skeleton(3).homology() + {0: Z, 1: Z x Z x C2, 2: Z, 3: Z} + """ + if self.is_finite(): + return DisjointUnionOfSimplicialSets_finite(tuple([X.n_skeleton(n) + for X in self._factors])) + start, skel = self._n_skeleton + if start == n: + return skel + elif start > n: + return skel.n_skeleton(n) + ans = DisjointUnionOfSimplicialSets_finite(tuple([X.n_skeleton(n) + for X in self._factors])) + self._n_skeleton = (n, ans) + return ans + + summands = Factors.factors + summand = Factors.factor + + def _repr_(self): + """ + Print representation + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: RP3 = simplicial_sets.RealProjectiveSpace(3) + sage: T.disjoint_union(T, RP3) + Disjoint union: (Torus u Torus u RP^3) + """ + s = 'Disjoint union: (' + s += ' u '.join([str(X) for X in self._factors]) + s += ')' + return s + + def _latex_(self): + """ + LaTeX representation + + EXAMPLES:: + + sage: RP4 = simplicial_sets.RealProjectiveSpace(4) + sage: S1 = simplicial_sets.Sphere(1) + sage: latex(S1.disjoint_union(RP4, S1)) + S^{1} \amalg RP^{4} \amalg S^{1} + """ + return ' \\amalg '.join([latex(X) for X in self._factors]) + + +class DisjointUnionOfSimplicialSets_finite(DisjointUnionOfSimplicialSets, + PushoutOfSimplicialSets_finite): + """ + The disjoint union of finite simplicial sets. + """ + def __init__(self, factors=None): + r""" + Return the disjoint union of finite simplicial sets. + + INPUT: + + - ``factors`` -- a tuple of simplicial sets + + Return the disjoint union of the simplicial sets in + ``factors``. The disjoint union comes equipped with a map + from each factor. If there are no factors, this returns the + empty simplicial set. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set_constructions import DisjointUnionOfSimplicialSets_finite + sage: from sage.topology.simplicial_set_examples import Empty + sage: S = simplicial_sets.Sphere(4) + sage: DisjointUnionOfSimplicialSets_finite((S,S,S)) + Disjoint union: (S^4 u S^4 u S^4) + sage: DisjointUnionOfSimplicialSets_finite([Empty(), Empty()]) == Empty() + True + """ + if not factors: + PushoutOfSimplicialSets_finite.__init__(self) + else: + PushoutOfSimplicialSets_finite.__init__(self, [space._map_from_empty_set() + for space in factors]) + self._factors = factors + + def inclusion_map(self, i): + """ + Return the inclusion map of the $i$-th factor. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: S2 = simplicial_sets.Sphere(2) + sage: W = S1.disjoint_union(S2, S1) + sage: W.inclusion_map(1) + Simplicial set morphism: + From: S^2 + To: Disjoint union: (S^1 u S^2 u S^1) + Defn: [v_0, sigma_2] --> [v_0, sigma_2] + sage: W.inclusion_map(0).domain() + S^1 + sage: W.inclusion_map(2).domain() + S^1 + """ + return self.structure_map(i) + + +class ConeOfSimplicialSet(SimplicialSet_arbitrary, UniqueRepresentation): + def __init__(self, base): + r""" + Return the unreduced cone on a finite simplicial set. + + INPUT: + + - ``base`` -- return the cone on this simplicial set. + + Add a point `*` (which will become the base point) and for + each simplex `\sigma` in ``base``, add both `\sigma` and a + simplex made up of `*` and `\sigma` (topologically, form the + join of `*` and `\sigma`). + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(1, name='e') + sage: X = SimplicialSet({e: (v, v)}) + sage: CX = X.cone() # indirect doctest + sage: CX.nondegenerate_simplices() + [*, v, (v,*), e, (e,*)] + sage: CX.base_point() + * + """ + Cat = SimplicialSets().Pointed() + if base.is_finite(): + Cat = Cat.Finite() + Parent.__init__(self, category=Cat) + star = AbstractSimplex(0, name='*') + self._base = base + self._basepoint = star + self._n_skeleton = (-1, Empty()) + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the simplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + In the case when the cone is infinite, the `n`-skeleton of the + cone is computed as the `n`-skeleton of the cone of the + `n`-skeleton. + + EXAMPLES:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: X = B.disjoint_union(B) + sage: CX = B.cone() + sage: CX.n_skeleton(3).homology() + {0: 0, 1: 0, 2: 0, 3: Z} + """ + if self.is_finite(): + return SimplicialSet_finite.n_skeleton(self, n) + start, skel = self._n_skeleton + if start == n: + return skel + elif start > n: + return skel.n_skeleton(n) + ans = ConeOfSimplicialSet_finite(self._base.n_skeleton(n)).n_skeleton(n) + self._n_skeleton = (n, ans) + self._basepoint = ans.base_point() + return ans + + def _repr_(self): + """ + Print representation + + EXAMPLES:: + + sage: simplicial_sets.Simplex(3).cone() + Cone of 3-simplex + """ + return 'Cone of {}'.format(self._base) + + def _latex_(self): + r""" + LaTeX representation + + EXAMPLES:: + + sage: latex(simplicial_sets.Simplex(3).cone()) + C \Delta^{3} + """ + return 'C {}'.format(latex(self._base)) + + +class ConeOfSimplicialSet_finite(ConeOfSimplicialSet, SimplicialSet_finite): + def __init__(self, base): + r""" + Return the unreduced cone on a finite simplicial set. + + INPUT: + + - ``base`` -- return the cone on this simplicial set. + + Add a point `*` (which will become the base point) and for + each simplex `\sigma` in ``base``, add both `\sigma` and a + simplex made up of `*` and `\sigma` (topologically, form the + join of `*` and `\sigma`). + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(1, name='e') + sage: X = SimplicialSet({e: (v, v)}) + sage: CX = X.cone() # indirect doctest + sage: CX.nondegenerate_simplices() + [*, v, (v,*), e, (e,*)] + sage: CX.base_point() + * + """ + star = AbstractSimplex(0, name='*') + data = {} + data[star] = None + # Dictionary for translating old simplices to new: keys are + # old simplices, corresponding value is the new simplex + # (sigma, *). + new_simplices = {'cone': star} + for sigma in base.nondegenerate_simplices(): + new = AbstractSimplex(sigma.dimension()+1, + name='({},*)'.format(sigma), + latex_name='({},*)'.format(latex(sigma))) + if sigma.dimension() == 0: + data[sigma] = None + data[new] = (star, sigma) + else: + sigma_faces = base.face_data()[sigma] + data[sigma] = sigma_faces + new_faces = [new_simplices[face.nondegenerate()].apply_degeneracies(*face.degeneracies()) + for face in sigma_faces] + data[new] = (new_faces + [sigma]) + new_simplices[sigma] = new + SimplicialSet_finite.__init__(self, data, base_point=star) + # self._base: original simplicial set. + self._base = base + # self._joins: dictionary, each key is a simplex sigma in + # base, the corresponding value is the new simplex (sigma, *) + # in the cone. Also, one other key is 'cone', and the value is + # the cone vertex. This is used in the suspension class to + # construct the suspension of a morphism. It could be used to + # construct the cone of a morphism, also, although cones of + # morphisms are not yet implemented. + self._joins = new_simplices + + def base_as_subset(self): + """ + If this is the cone `CX` on `X`, return `X` as a subsimplicial set. + + EXAMPLES:: + + sage: X = simplicial_sets.RealProjectiveSpace(4).unset_base_point() + sage: Y = X.cone() + sage: Y.base_as_subset() + Simplicial set with 5 non-degenerate simplices + sage: Y.base_as_subset() == X + True + """ + X = self._base + return self.subsimplicial_set(X.nondegenerate_simplices()) + + def map_from_base(self): + r""" + If this is the cone `CX` on `X`, return the inclusion map from `X`. + + EXAMPLES:: + + sage: X = simplicial_sets.Simplex(2).n_skeleton(1) + sage: Y = X.cone() + sage: Y.map_from_base() + Simplicial set morphism: + From: Simplicial set with 6 non-degenerate simplices + To: Cone of Simplicial set with 6 non-degenerate simplices + Defn: [(0,), (1,), (2,), (0, 1), (0, 2), (1, 2)] --> [(0,), (1,), (2,), (0, 1), (0, 2), (1, 2)] + """ + return self.base_as_subset().inclusion_map() + + +class ReducedConeOfSimplicialSet(QuotientOfSimplicialSet): + def __init__(self, base): + r""" + Return the reduced cone on a simplicial set. + + INPUT: + + - ``base`` -- return the cone on this simplicial set. + + Start with the unreduced cone: take ``base`` and add a point + `*` (which will become the base point) and for each simplex + `\sigma` in ``base``, add both `\sigma` and a simplex made up + of `*` and `\sigma` (topologically, form the join of `*` and + `\sigma`). + + Now reduce: take the quotient by the 1-simplex connecting the + old base point to the new one. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(1, name='e') + sage: X = SimplicialSet({e: (v, v)}) + sage: X = X.set_base_point(v) + sage: CX = X.cone() # indirect doctest + sage: CX.nondegenerate_simplices() + [*, e, (e,*)] + """ + C = ConeOfSimplicialSet(base) + for t in C.n_cells(1): + edge_faces = sorted([C.base_point(), base.base_point()]) + if sorted(C.faces(t)) == edge_faces: + edge = t + break + inc = C.subsimplicial_set([edge]).inclusion_map() + QuotientOfSimplicialSet.__init__(self, inc) + self._base = base + self._n_skeleton = (-1, Empty()) + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the simplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + In the case when the cone is infinite, the `n`-skeleton of the + cone is computed as the `n`-skeleton of the cone of the + `n`-skeleton. + + EXAMPLES:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: B.cone().n_skeleton(3).homology() + {0: 0, 1: 0, 2: 0, 3: Z} + """ + if self.is_finite(): + return SimplicialSet_finite.n_skeleton(self, n) + start, skel = self._n_skeleton + if start == n: + return skel + elif start > n: + return skel.n_skeleton(n) + ans = ReducedConeOfSimplicialSet_finite(self._base.n_skeleton(n)).n_skeleton(n) + self._n_skeleton = (n, ans) + return ans + + def _repr_(self): + """ + Print representation + + EXAMPLES:: + + sage: X = simplicial_sets.Sphere(4) + sage: X.cone() + Reduced cone of S^4 + """ + return 'Reduced cone of {}'.format(self._base) + + def _latex_(self): + """ + LaTeX representation + + EXAMPLES:: + + sage: latex(simplicial_sets.Sphere(4).cone()) + C S^{4} + """ + return 'C {}'.format(latex(self._base)) + + +class ReducedConeOfSimplicialSet_finite(ReducedConeOfSimplicialSet, + QuotientOfSimplicialSet_finite): + def __init__(self, base): + r""" + Return the reduced cone on a simplicial set. + + INPUT: + + - ``base`` -- return the cone on this simplicial set. + + Start with the unreduced cone: take ``base`` and add a point + `*` (which will become the base point) and for each simplex + `\sigma` in ``base``, add both `\sigma` and a simplex made up + of `*` and `\sigma` (topologically, form the join of `*` and + `\sigma`). + + Now reduce: take the quotient by the 1-simplex connecting the + old base point to the new one. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: e = AbstractSimplex(1, name='e') + sage: X = SimplicialSet({e: (v, v)}) + sage: X = X.set_base_point(v) + sage: CX = X.cone() # indirect doctest + sage: CX.nondegenerate_simplices() + [*, e, (e,*)] + """ + C = ConeOfSimplicialSet_finite(base) + edge_faces = sorted([C.base_point(), base.base_point()]) + for t in C.n_cells(1): + if sorted(C.faces(t)) == edge_faces: + edge = t + break + inc = C.subsimplicial_set([edge]).inclusion_map() + QuotientOfSimplicialSet_finite.__init__(self, inc) + self._base = base + q = self.quotient_map() + self._joins = {sigma:q(C._joins[sigma]) for sigma in C._joins} + + def map_from_base(self): + r""" + If this is the cone `\tilde{C}X` on `X`, return the map from `X`. + + The map is defined to be the composite `X \to CX \to + \tilde{C}X`. This is used by the + :class:`SuspensionOfSimplicialSet_finite` class to construct + the reduced suspension: take the quotient of the reduced cone + by the image of `X` therein. + + EXAMPLES:: + + sage: S3 = simplicial_sets.Sphere(3) + sage: CS3 = S3.cone() + sage: CS3.map_from_base() + Simplicial set morphism: + From: S^3 + To: Reduced cone of S^3 + Defn: [v_0, sigma_3] --> [*, sigma_3] + """ + quotient_map = self.quotient_map() + unreduced = quotient_map.domain() + temp_map = unreduced.map_from_base() + X = self._base + incl = X.Hom(unreduced)(temp_map._dictionary) + return quotient_map * incl + + +class SuspensionOfSimplicialSet(SimplicialSet_arbitrary, UniqueRepresentation): + def __init__(self, base): + r""" + Return the (reduced) suspension of a simplicial set. + + INPUT: + + - ``base`` -- return the suspension of this simplicial set. + + If this simplicial set ``X=base`` is not pointed, or if it is + itself an unreduced suspension, return the unreduced + suspension: the quotient `CX/X`, where `CX` is the (ordinary, + unreduced) cone on `X`. If `X` is pointed, then use the + reduced cone instead, and so return the reduced suspension. + + We use `S` to denote unreduced suspension, `\Sigma` for + reduced suspension. + + EXAMPLES:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: B.suspension() + Sigma(Classifying space of Multiplicative Abelian group isomorphic to C2) + sage: B.suspension().n_skeleton(3).homology() + {0: 0, 1: 0, 2: C2, 3: 0} + + If ``X`` is finite, the suspension comes with a quotient map + from the cone:: + + sage: S3 = simplicial_sets.Sphere(3) + sage: S4 = S3.suspension() + sage: S4.quotient_map() + Simplicial set morphism: + From: Reduced cone of S^3 + To: Sigma(S^3) + Defn: [*, sigma_3, (sigma_3,*)] --> [*, s_2 s_1 s_0 *, (sigma_3,*)] + + TESTS:: + + sage: S3.suspension() == S3.suspension() + True + sage: S3.suspension() == simplicial_sets.Sphere(3).suspension() + False + sage: B.suspension() == B.suspension() + True + """ + Cat = SimplicialSets() + if base.is_finite(): + Cat = Cat.Finite() + reduced = (base.is_pointed() + and (not hasattr(base, '_reduced') + or (hasattr(base, '_reduced') and base._reduced))) + if reduced: + Cat = Cat.Pointed() + Parent.__init__(self, category=Cat) + self._reduced = reduced + self._base = base + self._n_skeleton = (-1, Empty()) + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the simplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + In the case when the suspension is infinite, the `n`-skeleton + of the suspension is computed as the `n`-skeleton of the + suspension of the `n`-skeleton. + + EXAMPLES:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: SigmaB = B.suspension() + sage: SigmaB.n_skeleton(4).homology(base_ring=GF(2)) + {0: Vector space of dimension 0 over Finite Field of size 2, + 1: Vector space of dimension 0 over Finite Field of size 2, + 2: Vector space of dimension 1 over Finite Field of size 2, + 3: Vector space of dimension 1 over Finite Field of size 2, + 4: Vector space of dimension 1 over Finite Field of size 2} + """ + if self.is_finite(): + return SimplicialSet_finite.n_skeleton(self, n) + start, skel = self._n_skeleton + if start == n: + return skel + elif start > n: + return skel.n_skeleton(n) + ans = SuspensionOfSimplicialSet_finite(self._base.n_skeleton(n)).n_skeleton(n) + self._n_skeleton = (n, ans) + return ans + + def __repr_or_latex__(self, output_type=None): + r""" + Print representation, for either :meth:`_repr_` or :meth:`_latex_`. + + INPUT: + + - ``output_type`` -- either ``"latex"`` for LaTeX output or + anything else for ``str`` output. + + We use `S` to denote unreduced suspension, `\Sigma` for + reduced suspension. + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: K = T.suspension(10) + sage: K.__repr_or_latex__() + 'Sigma^10(Torus)' + sage: K.__repr_or_latex__('latex') + '\\Sigma^{10}(S^{1} \\times S^{1})' + """ + latex_output = (output_type == 'latex') + base = self._base + if self._reduced: + # Reduced suspension. + if latex_output: + symbol = '\\Sigma' + else: + symbol = 'Sigma' + else: + # Unreduced suspension. + symbol = 'S' + idx = 1 + while isinstance(base, SuspensionOfSimplicialSet): + idx += 1 + base = base._base + if latex_output: + base = latex(base) + exp = '^{{{}}}' + else: + exp = '^{}' + if idx > 1: + return ('{}' + exp + '({})').format(symbol, idx, base) + else: + return ('{}({})').format(symbol, base) + + def _repr_(self): + r""" + Print representation + + We use `S` to denote unreduced suspension, `\Sigma` for + reduced suspension. + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: S2.suspension(3) + Sigma^3(S^2) + sage: K = simplicial_sets.Simplex(2) + sage: K.suspension(3) + S^3(2-simplex) + sage: K.suspension() + S(2-simplex) + """ + return self.__repr_or_latex__() + + def _latex_(self): + r""" + LaTeX representation + + We use `S` to denote unreduced suspension, `\Sigma` for + reduced suspension. + + EXAMPLES:: + + sage: S2 = simplicial_sets.Sphere(2) + sage: latex(S2.suspension(3)) + \Sigma^{3}(S^{2}) + sage: K = simplicial_sets.Simplex(2) + sage: latex(K.suspension(3)) + S^{3}(\Delta^{2}) + sage: latex(K.suspension()) + S(\Delta^{2}) + """ + return self.__repr_or_latex__('latex') + + +class SuspensionOfSimplicialSet_finite(SuspensionOfSimplicialSet, + QuotientOfSimplicialSet_finite): + """ + The (reduced) suspension of a finite simplicial set. + + See :class:`SuspensionOfSimplicialSet` for more information. + """ + def __init__(self, base): + r""" + INPUT: + + - ``base`` -- return the suspension of this finite simplicial set. + + See :class:`SuspensionOfSimplicialSet` for more information. + + EXAMPLES:: + + sage: X = simplicial_sets.Sphere(3) + sage: X.suspension(2) + Sigma^2(S^3) + sage: Y = X.unset_base_point() + sage: Y.suspension(2) + S^2(Simplicial set with 2 non-degenerate simplices) + """ + self._base = base + reduced = (base.is_pointed() + and (not hasattr(base, '_reduced') + or (hasattr(base, '_reduced') and base._reduced))) + if reduced: + C = ReducedConeOfSimplicialSet_finite(base) + subcomplex = C.map_from_base().image() + else: + C = ConeOfSimplicialSet_finite(base) + subcomplex = C.base_as_subset() + QuotientOfSimplicialSet_finite.__init__(self, subcomplex.inclusion_map()) + self._reduced = reduced + # self._suspensions: dictionary, each key is a simplex sigma + # in base, the corresponding value is the new simplex (sigma, *) + # in S(base). Another key is 'cone', and its value is the cone + # vertex in C(base). This is used to construct the suspension of a + # morphism. + q = self.quotient_map() + self._suspensions = {sigma: q(C._joins[sigma]) for sigma in C._joins} diff --git a/src/sage/topology/simplicial_set_examples.py b/src/sage/topology/simplicial_set_examples.py new file mode 100644 index 00000000000..32d392e5294 --- /dev/null +++ b/src/sage/topology/simplicial_set_examples.py @@ -0,0 +1,793 @@ +# -*- coding: utf-8 -*- +r""" +Examples of simplicial sets. + +These are accessible via ``simplicial_sets.Sphere(3)``, +``simplicial_sets.Torus()``, etc. Type ``simplicial_sets.[TAB]`` to +see a complete list. + +AUTHORS: + +- John H. Palmieri (2016-07) +""" +#***************************************************************************** +# Copyright (C) 2016 John H. Palmieri +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +# +#***************************************************************************** + +import re +import os +from pyparsing import OneOrMore, nestedExpr + +from sage.env import SAGE_ENV +from sage.groups.abelian_gps.abelian_group import AbelianGroup +from sage.misc.cachefunc import cached_method, cached_function +from sage.misc.latex import latex +from sage.rings.infinity import Infinity +from sage.rings.integer import Integer +from sage.structure.parent import Parent + +from .delta_complex import delta_complexes +from .simplicial_set import AbstractSimplex, \ + SimplicialSet_arbitrary, SimplicialSet_finite + +import sage.topology.simplicial_complex_catalog as simplicial_complexes + +from sage.misc.lazy_import import lazy_import +lazy_import('sage.categories.simplicial_sets', 'SimplicialSets') + +######################################################################## +# The nerve of a finite monoid, used in sage.categories.finite_monoid. + +class Nerve(SimplicialSet_arbitrary): + def __init__(self, monoid): + """ + The nerve of a multiplicative monoid. + + INPUT: + + - ``monoid`` -- a multiplicative monoid + + See + :meth:`sage.categories.finite_monoids.FiniteMonoids.ParentMethods.nerve` + for full documentation. + + EXAMPLES:: + + sage: M = FiniteMonoids().example() + sage: M + An example of a finite multiplicative monoid: the integers modulo 12 + sage: X = M.nerve() + sage: list(M) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + sage: X.n_cells(0) + [1] + sage: X.n_cells(1) + [0, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9] + """ + category = SimplicialSets().Pointed() + Parent.__init__(self, category=category) + self.rename("Nerve of {}".format(str(monoid))) + self.rename_latex("B{}".format(latex(monoid))) + + e = AbstractSimplex(0, name=str(monoid.one()), + latex_name=latex(monoid.one())) + self._basepoint = e + vertex = SimplicialSet_finite({e: None}, base_point=e) + # self._n_skeleton: cache the highest dimensional skeleton + # calculated so far for this simplicial set, along with its + # dimension. + self._n_skeleton = (0, vertex) + self._monoid = monoid + # self._simplex_data: a tuple whose elements are pairs (simplex, list + # of monoid elements). Omit the base point. + self._simplex_data = () + + def __eq__(self, other): + """ + Return ``True`` if ``self`` and ``other`` are equal. + + This checks that the underlying monoids and the underlying + base points are the same. Because the base points will be + different each time the nerve is constructed, different + instances will not be equal. + + EXAMPLES:: + + sage: C3 = groups.misc.MultiplicativeAbelian([3]) + sage: C3.nerve() == C3.nerve() + False + sage: BC3 = C3.nerve() + sage: BC3 == BC3 + True + """ + return (isinstance(other, Nerve) + and self._monoid == other._monoid + and self.base_point() == other.base_point()) + + def __ne__(self, other): + """ + Return the negation of `__eq__`. + + EXAMPLES:: + + sage: C3 = groups.misc.MultiplicativeAbelian([3]) + sage: G3 = groups.permutation.Cyclic(3) + sage: C3.nerve() != G3.nerve() + True + sage: C3.nerve() != C3.nerve() + True + """ + return not self == other + + @cached_method + def __hash__(self): + """ + The hash is formed from the monoid and the base point. + + EXAMPLES:: + + sage: G3 = groups.permutation.Cyclic(3) + sage: hash(G3.nerve()) # random + 17 + + Different instances yield different base points, hence different hashes:: + + sage: X = G3.nerve() + sage: Y = G3.nerve() + sage: X.base_point() != Y.base_point() + True + sage: hash(X) != hash(Y) + True + """ + return hash(self._monoid) ^ hash(self.base_point()) + + def n_skeleton(self, n): + """ + Return the `n`-skeleton of this simplicial set. + + That is, the simplicial set generated by all nondegenerate + simplices of dimension at most `n`. + + INPUT: + + - ``n`` -- the dimension + + EXAMPLES:: + + sage: K4 = groups.misc.MultiplicativeAbelian([2,2]) + sage: BK4 = simplicial_sets.ClassifyingSpace(K4) + sage: BK4.n_skeleton(3) + Simplicial set with 40 non-degenerate simplices + sage: BK4.n_cells(1) == BK4.n_skeleton(3).n_cells(1) + True + sage: BK4.n_cells(3) == BK4.n_skeleton(1).n_cells(3) + False + """ + from .simplicial_set_constructions import SubSimplicialSet + monoid = self._monoid + one = monoid.one() + # Build up chains of elements inductively, from dimension d-1 + # to dimension d. We start with the cached + # self._n_skeleton. If only the 0-skeleton has been + # constructed, we construct the 1-cells by hand. + start, skel = self._n_skeleton + if start == n: + return skel + elif start > n: + return skel.n_skeleton(n) + + # There is a single vertex. Name it after the identity + # element of the monoid. + e = skel.n_cells(0)[0] + # Build the dictionary simplices, to be used for + # constructing the simplicial set. + simplices = skel.face_data() + + # face_dict: dictionary of simplices: keys are + # composites of monoid elements (as tuples), values are + # the corresponding simplices. + face_dict = dict(self._simplex_data) + + if start == 0: + for g in monoid: + if g != one: + x = AbstractSimplex(1, name=str(g), latex_name=latex(g)) + simplices[x] = (e, e) + face_dict[(g,)] = x + start = 1 + + for d in range(start+1, n+1): + for g in monoid: + if g == one: + continue + new_faces = {} + for t in face_dict.keys(): + if len(t) != d-1: + continue + # chain: chain of group elements to multiply, + # as a tuple. + chain = t + (g,) + # bdries: the face maps applied to chain, in a + # format suitable for passing to the DeltaComplex + # constructor. + x = AbstractSimplex(d, + name=' * '.join(str(_) for _ in chain), + latex_name = ' * '.join(latex(_) for _ in chain)) + new_faces[chain] = x + + # Compute faces of x. + faces = [face_dict[chain[1:]]] + for i in range(d-1): + product = chain[i] * chain[i+1] + if product == one: + # Degenerate. + if d == 2: + face = e.apply_degeneracies(i) + else: + face = (face_dict[chain[:i] + + chain[i+2:]].apply_degeneracies(i)) + else: + # Non-degenerate. + face = (face_dict[chain[:i] + + (product,) + chain[i+2:]]) + faces.append(face) + faces.append(face_dict[chain[:-1]]) + simplices[x] = faces + face_dict.update(new_faces) + + K = SubSimplicialSet(simplices, self) + self._n_skeleton = (n, K) + self._simplex_data = face_dict.items() + return K + + +######################################################################## +# Catalog of examples. These are accessed via simplicial_set_catalog.py. + +def Sphere(n): + r""" + Return the `n`-sphere as a simplicial set. + + It is constructed with two non-degenerate simplices: a vertex + `v_0` (which is the base point) and an `n`-simplex `\sigma_n`. + + INPUT: + + - ``n`` -- integer + + EXAMPLES:: + + sage: S0 = simplicial_sets.Sphere(0) + sage: S0 + S^0 + sage: S0.nondegenerate_simplices() + [v_0, w_0] + sage: S0.is_pointed() + True + sage: simplicial_sets.Sphere(4) + S^4 + sage: latex(simplicial_sets.Sphere(4)) + S^{4} + sage: simplicial_sets.Sphere(4).nondegenerate_simplices() + [v_0, sigma_4] + """ + v_0 = AbstractSimplex(0, name='v_0') + if n == 0: + w_0 = AbstractSimplex(0, name='w_0') + return SimplicialSet_finite({v_0: None, w_0: None}, base_point=v_0, + name='S^0') + degens = range(n-2, -1, -1) + degen_v = v_0.apply_degeneracies(*degens) + sigma = AbstractSimplex(n, name='sigma_{}'.format(n), + latex_name='\\sigma_{}'.format(n)) + return SimplicialSet_finite({sigma: [degen_v] * (n+1)}, base_point=v_0, + name='S^{}'.format(n), + latex_name='S^{{{}}}'.format(n)) + + +def ClassifyingSpace(group): + r""" + Return the classifying space of ``group``, as a simplicial set. + + INPUT: + + - ``group`` -- a finite group or finite monoid + + See + :meth:`sage.categories.finite_monoids.FiniteMonoids.ParentMethods.nerve` + for more details and more examples. + + EXAMPLES:: + + sage: C2 = groups.misc.MultiplicativeAbelian([2]) + sage: BC2 = simplicial_sets.ClassifyingSpace(C2) + sage: H = BC2.homology(range(9), base_ring=GF(2)) + sage: [H[i].dimension() for i in range(9)] + [0, 1, 1, 1, 1, 1, 1, 1, 1] + + sage: Klein4 = groups.misc.MultiplicativeAbelian([2, 2]) + sage: BK = simplicial_sets.ClassifyingSpace(Klein4) + sage: BK + Classifying space of Multiplicative Abelian group isomorphic to C2 x C2 + sage: BK.homology(range(5), base_ring=GF(2)) # long time (1 second) + {0: Vector space of dimension 0 over Finite Field of size 2, + 1: Vector space of dimension 2 over Finite Field of size 2, + 2: Vector space of dimension 3 over Finite Field of size 2, + 3: Vector space of dimension 4 over Finite Field of size 2, + 4: Vector space of dimension 5 over Finite Field of size 2} + """ + X = group.nerve() + X.rename('Classifying space of {}'.format(group)) + return X + + +def RealProjectiveSpace(n): + r""" + Return real `n`-dimensional projective space, as a simplicial set. + + This is constructed as the `n`-skeleton of the nerve of the group + of order 2, and therefore has a single non-degenerate simplex in + each dimension up to `n`. + + EXAMPLES:: + + sage: simplicial_sets.RealProjectiveSpace(7) + RP^7 + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP5.homology() + {0: 0, 1: C2, 2: 0, 3: C2, 4: 0, 5: Z} + sage: RP5 + RP^5 + sage: latex(RP5) + RP^{5} + + sage: BC2 = simplicial_sets.RealProjectiveSpace(Infinity) + sage: latex(BC2) + RP^{\infty} + """ + if n == Infinity: + X = AbelianGroup([2]).nerve() + X.rename('RP^oo') + X.rename_latex('RP^{\\infty}') + else: + X = RealProjectiveSpace(Infinity).n_skeleton(n) + X.rename('RP^{}'.format(n)) + X.rename_latex('RP^{{{}}}'.format(n)) + return X + + +def KleinBottle(): + r""" + Return the Klein bottle as a simplicial set. + + This converts the `\Delta`-complex version to a simplicial set. It + has one 0-simplex, three 1-simplices, and two 2-simplices. + + EXAMPLES:: + + sage: K = simplicial_sets.KleinBottle() + sage: K.f_vector() + [1, 3, 2] + sage: K.homology(reduced=False) + {0: Z, 1: Z x C2, 2: 0} + sage: K + Klein bottle + """ + temp = SimplicialSet_finite(delta_complexes.KleinBottle()) + pt = temp.n_cells(0)[0] + return SimplicialSet_finite(temp.face_data(), base_point=pt, + name='Klein bottle') + + +def Torus(): + r""" + Return the torus as a simplicial set. + + This computes the product of the circle with itself, where the + circle is represented using a single 0-simplex and a single + 1-simplex. Thus it has one 0-simplex, three 1-simplices, and two + 2-simplices. + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: T.f_vector() + [1, 3, 2] + sage: T.homology(reduced=False) + {0: Z, 1: Z x Z, 2: Z} + """ + S1 = Sphere(1) + T = S1.product(S1) + T.rename('Torus') + return T + + +def Simplex(n): + r""" + Return the `n`-simplex as a simplicial set. + + EXAMPLES:: + + sage: K = simplicial_sets.Simplex(2) + sage: K + 2-simplex + sage: latex(K) + \Delta^{2} + sage: K.n_cells(0) + [(0,), (1,), (2,)] + sage: K.n_cells(1) + [(0, 1), (0, 2), (1, 2)] + sage: K.n_cells(2) + [(0, 1, 2)] + """ + return SimplicialSet_finite(simplicial_complexes.Simplex(n), + name='{}-simplex'.format(n), + latex_name='\\Delta^{{{}}}'.format(n)) + + +@cached_function +def Empty(): + """ + Return the empty simplicial set. + + This should return the same simplicial set each time it is called. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set_examples import Empty + sage: E = Empty() + sage: E + Empty simplicial set + sage: E.nondegenerate_simplices() + [] + sage: E is Empty() + True + """ + return SimplicialSet_finite({}, name='Empty simplicial set') + + +@cached_function +def Point(): + """ + Return a single point called "*" as a simplicial set. + + This should return the same simplicial set each time it is called. + + EXAMPLES:: + + sage: P = simplicial_sets.Point() + sage: P.is_pointed() + True + sage: P.nondegenerate_simplices() + [*] + + sage: Q = simplicial_sets.Point() + sage: P is Q + True + sage: P == Q + True + """ + star = AbstractSimplex(0, name='*') + return SimplicialSet_finite({star: None}, base_point=star, + name='Point', + latex_name='*') + + +def Horn(n, k): + r""" + Return the horn $\Lambda^n_k$. + + This is the subsimplicial set of the $n$-simplex obtained by + removing its $k$-th face. + + EXAMPLES:: + + sage: L = simplicial_sets.Horn(3, 0) + sage: L + (3, 0)-Horn + sage: L.n_cells(3) + [] + sage: L.n_cells(2) + [(0, 1, 2), (0, 1, 3), (0, 2, 3)] + + sage: L20 = simplicial_sets.Horn(2, 0) + sage: latex(L20) + \Lambda^{2}_{0} + sage: L20.inclusion_map() + Simplicial set morphism: + From: (2, 0)-Horn + To: 2-simplex + Defn: [(0,), (1,), (2,), (0, 1), (0, 2)] --> [(0,), (1,), (2,), (0, 1), (0, 2)] + """ + K = Simplex(n) + sigma = K.n_cells(n)[0] + L = K.subsimplicial_set(K.faces(sigma)[:k] + K.faces(sigma)[k+1:]) + L.rename('({}, {})-Horn'.format(n, k)) + L.rename_latex('\\Lambda^{{{}}}_{{{}}}'.format(n, k)) + return L + + +def ComplexProjectiveSpace(n): + r""" + Return complex `n`-dimensional projective space, as a simplicial set. + + This is only defined when `n` is at most 4. It is constructed + using the simplicial set decomposition provided by Kenzo, as + described by Sergeraert [Ser2010]_ + + EXAMPLES:: + + sage: simplicial_sets.ComplexProjectiveSpace(2).homology(reduced=False) + {0: Z, 1: 0, 2: Z, 3: 0, 4: Z} + sage: CP3 = simplicial_sets.ComplexProjectiveSpace(3) + sage: CP3 + CP^3 + sage: latex(CP3) + CP^{3} + sage: CP3.f_vector() + [1, 0, 3, 10, 25, 30, 15] + + sage: K = CP3.suspension() # long time (1 second) + sage: R = K.cohomology_ring(GF(2)) # long time + sage: R.gens() # long time + (h^{0,0}, h^{3,0}, h^{5,0}, h^{7,0}) + sage: x = R.gens()[1] # long time + sage: x.Sq(2) # long time + h^{5,0} + + sage: simplicial_sets.ComplexProjectiveSpace(4).f_vector() + [1, 0, 4, 22, 97, 255, 390, 315, 105] + + sage: simplicial_sets.ComplexProjectiveSpace(5) + Traceback (most recent call last): + ... + ValueError: complex projective spaces are only available in dimensions between 0 and 4 + """ + if n < 0 or n > 4: + raise ValueError('complex projective spaces are only available in dimensions between 0 and 4') + if n == 0: + return Point() + if n == 1: + return Sphere(2) + if n == 2: + # v: Kenzo name <> + v = AbstractSimplex(0, name='v') + # f_2_i: Kenzo name <<- NIL>>> for i=1,2 + f2_1 = AbstractSimplex(2, name='rho_0') + f2_2 = AbstractSimplex(2, name='rho_1') + # f3_110: Kenzo name <<0 NIL><- NIL>>> + # f3_011: Kenzo name <<- (1)><- NIL>>> + # f3_111: Kenzo name <<- (1)><- NIL>>> + f3_110 = AbstractSimplex(3, name='sigma_0', latex_name='\\sigma_0') + f3_011 = AbstractSimplex(3, name='sigma_1', latex_name='\\sigma_1') + f3_111 = AbstractSimplex(3, name='sigma_2', latex_name='\\sigma_2') + # f4_101101: Kenzo name <<1-0 NIL><- (1)><- NIL>>> + # f4_201110: Kenzo name <<1 (1)><0 NIL><- NIL>>> + # f4_211010: Kenzo name <<0 (1)><0 NIL><- NIL>>> + f4_101101 = AbstractSimplex(4, name='tau_0', latex_name='\\tau_0') + f4_201110 = AbstractSimplex(4, name='tau_1', latex_name='\\tau_1') + f4_211010 = AbstractSimplex(4, name='tau_2', latex_name='\\tau_2') + K = SimplicialSet_finite({f2_1: (v.apply_degeneracies(0), + v.apply_degeneracies(0), + v.apply_degeneracies(0)), + f2_2: (v.apply_degeneracies(0), + v.apply_degeneracies(0), + v.apply_degeneracies(0)), + f3_110: (f2_1, f2_2, f2_1, v.apply_degeneracies(1, 0)), + f3_011: (f2_1, f2_1, f2_1, f2_1), + f3_111: (v.apply_degeneracies(1, 0), f2_1, f2_2, f2_1), + f4_101101: (f2_1.apply_degeneracies(0), + f2_1.apply_degeneracies(0), + f3_011, + f2_1.apply_degeneracies(2), + f2_1.apply_degeneracies(2)), + f4_201110: (f2_1.apply_degeneracies(1), + f3_111, + f3_011, + f3_110, + f2_1.apply_degeneracies(1)), + f4_211010: (f2_1.apply_degeneracies(2), + f3_111, + f2_1.apply_degeneracies(1), + f3_110, + f2_1.apply_degeneracies(0))}, + base_point=v, name='CP^2', + latex_name='CP^{2}') + return K + if n == 3: + file = os.path.join(SAGE_ENV['SAGE_EXTCODE'], 'kenzo', 'CP3.txt') + data = simplicial_data_from_kenzo_output(file) + v = [_ for _ in data.keys() if _.dimension() == 0][0] + K = SimplicialSet_finite(data, base_point=v, name='CP^3', + latex_name='CP^{3}') + return K + if n == 4: + file = os.path.join(SAGE_ENV['SAGE_EXTCODE'], 'kenzo', 'CP4.txt') + data = simplicial_data_from_kenzo_output(file) + v = [_ for _ in data.keys() if _.dimension() == 0][0] + K = SimplicialSet_finite(data, base_point=v, name='CP^4', + latex_name='CP^{4}') + return K + + +def simplicial_data_from_kenzo_output(filename): + """ + Return data to construct a simplicial set, given Kenzo output. + + INPUT: + + - ``filename`` -- name of file containing the output from Kenzo's + :func:`show-structure` function + + OUTPUT: data to construct a simplicial set from the Kenzo output + + Several files with Kenzo output are in the directory + :file:`SAGE_EXTCODE/kenzo/`. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set_examples import simplicial_data_from_kenzo_output + sage: from sage.topology.simplicial_set import SimplicialSet + sage: sphere = os.path.join(SAGE_ENV['SAGE_EXTCODE'], 'kenzo', 'S4.txt') + sage: S4 = SimplicialSet(simplicial_data_from_kenzo_output(sphere)) + sage: S4.homology(reduced=False) + {0: Z, 1: 0, 2: 0, 3: 0, 4: Z} + """ + with open(filename, 'r') as f: + data = f.read() + dim = 0 + start = 0 + # simplex_data: data for constructing the simplicial set. + simplex_data = {} + # simplex_names: simplices indexed by their names + simplex_names = {} + dim_idx = data.find('Dimension = {}:'.format(dim), start) + while dim_idx != -1: + start = dim_idx + len('Dimension = {}:'.format(dim)) + new_dim_idx = data.find('Dimension = {}:'.format(dim+1), start) + if new_dim_idx == -1: + end = len(data) + else: + end = new_dim_idx + if dim == 0: + simplex_string = data[data.find('Vertices :') + len('Vertices :'):end] + vertices = OneOrMore(nestedExpr()).parseString(simplex_string).asList()[0] + for v in vertices: + vertex = AbstractSimplex(0, name=v) + simplex_data[vertex] = None + simplex_names[v] = vertex + else: + simplex_string = data[start:end].strip() + + for s in [_.strip() for _ in simplex_string.split('Simplex : ')]: + if s: + name, face_str = [_.strip() for _ in s.split('Faces : ')] + face_str = face_str.strip('()') + face_str = face_str.split('', possibly with a trailing space. + # DEGENS is a hyphen-separated list, like + # '3-2-1-0' or '0' or '-'. + m = re.match('[-[0-9]+', f) + degen_str = m.group(0) + if degen_str.find('-') != -1: + if degen_str == '-': + degens = [] + else: + degens = [Integer(_) + for _ in degen_str.split('-')] + else: + degens = [Integer(degen_str)] + + face_name = f[m.end(0):].strip()[:-1] + nondegen = simplex_names[face_name] + faces.append(nondegen.apply_degeneracies(*degens)) + + simplex = AbstractSimplex(dim, name=name) + simplex_data[simplex] = faces + simplex_names[name] = simplex + dim += 1 + dim_idx = new_dim_idx + return simplex_data + + +def HopfMap(): + r""" + Return a simplicial model of the Hopf map `S^3 \to S^2` + + This is taken from Exemple II.1.19 in the thesis of Clemens Berger + [Ber1991]_. + + The Hopf map is a fibration `S^3 \to S^2`. If it is viewed as + attaching a 4-cell to the 2-sphere, the resulting adjunction space + is 2-dimensional complex projective space. The resulting model is + a bit larger than the one obtained from + ``simplicial_sets.ComplexProjectiveSpace(2)``. + + EXAMPLES:: + + sage: g = simplicial_sets.HopfMap() + sage: g.domain() + Simplicial set with 20 non-degenerate simplices + sage: g.codomain() + S^2 + + Using the Hopf map to attach a cell:: + + sage: X = g.mapping_cone() + sage: CP2 = simplicial_sets.ComplexProjectiveSpace(2) + sage: X.homology() == CP2.homology() + True + + sage: X.f_vector() + [1, 0, 5, 9, 6] + sage: CP2.f_vector() + [1, 0, 2, 3, 3] + """ + # The 2-sphere and its simplices. + S2 = Sphere(2) + sigma = S2.n_cells(2)[0] + s0_sigma = sigma.apply_degeneracies(0) + s1_sigma = sigma.apply_degeneracies(1) + s2_sigma = sigma.apply_degeneracies(2) + # The 3-sphere and its simplices. + w_0 = AbstractSimplex(0, name='w') + w_1 = w_0.apply_degeneracies(0) + w_2 = w_0.apply_degeneracies(0, 0) + beta_11 = AbstractSimplex(1, name='beta_11', latex_name='\\beta_{11}') + beta_22 = AbstractSimplex(1, name='beta_22', latex_name='\\beta_{22}') + beta_23 = AbstractSimplex(1, name='beta_23', latex_name='\\beta_{23}') + beta_44 = AbstractSimplex(1, name='beta_44', latex_name='\\beta_{44}') + beta_1 = AbstractSimplex(2, name='beta_1', latex_name='\\beta_1') + beta_2 = AbstractSimplex(2, name='beta_2', latex_name='\\beta_2') + beta_3 = AbstractSimplex(2, name='beta_3', latex_name='\\beta_3') + beta_4 = AbstractSimplex(2, name='beta_4', latex_name='\\beta_4') + alpha_12 = AbstractSimplex(2, name='alpha_12', latex_name='\\alpha_{12}') + alpha_23 = AbstractSimplex(2, name='alpha_23', latex_name='\\alpha_{23}') + alpha_34 = AbstractSimplex(2, name='alpha_34', latex_name='\\alpha_{34}') + alpha_45 = AbstractSimplex(2, name='alpha_45', latex_name='\\alpha_{45}') + alpha_56 = AbstractSimplex(2, name='alpha_56', latex_name='\\alpha_{56}') + alpha_1 = AbstractSimplex(3, name='alpha_1', latex_name='\\alpha_1') + alpha_2 = AbstractSimplex(3, name='alpha_2', latex_name='\\alpha_2') + alpha_3 = AbstractSimplex(3, name='alpha_3', latex_name='\\alpha_3') + alpha_4 = AbstractSimplex(3, name='alpha_4', latex_name='\\alpha_4') + alpha_5 = AbstractSimplex(3, name='alpha_5', latex_name='\\alpha_5') + alpha_6 = AbstractSimplex(3, name='alpha_6', latex_name='\\alpha_6') + S3 = SimplicialSet_finite({beta_11: (w_0, w_0), beta_22: (w_0, w_0), + beta_23: (w_0, w_0), beta_44: (w_0, w_0), + beta_1: (w_1, beta_11, w_1), + beta_2: (w_1, beta_22, beta_23), + beta_3: (w_1, beta_23, w_1), + beta_4: (w_1, beta_44, w_1), + alpha_12: (beta_11, beta_23, w_1), + alpha_23: (beta_11, beta_22, w_1), + alpha_34: (beta_11, beta_22, beta_44), + alpha_45: (w_1, beta_23, beta_44), + alpha_56: (w_1, beta_23, w_1), + alpha_1: (beta_1, beta_3, alpha_12, w_2), + alpha_2: (beta_11.apply_degeneracies(1), beta_2, + alpha_23, alpha_12), + alpha_3: (beta_11.apply_degeneracies(0), alpha_34, + alpha_23, beta_4), + alpha_4: (beta_1, beta_2, alpha_34, alpha_45), + alpha_5: (w_2, alpha_45, alpha_56, beta_4), + alpha_6: (w_2, beta_3, alpha_56, w_2)}, + base_point=w_0) + return S3.Hom(S2)({alpha_1:s0_sigma, alpha_2:s1_sigma, + alpha_3:s2_sigma, alpha_4:s0_sigma, + alpha_5:s2_sigma, alpha_6:s1_sigma}) + diff --git a/src/sage/topology/simplicial_set_morphism.py b/src/sage/topology/simplicial_set_morphism.py new file mode 100644 index 00000000000..a320703001c --- /dev/null +++ b/src/sage/topology/simplicial_set_morphism.py @@ -0,0 +1,1449 @@ +r""" +Morphisms and homsets for simplicial sets + +.. NOTE:: + + Morphisms with infinite domain are not implemented in general: + only constant maps and identity maps are currently implemented. + +AUTHORS: + +- John H. Palmieri (2016-07) + +This module implements morphisms and homsets of simplicial sets. +""" + +#***************************************************************************** +# Copyright (C) 2016 John H. Palmieri +# +# Distributed under the terms of the GNU General Public License (GPL) +# http://www.gnu.org/licenses/ +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +# +#***************************************************************************** + +import itertools + +from sage.categories.homset import Hom, Homset +from sage.categories.morphism import Morphism +from sage.categories.simplicial_sets import SimplicialSets +from sage.matrix.constructor import matrix, zero_matrix +from sage.misc.latex import latex +from sage.rings.integer_ring import ZZ + +from sage.homology.chain_complex_morphism import ChainComplexMorphism +from sage.homology.homology_morphism import InducedHomologyMorphism +from .simplicial_set import SimplicialSet_arbitrary + +class SimplicialSetHomset(Homset): + r""" + A set of morphisms between simplicial sets. + + Once a homset has been constructed in Sage, typically via + ``Hom(X,Y)`` or ``X.Hom(Y)``, one can use it to construct a + morphism `f` by specifying a dictionary, the keys of which are the + nondegenerate simplices in the domain, and the value corresponding + to `\sigma` is the simplex `f(\sigma)` in the codomain. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: f = AbstractSimplex(1, name='f') + sage: X = SimplicialSet({e: (v, w), f: (w, v)}) + sage: Y = SimplicialSet({e: (v, v)}) + + Define the homset:: + + sage: H = Hom(X, Y) + + Now define a morphism by specifying a dictionary:: + + sage: H({v: v, w: v, e: e, f: e}) + Simplicial set morphism: + From: Simplicial set with 4 non-degenerate simplices + To: Simplicial set with 2 non-degenerate simplices + Defn: [v, w, e, f] --> [v, v, e, e] + """ + def __call__(self, f, check=True): + r""" + INPUT: + + - ``f`` -- a dictionary with keys the simplices of the domain + and values simplices of the codomain + + - ``check`` -- optional, default ``True``. Pass this to the + morphism constructor. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: v0 = S1.n_cells(0)[0] + sage: e = S1.n_cells(1)[0] + sage: f = {v0: v0, e: v0.apply_degeneracies(0)} # constant map + sage: Hom(S1, S1)(f) + Simplicial set endomorphism of S^1 + Defn: Constant map at v_0 + """ + return SimplicialSetMorphism(f, self.domain(), self.codomain(), check=check) + + def diagonal_morphism(self): + r""" + Return the diagonal morphism in `\operatorname{Hom}(S, S \times S)`. + + EXAMPLES:: + + sage: RP2 = simplicial_sets.RealProjectiveSpace(2) + sage: Hom(RP2, RP2.product(RP2)).diagonal_morphism() + Simplicial set morphism: + From: RP^2 + To: RP^2 x RP^2 + Defn: [1, f, f * f] --> [(1, 1), (f, f), (f * f, f * f)] + """ + domain = self.domain() + codomain = self.codomain() + if not hasattr(codomain, 'factors'): + raise ValueError('diagonal morphism is only defined for Hom(X, XxX)') + factors = codomain.factors() + if len(factors) != 2 or factors[0] != domain or factors[1] != domain: + raise ValueError('diagonal morphism is only defined for Hom(X, XxX)') + f = {} + for i in range(domain.dimension()+1): + for s in domain.n_cells(i): + f[s] = dict(codomain._translation)[((s, ()), (s, ()))] + return self(f) + + def identity(self): + r""" + Return the identity morphism in `\operatorname{Hom}(S, S)`. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: Hom(S1, S1).identity() + Simplicial set endomorphism of S^1 + Defn: Identity map + sage: T = simplicial_sets.Torus() + sage: Hom(S1, T).identity() + Traceback (most recent call last): + ... + TypeError: identity map is only defined for endomorphism sets + """ + return SimplicialSetMorphism(domain=self.domain(), + codomain=self.codomain(), + identity=True) + + def constant_map(self, point=None): + r""" + Return the constant map in this homset. + + INPUT: + + - ``point`` -- optional, default ``None``. If specified, it + must be a 0-simplex in the codomain, and it will be the + target of the constant map. + + If ``point`` is specified, it is the target of the constant + map. Otherwise, if the codomain is pointed, the target is its + base point. If the codomain is not pointed and ``point`` is + not specified, raise an error. + + EXAMPLES:: + + sage: S3 = simplicial_sets.Sphere(3) + sage: T = simplicial_sets.Torus() + sage: T.n_cells(0)[0].rename('w') + sage: Hom(S3,T).constant_map() + Simplicial set morphism: + From: S^3 + To: Torus + Defn: Constant map at w + + sage: S0 = simplicial_sets.Sphere(0) + sage: v, w = S0.n_cells(0) + sage: Hom(S3, S0).constant_map(v) + Simplicial set morphism: + From: S^3 + To: S^0 + Defn: Constant map at v_0 + sage: Hom(S3, S0).constant_map(w) + Simplicial set morphism: + From: S^3 + To: S^0 + Defn: Constant map at w_0 + + This constant map is not pointed, since it doesn't send the + base point of `S^3` to the base point of `S^0`:: + + sage: Hom(S3, S0).constant_map(w).is_pointed() + False + + TESTS:: + + sage: S0 = S0.unset_base_point() + sage: Hom(S3, S0).constant_map() + Traceback (most recent call last): + ... + ValueError: codomain is not pointed, so specify a target for the constant map + """ + codomain = self.codomain() + if point is None: + if codomain.is_pointed(): + point = codomain.base_point() + else: + raise ValueError('codomain is not pointed, so specify a ' + 'target for the constant map') + return SimplicialSetMorphism(domain=self.domain(), + codomain=self.codomain(), + constant=point) + + def an_element(self): + """ + Return an element of this homset: a constant map. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: S2 = simplicial_sets.Sphere(2) + sage: Hom(S2, S1).an_element() + Simplicial set morphism: + From: S^2 + To: S^1 + Defn: Constant map at v_0 + + sage: K = simplicial_sets.Simplex(3) + sage: L = simplicial_sets.Simplex(4) + sage: d = {K.n_cells(3)[0]: L.n_cells(0)[0].apply_degeneracies(2, 1, 0)} + sage: Hom(K,L)(d) == Hom(K,L).an_element() + True + """ + codomain = self.codomain() + if codomain.is_pointed(): + target = codomain.base_point() + else: + target = codomain.n_cells(0)[0] + return self.constant_map(target) + + def __iter__(self): + """ + Iterate through all morphisms in this homset. + + This is very slow: it tries all possible targets for the + maximal nondegenerate simplices and yields those which are + valid morphisms of simplicial sets. ("Maximal" means + nondegenerate simplices which are not the faces of other + nondegenerate simplices.) So if either the domain or the + codomain has many simplices, the number of possibilities may + be quite large. + + This is only implemented when the domain is finite. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = simplicial_sets.Torus() + sage: H = Hom(S1, T) + sage: list(H) + [Simplicial set morphism: + From: S^1 + To: Torus + Defn: [v_0, sigma_1] --> [(v_0, v_0), (s_0 v_0, sigma_1)], + Simplicial set morphism: + From: S^1 + To: Torus + Defn: [v_0, sigma_1] --> [(v_0, v_0), (sigma_1, s_0 v_0)], + Simplicial set morphism: + From: S^1 + To: Torus + Defn: [v_0, sigma_1] --> [(v_0, v_0), (sigma_1, sigma_1)], + Simplicial set morphism: + From: S^1 + To: Torus + Defn: Constant map at (v_0, v_0)] + sage: [f.induced_homology_morphism().to_matrix() for f in H] + [ + [ 1| 0] [1|0] [1|0] [1|0] + [--+--] [-+-] [-+-] [-+-] + [ 0|-1] [0|1] [0|0] [0|0] + [ 0| 1] [0|0] [0|1] [0|0] + [--+--] [-+-] [-+-] [-+-] + [ 0| 0], [0|0], [0|0], [0|0] + ] + """ + if not self.domain().is_finite(): + raise NotImplementedError('domain must be finite to iterate ' + 'through all morphisms') + codomain = self.codomain() + facets = self.domain()._facets_() + dims = [f.dimension() for f in facets] + # Record all of the n-simplices in the codomain once for each + # relevant dimension. + all_n_simplices = {d: codomain.all_n_simplices(d) for d in set(dims)} + for target in itertools.product(*[all_n_simplices[d] for d in dims]): + try: + yield self({sigma: tau for (sigma, tau) in zip(facets, target)}) + except ValueError: + # Not a valid morphism. + pass + + def _latex_(self): + r""" + LaTeX representation + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = simplicial_sets.Torus() + sage: H = Hom(S1, T) + sage: latex(H) + \operatorname{Map} (S^{1}, S^{1} \times S^{1}) + """ + return '\\operatorname{{Map}} ({}, {})'.format(latex(self.domain()), latex(self.codomain())) + + +class SimplicialSetMorphism(Morphism): + def __init__(self, data=None, domain=None, codomain=None, + constant=None, identity=False, check=True): + r""" + Return a morphism of simplicial sets. + + INPUT: + + - ``data`` -- optional. Dictionary defining the map. + - ``domain`` -- simplicial set + - ``codomain`` -- simplicial set + - ``constant`` -- optional: if not ``None``, then this should + be a vertex in the codomain, in which case return the + constant map with this vertex as the target. + - ``identity`` -- optional: if ``True``, return the identity + morphism. + - ``check`` -- optional, default ``True``. If ``True``, check + that this is actually a morphism: it commutes with the face + maps. + + So to define a map, you must specify ``domain`` and + ``codomain``. If the map is constant, specify the target (a + vertex in the codomain) as ``constant``. If the map is the + identity map, specify ``identity=True``. Otherwise, pass a + dictionary, ``data``. The keys of the dictionary are the + nondegenerate simplices of the domain, the corresponding + values are simplices in the codomain. + + In fact, the keys in ``data`` do not need to include all of + the nondegenerate simplices, only those which are not faces of + other nondegenerate simplices: if `\sigma` is a face of + `\tau`, then the image of `\sigma` need not be specified. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set_morphism import SimplicialSetMorphism + sage: K = simplicial_sets.Simplex(1) + sage: S1 = simplicial_sets.Sphere(1) + sage: v0 = K.n_cells(0)[0] + sage: v1 = K.n_cells(0)[1] + sage: e01 = K.n_cells(1)[0] + sage: w = S1.n_cells(0)[0] + sage: sigma = S1.n_cells(1)[0] + + sage: f = {v0: w, v1: w, e01: sigma} + sage: SimplicialSetMorphism(f, K, S1) + Simplicial set morphism: + From: 1-simplex + To: S^1 + Defn: [(0,), (1,), (0, 1)] --> [v_0, v_0, sigma_1] + + The same map can be defined as follows:: + + sage: H = Hom(K, S1) + sage: H(f) + Simplicial set morphism: + From: 1-simplex + To: S^1 + Defn: [(0,), (1,), (0, 1)] --> [v_0, v_0, sigma_1] + + Also, this map can be defined by specifying where the + 1-simplex goes; the vertices then go where they have to, to + satisfy the condition `d_i \circ f = f \circ d_i`:: + + sage: H = Hom(K, S1) + sage: H({e01: sigma}) + Simplicial set morphism: + From: 1-simplex + To: S^1 + Defn: [(0,), (1,), (0, 1)] --> [v_0, v_0, sigma_1] + + A constant map:: + + sage: g = {e01: w.apply_degeneracies(0)} + sage: SimplicialSetMorphism(g, K, S1) + Simplicial set morphism: + From: 1-simplex + To: S^1 + Defn: Constant map at v_0 + + The same constant map:: + + sage: SimplicialSetMorphism(domain=K, codomain=S1, constant=w) + Simplicial set morphism: + From: 1-simplex + To: S^1 + Defn: Constant map at v_0 + + An identity map:: + + sage: SimplicialSetMorphism(domain=K, codomain=K, identity=True) + Simplicial set endomorphism of 1-simplex + Defn: Identity map + + Defining a map by specifying it on only some of the simplices + in the domain:: + + sage: S5 = simplicial_sets.Sphere(5) + sage: s = S5.n_cells(5)[0] + sage: one = S5.Hom(S5)({s: s}) + sage: one + Simplicial set endomorphism of S^5 + Defn: Identity map + + TESTS: + + A non-map:: + + sage: h = {w: v0, sigma: e01} + sage: SimplicialSetMorphism(h, S1, K) + Traceback (most recent call last): + ... + ValueError: the dictionary does not define a map of simplicial sets + + Another non-map:: + + sage: h = {w: v0, v0: w, sigma: e01} + sage: SimplicialSetMorphism(h, S1, K) + Traceback (most recent call last): + ... + ValueError: at least one simplex in the defining dictionary is not in the domain + + A non-identity map:: + + sage: SimplicialSetMorphism(domain=K, codomain=S1, identity=True) + Traceback (most recent call last): + ... + TypeError: identity map is only defined for endomorphism sets + + An improperly partially defined map:: + + sage: h = {w: v0} + sage: SimplicialSetMorphism(h, S1, K) + Traceback (most recent call last): + ... + ValueError: the image of at least one simplex in the domain is not defined + """ + self._is_identity = False + if not domain.is_finite(): + if identity: + if codomain is None: + codomain = domain + elif domain is not codomain: + raise TypeError("identity map is only defined for endomorphism sets") + self._is_identity = True + Morphism.__init__(self, Hom(domain, codomain, SimplicialSets())) + return + if constant is not None: + # If self._constant is set, it should be a vertex in + # the codomain, the target of the constant map. + self._constant = constant + Morphism.__init__(self, Hom(domain, codomain, SimplicialSets())) + return + raise NotImplementedError('morphisms with infinite domain ' + 'are not implemented in general') + else: + if identity: + self._is_identity = True + check = False + if domain is not codomain: + raise TypeError("identity map is only defined for endomorphism sets") + data = {} + for i in range(domain.dimension() + 1): + for s in domain.n_cells(i): + data[s] = s + if constant is not None: + self._constant = constant + check = False + data = {sigma: constant.apply_degeneracies(*range(sigma.dimension()-1,-1,-1)) + for sigma in domain.nondegenerate_simplices()} + + if (not isinstance(domain, SimplicialSet_arbitrary) + or not isinstance(codomain, SimplicialSet_arbitrary)): + raise TypeError('the domain and codomain must be simplicial sets') + if any(x.nondegenerate() not in + domain.nondegenerate_simplices() for x in data.keys()): + raise ValueError('at least one simplex in the defining ' + 'dictionary is not in the domain') + # Remove degenerate simplices from the domain specification. + d = {sigma:data[sigma] for sigma in data if sigma.is_nondegenerate()} + # For each simplex in d.keys(), add its faces, and the faces + # of its faces, etc., to d. + for simplex in list(d): + faces = domain.faces(simplex) + add = [] + if faces: + for (i, sigma) in enumerate(faces): + nondegen = sigma.nondegenerate() + if nondegen not in d: + add.append((sigma, i, simplex)) + while add: + (sigma, i, tau) = add.pop() + # sigma is the ith face of tau. + face_f = codomain.face(d[tau], i) + degens = sigma.degeneracies() + x = face_f + for j in degens: + x = codomain.face(x, j) + d[sigma.nondegenerate()] = x + faces = domain.faces(sigma.nondegenerate()) + if faces: + for (i,rho) in enumerate(faces): + nondegen = rho.nondegenerate() + if nondegen not in d: + add.append((rho,i,sigma)) + # Now check that the proposed map commutes with the face + # maps. (The degeneracy maps should work automatically.) + if check: + for simplex in d: + # Compare d[d_i (simplex)] to d_i d[simplex]. Since + # d_i(simplex) may be degenerate, we have to be careful + # when applying f to it. We can skip vertices and start + # with 1-simplices. + bad = False + for i in range(simplex.dimension()+1): + face_f = codomain.face(d[simplex], i) + face = domain.face(simplex, i) + if face is None: + f_face = None + elif face.is_nondegenerate(): + f_face = d[face] + else: + nondegen = face.nondegenerate() + f_face = d[nondegen].apply_degeneracies(*face.degeneracies()) + if face_f != f_face: + bad = True + break + if bad: + raise ValueError('the dictionary does not define a map of simplicial sets') + if any(x not in d.keys() for x in domain.nondegenerate_simplices()): + raise ValueError('the image of at least one simplex in ' + 'the domain is not defined') + self._dictionary = d + Morphism.__init__(self, Hom(domain, codomain, SimplicialSets())) + + def __eq__(self, other): + """ + Two morphisms are equal iff their domains are the same, their + codomains are the same, and their defining dictionaries are + the same. + + EXAMPLES:: + + sage: S = simplicial_sets.Sphere(1) + sage: T = simplicial_sets.Torus() + sage: T_c = T.constant_map() * T.base_point_map() + sage: S_c = S.constant_map() * S.base_point_map() + sage: T_c == S_c + True + sage: T.constant_map() == S.constant_map() + False + sage: K = simplicial_sets.Sphere(1) + sage: K.constant_map() == S.constant_map() + False + + sage: Point = simplicial_sets.Point() + sage: f = Point._map_from_empty_set() + sage: Empty = f.domain() + sage: g = Empty.constant_map() + sage: f == g + True + """ + if self.domain().is_finite() and other.domain().is_finite(): + return (self.domain() == other.domain() + and self.codomain() == other.codomain() + and self._dictionary == other._dictionary) + else: + return False + + def __ne__(self, other): + """ + The negation of ``__eq__``. + + EXAMPLES:: + + sage: S0 = simplicial_sets.Sphere(0) + sage: v,w = S0.n_cells(0) + sage: H = Hom(S0, S0) + sage: H({v:v, w:w}) != H({v:w, w:v}) + True + sage: H({v:v, w:w}) != H({w:w, v:v}) + False + """ + return not self == other + + def __call__(self, x): + """ + INPUT: a simplex of the domain. + + Return its image under this morphism. + + EXAMPLES:: + + sage: K = simplicial_sets.Simplex(1) + sage: S1 = simplicial_sets.Sphere(1) + sage: v0 = K.n_cells(0)[0] + sage: v1 = K.n_cells(0)[1] + sage: e01 = K.n_cells(1)[0] + sage: w = S1.n_cells(0)[0] + sage: sigma = S1.n_cells(1)[0] + sage: d = {v0: w, v1: w, e01: sigma} + sage: f = Hom(K, S1)(d) + sage: f(e01) # indirect doctest + sigma_1 + + sage: one = Hom(S1, S1).identity() + sage: e = S1.n_cells(1)[0] + sage: one(e) == e + True + + sage: B = AbelianGroup([2]).nerve() + sage: c = B.constant_map() + sage: c(B.n_cells(2)[0]) + s_1 s_0 * + """ + if x not in self.domain(): + raise ValueError('element is not a simplex in the domain') + if self.is_constant(): + target = self._constant + return target.apply_degeneracies(*range(x.dimension()-1, -1, -1)) + if self._is_identity: + return x + return self._dictionary[x.nondegenerate()].apply_degeneracies(*x.degeneracies()) + + def _composition_(self, right, homset): + """ + Return the composition of two morphisms. + + INPUT: + + - ``self``, ``right`` -- maps + - ``homset`` -- a homset + + ASSUMPTION: + + The codomain of ``right`` is contained in the domain of + ``self``. This assumption should be verified by the + ``Map.__mul__`` method in ``categories/map.pyx``, so we don't + need to check it here. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: f = S1.Hom(S1).identity() + sage: f * f # indirect doctest + Simplicial set endomorphism of S^1 + Defn: Identity map + sage: T = S1.product(S1) + sage: K = T.factor(0, as_subset=True) + sage: g = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) + sage: g + Simplicial set morphism: + From: S^1 + To: S^1 x S^1 + Defn: [v_0, sigma_1] --> [(v_0, v_0), (sigma_1, s_0 v_0)] + sage: (g*f).image() + Simplicial set with 2 non-degenerate simplices + sage: f.image().homology() + {0: 0, 1: Z} + """ + if self.is_identity(): + return right + if right.is_identity(): + return self + d = {} + for sigma in right._dictionary: + d[sigma] = self(right(sigma)) + return homset(d) + + def image(self): + """ + Return the image of this morphism as a subsimplicial set of the + codomain. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: T = S1.product(S1) + sage: K = T.factor(0, as_subset=True) + sage: f = S1.Hom(T)({S1.n_cells(0)[0]:K.n_cells(0)[0], S1.n_cells(1)[0]:K.n_cells(1)[0]}) + sage: f + Simplicial set morphism: + From: S^1 + To: S^1 x S^1 + Defn: [v_0, sigma_1] --> [(v_0, v_0), (sigma_1, s_0 v_0)] + sage: f.image() + Simplicial set with 2 non-degenerate simplices + sage: f.image().homology() + {0: 0, 1: Z} + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: B.constant_map().image() + Point + sage: Hom(B,B).identity().image() == B + True + """ + if self._is_identity: + return self.codomain() + if self.is_constant(): + return self.codomain().subsimplicial_set([self._constant]) + simplices = self._dictionary.values() + if set(simplices) == set(self.codomain().nondegenerate_simplices()): + return self.codomain() + return self.codomain().subsimplicial_set(simplices) + + def is_identity(self): + """ + Return ``True`` if this morphism is an identity map. + + EXAMPLES:: + + sage: K = simplicial_sets.Simplex(1) + sage: v0 = K.n_cells(0)[0] + sage: v1 = K.n_cells(0)[1] + sage: e01 = K.n_cells(1)[0] + sage: L = simplicial_sets.Simplex(2).n_skeleton(1) + sage: w0 = L.n_cells(0)[0] + sage: w1 = L.n_cells(0)[1] + sage: w2 = L.n_cells(0)[2] + sage: f01 = L.n_cells(1)[0] + sage: f02 = L.n_cells(1)[1] + sage: f12 = L.n_cells(1)[2] + + sage: d = {v0:w0, v1:w1, e01:f01} + sage: f = K.Hom(L)(d) + sage: f.is_identity() + False + sage: d = {w0:v0, w1:v1, w2:v1, f01:e01, f02:e01, f12: v1.apply_degeneracies(0,)} + sage: g = L.Hom(K)(d) + sage: (g*f).is_identity() + True + sage: (f*g).is_identity() + False + sage: (f*g).induced_homology_morphism().to_matrix(1) + [0] + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP5.n_skeleton(2).inclusion_map().is_identity() + False + sage: RP5.n_skeleton(5).inclusion_map().is_identity() + True + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: Hom(B,B).identity().is_identity() + True + sage: Hom(B,B).constant_map().is_identity() + False + """ + ans = (self._is_identity or + (self.domain() == self.codomain() + and self.domain().is_finite() + and all(a == b for a,b in self._dictionary.items()))) + self._is_identity = ans + return ans + + def is_surjective(self): + """ + Return ``True`` if this map is surjective. + + EXAMPLES:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP2 = RP5.n_skeleton(2) + sage: RP2.inclusion_map().is_surjective() + False + + sage: RP5_2 = RP5.quotient(RP2) + sage: RP5_2.quotient_map().is_surjective() + True + + sage: K = RP5_2.pullback(RP5_2.quotient_map(), RP5_2.base_point_map()) + sage: f = K.universal_property(RP2.inclusion_map(), RP2.constant_map()) + sage: f.is_surjective() + True + """ + return self._is_identity or self.image() == self.codomain() + + def is_injective(self): + """ + Return ``True`` if this map is injective. + + EXAMPLES:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP2 = RP5.n_skeleton(2) + sage: RP2.inclusion_map().is_injective() + True + + sage: RP5_2 = RP5.quotient(RP2) + sage: RP5_2.quotient_map().is_injective() + False + + sage: K = RP5_2.pullback(RP5_2.quotient_map(), RP5_2.base_point_map()) + sage: f = K.universal_property(RP2.inclusion_map(), RP2.constant_map()) + sage: f.is_injective() + True + """ + if self._is_identity: + return True + domain = self.domain() + for n in range(domain.dimension()+1): + input = domain.n_cells(n) + output = set([self(sigma) for sigma in input if self(sigma).is_nondegenerate()]) + if len(input) > len(output): + return False + return True + + def is_bijective(self): + """ + Return ``True`` if this map is bijective. + + EXAMPLES:: + + sage: RP5 = simplicial_sets.RealProjectiveSpace(5) + sage: RP2 = RP5.n_skeleton(2) + sage: RP2.inclusion_map().is_bijective() + False + + sage: RP5_2 = RP5.quotient(RP2) + sage: RP5_2.quotient_map().is_bijective() + False + + sage: K = RP5_2.pullback(RP5_2.quotient_map(), RP5_2.base_point_map()) + sage: f = K.universal_property(RP2.inclusion_map(), RP2.constant_map()) + sage: f.is_bijective() + True + """ + return self.is_injective() and self.is_surjective() + + def is_pointed(self): + """ + Return ``True`` if this is a pointed map. + + That is, return ``True`` if the domain and codomain are + pointed and this morphism preserves the base point. + + EXAMPLES:: + + sage: S0 = simplicial_sets.Sphere(0) + sage: f = Hom(S0,S0).identity() + sage: f.is_pointed() + True + sage: v = S0.n_cells(0)[0] + sage: w = S0.n_cells(0)[1] + sage: g = Hom(S0,S0)({v:v, w:v}) + sage: g.is_pointed() + True + sage: t = Hom(S0,S0)({v:w, w:v}) + sage: t.is_pointed() + False + """ + return (self.domain().is_pointed() and self.codomain().is_pointed() + and self(self.domain().base_point()) == self.codomain().base_point()) + + def is_constant(self): + """ + Return ``True`` if this morphism is a constant map. + + EXAMPLES:: + + sage: K = simplicial_sets.KleinBottle() + sage: S4 = simplicial_sets.Sphere(4) + sage: c = Hom(K, S4).constant_map() + sage: c.is_constant() + True + sage: X = S4.n_skeleton(3) # a point + sage: X.inclusion_map().is_constant() + True + sage: eta = simplicial_sets.HopfMap() + sage: eta.is_constant() + False + """ + try: + return self._constant is not None + except AttributeError: + pass + if not self.domain().is_finite(): + # The domain is infinite, so there is no safe way to + # determine if the map is constant. + return False + targets = [tau.nondegenerate() for tau in self._dictionary.values()] + if len(set(targets)) == 1: + # It's constant, so save the target. + self._constant = targets[0] + return True + return False + + def pushout(self, *others): + """ + Return the pushout of this morphism along with ``others``. + + INPUT: + + - ``others`` -- morphisms of simplicial sets, the domains of + which must all equal that of ``self``. + + This returns the pushout as a simplicial set. See + :class:`sage.topology.simplicial_set_constructions.PushoutOfSimplicialSets` + for more documentation and examples. + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: K = simplicial_sets.KleinBottle() + sage: init_T = T._map_from_empty_set() + sage: init_K = K._map_from_empty_set() + sage: D = init_T.pushout(init_K) # the disjoint union as a pushout + sage: D + Pushout of maps: + Simplicial set morphism: + From: Empty simplicial set + To: Torus + Defn: [] --> [] + Simplicial set morphism: + From: Empty simplicial set + To: Klein bottle + Defn: [] --> [] + """ + domain = self.domain() + if any(domain != f.domain() for f in others): + raise ValueError('the domains of the maps must be equal') + return self.domain().pushout(*(self,) + others) + + def pullback(self, *others): + """ + Return the pullback of this morphism along with ``others``. + + INPUT: + + - ``others`` -- morphisms of simplicial sets, the codomains of + which must all equal that of ``self``. + + This returns the pullback as a simplicial set. See + :class:`sage.topology.simplicial_set_constructions.PullbackOfSimplicialSets` + for more documentation and examples. + + EXAMPLES:: + + sage: T = simplicial_sets.Torus() + sage: K = simplicial_sets.KleinBottle() + sage: term_T = T.constant_map() + sage: term_K = K.constant_map() + sage: P = term_T.pullback(term_K) # the product as a pullback + sage: P + Pullback of maps: + Simplicial set morphism: + From: Torus + To: Point + Defn: Constant map at * + Simplicial set morphism: + From: Klein bottle + To: Point + Defn: Constant map at * + """ + codomain = self.codomain() + if any(codomain != f.codomain() for f in others): + raise ValueError('the codomains of the maps must be equal') + return self.codomain().pullback(*(self,) + others) + + def equalizer(self, other): + r""" + Return the equalizer of this map with ``other``. + + INPUT: + + - ``other`` -- a morphism with the same domain and codomain as this map + + If the two maps are `f, g: X \to Y`, then the equalizer `P` is + constructed as the pullback :: + + P ----> X + | | + V V + X --> X x Y + + where the two maps `X \to X \times Y` are `(1,f)` and `(1,g)`. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: x = AbstractSimplex(0, name='x') + sage: evw = AbstractSimplex(1, name='vw') + sage: evx = AbstractSimplex(1, name='vx') + sage: ewx = AbstractSimplex(1, name='wx') + sage: X = SimplicialSet({evw: (w, v), evx: (x, v)}) + sage: Y = SimplicialSet({evw: (w, v), evx: (x, v), ewx: (x, w)}) + + Here `X` is a wedge of two 1-simplices (a horn, that is), and + `Y` is the boundary of a 2-simplex. The map `f` includes the + two 1-simplices into `Y`, while the map `g` maps both + 1-simplices to the same edge in `Y`. :: + + sage: f = Hom(X, Y)({v:v, w:w, x:x, evw:evw, evx:evx}) + sage: g = Hom(X, Y)({v:v, w:x, x:x, evw:evx, evx:evx}) + sage: P = f.equalizer(g) + sage: P + Pullback of maps: + Simplicial set morphism: + From: Simplicial set with 5 non-degenerate simplices + To: Simplicial set with 5 non-degenerate simplices x Simplicial set with 6 non-degenerate simplices + Defn: [v, w, x, vw, vx] --> [(v, v), (w, w), (x, x), (vw, vw), (vx, vx)] + Simplicial set morphism: + From: Simplicial set with 5 non-degenerate simplices + To: Simplicial set with 5 non-degenerate simplices x Simplicial set with 6 non-degenerate simplices + Defn: [v, w, x, vw, vx] --> [(v, v), (w, x), (x, x), (vw, vx), (vx, vx)] + """ + domain = self.domain() + codomain = self.codomain() + if domain != other.domain() or codomain != other.codomain(): + raise ValueError('the maps must have the same domain and the same codomain') + prod = domain.product(codomain) + one = domain.Hom(domain).identity() + f = prod.universal_property(one, self) + g = prod.universal_property(one, other) + return f.pullback(g) + + def coequalizer(self, other): + r""" + Return the coequalizer of this map with ``other``. + + INPUT: + + - ``other`` -- a morphism with the same domain and codomain as this map + + If the two maps are `f, g: X \to Y`, then the coequalizer `P` is + constructed as the pushout :: + + X v Y --> Y + | | + V V + Y ----> P + + where the upper left corner is the coproduct of `X` and `Y` + (the wedge if they are pointed, the disjoint union otherwise), + and the two maps `X \amalg Y \to Y` are `f \amalg 1` and `g + \amalg 1`. + + EXAMPLES:: + + sage: L = simplicial_sets.Simplex(2) + sage: pt = L.n_cells(0)[0] + sage: e = L.n_cells(1)[0] + sage: K = L.subsimplicial_set([e]) + sage: f = K.inclusion_map() + sage: v,w = K.n_cells(0) + sage: g = Hom(K,L)({v:pt, w:pt, e:pt.apply_degeneracies(0)}) + sage: P = f.coequalizer(g) + sage: P + Pushout of maps: + Simplicial set morphism: + From: Disjoint union: (Simplicial set with 3 non-degenerate simplices u 2-simplex) + To: 2-simplex + Defn: ... + Simplicial set morphism: + From: Disjoint union: (Simplicial set with 3 non-degenerate simplices u 2-simplex) + To: 2-simplex + Defn: ... + """ + domain = self.domain() + codomain = self.codomain() + if domain != other.domain() or codomain != other.codomain(): + raise ValueError('the maps must have the same domain and the same codomain') + coprod = domain.coproduct(codomain) + one = codomain.Hom(codomain).identity() + f = coprod.universal_property(self, one) + g = coprod.universal_property(other, one) + return f.pushout(g) + + def mapping_cone(self): + r""" + Return the mapping cone defined by this map. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: v_0, sigma_1 = S1.nondegenerate_simplices() + sage: K = simplicial_sets.Simplex(2).n_skeleton(1) + + The mapping cone will be a little smaller if we use only + pointed simplicial sets. `S^1` is already pointed, but not + `K`. :: + + sage: L = K.set_base_point(K.n_cells(0)[0]) + sage: u,v,w = L.n_cells(0) + sage: e,f,g = L.n_cells(1) + sage: h = L.Hom(S1)({u:v_0, v:v_0, w:v_0, e:sigma_1, f:v_0.apply_degeneracies(0), g:sigma_1}) + sage: h + Simplicial set morphism: + From: Simplicial set with 6 non-degenerate simplices + To: S^1 + Defn: [(0,), (1,), (2,), (0, 1), (0, 2), (1, 2)] --> [v_0, v_0, v_0, sigma_1, s_0 v_0, sigma_1] + sage: h.induced_homology_morphism().to_matrix() + [1|0] + [-+-] + [0|2] + sage: X = h.mapping_cone() + sage: X.homology() == simplicial_sets.RealProjectiveSpace(2).homology() + True + """ + dom = self.domain() + cone = dom.cone() + i = cone.map_from_base() + return self.pushout(i) + + def product(self, *others): + r""" + Return the product of this map with ``others``. + + - ``others`` -- morphisms of simplicial sets. + + If the relevant maps are `f_i: X_i \to Y_i`, this returns the + natural map `\prod X_i \to \prod Y_i`. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: f = Hom(S1,S1).identity() + sage: f.product(f).is_bijective() + True + sage: g = S1.constant_map(S1) + sage: g.product(g).is_bijective() + False + """ + domain = self.domain().product(*[g.domain() for g in others]) + codomain = self.codomain().product(*[g.codomain() for g in others]) + factors = [] + for (i,f) in enumerate([self] + list(others)): + factors.append(f * domain.projection_map(i)) + return codomain.universal_property(*factors) + + def coproduct(self, *others): + r""" + Return the coproduct of this map with ``others``. + + - ``others`` -- morphisms of simplicial sets. + + If the relevant maps are `f_i: X_i \to Y_i`, this returns the + natural map `\amalg X_i \to \amalg Y_i`. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: f = Hom(S1,S1).identity() + sage: f.coproduct(f).is_bijective() + True + sage: g = S1.constant_map(S1) + sage: g.coproduct(g).is_bijective() + False + """ + codomain = self.codomain().coproduct(*[g.codomain() for g in others]) + factors = [] + for i, f in enumerate([self] + list(others)): + factors.append(codomain.inclusion_map(i) * f) + return codomain.universal_property(*factors) + + def suspension(self, n=1): + """ + Return the `n`-th suspension of this morphism of simplicial sets. + + INPUT: + + - ``n`` (optional) -- non-negative integer, default 1 + + EXAMPLES:: + + sage: eta = simplicial_sets.HopfMap() + sage: susp_eta = eta.suspension() + sage: susp_eta.mapping_cone().homology() == eta.mapping_cone().suspension().homology() + True + + This uses reduced suspensions if the original morphism is + pointed, unreduced otherwise. So for example, if a constant + map is not pointed, its suspension is not a constant map:: + + sage: L = simplicial_sets.Simplex(1) + sage: L.constant_map().is_pointed() + False + sage: f = L.constant_map().suspension() + sage: f.is_constant() + False + + sage: K = simplicial_sets.Sphere(3) + sage: K.constant_map().is_pointed() + True + sage: g = K.constant_map().suspension() + sage: g.is_constant() + True + + sage: h = K.identity().suspension() + sage: h.is_identity() + True + """ + domain = self.domain() + codomain = self.codomain() + if not self.is_pointed(): + # Make sure to use unreduced suspensions for both domain + # and codomain. + if domain.is_pointed(): + domain = domain.unset_base_point() + if codomain.is_pointed(): + codomain = codomain.unset_base_point() + f = self + for i in range(n): + new_dom = domain.suspension() + new_cod = codomain.suspension() + data = {new_dom.base_point(): new_cod.base_point()} + for sigma in f._dictionary: + target = f(sigma) + underlying = target.nondegenerate() + degens = target.degeneracies() + data[new_dom._suspensions[sigma]] = new_cod._suspensions[underlying].apply_degeneracies(*degens) + f = new_dom.Hom(new_cod)(data) + domain = f.domain() + codomain = f.codomain() + return f + + def n_skeleton(self, n, domain=None, codomain=None): + """ + Return the restriction of this morphism to the n-skeleta of the + domain and codomain + + INPUT: + + - ``n`` -- the dimension + + - ``domain`` -- optional, the domain. Specify this to + explicitly specify the domain; otherwise, Sage will attempt + to compute it. Specifying this can be useful if the domain + is built as a pushout or pullback, so trying to compute it + may lead to computing the `n`-skeleton of a map, causing an + infinite recursion. (Users should not have to specify this, + but it may be useful for developers.) + + - ``codomain`` -- optional, the codomain. + + EXAMPLES:: + + sage: B = simplicial_sets.ClassifyingSpace(groups.misc.MultiplicativeAbelian([2])) + sage: one = Hom(B,B).identity() + sage: one.n_skeleton(3) + Simplicial set endomorphism of Simplicial set with 4 non-degenerate simplices + Defn: Identity map + sage: c = Hom(B,B).constant_map() + sage: c.n_skeleton(3) + Simplicial set endomorphism of Simplicial set with 4 non-degenerate simplices + Defn: Constant map at 1 + + sage: K = simplicial_sets.Simplex(2) + sage: L = K.subsimplicial_set(K.n_cells(0)[:2]) + sage: L.nondegenerate_simplices() + [(0,), (1,)] + sage: L.inclusion_map() + Simplicial set morphism: + From: Simplicial set with 2 non-degenerate simplices + To: 2-simplex + Defn: [(0,), (1,)] --> [(0,), (1,)] + sage: L.inclusion_map().n_skeleton(1) + Simplicial set morphism: + From: Simplicial set with 2 non-degenerate simplices + To: Simplicial set with 6 non-degenerate simplices + Defn: [(0,), (1,)] --> [(0,), (1,)] + """ + if domain is None: + domain = self.domain().n_skeleton(n) + if codomain is None: + codomain = self.codomain().n_skeleton(n) + if self.is_constant(): + return Hom(domain, codomain).constant_map(self._constant) + if self.is_identity(): + return Hom(domain, domain).identity() + old = self._dictionary + new = {d: old[d] for d in old if d.dimension() <= n} + return Hom(domain, codomain)(new) + + def associated_chain_complex_morphism(self, base_ring=ZZ, + augmented=False, cochain=False): + """ + Return the associated chain complex morphism of ``self``. + + INPUT: + + - ``base_ring`` -- default ``ZZ`` + - ``augmented`` -- boolean, default ``False``. If ``True``, + return the augmented complex. + - ``cochain`` -- boolean, default ``False``. If ``True``, + return the cochain complex. + + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: v0 = S1.n_cells(0)[0] + sage: e = S1.n_cells(1)[0] + sage: f = {v0: v0, e: v0.apply_degeneracies(0)} # constant map + sage: g = Hom(S1, S1)(f) + sage: g.associated_chain_complex_morphism().to_matrix() + [1|0] + [-+-] + [0|0] + """ + # One or the other chain complex is trivial between these + # dimensions: + max_dim = max(self.domain().dimension(), self.codomain().dimension()) + min_dim = min(self.domain().dimension(), self.codomain().dimension()) + matrices = {} + if augmented is True: + m = matrix(base_ring,1,1,1) + if not cochain: + matrices[-1] = m + else: + matrices[-1] = m.transpose() + for dim in range(min_dim+1): + X_faces = list(self.domain().n_cells(dim)) + Y_faces = list(self.codomain().n_cells(dim)) + num_faces_X = len(X_faces) + num_faces_Y = len(Y_faces) + mval = [0 for _ in range(num_faces_X * num_faces_Y)] + for idx,x in enumerate(X_faces): + y = self(x) + if y.is_nondegenerate(): + mval[idx + (Y_faces.index(y) * num_faces_X)] = 1 + m = matrix(base_ring, num_faces_Y, num_faces_X, mval, sparse=True) + if not cochain: + matrices[dim] = m + else: + matrices[dim] = m.transpose() + for dim in range(min_dim+1,max_dim+1): + try: + l1 = len(self.codomain().n_cells(dim)) + except KeyError: + l1 = 0 + try: + l2 = len(self.domain().n_cells(dim)) + except KeyError: + l2 = 0 + m = zero_matrix(base_ring,l1,l2,sparse=True) + if not cochain: + matrices[dim] = m + else: + matrices[dim] = m.transpose() + if not cochain: + return ChainComplexMorphism(matrices, + self.domain().chain_complex(base_ring=base_ring, augmented=augmented, cochain=False), + self.codomain().chain_complex(base_ring=base_ring, augmented=augmented, cochain=False)) + else: + return ChainComplexMorphism(matrices, + self.codomain().chain_complex(base_ring=base_ring, augmented=augmented, cochain=True), + self.domain().chain_complex(base_ring=base_ring, augmented=augmented, cochain=True)) + + def induced_homology_morphism(self, base_ring=None, cohomology=False): + """ + Return the map in (co)homology induced by this map + + INPUT: + + - ``base_ring`` -- must be a field (optional, default ``QQ``) + + - ``cohomology`` -- boolean (optional, default ``False``). If + ``True``, the map induced in cohomology rather than homology. + + EXAMPLES:: + + sage: from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet + sage: v = AbstractSimplex(0, name='v') + sage: w = AbstractSimplex(0, name='w') + sage: e = AbstractSimplex(1, name='e') + sage: f = AbstractSimplex(1, name='f') + sage: X = SimplicialSet({e: (v, w), f: (w, v)}) + sage: Y = SimplicialSet({e: (v, v)}) + sage: H = Hom(X, Y) + sage: f = H({v: v, w: v, e: e, f: e}) + sage: g = f.induced_homology_morphism() + sage: g.to_matrix() + [1|0] + [-+-] + [0|2] + sage: g3 = f.induced_homology_morphism(base_ring=GF(3), cohomology=True) + sage: g3.to_matrix() + [1|0] + [-+-] + [0|2] + """ + return InducedHomologyMorphism(self, base_ring, cohomology) + + def _repr_type(self): + """ + EXAMPLES:: + + sage: S1 = simplicial_sets.Sphere(1) + sage: f = Hom(S1,S1).identity() + sage: f._repr_type() + 'Simplicial set' + """ + return "Simplicial set" + + def _repr_defn(self): + """ + EXAMPLES:: + + sage: K1 = simplicial_sets.Simplex(1) + sage: v = K1.n_cells(0)[0] + sage: e = K1.n_cells(1)[0] + sage: f = Hom(K1,K1)({e:v.apply_degeneracies(0)}) + sage: f._repr_defn() + 'Constant map at (0,)' + + sage: K2 = simplicial_sets.Simplex(2) + sage: tau = K2.n_cells(1)[0] + sage: Hom(K1, K2)({e:tau})._repr_defn() + '[(0,), (1,), (0, 1)] --> [(0,), (1,), (0, 1)]' + + sage: S1 = simplicial_sets.Sphere(1) + sage: Hom(S1,S1).identity()._repr_defn() + 'Identity map' + """ + if self.is_identity(): + return 'Identity map' + if self.is_constant(): + return 'Constant map at {}'.format(self._constant) + d = self._dictionary + keys = sorted(d.keys()) + return "{} --> {}".format(keys, [d[x] for x in keys]) + + def _latex_(self): + """ + LaTeX representation. + + EXAMPLES:: + + sage: eta = simplicial_sets.HopfMap() + sage: eta.domain().rename_latex('S^{3}') + sage: latex(eta) + S^{3} \to S^{2} + """ + return '{} \\to {}'.format(latex(self.domain()), latex(self.codomain())) diff --git a/src/sage/version.py b/src/sage/version.py index 3289093f8e6..6cc84191475 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.4.beta0' -date = '2021-05-25' -banner = 'SageMath version 9.4.beta0, Release Date: 2021-05-25' +version = '9.4.beta4' +date = '2021-07-01' +banner = 'SageMath version 9.4.beta4, Release Date: 2021-07-01' diff --git a/src/sage_docbuild/__init__.py b/src/sage_docbuild/__init__.py index 79005b903ab..b63e53539ed 100644 --- a/src/sage_docbuild/__init__.py +++ b/src/sage_docbuild/__init__.py @@ -116,7 +116,7 @@ def f(self, *args, **kwds): # WEBSITESPHINXOPTS is either empty or " -A hide_pdf_links=1 " options += WEBSITESPHINXOPTS - if kwds.get('use_multidoc_inventory', True): + if kwds.get('use_multidoc_inventory', True) and type != 'inventory': options += ' -D multidoc_first_pass=0' else: options += ' -D multidoc_first_pass=1' @@ -516,7 +516,7 @@ def __init__(self, name, lang='en'): self.name = doc[0] self.lang = lang - def _output_dir(self, type, lang='en'): + def _output_dir(self, type, lang=None): """ Return the directory where the output of type ``type`` is stored. @@ -530,152 +530,63 @@ def _output_dir(self, type, lang='en'): sage: b._output_dir('html') '.../html/en/reference' """ + if lang is None: + lang = self.lang d = os.path.join(SAGE_DOC, type, lang, self.name) sage_makedirs(d) return d - def _refdir(self, lang): - return os.path.join(SAGE_DOC_SRC, lang, self.name) + def _refdir(self): + return os.path.join(SAGE_DOC_SRC, self.lang, self.name) - def _build_bibliography(self, lang, format, *args, **kwds): + def _build_bibliography(self, format, *args, **kwds): """ Build the bibliography only The bibliography references.aux is referenced by the other manuals and needs to be built first. """ - refdir = self._refdir(lang) + refdir = self._refdir() references = [ - (doc, lang, format, kwds) + args for doc in self.get_all_documents(refdir) + (doc, self.lang, format, kwds) + args for doc in self.get_all_documents(refdir) if doc == 'reference/references' ] build_many(build_ref_doc, references) - def _build_everything_except_bibliography(self, lang, format, *args, **kwds): + def _build_everything_except_bibliography(self, format, *args, **kwds): """ Build the entire reference manual except the bibliography """ - refdir = self._refdir(lang) + refdir = self._refdir() non_references = [ - (doc, lang, format, kwds) + args for doc in self.get_all_documents(refdir) + (doc, self.lang, format, kwds) + args for doc in self.get_all_documents(refdir) if doc != 'reference/references' ] build_many(build_ref_doc, non_references) + def _build_top_level(self, format, *args, **kwds): + """ + Build top-level document. + """ + getattr(ReferenceTopBuilder('reference'), format)(*args, **kwds) + def _wrapper(self, format, *args, **kwds): """ - Builds reference manuals. For each language, it builds the + Builds reference manuals: build the top-level document and its components. """ - for lang in LANGUAGES: - refdir = self._refdir(lang) - if not os.path.exists(refdir): - continue - logger.info('Building bibliography') - self._build_bibliography(lang, format, *args, **kwds) - logger.info('Bibliography finished, building dependent manuals') - self._build_everything_except_bibliography(lang, format, *args, **kwds) - # The html refman must be build at the end to ensure correct - # merging of indexes and inventories. - # Sphinx is run here in the current process (not in a - # subprocess) and the IntersphinxCache gets populated to be - # used for the second pass of the reference manual and for - # the other documents. - getattr(DocBuilder(self.name, lang), format)(*args, **kwds) - - # PDF: we need to build master index file which lists all - # of the PDF file. So we create an html file, based on - # the file index.html from the "website" target. - if format == 'pdf': - # First build the website page. This only takes a few seconds. - getattr(get_builder('website'), 'html')() - - website_dir = os.path.join(SAGE_DOC, 'html', 'en', 'website') - output_dir = self._output_dir(format, lang) - - # Install in output_dir a symlink to the directory containing static files. - try: - os.symlink(os.path.join(website_dir, '_static'), os.path.join(output_dir, '_static')) - except FileExistsError: - pass - - # Now modify website's index.html page and write it to - # output_dir. - with open(os.path.join(website_dir, 'index.html')) as f: - html = f.read().replace('Documentation', 'Reference') - html_output_dir = os.path.dirname(website_dir) - html = html.replace('http://www.sagemath.org', - os.path.join(html_output_dir, 'index.html')) - # From index.html, we want the preamble and the tail. - html_end_preamble = html.find('

Sage Reference') - html_bottom = html.rfind('') + len('') - - # For the content, we modify doc/en/reference/index.rst, which - # has two parts: the body and the table of contents. - with open(os.path.join(SAGE_DOC_SRC, lang, 'reference', 'index.rst')) as f: - rst = f.read() - # Get rid of todolist and miscellaneous rst markup. - rst = rst.replace('.. _reference-manual:\n\n', '') - rst = re.sub(r'\\\\', r'\\', rst) - # Replace rst links with html links. There are three forms: - # - # `blah`__ followed by __ LINK - # - # `blah `_ - # - # :doc:`blah ` - # - # Change the first and the second forms to - # - # blah - # - # Change the third form to - # - # blah - # - rst = re.sub(r'`([^`\n]*)`__.*\n\n__ (.*)', - r'\1.', rst) - rst = re.sub(r'`([^<\n]*)\s+<(.*)>`_', - r'\1', rst) - rst = re.sub(r':doc:`([^<]*?)\s+<(.*)/index>`', - r'\1 ', rst) - # Body: add paragraph

markup. - start = rst.rfind('*\n') + 1 - end = rst.find('\nUser Interfaces') - rst_body = rst[start:end] - rst_body = rst_body.replace('\n\n', '

\n

') - # TOC: don't include the indices - start = rst.find('\nUser Interfaces') - end = rst.find('Indices and Tables') - rst_toc = rst[start:end] - # change * to

  • ; change rst headers to html headers - rst_toc = re.sub(r'\*(.*)\n', - r'
  • \1
  • \n', rst_toc) - rst_toc = re.sub(r'\n([A-Z][a-zA-Z, ]*)\n[=]*\n', - r'\n\n\n

    \1

    \n\n
      \n', rst_toc) - rst_toc = re.sub(r'\n([A-Z][a-zA-Z, ]*)\n[-]*\n', - r'
    \n\n\n

    \1

    \n\n
      \n', rst_toc) - # now write the file. - with open(os.path.join(output_dir, 'index.html'), 'w') as new_index: - new_index.write(html[:html_end_preamble]) - new_index.write('

      Sage Reference Manual (PDF version)'+ '

      ') - new_index.write(rst_body) - new_index.write('
        ') - new_index.write(rst_toc) - new_index.write('
      \n\n') - new_index.write(html[html_bottom:]) - logger.warning(''' -PDF documents have been created in subdirectories of - - %s - -Alternatively, you can open - - %s - -for a webpage listing all of the documents.''' % (output_dir, - os.path.join(output_dir, - 'index.html'))) + refdir = self._refdir() + logger.info('Building bibliography') + self._build_bibliography(format, *args, **kwds) + logger.info('Bibliography finished, building dependent manuals') + self._build_everything_except_bibliography(format, *args, **kwds) + # The html refman must be build at the end to ensure correct + # merging of indexes and inventories. + # Sphinx is run here in the current process (not in a + # subprocess) and the IntersphinxCache gets populated to be + # used for the second pass of the reference manual and for + # the other documents. + self._build_top_level(format, *args, **kwds) def get_all_documents(self, refdir): """ @@ -708,6 +619,135 @@ def get_all_documents(self, refdir): return [ doc[1] for doc in sorted(documents) ] +class ReferenceTopBuilder(DocBuilder): + """ + This class builds the top-level page of the reference manual. + """ + def __init__(self, *args, **kwds): + DocBuilder.__init__(self, *args, **kwds) + self.name = 'reference' + self.lang = 'en' + + def _output_dir(self, type, lang=None): + """ + Return the directory where the output of type ``type`` is stored. + + If the directory does not exist, then it will automatically be + created. + + EXAMPLES:: + + sage: from sage_docbuild import ReferenceTopBuilder + sage: b = ReferenceTopBuilder('reference') + sage: b._output_dir('html') + '.../html/en/reference' + """ + if lang is None: + lang = self.lang + d = os.path.join(SAGE_DOC, type, lang, self.name) + sage_makedirs(d) + return d + + def _wrapper(self, format, *args, **kwds): + """ + Build top-level document. + """ + getattr(DocBuilder(self.name, self.lang), format)(*args, **kwds) + # PDF: we need to build master index file which lists all + # of the PDF file. So we create an html file, based on + # the file index.html from the "website" target. + if format == 'pdf': + # First build the website page. This only takes a few seconds. + getattr(get_builder('website'), 'html')() + + website_dir = os.path.join(SAGE_DOC, 'html', 'en', 'website') + output_dir = self._output_dir(format) + + # Install in output_dir a symlink to the directory containing static files. + try: + os.symlink(os.path.join(website_dir, '_static'), os.path.join(output_dir, '_static')) + except FileExistsError: + pass + + # Now modify website's index.html page and write it to + # output_dir. + with open(os.path.join(website_dir, 'index.html')) as f: + html = f.read().replace('Documentation', 'Reference') + html_output_dir = os.path.dirname(website_dir) + html = html.replace('http://www.sagemath.org', + os.path.join(html_output_dir, 'index.html')) + # From index.html, we want the preamble and the tail. + html_end_preamble = html.find('

      Sage Reference') + html_bottom = html.rfind('') + len('') + + # For the content, we modify doc/en/reference/index.rst, which + # has two parts: the body and the table of contents. + with open(os.path.join(SAGE_DOC_SRC, self.lang, 'reference', 'index.rst')) as f: + rst = f.read() + # Get rid of todolist and miscellaneous rst markup. + rst = rst.replace('.. _reference-manual:\n\n', '') + rst = re.sub(r'\\\\', r'\\', rst) + # Replace rst links with html links. There are three forms: + # + # `blah`__ followed by __ LINK + # + # `blah `_ + # + # :doc:`blah ` + # + # Change the first and the second forms to + # + # blah + # + # Change the third form to + # + # blah + # + rst = re.sub(r'`([^`\n]*)`__.*\n\n__ (.*)', + r'\1.', rst) + rst = re.sub(r'`([^<\n]*)\s+<(.*)>`_', + r'\1', rst) + rst = re.sub(r':doc:`([^<]*?)\s+<(.*)/index>`', + r'\1 ', rst) + # Body: add paragraph

      markup. + start = rst.rfind('*\n') + 1 + end = rst.find('\nUser Interfaces') + rst_body = rst[start:end] + rst_body = rst_body.replace('\n\n', '

      \n

      ') + # TOC: don't include the indices + start = rst.find('\nUser Interfaces') + end = rst.find('Indices and Tables') + rst_toc = rst[start:end] + # change * to

    • ; change rst headers to html headers + rst_toc = re.sub(r'\*(.*)\n', + r'
    • \1
    • \n', rst_toc) + rst_toc = re.sub(r'\n([A-Z][a-zA-Z, ]*)\n[=]*\n', + r'

    \n\n\n

    \1

    \n\n
      \n', rst_toc) + rst_toc = re.sub(r'\n([A-Z][a-zA-Z, ]*)\n[-]*\n', + r'
    \n\n\n

    \1

    \n\n
      \n', rst_toc) + # now write the file. + with open(os.path.join(output_dir, 'index.html'), 'w') as new_index: + new_index.write(html[:html_end_preamble]) + new_index.write('

      Sage Reference Manual (PDF version)'+ '

      ') + new_index.write(rst_body) + new_index.write('
        ') + new_index.write(rst_toc) + new_index.write('
      \n\n') + new_index.write(html[html_bottom:]) + logger.warning(''' +PDF documents have been created in subdirectories of + + %s + +Alternatively, you can open + + %s + +for a webpage listing all of the documents.''' % (output_dir, + os.path.join(output_dir, + 'index.html'))) + + class ReferenceSubBuilder(DocBuilder): """ This class builds sub-components of the reference manual. It is @@ -1274,7 +1314,12 @@ def get_builder(name): documentation. """ if name == 'all': + from sage.misc.superseded import deprecation + deprecation(31948, 'avoid using "sage --docbuild all html" and "sage --docbuild all pdf"; ' + 'use "make doc" and "make doc-pdf" instead, if available.') return AllBuilder() + elif name == 'reference_top': + return ReferenceTopBuilder('reference') elif name.endswith('reference'): return ReferenceBuilder(name) elif 'reference' in name and os.path.exists(os.path.join(SAGE_DOC_SRC, 'en', name)): @@ -1377,8 +1422,8 @@ def help_documents(s=""): """ docs = get_documents() s += "DOCUMENTs:\n" - s += format_columns(docs + ['all (!)']) - s += "(!) Builds everything.\n\n" + s += format_columns(docs) + s += "\n" if 'reference' in docs: s+= "Other valid document names take the form 'reference/DIR', where\n" s+= "DIR is a subdirectory of SAGE_DOC_SRC/en/reference/.\n" @@ -1571,6 +1616,9 @@ def setup_parser(): standard.add_option("--check-nested", dest="check_nested", action="store_true", help="check picklability of nested classes in DOCUMENT 'reference'") + standard.add_option("--no-prune-empty-dirs", dest="no_prune_empty_dirs", + action="store_true", + help="do not prune empty directories in the documentation sources") standard.add_option("-N", "--no-colors", dest="color", default=True, action="store_false", help="do not color output; does not affect children") @@ -1747,13 +1795,16 @@ def excepthook(*exc_info): ABORT_ON_ERROR = not options.keep_going - # Delete empty directories. This is needed in particular for empty - # directories due to "git checkout" which never deletes empty - # directories it leaves behind. See Trac #20010. - for dirpath, dirnames, filenames in os.walk(SAGE_DOC_SRC, topdown=False): - if not dirnames + filenames: - logger.warning('Deleting empty directory {0}'.format(dirpath)) - os.rmdir(dirpath) + if not options.no_prune_empty_dirs: + # Delete empty directories. This is needed in particular for empty + # directories due to "git checkout" which never deletes empty + # directories it leaves behind. See Trac #20010. + # Trac #31948: This is not parallelization-safe; use the option + # --no-prune-empty-dirs to turn it off + for dirpath, dirnames, filenames in os.walk(SAGE_DOC_SRC, topdown=False): + if not dirnames + filenames: + logger.warning('Deleting empty directory {0}'.format(dirpath)) + os.rmdir(dirpath) # Set up Intersphinx cache C = IntersphinxCache() diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index ac9f8538bc7..c4523f2a531 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -34,13 +34,12 @@ from docutils.statemachine import ViewList import sphinx -from sphinx.ext.autodoc import mock +from sphinx.ext.autodoc import mock, ObjectMember from sphinx.ext.autodoc.importer import import_object, get_object_members, get_module_members from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer from sphinx.errors import PycodeError from sphinx.util import logging -from sphinx.util import rpartition, force_decode from sphinx.util.docstrings import prepare_docstring from sphinx.util.inspect import isdescriptor, \ safe_getattr, object_description, is_builtin_class_method, \ @@ -488,10 +487,7 @@ def get_doc(self, encoding=None, ignore=1): # make sure we have Unicode docstrings, then sanitize and split # into lines if isinstance(docstring, str): - return [prepare_docstring(docstring, ignore)] - elif isinstance(docstring, str): # this will not trigger on Py3 - return [prepare_docstring(force_decode(docstring, encoding), - ignore)] + return [prepare_docstring(docstring)] # ... else it is something strange, let's ignore it return [] @@ -536,7 +532,7 @@ def add_content(self, more_content, no_docstring=False): # add content from docstrings if not no_docstring: - encoding = self.analyzer and self.analyzer._encoding + encoding = self.analyzer docstrings = self.get_doc(encoding) if not docstrings: # append at least a dummy docstring, so that the event @@ -876,13 +872,42 @@ def add_directive_header(self, sig): if self.options.deprecated: self.add_line(u' :deprecated:', sourcename) + def get_module_members(self): + """Get members of target module.""" + if self.analyzer: + attr_docs = self.analyzer.attr_docs + else: + attr_docs = {} + + members = {} # type: Dict[str, ObjectMember] + for name in dir(self.object): + try: + value = safe_getattr(self.object, name, None) + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, value, docstring="\n".join(docstring)) + except AttributeError: + continue + + # annotation only member (ex. attr: int) + try: + for name in inspect.getannotations(self.object): + if name not in members: + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, INSTANCEATTR, + docstring="\n".join(docstring)) + except AttributeError: + pass + + return members + def get_object_members(self, want_all): # type: (bool) -> Tuple[bool, List[Tuple[unicode, object]]] + members = self.get_module_members() if want_all: if not hasattr(self.object, '__all__'): # for implicit module members, check __module__ to avoid # documenting imported objects - return True, get_module_members(self.object) + return True, list(members.values()) else: memberlist = self.object.__all__ # Sometimes __all__ is broken... @@ -893,14 +918,14 @@ def get_object_members(self, want_all): '(in module %s) -- ignoring __all__' % (memberlist, self.fullname)) # fall back to all members - return True, get_module_members(self.object) + return True, list(members.values()) else: memberlist = self.options.members or [] ret = [] for mname in memberlist: - try: - ret.append((mname, safe_getattr(self.object, mname))) - except AttributeError: + if mname in members: + ret.append(members[mname]) + else: logger.warning( 'missing attribute mentioned in :members: or __all__: ' 'module %s, attribute %s' % @@ -951,7 +976,7 @@ def resolve_name(self, modname, parents, path, base): # ... if still None, there's no way to know if mod_cls is None: return None, [] - modname, cls = rpartition(mod_cls, '.') # type: ignore + modname, _, cls = mod_cls.rpartition('.') # type: ignore parents = [cls] # if the module name is still missing, get it like above if not modname: @@ -1275,10 +1300,7 @@ def get_doc(self, encoding=None, ignore=1): doc = [] for docstring in docstrings: if isinstance(docstring, str): - doc.append(prepare_docstring(docstring, ignore)) - elif isinstance(docstring, str): # this will not trigger on Py3 - doc.append(prepare_docstring(force_decode(docstring, encoding), - ignore)) + doc.append(prepare_docstring(docstring)) return doc def add_content(self, more_content, no_docstring=False): diff --git a/src/sage_docbuild/sphinxbuild.py b/src/sage_docbuild/sphinxbuild.py index f58f6c61d76..d917c3e9d41 100644 --- a/src/sage_docbuild/sphinxbuild.py +++ b/src/sage_docbuild/sphinxbuild.py @@ -110,6 +110,7 @@ def _init_chatter(self): re.compile('WARNING: Any IDs not assiend for figure node'), re.compile('WARNING: .* is not referenced'), re.compile('WARNING: Build finished'), + re.compile('WARNING: rST localisation for language .* not found') ) # The warning "unknown config value 'multidoc_first_pass'..." # should only appear when building the documentation for a diff --git a/src/sage_setup/setenv.py b/src/sage_setup/setenv.py new file mode 100644 index 00000000000..c8b9d22e0c3 --- /dev/null +++ b/src/sage_setup/setenv.py @@ -0,0 +1,44 @@ +# Set some environment variables in the running process + +import os +import sage.env +from pathlib import Path + +def _environ_prepend(var, value, separator=':'): + if value: + if var in os.environ: + os.environ[var] = value + separator + os.environ[var] + else: + os.environ[var] = value + +def setenv(): + from sage.env import UNAME, SAGE_LOCAL, SAGE_VENV, SAGE_ARCHFLAGS, SAGE_PKG_CONFIG_PATH + + ## + ## from sage-env: + ## + + # not done: CC, CXX, FC, OBJC, OBJCXX, F77, F90, F95 + if 'ARCHFLAGS' not in os.environ and SAGE_ARCHFLAGS != "unset": + os.environ['ARCHFLAGS'] = SAGE_ARCHFLAGS + _environ_prepend('PKG_CONFIG_PATH', SAGE_PKG_CONFIG_PATH) + if SAGE_LOCAL: + _environ_prepend('PATH', f'{SAGE_LOCAL}/bin') + _environ_prepend('LIBRARY_PATH', f'{SAGE_LOCAL}/lib') + _environ_prepend('CPATH', f'{SAGE_LOCAL}/include') + _environ_prepend('LDFLAGS', f'-L{SAGE_LOCAL}/lib -Wl,-rpath,{SAGE_LOCAL}/lib', + separator=' ') + if UNAME == 'Linux': + _environ_prepend('LDFLAGS', f'-Wl,-rpath-link,{SAGE_LOCAL}/lib', + separator=' ') + if Path(SAGE_VENV).resolve() != Path(SAGE_LOCAL).resolve(): + _environ_prepend('PATH', f'{SAGE_VENV}/bin') + # the following two are not done by sage-env + #_environ_prepend('LIBRARY_PATH', f'{SAGE_VENV}/lib') + #_environ_prepend('CPATH', f'{SAGE_VENV}/include') + + # not done: PATH prepend of SAGE_SRC/bin, SAGE_ROOT/build/bin + # not done: MACOSX_DEPLOYMENT_TARGET + # not done: PATH prepend for ccache & CCACHE_BASEDIR + # not done: Cygwin LD_LIBRARY_PATH + # not done: OPENBLAS_NUM_THREADS diff --git a/src/setup.py b/src/setup.py index df8242c71a0..ab0f05af22b 100755 --- a/src/setup.py +++ b/src/setup.py @@ -1,5 +1,11 @@ #!/usr/bin/env python +## This version of setup.py is used by the Sage distribution +## only when configure --enable-editable has been used. +## +## Distribution packaging should use build/pkgs/sagelib/src/setup.py +## instead. + from __future__ import print_function import os @@ -39,6 +45,9 @@ sys.excepthook = excepthook +from sage_setup.setenv import setenv +setenv() + # ######################################################## # ## Configuration # ######################################################## @@ -76,7 +85,7 @@ log.debug(f"files_to_exclude = {files_to_exclude}") -python_packages = find_namespace_packages(where=SAGE_SRC, include=['sage', 'sage_setup']) +python_packages = find_namespace_packages(where=SAGE_SRC, include=['sage', 'sage_setup', 'sage.*', 'sage_setup.*']) log.debug(f"python_packages = {python_packages}") log.info(f"Discovered Python/Cython sources, time: {(time.time() - t):.2f} seconds.") @@ -105,7 +114,7 @@ nthreads=4) except Exception as exception: log.warn(f"Exception while generating and cythonizing source files: {exception}") - extensions = None + raise # ######################################################## # ## Distutils diff --git a/tox.ini b/tox.ini index cc17f99d4c8..21e342350d5 100644 --- a/tox.ini +++ b/tox.ini @@ -131,7 +131,10 @@ setenv = WITH_SYSTEM_SPKG=yes # Set this to 'yes' instead of 'no' to ignore missing system packages - by installing them one by one # and ignoring errors. We use that to take care of old versions of distributions. - IGNORE_MISSING_SYSTEM_PACKAGES=no + # For -maximal environments, the default is 'yes' but later we override it for rolling distributions + # (but not for unstable distributions that often have intermittent issues). + IGNORE_MISSING_SYSTEM_PACKAGES=no + maximal: IGNORE_MISSING_SYSTEM_PACKAGES=yes # What system packages should be installed. Default: All standard packages with spkg-configure. SAGE_PACKAGE_LIST_ARGS=--has-file=spkg-configure.m4 :standard: minimal: SAGE_PACKAGE_LIST_ARGS=_prereq @@ -150,7 +153,7 @@ setenv = docker: BASE_TAG=latest # # https://hub.docker.com/_/ubuntu?tab=description - # as of 2020-11, latest=focal=20.04, rolling=groovy=20.10, devel=hirsute=21.04 + # as of 2021-06, latest=focal=20.04, rolling=hirsute=21.04, impish=devel=21.10 # ubuntu: SYSTEM=debian ubuntu: BASE_IMAGE=ubuntu @@ -162,7 +165,11 @@ setenv = ubuntu-bionic: IGNORE_MISSING_SYSTEM_PACKAGES=yes ubuntu-focal: BASE_TAG=focal ubuntu-groovy: BASE_TAG=groovy + ubuntu-groovy: IGNORE_MISSING_SYSTEM_PACKAGES=no ubuntu-hirsute: BASE_TAG=hirsute + ubuntu-hirsute: IGNORE_MISSING_SYSTEM_PACKAGES=no + ubuntu-impish: BASE_TAG=impish + ubuntu-impish: IGNORE_MISSING_SYSTEM_PACKAGES=yes # # https://hub.docker.com/_/debian # debian-bullseye does not have libgiac-dev @@ -190,9 +197,10 @@ setenv = linuxmint-19.3: BASE_IMAGE=linuxmintd/mint19.3 linuxmint-20: BASE_IMAGE=linuxmintd/mint20 linuxmint-20.1: BASE_IMAGE=linuxmintd/mint20.1 + linuxmint-20.2: BASE_IMAGE=linuxmintd/mint20.2 # # https://hub.docker.com/_/fedora - # as of 2020-11, latest=33, rawhide=34 + # as of 2021-06, latest=34, rawhide=35 fedora: SYSTEM=fedora fedora: BASE_IMAGE=fedora fedora-26: BASE_TAG=26 @@ -207,7 +215,11 @@ setenv = fedora-31: BASE_TAG=31 fedora-32: BASE_TAG=32 fedora-33: BASE_TAG=33 + fedora-33: IGNORE_MISSING_SYSTEM_PACKAGES=no fedora-34: BASE_TAG=34 + fedora-34: IGNORE_MISSING_SYSTEM_PACKAGES=no + fedora-35: BASE_TAG=35 + fedora-35: IGNORE_MISSING_SYSTEM_PACKAGES=yes # # https://hub.docker.com/r/scientificlinux/sl # @@ -234,23 +246,27 @@ setenv = gentoo: BASE_IMAGE=sheerluck/sage-on-gentoo-stage4 gentoo-python3.7: BASE_TAG=latest-py37 gentoo-python3.9: BASE_TAG=latest-py39 + gentoo: IGNORE_MISSING_SYSTEM_PACKAGES=no # # https://hub.docker.com/_/archlinux/ # archlinux: SYSTEM=arch archlinux: BASE_IMAGE=archlinux + archlinux: IGNORE_MISSING_SYSTEM_PACKAGES=no # # https://hub.docker.com/r/vbatts/slackware # slackware: SYSTEM=slackware slackware: BASE_IMAGE=vbatts/slackware slackware-14.2: BASE_TAG=14.2 + slackware: IGNORE_MISSING_SYSTEM_PACKAGES=no # # https://hub.docker.com/r/voidlinux/ # voidlinux: SYSTEM=void voidlinux: BASE_IMAGE=voidlinux/masterdir-x86_64-musl voidlinux: BASE_TAG=20200104 + voidlinux: IGNORE_MISSING_SYSTEM_PACKAGES=no # # https://hub.docker.com/r/continuumio # @@ -258,8 +274,9 @@ setenv = conda: CONDARC=/dev/null conda-forge: BASE_IMAGE=continuumio/miniconda3 conda-forge: CONDARC=condarc.yml + conda-forge: IGNORE_MISSING_SYSTEM_PACKAGES=no conda-anaconda3: BASE_IMAGE=continuumio/anaconda3 - conda-anaconda3: IGNORE_MISSING_SYSTEM_PACKAGES=yes + conda-anaconda3: IGNORE_MISSING_SYSTEM_PACKAGES=yes # # https://hub.docker.com/r/nixos/nix/ # @@ -276,6 +293,7 @@ setenv = # opensuse: SYSTEM=opensuse opensuse: BASE_IMAGE=opensuse/leap + opensuse: IGNORE_MISSING_SYSTEM_PACKAGES=yes opensuse-42: BASE_TAG=42 opensuse-15.0: BASE_TAG=15.0 opensuse-15.1: BASE_TAG=15.1 @@ -283,6 +301,7 @@ setenv = opensuse-15.3: BASE_TAG=15.3 opensuse-15: BASE_TAG=15 opensuse-tumbleweed: BASE_IMAGE=opensuse/tumbleweed + opensuse-tumbleweed: IGNORE_MISSING_SYSTEM_PACKAGES=no # # Other architectures: # @@ -385,7 +404,8 @@ setenv = local-homebrew: PATH={env:HOMEBREW}/bin:/usr/bin:/bin:/usr/sbin:/sbin local-{homebrew-nokegonly,nohomebrew}: BOOTSTRAP=ACLOCAL_PATH="$HOMEBREW/opt/gettext/share/aclocal:$ACLOCAL_PATH" PATH="$HOMEBREW/opt/gettext/bin/:$HOMEBREW/bin:$PATH" ./bootstrap local-homebrew-!nokegonly: SETENV=. .homebrew-build-env - # + local-homebrew: IGNORE_MISSING_SYSTEM_PACKAGES=no + # conda local-conda: CONDA_PREFIX={envdir}/conda local-conda: PATH={env:CONDA_PREFIX}/bin:/usr/bin:/bin:/usr/sbin:/sbin local-conda: CONDA_PKGS_DIRS={env:SHARED_CACHE_DIR}/conda_pkgs